diff --git a/src/cancellationToken/cancellationToken.ts b/src/cancellationToken/cancellationToken.ts index 059ef37c8f1e0..34cfe6aac5765 100644 --- a/src/cancellationToken/cancellationToken.ts +++ b/src/cancellationToken/cancellationToken.ts @@ -1,13 +1,10 @@ /// - import fs = require("fs"); - interface ServerCancellationToken { isCancellationRequested(): boolean; setRequest(requestId: number): void; resetRequest(requestId: number): void; } - function pipeExists(name: string): boolean { try { fs.statSync(name); @@ -17,7 +14,6 @@ function pipeExists(name: string): boolean { return false; } } - function createCancellationToken(args: string[]): ServerCancellationToken { let cancellationPipeName: string | undefined; for (let i = 0; i < args.length - 1; i++) { @@ -61,7 +57,7 @@ function createCancellationToken(args: string[]): ServerCancellationToken { } else { return { - isCancellationRequested: () => pipeExists(cancellationPipeName!), // TODO: GH#18217 + isCancellationRequested: () => pipeExists(cancellationPipeName!), setRequest: (_requestId: number): void => void 0, resetRequest: (_requestId: number): void => void 0 }; diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 990674b7b40a1..d1cc9e04dc80a 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1,4213 +1,3811 @@ - +import { __String, FlowLabel, ModuleDeclaration, Node, createMap, getNodeId, SyntaxKind, isEnumConst, EnumDeclaration, hasModifier, ModifierFlags, ExportDeclaration, forEachChild, Debug, Identifier, ExportSpecifier, isBlock, isModuleBlock, isSourceFile, nodeHasName, FlowNode, SourceFile, CompilerOptions, perfLogger, ScriptTarget, JSDocTypedefTag, JSDocCallbackTag, JSDocEnumTag, NodeFlags, SymbolFlags, UnderscoreEscapedMap, FlowFlags, TransformFlags, DiagnosticMessage, DiagnosticWithLocation, createDiagnosticForNodeInSourceFile, getSourceFileOfNode, getEmitScriptTarget, createUnderscoreEscapedMap, objectAllocator, getStrictOptionValue, Declaration, appendIfUnique, createSymbolTable, isAssignmentDeclaration, isEffectiveModuleDeclaration, ExportAssignment, InternalSymbolName, getNameOfDeclaration, isAmbientModule, getTextOfIdentifierOrLiteral, StringLiteral, isGlobalScopeAugmentation, isStringOrNumericLiteralLike, escapeLeadingUnderscores, isSignedNumericLiteral, tokenToString, isWellKnownSymbolSyntactically, getPropertyNameForKnownSymbolName, idText, PropertyAccessExpression, isPropertyNameLiteral, getEscapedTextOfIdentifierOrLiteral, getAssignmentDeclarationKind, BinaryExpression, AssignmentDeclarationKind, isJSDocConstructSignature, JSDocFunctionType, ParameterDeclaration, isNamedDeclaration, declarationNameToString, unescapeLeadingUnderscores, SymbolTable, hasDynamicName, Diagnostics, length, DiagnosticRelatedInformation, forEach, addRelatedInfo, getCombinedModifierFlags, isJSDocTypeAlias, isInJSFile, FunctionLikeDeclaration, getImmediatelyInvokedFunctionExpression, FunctionExpression, ArrowFunction, MethodDeclaration, nodeIsPresent, ConstructorDeclaration, NodeArray, WhileStatement, DoStatement, ForStatement, ForInOrOfStatement, IfStatement, ReturnStatement, ThrowStatement, BreakOrContinueStatement, TryStatement, SwitchStatement, CaseBlock, CaseClause, ExpressionStatement, LabeledStatement, PrefixUnaryExpression, PostfixUnaryExpression, DeleteExpression, ConditionalExpression, VariableDeclaration, AccessExpression, CallExpression, Block, Expression, ParenthesizedExpression, TypeOfExpression, isPropertyAccessExpression, isNonNullExpression, isParenthesizedExpression, isElementAccessExpression, isOptionalChain, isTypeOfExpression, isStringLiteralLike, contains, isExpressionOfOptionalChainRoot, isNullishCoalesce, isPrefixUnaryExpression, isOutermostOptionalChain, Statement, PreFinallyFlow, AfterFinallyFlow, isDottedName, unusedLabelIsError, ArrayLiteralExpression, SpreadElement, ObjectLiteralExpression, isAssignmentOperator, isAssignmentTarget, ElementAccessExpression, ArrayBindingElement, isOmittedExpression, isBindingPattern, isForInOrOfStatement, JSDocClassTag, getHostSignatureFromJSDoc, OptionalChain, skipParentheses, isPushOrUnshiftIdentifier, isObjectLiteralOrClassExpressionMethod, PropertyDeclaration, isFunctionLike, isExternalModule, tryCast, isExportDeclaration, isExportAssignment, isModuleAugmentationExternal, Pattern, hasZeroOrOneAsteriskCharacter, tryParsePattern, append, PatternAmbientModule, SignatureDeclaration, JSDocSignature, getErrorSpanForNode, createFileDiagnostic, JsxAttributes, JsxAttribute, isExternalOrCommonJsModule, getJSDocHost, findAncestor, getEnclosingBlockScopeContainer, isJSDocEnumTag, isPropertyAccessEntityNameExpression, getAssignmentDeclarationPropertyAccessKind, isIdentifierName, getContainingClass, isLeftHandSideExpression, CatchClause, isIdentifier, FunctionDeclaration, NumericLiteral, TokenFlags, WithStatement, isDeclarationStatement, isVariableStatement, getSpanOfTokenAtPosition, getTokenPosOfNode, TextRange, DiagnosticCategory, hasJSDocNodes, isPrologueDirective, getSourceTextOfNodeFromSourceFile, isExpression, isSpecialPropertyDeclaration, isModuleExportsAccessExpression, BindableStaticPropertyAssignmentExpression, BindablePropertyAssignmentExpression, TypeParameterDeclaration, BindingElement, PropertySignature, isObjectLiteralMethod, TypeLiteralNode, MappedTypeNode, JSDocTypeLiteral, BindableObjectDefinePropertyCall, ClassLikeDeclaration, NamespaceExportDeclaration, ImportClause, ModuleBlock, JSDocParameterTag, JSDocPropertyLikeTag, isJsonSourceFile, removeFileExtension, exportAssignmentIsAlias, isClassExpression, getRightMostAssignedExpression, isEmptyObjectLiteral, LiteralLikeElementAccessExpression, getThisContainer, isBinaryExpression, isBindableStaticAccessExpression, isPrototypeAccess, DynamicNamedDeclaration, EntityNameExpression, BindableStaticAccessExpression, isFunctionSymbol, cast, BindableStaticNameExpression, isFunctionLikeDeclaration, getAssignedExpandoInitializer, isCallExpression, isBindableObjectDefinePropertyCall, some, BindableAccessExpression, isVariableDeclaration, getExpandoInitializer, getElementOrPropertyAccessName, getNameOrArgument, isRequireCall, symbolName, isBlockOrCatchScoped, isParameterDeclaration, isParameterPropertyDeclaration, isAsyncFunction, ConditionalTypeNode, isConditionalTypeNode, isJSDocTemplateTag, find, JSDoc, isStatementButNotDeclaration, unreachableCodeIsError, getCombinedNodeFlags, isStatement, sliceAfter, getRangesWhere, isFunctionDeclaration, isEnumDeclaration, isExportsIdentifier, isAssignmentExpression, Symbol, NewExpression, VariableDeclarationList, VariableStatement, ClassDeclaration, ClassExpression, HeritageClause, ExpressionWithTypeArguments, AccessorDeclaration, ImportEqualsDeclaration, skipOuterExpressions, isSuperOrSuperProperty, isSuperProperty, isThisIdentifier, isComputedPropertyName, hasStaticModifier, getModifierFlags, isIterationStatement, isExternalModuleImportEqualsDeclaration, ForOfStatement, PropertyName } from "./ts"; +import { mark, measure } from "./ts.performance"; +import * as ts from "./ts"; /* @internal */ -namespace ts { - export const enum ModuleInstanceState { - NonInstantiated = 0, - Instantiated = 1, - ConstEnumOnly = 2 - } - - interface ActiveLabel { - next: ActiveLabel | undefined; - name: __String; - breakTarget: FlowLabel; - continueTarget: FlowLabel | undefined; - referenced: boolean; - } - - export function getModuleInstanceState(node: ModuleDeclaration, visited?: Map): ModuleInstanceState { - if (node.body && !node.body.parent) { - // getModuleInstanceStateForAliasTarget needs to walk up the parent chain, so parent pointers must be set on this tree already - setParentPointers(node, node.body); - } - return node.body ? getModuleInstanceStateCached(node.body, visited) : ModuleInstanceState.Instantiated; - } - - function getModuleInstanceStateCached(node: Node, visited = createMap()) { - const nodeId = "" + getNodeId(node); - if (visited.has(nodeId)) { - return visited.get(nodeId) || ModuleInstanceState.NonInstantiated; - } - visited.set(nodeId, undefined); - const result = getModuleInstanceStateWorker(node, visited); - visited.set(nodeId, result); - return result; +export const enum ModuleInstanceState { + NonInstantiated = 0, + Instantiated = 1, + ConstEnumOnly = 2 +} +/* @internal */ +interface ActiveLabel { + next: ActiveLabel | undefined; + name: __String; + breakTarget: FlowLabel; + continueTarget: FlowLabel | undefined; + referenced: boolean; +} +/* @internal */ +export function getModuleInstanceState(node: ModuleDeclaration, visited?: ts.Map): ModuleInstanceState { + if (node.body && !node.body.parent) { + // getModuleInstanceStateForAliasTarget needs to walk up the parent chain, so parent pointers must be set on this tree already + setParentPointers(node, node.body); } - - function getModuleInstanceStateWorker(node: Node, visited: Map): ModuleInstanceState { - // A module is uninstantiated if it contains only - switch (node.kind) { - // 1. interface declarations, type alias declarations - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: + return node.body ? getModuleInstanceStateCached(node.body, visited) : ModuleInstanceState.Instantiated; +} +/* @internal */ +function getModuleInstanceStateCached(node: Node, visited = createMap()) { + const nodeId = "" + getNodeId(node); + if (visited.has(nodeId)) { + return visited.get(nodeId) || ModuleInstanceState.NonInstantiated; + } + visited.set(nodeId, undefined); + const result = getModuleInstanceStateWorker(node, visited); + visited.set(nodeId, result); + return result; +} +/* @internal */ +function getModuleInstanceStateWorker(node: Node, visited: ts.Map): ModuleInstanceState { + // A module is uninstantiated if it contains only + switch (node.kind) { + // 1. interface declarations, type alias declarations + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + return ModuleInstanceState.NonInstantiated; + // 2. const enum declarations + case SyntaxKind.EnumDeclaration: + if (isEnumConst((node as EnumDeclaration))) { + return ModuleInstanceState.ConstEnumOnly; + } + break; + // 3. non-exported import declarations + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + if (!(hasModifier(node, ModifierFlags.Export))) { return ModuleInstanceState.NonInstantiated; - // 2. const enum declarations - case SyntaxKind.EnumDeclaration: - if (isEnumConst(node as EnumDeclaration)) { - return ModuleInstanceState.ConstEnumOnly; - } - break; - // 3. non-exported import declarations - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - if (!(hasModifier(node, ModifierFlags.Export))) { - return ModuleInstanceState.NonInstantiated; - } - break; - // 4. Export alias declarations pointing at only uninstantiated modules or things uninstantiated modules contain - case SyntaxKind.ExportDeclaration: - if (!(node as ExportDeclaration).moduleSpecifier && !!(node as ExportDeclaration).exportClause) { - let state = ModuleInstanceState.NonInstantiated; - for (const specifier of (node as ExportDeclaration).exportClause!.elements) { - const specifierState = getModuleInstanceStateForAliasTarget(specifier, visited); - if (specifierState > state) { - state = specifierState; - } - if (state === ModuleInstanceState.Instantiated) { - return state; - } - } - return state; - } - break; - // 5. other uninstantiated module declarations. - case SyntaxKind.ModuleBlock: { + } + break; + // 4. Export alias declarations pointing at only uninstantiated modules or things uninstantiated modules contain + case SyntaxKind.ExportDeclaration: + if (!(node as ExportDeclaration).moduleSpecifier && !!(node as ExportDeclaration).exportClause) { let state = ModuleInstanceState.NonInstantiated; - forEachChild(node, n => { - const childState = getModuleInstanceStateCached(n, visited); - switch (childState) { - case ModuleInstanceState.NonInstantiated: - // child is non-instantiated - continue searching - return; - case ModuleInstanceState.ConstEnumOnly: - // child is const enum only - record state and continue searching - state = ModuleInstanceState.ConstEnumOnly; - return; - case ModuleInstanceState.Instantiated: - // child is instantiated - record state and stop - state = ModuleInstanceState.Instantiated; - return true; - default: - Debug.assertNever(childState); + for (const specifier of (node as ExportDeclaration).exportClause!.elements) { + const specifierState = getModuleInstanceStateForAliasTarget(specifier, visited); + if (specifierState > state) { + state = specifierState; } - }); + if (state === ModuleInstanceState.Instantiated) { + return state; + } + } return state; } - case SyntaxKind.ModuleDeclaration: - return getModuleInstanceState(node as ModuleDeclaration, visited); - case SyntaxKind.Identifier: - // Only jsdoc typedef definition can exist in jsdoc namespace, and it should - // be considered the same as type alias - if ((node).isInJSDocNamespace) { - return ModuleInstanceState.NonInstantiated; + break; + // 5. other uninstantiated module declarations. + case SyntaxKind.ModuleBlock: { + let state = ModuleInstanceState.NonInstantiated; + forEachChild(node, n => { + const childState = getModuleInstanceStateCached(n, visited); + switch (childState) { + case ModuleInstanceState.NonInstantiated: + // child is non-instantiated - continue searching + return; + case ModuleInstanceState.ConstEnumOnly: + // child is const enum only - record state and continue searching + state = ModuleInstanceState.ConstEnumOnly; + return; + case ModuleInstanceState.Instantiated: + // child is instantiated - record state and stop + state = ModuleInstanceState.Instantiated; + return true; + default: + Debug.assertNever(childState); } + }); + return state; } - return ModuleInstanceState.Instantiated; - } - - function getModuleInstanceStateForAliasTarget(specifier: ExportSpecifier, visited: Map) { - const name = specifier.propertyName || specifier.name; - let p: Node | undefined = specifier.parent; - while (p) { - if (isBlock(p) || isModuleBlock(p) || isSourceFile(p)) { - const statements = p.statements; - let found: ModuleInstanceState | undefined; - for (const statement of statements) { - if (nodeHasName(statement, name)) { - if (!statement.parent) { - setParentPointers(p, statement); - } - const state = getModuleInstanceStateCached(statement, visited); - if (found === undefined || state > found) { - found = state; - } - if (found === ModuleInstanceState.Instantiated) { - return found; - } + case SyntaxKind.ModuleDeclaration: + return getModuleInstanceState((node as ModuleDeclaration), visited); + case SyntaxKind.Identifier: + // Only jsdoc typedef definition can exist in jsdoc namespace, and it should + // be considered the same as type alias + if ((node).isInJSDocNamespace) { + return ModuleInstanceState.NonInstantiated; + } + } + return ModuleInstanceState.Instantiated; +} +/* @internal */ +function getModuleInstanceStateForAliasTarget(specifier: ExportSpecifier, visited: ts.Map) { + const name = specifier.propertyName || specifier.name; + let p: Node | undefined = specifier.parent; + while (p) { + if (isBlock(p) || isModuleBlock(p) || isSourceFile(p)) { + const statements = p.statements; + let found: ModuleInstanceState | undefined; + for (const statement of statements) { + if (nodeHasName(statement, name)) { + if (!statement.parent) { + setParentPointers(p, statement); + } + const state = getModuleInstanceStateCached(statement, visited); + if (found === undefined || state > found) { + found = state; + } + if (found === ModuleInstanceState.Instantiated) { + return found; } - } - if (found !== undefined) { - return found; } } - p = p.parent; - } - return ModuleInstanceState.Instantiated; // Couldn't locate, assume could refer to a value - } - - const enum ContainerFlags { - // The current node is not a container, and no container manipulation should happen before - // recursing into it. - None = 0, - - // The current node is a container. It should be set as the current container (and block- - // container) before recursing into it. The current node does not have locals. Examples: - // - // Classes, ObjectLiterals, TypeLiterals, Interfaces... - IsContainer = 1 << 0, - - // The current node is a block-scoped-container. It should be set as the current block- - // container before recursing into it. Examples: - // - // Blocks (when not parented by functions), Catch clauses, For/For-in/For-of statements... - IsBlockScopedContainer = 1 << 1, - - // The current node is the container of a control flow path. The current control flow should - // be saved and restored, and a new control flow initialized within the container. - IsControlFlowContainer = 1 << 2, - - IsFunctionLike = 1 << 3, - IsFunctionExpression = 1 << 4, - HasLocals = 1 << 5, - IsInterface = 1 << 6, - IsObjectLiteralOrClassExpressionMethod = 1 << 7, - } - - function initFlowNode(node: T) { - Debug.attachFlowNodeDebugInfo(node); - return node; - } - - const binder = createBinder(); - - export function bindSourceFile(file: SourceFile, options: CompilerOptions) { - performance.mark("beforeBind"); - perfLogger.logStartBindFile("" + file.fileName); - binder(file, options); - perfLogger.logStopBindFile(); - performance.mark("afterBind"); - performance.measure("Bind", "beforeBind", "afterBind"); - } - - function createBinder(): (file: SourceFile, options: CompilerOptions) => void { - let file: SourceFile; - let options: CompilerOptions; - let languageVersion: ScriptTarget; - let parent: Node; - let container: Node; - let thisParentContainer: Node; // Container one level up - let blockScopeContainer: Node; - let lastContainer: Node; - let delayedTypeAliases: (JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag)[]; - let seenThisKeyword: boolean; - - // state used by control flow analysis - let currentFlow: FlowNode; - let currentBreakTarget: FlowLabel | undefined; - let currentContinueTarget: FlowLabel | undefined; - let currentReturnTarget: FlowLabel | undefined; - let currentTrueTarget: FlowLabel | undefined; - let currentFalseTarget: FlowLabel | undefined; - let currentExceptionTarget: FlowLabel | undefined; - let preSwitchCaseFlow: FlowNode | undefined; - let activeLabelList: ActiveLabel | undefined; - let hasExplicitReturn: boolean; - - // state used for emit helpers - let emitFlags: NodeFlags; - - // If this file is an external module, then it is automatically in strict-mode according to - // ES6. If it is not an external module, then we'll determine if it is in strict mode or - // not depending on if we see "use strict" in certain places or if we hit a class/namespace - // or if compiler options contain alwaysStrict. - let inStrictMode: boolean; - - let symbolCount = 0; - - let Symbol: new (flags: SymbolFlags, name: __String) => Symbol; - let classifiableNames: UnderscoreEscapedMap; - - const unreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; - const reportedUnreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; - - // state used to aggregate transform flags during bind. - let subtreeTransformFlags: TransformFlags = TransformFlags.None; - let skipTransformFlagAggregation: boolean; - - /** - * Inside the binder, we may create a diagnostic for an as-yet unbound node (with potentially no parent pointers, implying no accessible source file) - * If so, the node _must_ be in the current file (as that's the only way anything could have traversed to it to yield it as the error node) - * This version of `createDiagnosticForNode` uses the binder's context to account for this, and always yields correct diagnostics even in these situations. - */ - function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): DiagnosticWithLocation { - return createDiagnosticForNodeInSourceFile(getSourceFileOfNode(node) || file, node, message, arg0, arg1, arg2); - } - - function bindSourceFile(f: SourceFile, opts: CompilerOptions) { - file = f; - options = opts; - languageVersion = getEmitScriptTarget(options); - inStrictMode = bindInStrictMode(file, opts); - classifiableNames = createUnderscoreEscapedMap(); - symbolCount = 0; - skipTransformFlagAggregation = file.isDeclarationFile; - - Symbol = objectAllocator.getSymbolConstructor(); - - // Attach debugging information if necessary - Debug.attachFlowNodeDebugInfo(unreachableFlow); - Debug.attachFlowNodeDebugInfo(reportedUnreachableFlow); - - if (!file.locals) { - bind(file); - file.symbolCount = symbolCount; - file.classifiableNames = classifiableNames; - delayedBindJSDocTypedefTag(); + if (found !== undefined) { + return found; } - - file = undefined!; - options = undefined!; - languageVersion = undefined!; - parent = undefined!; - container = undefined!; - thisParentContainer = undefined!; - blockScopeContainer = undefined!; - lastContainer = undefined!; - delayedTypeAliases = undefined!; - seenThisKeyword = false; - currentFlow = undefined!; - currentBreakTarget = undefined; - currentContinueTarget = undefined; - currentReturnTarget = undefined; - currentTrueTarget = undefined; - currentFalseTarget = undefined; - currentExceptionTarget = undefined; - activeLabelList = undefined; - hasExplicitReturn = false; - emitFlags = NodeFlags.None; - subtreeTransformFlags = TransformFlags.None; } - - return bindSourceFile; - - function bindInStrictMode(file: SourceFile, opts: CompilerOptions): boolean { - if (getStrictOptionValue(opts, "alwaysStrict") && !file.isDeclarationFile) { - // bind in strict mode source files with alwaysStrict option - return true; - } - else { - return !!file.externalModuleIndicator; - } + p = p.parent; + } + return ModuleInstanceState.Instantiated; // Couldn't locate, assume could refer to a value +} +/* @internal */ +const enum ContainerFlags { + // The current node is not a container, and no container manipulation should happen before + // recursing into it. + None = 0, + // The current node is a container. It should be set as the current container (and block- + // container) before recursing into it. The current node does not have locals. Examples: + // + // Classes, ObjectLiterals, TypeLiterals, Interfaces... + IsContainer = 1 << 0, + // The current node is a block-scoped-container. It should be set as the current block- + // container before recursing into it. Examples: + // + // Blocks (when not parented by functions), Catch clauses, For/For-in/For-of statements... + IsBlockScopedContainer = 1 << 1, + // The current node is the container of a control flow path. The current control flow should + // be saved and restored, and a new control flow initialized within the container. + IsControlFlowContainer = 1 << 2, + IsFunctionLike = 1 << 3, + IsFunctionExpression = 1 << 4, + HasLocals = 1 << 5, + IsInterface = 1 << 6, + IsObjectLiteralOrClassExpressionMethod = 1 << 7 +} +/* @internal */ +function initFlowNode(node: T) { + Debug.attachFlowNodeDebugInfo(node); + return node; +} +/* @internal */ +const binder = createBinder(); +/* @internal */ +export function bindSourceFile(file: SourceFile, options: CompilerOptions) { + mark("beforeBind"); + perfLogger.logStartBindFile("" + file.fileName); + binder(file, options); + perfLogger.logStopBindFile(); + mark("afterBind"); + measure("Bind", "beforeBind", "afterBind"); +} +/* @internal */ +function createBinder(): (file: SourceFile, options: CompilerOptions) => void { + let file: SourceFile; + let options: CompilerOptions; + let languageVersion: ScriptTarget; + let parent: Node; + let container: Node; + let thisParentContainer: Node; // Container one level up + let blockScopeContainer: Node; + let lastContainer: Node; + let delayedTypeAliases: (JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag)[]; + let seenThisKeyword: boolean; + // state used by control flow analysis + let currentFlow: FlowNode; + let currentBreakTarget: FlowLabel | undefined; + let currentContinueTarget: FlowLabel | undefined; + let currentReturnTarget: FlowLabel | undefined; + let currentTrueTarget: FlowLabel | undefined; + let currentFalseTarget: FlowLabel | undefined; + let currentExceptionTarget: FlowLabel | undefined; + let preSwitchCaseFlow: FlowNode | undefined; + let activeLabelList: ActiveLabel | undefined; + let hasExplicitReturn: boolean; + // state used for emit helpers + let emitFlags: NodeFlags; + // If this file is an external module, then it is automatically in strict-mode according to + // ES6. If it is not an external module, then we'll determine if it is in strict mode or + // not depending on if we see "use strict" in certain places or if we hit a class/namespace + // or if compiler options contain alwaysStrict. + let inStrictMode: boolean; + let symbolCount = 0; + let Symbol: new (flags: SymbolFlags, name: __String) => ts.Symbol; + let classifiableNames: UnderscoreEscapedMap; + const unreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; + const reportedUnreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; + // state used to aggregate transform flags during bind. + let subtreeTransformFlags: TransformFlags = TransformFlags.None; + let skipTransformFlagAggregation: boolean; + /** + * Inside the binder, we may create a diagnostic for an as-yet unbound node (with potentially no parent pointers, implying no accessible source file) + * If so, the node _must_ be in the current file (as that's the only way anything could have traversed to it to yield it as the error node) + * This version of `createDiagnosticForNode` uses the binder's context to account for this, and always yields correct diagnostics even in these situations. + */ + function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): DiagnosticWithLocation { + return createDiagnosticForNodeInSourceFile(getSourceFileOfNode(node) || file, node, message, arg0, arg1, arg2); + } + function bindSourceFile(f: SourceFile, opts: CompilerOptions) { + file = f; + options = opts; + languageVersion = getEmitScriptTarget(options); + inStrictMode = bindInStrictMode(file, opts); + classifiableNames = createUnderscoreEscapedMap(); + symbolCount = 0; + skipTransformFlagAggregation = file.isDeclarationFile; + Symbol = objectAllocator.getSymbolConstructor(); + // Attach debugging information if necessary + Debug.attachFlowNodeDebugInfo(unreachableFlow); + Debug.attachFlowNodeDebugInfo(reportedUnreachableFlow); + if (!file.locals) { + bind(file); + file.symbolCount = symbolCount; + file.classifiableNames = classifiableNames; + delayedBindJSDocTypedefTag(); + } + file = undefined!; + options = undefined!; + languageVersion = undefined!; + parent = undefined!; + container = undefined!; + thisParentContainer = undefined!; + blockScopeContainer = undefined!; + lastContainer = undefined!; + delayedTypeAliases = undefined!; + seenThisKeyword = false; + currentFlow = undefined!; + currentBreakTarget = undefined; + currentContinueTarget = undefined; + currentReturnTarget = undefined; + currentTrueTarget = undefined; + currentFalseTarget = undefined; + currentExceptionTarget = undefined; + activeLabelList = undefined; + hasExplicitReturn = false; + emitFlags = NodeFlags.None; + subtreeTransformFlags = TransformFlags.None; + } + return bindSourceFile; + function bindInStrictMode(file: SourceFile, opts: CompilerOptions): boolean { + if (getStrictOptionValue(opts, "alwaysStrict") && !file.isDeclarationFile) { + // bind in strict mode source files with alwaysStrict option + return true; } - - function createSymbol(flags: SymbolFlags, name: __String): Symbol { - symbolCount++; - return new Symbol(flags, name); - } - - function addDeclarationToSymbol(symbol: Symbol, node: Declaration, symbolFlags: SymbolFlags) { - symbol.flags |= symbolFlags; - - node.symbol = symbol; - symbol.declarations = appendIfUnique(symbol.declarations, node); - - if (symbolFlags & (SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.Module | SymbolFlags.Variable) && !symbol.exports) { - symbol.exports = createSymbolTable(); - } - - if (symbolFlags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && !symbol.members) { - symbol.members = createSymbolTable(); - } - - // On merge of const enum module with class or function, reset const enum only flag (namespaces will already recalculate) - if (symbol.constEnumOnlyModule && (symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.RegularEnum))) { - symbol.constEnumOnlyModule = false; - } - - if (symbolFlags & SymbolFlags.Value) { - setValueDeclaration(symbol, node); - } + else { + return !!file.externalModuleIndicator; } - - function setValueDeclaration(symbol: Symbol, node: Declaration): void { - const { valueDeclaration } = symbol; - if (!valueDeclaration || - (isAssignmentDeclaration(valueDeclaration) && !isAssignmentDeclaration(node)) || - (valueDeclaration.kind !== node.kind && isEffectiveModuleDeclaration(valueDeclaration))) { - // other kinds of value declarations take precedence over modules and assignment declarations - symbol.valueDeclaration = node; - } + } + function createSymbol(flags: SymbolFlags, name: __String): ts.Symbol { + symbolCount++; + return new Symbol(flags, name); + } + function addDeclarationToSymbol(symbol: ts.Symbol, node: Declaration, symbolFlags: SymbolFlags) { + symbol.flags |= symbolFlags; + node.symbol = symbol; + symbol.declarations = appendIfUnique(symbol.declarations, node); + if (symbolFlags & (SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.Module | SymbolFlags.Variable) && !symbol.exports) { + symbol.exports = createSymbolTable(); + } + if (symbolFlags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && !symbol.members) { + symbol.members = createSymbolTable(); + } + // On merge of const enum module with class or function, reset const enum only flag (namespaces will already recalculate) + if (symbol.constEnumOnlyModule && (symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.RegularEnum))) { + symbol.constEnumOnlyModule = false; + } + if (symbolFlags & SymbolFlags.Value) { + setValueDeclaration(symbol, node); + } + } + function setValueDeclaration(symbol: ts.Symbol, node: Declaration): void { + const { valueDeclaration } = symbol; + if (!valueDeclaration || + (isAssignmentDeclaration(valueDeclaration) && !isAssignmentDeclaration(node)) || + (valueDeclaration.kind !== node.kind && isEffectiveModuleDeclaration(valueDeclaration))) { + // other kinds of value declarations take precedence over modules and assignment declarations + symbol.valueDeclaration = node; } - - // Should not be called on a declaration with a computed property name, - // unless it is a well known Symbol. - function getDeclarationName(node: Declaration): __String | undefined { - if (node.kind === SyntaxKind.ExportAssignment) { - return (node).isExportEquals ? InternalSymbolName.ExportEquals : InternalSymbolName.Default; + } + // Should not be called on a declaration with a computed property name, + // unless it is a well known Symbol. + function getDeclarationName(node: Declaration): __String | undefined { + if (node.kind === SyntaxKind.ExportAssignment) { + return (node).isExportEquals ? InternalSymbolName.ExportEquals : InternalSymbolName.Default; + } + const name = getNameOfDeclaration(node); + if (name) { + if (isAmbientModule(node)) { + const moduleName = getTextOfIdentifierOrLiteral((name as Identifier | StringLiteral)); + return (isGlobalScopeAugmentation((node)) ? "__global" : `"${moduleName}"`) as __String; } - - const name = getNameOfDeclaration(node); - if (name) { - if (isAmbientModule(node)) { - const moduleName = getTextOfIdentifierOrLiteral(name as Identifier | StringLiteral); - return (isGlobalScopeAugmentation(node) ? "__global" : `"${moduleName}"`) as __String; - } - if (name.kind === SyntaxKind.ComputedPropertyName) { - const nameExpression = name.expression; - // treat computed property names where expression is string/numeric literal as just string/numeric literal - if (isStringOrNumericLiteralLike(nameExpression)) { - return escapeLeadingUnderscores(nameExpression.text); - } - if (isSignedNumericLiteral(nameExpression)) { - return tokenToString(nameExpression.operator) + nameExpression.operand.text as __String; - } - - Debug.assert(isWellKnownSymbolSyntactically(nameExpression)); - return getPropertyNameForKnownSymbolName(idText((nameExpression).name)); + if (name.kind === SyntaxKind.ComputedPropertyName) { + const nameExpression = name.expression; + // treat computed property names where expression is string/numeric literal as just string/numeric literal + if (isStringOrNumericLiteralLike(nameExpression)) { + return escapeLeadingUnderscores(nameExpression.text); } - if (isWellKnownSymbolSyntactically(name)) { - return getPropertyNameForKnownSymbolName(idText(name.name)); + if (isSignedNumericLiteral(nameExpression)) { + return tokenToString(nameExpression.operator) + nameExpression.operand.text as __String; } - return isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined; + Debug.assert(isWellKnownSymbolSyntactically(nameExpression)); + return getPropertyNameForKnownSymbolName(idText((nameExpression).name)); } - switch (node.kind) { - case SyntaxKind.Constructor: - return InternalSymbolName.Constructor; - case SyntaxKind.FunctionType: - case SyntaxKind.CallSignature: - case SyntaxKind.JSDocSignature: - return InternalSymbolName.Call; - case SyntaxKind.ConstructorType: - case SyntaxKind.ConstructSignature: - return InternalSymbolName.New; - case SyntaxKind.IndexSignature: - return InternalSymbolName.Index; - case SyntaxKind.ExportDeclaration: - return InternalSymbolName.ExportStar; - case SyntaxKind.SourceFile: - // json file should behave as + if (isWellKnownSymbolSyntactically(name)) { + return getPropertyNameForKnownSymbolName(idText(name.name)); + } + return isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined; + } + switch (node.kind) { + case SyntaxKind.Constructor: + return InternalSymbolName.Constructor; + case SyntaxKind.FunctionType: + case SyntaxKind.CallSignature: + case SyntaxKind.JSDocSignature: + return InternalSymbolName.Call; + case SyntaxKind.ConstructorType: + case SyntaxKind.ConstructSignature: + return InternalSymbolName.New; + case SyntaxKind.IndexSignature: + return InternalSymbolName.Index; + case SyntaxKind.ExportDeclaration: + return InternalSymbolName.ExportStar; + case SyntaxKind.SourceFile: + // json file should behave as + // module.exports = ... + return InternalSymbolName.ExportEquals; + case SyntaxKind.BinaryExpression: + if (getAssignmentDeclarationKind((node as BinaryExpression)) === AssignmentDeclarationKind.ModuleExports) { // module.exports = ... return InternalSymbolName.ExportEquals; - case SyntaxKind.BinaryExpression: - if (getAssignmentDeclarationKind(node as BinaryExpression) === AssignmentDeclarationKind.ModuleExports) { - // module.exports = ... - return InternalSymbolName.ExportEquals; - } - Debug.fail("Unknown binary declaration kind"); - break; - case SyntaxKind.JSDocFunctionType: - return (isJSDocConstructSignature(node) ? InternalSymbolName.New : InternalSymbolName.Call); - case SyntaxKind.Parameter: - // Parameters with names are handled at the top of this function. Parameters - // without names can only come from JSDocFunctionTypes. - Debug.assert(node.parent.kind === SyntaxKind.JSDocFunctionType, "Impossible parameter parent kind", () => `parent is: ${(ts as any).SyntaxKind ? (ts as any).SyntaxKind[node.parent.kind] : node.parent.kind}, expected JSDocFunctionType`); - const functionType = node.parent; - const index = functionType.parameters.indexOf(node as ParameterDeclaration); - return "arg" + index as __String; - } + } + Debug.fail("Unknown binary declaration kind"); + break; + case SyntaxKind.JSDocFunctionType: + return (isJSDocConstructSignature(node) ? InternalSymbolName.New : InternalSymbolName.Call); + case SyntaxKind.Parameter: + // Parameters with names are handled at the top of this function. Parameters + // without names can only come from JSDocFunctionTypes. + Debug.assert(node.parent.kind === SyntaxKind.JSDocFunctionType, "Impossible parameter parent kind", () => `parent is: ${(ts as any).SyntaxKind ? (ts as any).SyntaxKind[node.parent.kind] : node.parent.kind}, expected JSDocFunctionType`); + const functionType = (node.parent); + const index = functionType.parameters.indexOf((node as ParameterDeclaration)); + return "arg" + index as __String; } - - function getDisplayName(node: Declaration): string { - return isNamedDeclaration(node) ? declarationNameToString(node.name) : unescapeLeadingUnderscores(Debug.assertDefined(getDeclarationName(node))); - } - - /** - * Declares a Symbol for the node and adds it to symbols. Reports errors for conflicting identifier names. - * @param symbolTable - The symbol table which node will be added to. - * @param parent - node's parent declaration. - * @param node - The declaration to be added to the symbol table - * @param includes - The SymbolFlags that node has in addition to its declaration type (eg: export, ambient, etc.) - * @param excludes - The flags which node cannot be declared alongside in a symbol table. Used to report forbidden declarations. - */ - function declareSymbol(symbolTable: SymbolTable, parent: Symbol | undefined, node: Declaration, includes: SymbolFlags, excludes: SymbolFlags, isReplaceableByMethod?: boolean): Symbol { - Debug.assert(!hasDynamicName(node)); - - const isDefaultExport = hasModifier(node, ModifierFlags.Default); - - // The exported symbol for an export default function/class node is always named "default" - const name = isDefaultExport && parent ? InternalSymbolName.Default : getDeclarationName(node); - - let symbol: Symbol | undefined; - if (name === undefined) { - symbol = createSymbol(SymbolFlags.None, InternalSymbolName.Missing); + } + function getDisplayName(node: Declaration): string { + return isNamedDeclaration(node) ? declarationNameToString(node.name) : unescapeLeadingUnderscores(Debug.assertDefined(getDeclarationName(node))); + } + /** + * Declares a Symbol for the node and adds it to symbols. Reports errors for conflicting identifier names. + * @param symbolTable - The symbol table which node will be added to. + * @param parent - node's parent declaration. + * @param node - The declaration to be added to the symbol table + * @param includes - The SymbolFlags that node has in addition to its declaration type (eg: export, ambient, etc.) + * @param excludes - The flags which node cannot be declared alongside in a symbol table. Used to report forbidden declarations. + */ + function declareSymbol(symbolTable: SymbolTable, parent: ts.Symbol | undefined, node: Declaration, includes: SymbolFlags, excludes: SymbolFlags, isReplaceableByMethod?: boolean): ts.Symbol { + Debug.assert(!hasDynamicName(node)); + const isDefaultExport = hasModifier(node, ModifierFlags.Default); + // The exported symbol for an export default function/class node is always named "default" + const name = isDefaultExport && parent ? InternalSymbolName.Default : getDeclarationName(node); + let symbol: ts.Symbol | undefined; + if (name === undefined) { + symbol = createSymbol(SymbolFlags.None, InternalSymbolName.Missing); + } + else { + // Check and see if the symbol table already has a symbol with this name. If not, + // create a new symbol with this name and add it to the table. Note that we don't + // give the new symbol any flags *yet*. This ensures that it will not conflict + // with the 'excludes' flags we pass in. + // + // If we do get an existing symbol, see if it conflicts with the new symbol we're + // creating. For example, a 'var' symbol and a 'class' symbol will conflict within + // the same symbol table. If we have a conflict, report the issue on each + // declaration we have for this symbol, and then create a new symbol for this + // declaration. + // + // Note that when properties declared in Javascript constructors + // (marked by isReplaceableByMethod) conflict with another symbol, the property loses. + // Always. This allows the common Javascript pattern of overwriting a prototype method + // with an bound instance method of the same type: `this.method = this.method.bind(this)` + // + // If we created a new symbol, either because we didn't have a symbol with this name + // in the symbol table, or we conflicted with an existing symbol, then just add this + // node as the sole declaration of the new symbol. + // + // Otherwise, we'll be merging into a compatible existing symbol (for example when + // you have multiple 'vars' with the same name in the same container). In this case + // just add this node into the declarations list of the symbol. + symbol = symbolTable.get(name); + if (includes & SymbolFlags.Classifiable) { + classifiableNames.set(name, true); + } + if (!symbol) { + symbolTable.set(name, symbol = createSymbol(SymbolFlags.None, name)); + if (isReplaceableByMethod) + symbol.isReplaceableByMethod = true; + } + else if (isReplaceableByMethod && !symbol.isReplaceableByMethod) { + // A symbol already exists, so don't add this as a declaration. + return symbol; } - else { - // Check and see if the symbol table already has a symbol with this name. If not, - // create a new symbol with this name and add it to the table. Note that we don't - // give the new symbol any flags *yet*. This ensures that it will not conflict - // with the 'excludes' flags we pass in. - // - // If we do get an existing symbol, see if it conflicts with the new symbol we're - // creating. For example, a 'var' symbol and a 'class' symbol will conflict within - // the same symbol table. If we have a conflict, report the issue on each - // declaration we have for this symbol, and then create a new symbol for this - // declaration. - // - // Note that when properties declared in Javascript constructors - // (marked by isReplaceableByMethod) conflict with another symbol, the property loses. - // Always. This allows the common Javascript pattern of overwriting a prototype method - // with an bound instance method of the same type: `this.method = this.method.bind(this)` - // - // If we created a new symbol, either because we didn't have a symbol with this name - // in the symbol table, or we conflicted with an existing symbol, then just add this - // node as the sole declaration of the new symbol. - // - // Otherwise, we'll be merging into a compatible existing symbol (for example when - // you have multiple 'vars' with the same name in the same container). In this case - // just add this node into the declarations list of the symbol. - symbol = symbolTable.get(name); - - if (includes & SymbolFlags.Classifiable) { - classifiableNames.set(name, true); - } - - if (!symbol) { + else if (symbol.flags & excludes) { + if (symbol.isReplaceableByMethod) { + // Javascript constructor-declared symbols can be discarded in favor of + // prototype symbols like methods. symbolTable.set(name, symbol = createSymbol(SymbolFlags.None, name)); - if (isReplaceableByMethod) symbol.isReplaceableByMethod = true; - } - else if (isReplaceableByMethod && !symbol.isReplaceableByMethod) { - // A symbol already exists, so don't add this as a declaration. - return symbol; } - else if (symbol.flags & excludes) { - if (symbol.isReplaceableByMethod) { - // Javascript constructor-declared symbols can be discarded in favor of - // prototype symbols like methods. - symbolTable.set(name, symbol = createSymbol(SymbolFlags.None, name)); + else if (!(includes & SymbolFlags.Variable && symbol.flags & SymbolFlags.Assignment)) { + // Assignment declarations are allowed to merge with variables, no matter what other flags they have. + if (isNamedDeclaration(node)) { + node.name.parent = node; } - else if (!(includes & SymbolFlags.Variable && symbol.flags & SymbolFlags.Assignment)) { - // Assignment declarations are allowed to merge with variables, no matter what other flags they have. - if (isNamedDeclaration(node)) { - node.name.parent = node; - } - - // Report errors every position with duplicate declaration - // Report errors on previous encountered declarations - let message = symbol.flags & SymbolFlags.BlockScopedVariable - ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 - : Diagnostics.Duplicate_identifier_0; - let messageNeedsName = true; - - if (symbol.flags & SymbolFlags.Enum || includes & SymbolFlags.Enum) { - message = Diagnostics.Enum_declarations_can_only_merge_with_namespace_or_other_enum_declarations; + // Report errors every position with duplicate declaration + // Report errors on previous encountered declarations + let message = symbol.flags & SymbolFlags.BlockScopedVariable + ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 + : Diagnostics.Duplicate_identifier_0; + let messageNeedsName = true; + if (symbol.flags & SymbolFlags.Enum || includes & SymbolFlags.Enum) { + message = Diagnostics.Enum_declarations_can_only_merge_with_namespace_or_other_enum_declarations; + messageNeedsName = false; + } + let multipleDefaultExports = false; + if (length(symbol.declarations)) { + // If the current node is a default export of some sort, then check if + // there are any other default exports that we need to error on. + // We'll know whether we have other default exports depending on if `symbol` already has a declaration list set. + if (isDefaultExport) { + message = Diagnostics.A_module_cannot_have_multiple_default_exports; messageNeedsName = false; + multipleDefaultExports = true; } - - let multipleDefaultExports = false; - if (length(symbol.declarations)) { - // If the current node is a default export of some sort, then check if - // there are any other default exports that we need to error on. - // We'll know whether we have other default exports depending on if `symbol` already has a declaration list set. - if (isDefaultExport) { + else { + // This is to properly report an error in the case "export default { }" is after export default of class declaration or function declaration. + // Error on multiple export default in the following case: + // 1. multiple export default of class declaration or function declaration by checking NodeFlags.Default + // 2. multiple export default of export assignment. This one doesn't have NodeFlags.Default on (as export default doesn't considered as modifiers) + if (symbol.declarations && symbol.declarations.length && + (node.kind === SyntaxKind.ExportAssignment && !(node).isExportEquals)) { message = Diagnostics.A_module_cannot_have_multiple_default_exports; messageNeedsName = false; multipleDefaultExports = true; } - else { - // This is to properly report an error in the case "export default { }" is after export default of class declaration or function declaration. - // Error on multiple export default in the following case: - // 1. multiple export default of class declaration or function declaration by checking NodeFlags.Default - // 2. multiple export default of export assignment. This one doesn't have NodeFlags.Default on (as export default doesn't considered as modifiers) - if (symbol.declarations && symbol.declarations.length && - (node.kind === SyntaxKind.ExportAssignment && !(node).isExportEquals)) { - message = Diagnostics.A_module_cannot_have_multiple_default_exports; - messageNeedsName = false; - multipleDefaultExports = true; - } - } } - - const declarationName = getNameOfDeclaration(node) || node; - const relatedInformation: DiagnosticRelatedInformation[] = []; - forEach(symbol.declarations, (declaration, index) => { - const decl = getNameOfDeclaration(declaration) || declaration; - const diag = createDiagnosticForNode(decl, message, messageNeedsName ? getDisplayName(declaration) : undefined); - file.bindDiagnostics.push( - multipleDefaultExports ? addRelatedInfo(diag, createDiagnosticForNode(declarationName, index === 0 ? Diagnostics.Another_export_default_is_here : Diagnostics.and_here)) : diag - ); - if (multipleDefaultExports) { - relatedInformation.push(createDiagnosticForNode(decl, Diagnostics.The_first_export_default_is_here)); - } - }); - - const diag = createDiagnosticForNode(declarationName, message, messageNeedsName ? getDisplayName(node) : undefined); - file.bindDiagnostics.push(multipleDefaultExports ? addRelatedInfo(diag, ...relatedInformation) : diag); - - symbol = createSymbol(SymbolFlags.None, name); } + const declarationName = getNameOfDeclaration(node) || node; + const relatedInformation: DiagnosticRelatedInformation[] = []; + forEach(symbol.declarations, (declaration, index) => { + const decl = getNameOfDeclaration(declaration) || declaration; + const diag = createDiagnosticForNode(decl, message, messageNeedsName ? getDisplayName(declaration) : undefined); + file.bindDiagnostics.push(multipleDefaultExports ? addRelatedInfo(diag, createDiagnosticForNode(declarationName, index === 0 ? Diagnostics.Another_export_default_is_here : Diagnostics.and_here)) : diag); + if (multipleDefaultExports) { + relatedInformation.push(createDiagnosticForNode(decl, Diagnostics.The_first_export_default_is_here)); + } + }); + const diag = createDiagnosticForNode(declarationName, message, messageNeedsName ? getDisplayName(node) : undefined); + file.bindDiagnostics.push(multipleDefaultExports ? addRelatedInfo(diag, ...relatedInformation) : diag); + symbol = createSymbol(SymbolFlags.None, name); } } - - addDeclarationToSymbol(symbol, node, includes); - if (symbol.parent) { - Debug.assert(symbol.parent === parent, "Existing symbol parent should match new one"); - } - else { - symbol.parent = parent; - } - - return symbol; } - - function declareModuleMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): Symbol { - const hasExportModifier = getCombinedModifierFlags(node) & ModifierFlags.Export; - if (symbolFlags & SymbolFlags.Alias) { - if (node.kind === SyntaxKind.ExportSpecifier || (node.kind === SyntaxKind.ImportEqualsDeclaration && hasExportModifier)) { - return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); - } - else { - return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); - } + addDeclarationToSymbol(symbol, node, includes); + if (symbol.parent) { + Debug.assert(symbol.parent === parent, "Existing symbol parent should match new one"); + } + else { + symbol.parent = parent; + } + return symbol; + } + function declareModuleMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): ts.Symbol { + const hasExportModifier = getCombinedModifierFlags(node) & ModifierFlags.Export; + if (symbolFlags & SymbolFlags.Alias) { + if (node.kind === SyntaxKind.ExportSpecifier || (node.kind === SyntaxKind.ImportEqualsDeclaration && hasExportModifier)) { + return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); } else { - // Exported module members are given 2 symbols: A local symbol that is classified with an ExportValue flag, - // and an associated export symbol with all the correct flags set on it. There are 2 main reasons: - // - // 1. We treat locals and exports of the same name as mutually exclusive within a container. - // That means the binder will issue a Duplicate Identifier error if you mix locals and exports - // with the same name in the same container. - // TODO: Make this a more specific error and decouple it from the exclusion logic. - // 2. When we checkIdentifier in the checker, we set its resolved symbol to the local symbol, - // but return the export symbol (by calling getExportSymbolOfValueSymbolIfExported). That way - // when the emitter comes back to it, it knows not to qualify the name if it was found in a containing scope. - - // NOTE: Nested ambient modules always should go to to 'locals' table to prevent their automatic merge - // during global merging in the checker. Why? The only case when ambient module is permitted inside another module is module augmentation - // and this case is specially handled. Module augmentations should only be merged with original module definition - // and should never be merged directly with other augmentation, and the latter case would be possible if automatic merge is allowed. - if (isJSDocTypeAlias(node)) Debug.assert(isInJSFile(node)); // We shouldn't add symbols for JSDoc nodes if not in a JS file. - if ((!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) || isJSDocTypeAlias(node)) { - if (!container.locals || (hasModifier(node, ModifierFlags.Default) && !getDeclarationName(node))) { - return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); // No local symbol for an unnamed default! - } - const exportKind = symbolFlags & SymbolFlags.Value ? SymbolFlags.ExportValue : 0; - const local = declareSymbol(container.locals, /*parent*/ undefined, node, exportKind, symbolExcludes); - local.exportSymbol = declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); - node.localSymbol = local; - return local; - } - else { - return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); - } + return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); } } - - // All container nodes are kept on a linked list in declaration order. This list is used by - // the getLocalNameOfContainer function in the type checker to validate that the local name - // used for a container is unique. - function bindContainer(node: Node, containerFlags: ContainerFlags) { - // Before we recurse into a node's children, we first save the existing parent, container - // and block-container. Then after we pop out of processing the children, we restore - // these saved values. - const saveContainer = container; - const saveThisParentContainer = thisParentContainer; - const savedBlockScopeContainer = blockScopeContainer; - - // Depending on what kind of node this is, we may have to adjust the current container - // and block-container. If the current node is a container, then it is automatically - // considered the current block-container as well. Also, for containers that we know - // may contain locals, we eagerly initialize the .locals field. We do this because - // it's highly likely that the .locals will be needed to place some child in (for example, - // a parameter, or variable declaration). - // - // However, we do not proactively create the .locals for block-containers because it's - // totally normal and common for block-containers to never actually have a block-scoped - // variable in them. We don't want to end up allocating an object for every 'block' we - // run into when most of them won't be necessary. + else { + // Exported module members are given 2 symbols: A local symbol that is classified with an ExportValue flag, + // and an associated export symbol with all the correct flags set on it. There are 2 main reasons: // - // Finally, if this is a block-container, then we clear out any existing .locals object - // it may contain within it. This happens in incremental scenarios. Because we can be - // reusing a node from a previous compilation, that node may have had 'locals' created - // for it. We must clear this so we don't accidentally move any stale data forward from - // a previous compilation. - if (containerFlags & ContainerFlags.IsContainer) { - if (node.kind !== SyntaxKind.ArrowFunction) { - thisParentContainer = container; - } - container = blockScopeContainer = node; - if (containerFlags & ContainerFlags.HasLocals) { - container.locals = createSymbolTable(); - } - addToContainerChain(container); + // 1. We treat locals and exports of the same name as mutually exclusive within a container. + // That means the binder will issue a Duplicate Identifier error if you mix locals and exports + // with the same name in the same container. + // TODO: Make this a more specific error and decouple it from the exclusion logic. + // 2. When we checkIdentifier in the checker, we set its resolved symbol to the local symbol, + // but return the export symbol (by calling getExportSymbolOfValueSymbolIfExported). That way + // when the emitter comes back to it, it knows not to qualify the name if it was found in a containing scope. + // NOTE: Nested ambient modules always should go to to 'locals' table to prevent their automatic merge + // during global merging in the checker. Why? The only case when ambient module is permitted inside another module is module augmentation + // and this case is specially handled. Module augmentations should only be merged with original module definition + // and should never be merged directly with other augmentation, and the latter case would be possible if automatic merge is allowed. + if (isJSDocTypeAlias(node)) + Debug.assert(isInJSFile(node)); // We shouldn't add symbols for JSDoc nodes if not in a JS file. + if ((!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) || isJSDocTypeAlias(node)) { + if (!container.locals || (hasModifier(node, ModifierFlags.Default) && !getDeclarationName(node))) { + return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); // No local symbol for an unnamed default! + } + const exportKind = symbolFlags & SymbolFlags.Value ? SymbolFlags.ExportValue : 0; + const local = declareSymbol(container.locals, /*parent*/ undefined, node, exportKind, symbolExcludes); + local.exportSymbol = declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); + node.localSymbol = local; + return local; } - else if (containerFlags & ContainerFlags.IsBlockScopedContainer) { - blockScopeContainer = node; - blockScopeContainer.locals = undefined; + else { + return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); } - if (containerFlags & ContainerFlags.IsControlFlowContainer) { - const saveCurrentFlow = currentFlow; - const saveBreakTarget = currentBreakTarget; - const saveContinueTarget = currentContinueTarget; - const saveReturnTarget = currentReturnTarget; - const saveExceptionTarget = currentExceptionTarget; - const saveActiveLabelList = activeLabelList; - const saveHasExplicitReturn = hasExplicitReturn; - const isIIFE = containerFlags & ContainerFlags.IsFunctionExpression && !hasModifier(node, ModifierFlags.Async) && - !(node).asteriskToken && !!getImmediatelyInvokedFunctionExpression(node); - // A non-async, non-generator IIFE is considered part of the containing control flow. Return statements behave - // similarly to break statements that exit to a label just past the statement body. - if (!isIIFE) { - currentFlow = initFlowNode({ flags: FlowFlags.Start }); - if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethod)) { - currentFlow.node = node; - } - } - // We create a return control flow graph for IIFEs and constructors. For constructors - // we use the return control flow graph in strict property initialization checks. - currentReturnTarget = isIIFE || node.kind === SyntaxKind.Constructor ? createBranchLabel() : undefined; - currentExceptionTarget = undefined; - currentBreakTarget = undefined; - currentContinueTarget = undefined; - activeLabelList = undefined; - hasExplicitReturn = false; - bindChildren(node); - // Reset all reachability check related flags on node (for incremental scenarios) - node.flags &= ~NodeFlags.ReachabilityAndEmitFlags; - if (!(currentFlow.flags & FlowFlags.Unreachable) && containerFlags & ContainerFlags.IsFunctionLike && nodeIsPresent((node).body)) { - node.flags |= NodeFlags.HasImplicitReturn; - if (hasExplicitReturn) node.flags |= NodeFlags.HasExplicitReturn; - (node).endFlowNode = currentFlow; - } - if (node.kind === SyntaxKind.SourceFile) { - node.flags |= emitFlags; - } - - if (currentReturnTarget) { - addAntecedent(currentReturnTarget, currentFlow); - currentFlow = finishFlowLabel(currentReturnTarget); - if (node.kind === SyntaxKind.Constructor) { - (node).returnFlowNode = currentFlow; - } - } - if (!isIIFE) { - currentFlow = saveCurrentFlow; + } + } + // All container nodes are kept on a linked list in declaration order. This list is used by + // the getLocalNameOfContainer function in the type checker to validate that the local name + // used for a container is unique. + function bindContainer(node: Node, containerFlags: ContainerFlags) { + // Before we recurse into a node's children, we first save the existing parent, container + // and block-container. Then after we pop out of processing the children, we restore + // these saved values. + const saveContainer = container; + const saveThisParentContainer = thisParentContainer; + const savedBlockScopeContainer = blockScopeContainer; + // Depending on what kind of node this is, we may have to adjust the current container + // and block-container. If the current node is a container, then it is automatically + // considered the current block-container as well. Also, for containers that we know + // may contain locals, we eagerly initialize the .locals field. We do this because + // it's highly likely that the .locals will be needed to place some child in (for example, + // a parameter, or variable declaration). + // + // However, we do not proactively create the .locals for block-containers because it's + // totally normal and common for block-containers to never actually have a block-scoped + // variable in them. We don't want to end up allocating an object for every 'block' we + // run into when most of them won't be necessary. + // + // Finally, if this is a block-container, then we clear out any existing .locals object + // it may contain within it. This happens in incremental scenarios. Because we can be + // reusing a node from a previous compilation, that node may have had 'locals' created + // for it. We must clear this so we don't accidentally move any stale data forward from + // a previous compilation. + if (containerFlags & ContainerFlags.IsContainer) { + if (node.kind !== SyntaxKind.ArrowFunction) { + thisParentContainer = container; + } + container = blockScopeContainer = node; + if (containerFlags & ContainerFlags.HasLocals) { + container.locals = createSymbolTable(); + } + addToContainerChain(container); + } + else if (containerFlags & ContainerFlags.IsBlockScopedContainer) { + blockScopeContainer = node; + blockScopeContainer.locals = undefined; + } + if (containerFlags & ContainerFlags.IsControlFlowContainer) { + const saveCurrentFlow = currentFlow; + const saveBreakTarget = currentBreakTarget; + const saveContinueTarget = currentContinueTarget; + const saveReturnTarget = currentReturnTarget; + const saveExceptionTarget = currentExceptionTarget; + const saveActiveLabelList = activeLabelList; + const saveHasExplicitReturn = hasExplicitReturn; + const isIIFE = containerFlags & ContainerFlags.IsFunctionExpression && !hasModifier(node, ModifierFlags.Async) && + !(node).asteriskToken && !!getImmediatelyInvokedFunctionExpression(node); + // A non-async, non-generator IIFE is considered part of the containing control flow. Return statements behave + // similarly to break statements that exit to a label just past the statement body. + if (!isIIFE) { + currentFlow = initFlowNode({ flags: FlowFlags.Start }); + if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethod)) { + currentFlow.node = (node); } - currentBreakTarget = saveBreakTarget; - currentContinueTarget = saveContinueTarget; - currentReturnTarget = saveReturnTarget; - currentExceptionTarget = saveExceptionTarget; - activeLabelList = saveActiveLabelList; - hasExplicitReturn = saveHasExplicitReturn; } - else if (containerFlags & ContainerFlags.IsInterface) { - seenThisKeyword = false; - bindChildren(node); - node.flags = seenThisKeyword ? node.flags | NodeFlags.ContainsThis : node.flags & ~NodeFlags.ContainsThis; - } - else { - bindChildren(node); + // We create a return control flow graph for IIFEs and constructors. For constructors + // we use the return control flow graph in strict property initialization checks. + currentReturnTarget = isIIFE || node.kind === SyntaxKind.Constructor ? createBranchLabel() : undefined; + currentExceptionTarget = undefined; + currentBreakTarget = undefined; + currentContinueTarget = undefined; + activeLabelList = undefined; + hasExplicitReturn = false; + bindChildren(node); + // Reset all reachability check related flags on node (for incremental scenarios) + node.flags &= ~NodeFlags.ReachabilityAndEmitFlags; + if (!(currentFlow.flags & FlowFlags.Unreachable) && containerFlags & ContainerFlags.IsFunctionLike && nodeIsPresent((node).body)) { + node.flags |= NodeFlags.HasImplicitReturn; + if (hasExplicitReturn) + node.flags |= NodeFlags.HasExplicitReturn; + (node).endFlowNode = currentFlow; } - - container = saveContainer; - thisParentContainer = saveThisParentContainer; - blockScopeContainer = savedBlockScopeContainer; - } - - function bindChildren(node: Node): void { - if (skipTransformFlagAggregation) { - bindChildrenWorker(node); + if (node.kind === SyntaxKind.SourceFile) { + node.flags |= emitFlags; } - else if (node.transformFlags & TransformFlags.HasComputedFlags) { - skipTransformFlagAggregation = true; - bindChildrenWorker(node); - skipTransformFlagAggregation = false; - subtreeTransformFlags |= node.transformFlags & ~getTransformFlagsSubtreeExclusions(node.kind); + if (currentReturnTarget) { + addAntecedent(currentReturnTarget, currentFlow); + currentFlow = finishFlowLabel(currentReturnTarget); + if (node.kind === SyntaxKind.Constructor) { + (node).returnFlowNode = currentFlow; + } } - else { - const savedSubtreeTransformFlags = subtreeTransformFlags; - subtreeTransformFlags = 0; - bindChildrenWorker(node); - subtreeTransformFlags = savedSubtreeTransformFlags | computeTransformFlagsForNode(node, subtreeTransformFlags); + if (!isIIFE) { + currentFlow = saveCurrentFlow; } + currentBreakTarget = saveBreakTarget; + currentContinueTarget = saveContinueTarget; + currentReturnTarget = saveReturnTarget; + currentExceptionTarget = saveExceptionTarget; + activeLabelList = saveActiveLabelList; + hasExplicitReturn = saveHasExplicitReturn; } - - function bindEachFunctionsFirst(nodes: NodeArray | undefined): void { - bindEach(nodes, n => n.kind === SyntaxKind.FunctionDeclaration ? bind(n) : undefined); - bindEach(nodes, n => n.kind !== SyntaxKind.FunctionDeclaration ? bind(n) : undefined); + else if (containerFlags & ContainerFlags.IsInterface) { + seenThisKeyword = false; + bindChildren(node); + node.flags = seenThisKeyword ? node.flags | NodeFlags.ContainsThis : node.flags & ~NodeFlags.ContainsThis; } - - function bindEach(nodes: NodeArray | undefined, bindFunction: (node: Node) => void = bind): void { - if (nodes === undefined) { - return; - } - - if (skipTransformFlagAggregation) { - forEach(nodes, bindFunction); - } - else { - const savedSubtreeTransformFlags = subtreeTransformFlags; - subtreeTransformFlags = TransformFlags.None; - let nodeArrayFlags = TransformFlags.None; - for (const node of nodes) { - bindFunction(node); - nodeArrayFlags |= node.transformFlags & ~TransformFlags.HasComputedFlags; - } - nodes.transformFlags = nodeArrayFlags | TransformFlags.HasComputedFlags; - subtreeTransformFlags |= savedSubtreeTransformFlags; - } + else { + bindChildren(node); } - - function bindEachChild(node: Node) { - forEachChild(node, bind, bindEach); + container = saveContainer; + thisParentContainer = saveThisParentContainer; + blockScopeContainer = savedBlockScopeContainer; + } + function bindChildren(node: Node): void { + if (skipTransformFlagAggregation) { + bindChildrenWorker(node); } - - function bindChildrenWorker(node: Node): void { - if (checkUnreachable(node)) { - bindEachChild(node); - bindJSDoc(node); - return; - } - if (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement && !options.allowUnreachableCode) { - node.flowNode = currentFlow; - } - switch (node.kind) { - case SyntaxKind.WhileStatement: - bindWhileStatement(node); - break; - case SyntaxKind.DoStatement: - bindDoStatement(node); - break; - case SyntaxKind.ForStatement: - bindForStatement(node); - break; - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - bindForInOrForOfStatement(node); - break; - case SyntaxKind.IfStatement: - bindIfStatement(node); - break; - case SyntaxKind.ReturnStatement: - case SyntaxKind.ThrowStatement: - bindReturnOrThrow(node); - break; - case SyntaxKind.BreakStatement: - case SyntaxKind.ContinueStatement: - bindBreakOrContinueStatement(node); - break; - case SyntaxKind.TryStatement: - bindTryStatement(node); - break; - case SyntaxKind.SwitchStatement: - bindSwitchStatement(node); - break; - case SyntaxKind.CaseBlock: - bindCaseBlock(node); - break; - case SyntaxKind.CaseClause: - bindCaseClause(node); - break; - case SyntaxKind.ExpressionStatement: - bindExpressionStatement(node); - break; - case SyntaxKind.LabeledStatement: - bindLabeledStatement(node); - break; - case SyntaxKind.PrefixUnaryExpression: - bindPrefixUnaryExpressionFlow(node); - break; - case SyntaxKind.PostfixUnaryExpression: - bindPostfixUnaryExpressionFlow(node); - break; - case SyntaxKind.BinaryExpression: - bindBinaryExpressionFlow(node); - break; - case SyntaxKind.DeleteExpression: - bindDeleteExpressionFlow(node); - break; - case SyntaxKind.ConditionalExpression: - bindConditionalExpressionFlow(node); - break; - case SyntaxKind.VariableDeclaration: - bindVariableDeclarationFlow(node); - break; - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - bindAccessExpressionFlow(node); - break; - case SyntaxKind.CallExpression: - bindCallExpressionFlow(node); - break; - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocEnumTag: - bindJSDocTypeAlias(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag); - break; - // In source files and blocks, bind functions first to match hoisting that occurs at runtime - case SyntaxKind.SourceFile: { - bindEachFunctionsFirst((node as SourceFile).statements); - bind((node as SourceFile).endOfFileToken); - break; - } - case SyntaxKind.Block: - case SyntaxKind.ModuleBlock: - bindEachFunctionsFirst((node as Block).statements); - break; - default: - bindEachChild(node); - break; - } - bindJSDoc(node); + else if (node.transformFlags & TransformFlags.HasComputedFlags) { + skipTransformFlagAggregation = true; + bindChildrenWorker(node); + skipTransformFlagAggregation = false; + subtreeTransformFlags |= node.transformFlags & ~getTransformFlagsSubtreeExclusions(node.kind); } - - function isNarrowingExpression(expr: Expression): boolean { - switch (expr.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.ThisKeyword: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - return isNarrowableReference(expr); - case SyntaxKind.CallExpression: - return hasNarrowableArgument(expr); - case SyntaxKind.ParenthesizedExpression: - return isNarrowingExpression((expr).expression); - case SyntaxKind.BinaryExpression: - return isNarrowingBinaryExpression(expr); - case SyntaxKind.PrefixUnaryExpression: - return (expr).operator === SyntaxKind.ExclamationToken && isNarrowingExpression((expr).operand); - case SyntaxKind.TypeOfExpression: - return isNarrowingExpression((expr).expression); - } - return false; + else { + const savedSubtreeTransformFlags = subtreeTransformFlags; + subtreeTransformFlags = 0; + bindChildrenWorker(node); + subtreeTransformFlags = savedSubtreeTransformFlags | computeTransformFlagsForNode(node, subtreeTransformFlags); } - - function isNarrowableReference(expr: Expression): boolean { - return expr.kind === SyntaxKind.Identifier || expr.kind === SyntaxKind.ThisKeyword || expr.kind === SyntaxKind.SuperKeyword || - (isPropertyAccessExpression(expr) || isNonNullExpression(expr) || isParenthesizedExpression(expr)) && isNarrowableReference(expr.expression) || - isElementAccessExpression(expr) && isStringOrNumericLiteralLike(expr.argumentExpression) && isNarrowableReference(expr.expression) || - isOptionalChain(expr); - } - - function hasNarrowableArgument(expr: CallExpression) { - if (expr.arguments) { - for (const argument of expr.arguments) { - if (isNarrowableReference(argument)) { - return true; - } - } - } - if (expr.expression.kind === SyntaxKind.PropertyAccessExpression && - isNarrowableReference((expr.expression).expression)) { - return true; - } - return false; + } + function bindEachFunctionsFirst(nodes: NodeArray | undefined): void { + bindEach(nodes, n => n.kind === SyntaxKind.FunctionDeclaration ? bind(n) : undefined); + bindEach(nodes, n => n.kind !== SyntaxKind.FunctionDeclaration ? bind(n) : undefined); + } + function bindEach(nodes: NodeArray | undefined, bindFunction: (node: Node) => void = bind): void { + if (nodes === undefined) { + return; } - - function isNarrowingTypeofOperands(expr1: Expression, expr2: Expression) { - return isTypeOfExpression(expr1) && isNarrowableOperand(expr1.expression) && isStringLiteralLike(expr2); - } - - function isNarrowableInOperands(left: Expression, right: Expression) { - return isStringLiteralLike(left) && isNarrowingExpression(right); - } - - function isNarrowingBinaryExpression(expr: BinaryExpression) { - switch (expr.operatorToken.kind) { - case SyntaxKind.EqualsToken: - return isNarrowableReference(expr.left); - case SyntaxKind.EqualsEqualsToken: - case SyntaxKind.ExclamationEqualsToken: - case SyntaxKind.EqualsEqualsEqualsToken: - case SyntaxKind.ExclamationEqualsEqualsToken: - return isNarrowableOperand(expr.left) || isNarrowableOperand(expr.right) || - isNarrowingTypeofOperands(expr.right, expr.left) || isNarrowingTypeofOperands(expr.left, expr.right); - case SyntaxKind.InstanceOfKeyword: - return isNarrowableOperand(expr.left); - case SyntaxKind.InKeyword: - return isNarrowableInOperands(expr.left, expr.right); - case SyntaxKind.CommaToken: - return isNarrowingExpression(expr.right); - } - return false; + if (skipTransformFlagAggregation) { + forEach(nodes, bindFunction); } - - function isNarrowableOperand(expr: Expression): boolean { - switch (expr.kind) { - case SyntaxKind.ParenthesizedExpression: - return isNarrowableOperand((expr).expression); - case SyntaxKind.BinaryExpression: - switch ((expr).operatorToken.kind) { - case SyntaxKind.EqualsToken: - return isNarrowableOperand((expr).left); - case SyntaxKind.CommaToken: - return isNarrowableOperand((expr).right); - } - } - return isNarrowableReference(expr); - } - - function createBranchLabel(): FlowLabel { - return initFlowNode({ flags: FlowFlags.BranchLabel, antecedents: undefined }); - } - - function createLoopLabel(): FlowLabel { - return initFlowNode({ flags: FlowFlags.LoopLabel, antecedents: undefined }); - } - - function setFlowNodeReferenced(flow: FlowNode) { - // On first reference we set the Referenced flag, thereafter we set the Shared flag - flow.flags |= flow.flags & FlowFlags.Referenced ? FlowFlags.Shared : FlowFlags.Referenced; - } - - function addAntecedent(label: FlowLabel, antecedent: FlowNode): void { - if (!(antecedent.flags & FlowFlags.Unreachable) && !contains(label.antecedents, antecedent)) { - (label.antecedents || (label.antecedents = [])).push(antecedent); - setFlowNodeReferenced(antecedent); + else { + const savedSubtreeTransformFlags = subtreeTransformFlags; + subtreeTransformFlags = TransformFlags.None; + let nodeArrayFlags = TransformFlags.None; + for (const node of nodes) { + bindFunction(node); + nodeArrayFlags |= node.transformFlags & ~TransformFlags.HasComputedFlags; } + nodes.transformFlags = nodeArrayFlags | TransformFlags.HasComputedFlags; + subtreeTransformFlags |= savedSubtreeTransformFlags; } - - function createFlowCondition(flags: FlowFlags, antecedent: FlowNode, expression: Expression | undefined): FlowNode { - if (antecedent.flags & FlowFlags.Unreachable) { - return antecedent; - } - if (!expression) { - return flags & FlowFlags.TrueCondition ? antecedent : unreachableFlow; - } - if ((expression.kind === SyntaxKind.TrueKeyword && flags & FlowFlags.FalseCondition || - expression.kind === SyntaxKind.FalseKeyword && flags & FlowFlags.TrueCondition) && - !isExpressionOfOptionalChainRoot(expression) && !isNullishCoalesce(expression.parent)) { - return unreachableFlow; - } - if (!isNarrowingExpression(expression)) { - return antecedent; - } - setFlowNodeReferenced(antecedent); - return initFlowNode({ flags, antecedent, node: expression }); + } + function bindEachChild(node: Node) { + forEachChild(node, bind, bindEach); + } + function bindChildrenWorker(node: Node): void { + if (checkUnreachable(node)) { + bindEachChild(node); + bindJSDoc(node); + return; } - - function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): FlowNode { - setFlowNodeReferenced(antecedent); - return initFlowNode({ flags: FlowFlags.SwitchClause, antecedent, switchStatement, clauseStart, clauseEnd }); + if (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement && !options.allowUnreachableCode) { + node.flowNode = currentFlow; } - - function createFlowMutation(flags: FlowFlags, antecedent: FlowNode, node: Node): FlowNode { - setFlowNodeReferenced(antecedent); - const result = initFlowNode({ flags, antecedent, node }); - if (currentExceptionTarget) { - addAntecedent(currentExceptionTarget, result); + switch (node.kind) { + case SyntaxKind.WhileStatement: + bindWhileStatement((node)); + break; + case SyntaxKind.DoStatement: + bindDoStatement((node)); + break; + case SyntaxKind.ForStatement: + bindForStatement((node)); + break; + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + bindForInOrForOfStatement((node)); + break; + case SyntaxKind.IfStatement: + bindIfStatement((node)); + break; + case SyntaxKind.ReturnStatement: + case SyntaxKind.ThrowStatement: + bindReturnOrThrow((node)); + break; + case SyntaxKind.BreakStatement: + case SyntaxKind.ContinueStatement: + bindBreakOrContinueStatement((node)); + break; + case SyntaxKind.TryStatement: + bindTryStatement((node)); + break; + case SyntaxKind.SwitchStatement: + bindSwitchStatement((node)); + break; + case SyntaxKind.CaseBlock: + bindCaseBlock((node)); + break; + case SyntaxKind.CaseClause: + bindCaseClause((node)); + break; + case SyntaxKind.ExpressionStatement: + bindExpressionStatement((node)); + break; + case SyntaxKind.LabeledStatement: + bindLabeledStatement((node)); + break; + case SyntaxKind.PrefixUnaryExpression: + bindPrefixUnaryExpressionFlow((node)); + break; + case SyntaxKind.PostfixUnaryExpression: + bindPostfixUnaryExpressionFlow((node)); + break; + case SyntaxKind.BinaryExpression: + bindBinaryExpressionFlow((node)); + break; + case SyntaxKind.DeleteExpression: + bindDeleteExpressionFlow((node)); + break; + case SyntaxKind.ConditionalExpression: + bindConditionalExpressionFlow((node)); + break; + case SyntaxKind.VariableDeclaration: + bindVariableDeclarationFlow((node)); + break; + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + bindAccessExpressionFlow((node)); + break; + case SyntaxKind.CallExpression: + bindCallExpressionFlow((node)); + break; + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocEnumTag: + bindJSDocTypeAlias((node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag)); + break; + // In source files and blocks, bind functions first to match hoisting that occurs at runtime + case SyntaxKind.SourceFile: { + bindEachFunctionsFirst((node as SourceFile).statements); + bind((node as SourceFile).endOfFileToken); + break; } - return result; + case SyntaxKind.Block: + case SyntaxKind.ModuleBlock: + bindEachFunctionsFirst((node as Block).statements); + break; + default: + bindEachChild(node); + break; } - - function createFlowCall(antecedent: FlowNode, node: CallExpression): FlowNode { - setFlowNodeReferenced(antecedent); - return initFlowNode({ flags: FlowFlags.Call, antecedent, node }); + bindJSDoc(node); + } + function isNarrowingExpression(expr: Expression): boolean { + switch (expr.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.ThisKeyword: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return isNarrowableReference(expr); + case SyntaxKind.CallExpression: + return hasNarrowableArgument((expr)); + case SyntaxKind.ParenthesizedExpression: + return isNarrowingExpression((expr).expression); + case SyntaxKind.BinaryExpression: + return isNarrowingBinaryExpression((expr)); + case SyntaxKind.PrefixUnaryExpression: + return (expr).operator === SyntaxKind.ExclamationToken && isNarrowingExpression((expr).operand); + case SyntaxKind.TypeOfExpression: + return isNarrowingExpression((expr).expression); } - - function finishFlowLabel(flow: FlowLabel): FlowNode { - const antecedents = flow.antecedents; - if (!antecedents) { - return unreachableFlow; - } - if (antecedents.length === 1) { - return antecedents[0]; - } - return flow; - } - - function isStatementCondition(node: Node) { - const parent = node.parent; - switch (parent.kind) { - case SyntaxKind.IfStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.DoStatement: - return (parent).expression === node; - case SyntaxKind.ForStatement: - case SyntaxKind.ConditionalExpression: - return (parent).condition === node; + return false; + } + function isNarrowableReference(expr: Expression): boolean { + return expr.kind === SyntaxKind.Identifier || expr.kind === SyntaxKind.ThisKeyword || expr.kind === SyntaxKind.SuperKeyword || + (isPropertyAccessExpression(expr) || isNonNullExpression(expr) || isParenthesizedExpression(expr)) && isNarrowableReference(expr.expression) || + isElementAccessExpression(expr) && isStringOrNumericLiteralLike(expr.argumentExpression) && isNarrowableReference(expr.expression) || + isOptionalChain(expr); + } + function hasNarrowableArgument(expr: CallExpression) { + if (expr.arguments) { + for (const argument of expr.arguments) { + if (isNarrowableReference(argument)) { + return true; + } } - return false; } - - function isLogicalExpression(node: Node) { - while (true) { - if (node.kind === SyntaxKind.ParenthesizedExpression) { - node = (node).expression; - } - else if (node.kind === SyntaxKind.PrefixUnaryExpression && (node).operator === SyntaxKind.ExclamationToken) { - node = (node).operand; - } - else { - return node.kind === SyntaxKind.BinaryExpression && ( - (node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || - (node).operatorToken.kind === SyntaxKind.BarBarToken || - (node).operatorToken.kind === SyntaxKind.QuestionQuestionToken); + if (expr.expression.kind === SyntaxKind.PropertyAccessExpression && + isNarrowableReference((expr.expression).expression)) { + return true; + } + return false; + } + function isNarrowingTypeofOperands(expr1: Expression, expr2: Expression) { + return isTypeOfExpression(expr1) && isNarrowableOperand(expr1.expression) && isStringLiteralLike(expr2); + } + function isNarrowableInOperands(left: Expression, right: Expression) { + return isStringLiteralLike(left) && isNarrowingExpression(right); + } + function isNarrowingBinaryExpression(expr: BinaryExpression) { + switch (expr.operatorToken.kind) { + case SyntaxKind.EqualsToken: + return isNarrowableReference(expr.left); + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + return isNarrowableOperand(expr.left) || isNarrowableOperand(expr.right) || + isNarrowingTypeofOperands(expr.right, expr.left) || isNarrowingTypeofOperands(expr.left, expr.right); + case SyntaxKind.InstanceOfKeyword: + return isNarrowableOperand(expr.left); + case SyntaxKind.InKeyword: + return isNarrowableInOperands(expr.left, expr.right); + case SyntaxKind.CommaToken: + return isNarrowingExpression(expr.right); + } + return false; + } + function isNarrowableOperand(expr: Expression): boolean { + switch (expr.kind) { + case SyntaxKind.ParenthesizedExpression: + return isNarrowableOperand((expr).expression); + case SyntaxKind.BinaryExpression: + switch ((expr).operatorToken.kind) { + case SyntaxKind.EqualsToken: + return isNarrowableOperand((expr).left); + case SyntaxKind.CommaToken: + return isNarrowableOperand((expr).right); } - } } - - function isTopLevelLogicalExpression(node: Node): boolean { - while (isParenthesizedExpression(node.parent) || - isPrefixUnaryExpression(node.parent) && node.parent.operator === SyntaxKind.ExclamationToken) { - node = node.parent; - } - return !isStatementCondition(node) && - !isLogicalExpression(node.parent) && - !(isOptionalChain(node.parent) && node.parent.expression === node); - } - - function doWithConditionalBranches(action: (value: T) => void, value: T, trueTarget: FlowLabel, falseTarget: FlowLabel) { - const savedTrueTarget = currentTrueTarget; - const savedFalseTarget = currentFalseTarget; - currentTrueTarget = trueTarget; - currentFalseTarget = falseTarget; - action(value); - currentTrueTarget = savedTrueTarget; - currentFalseTarget = savedFalseTarget; - } - - function bindCondition(node: Expression | undefined, trueTarget: FlowLabel, falseTarget: FlowLabel) { - doWithConditionalBranches(bind, node, trueTarget, falseTarget); - if (!node || !isLogicalExpression(node) && !(isOptionalChain(node) && isOutermostOptionalChain(node))) { - addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); - addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); - } + return isNarrowableReference(expr); + } + function createBranchLabel(): FlowLabel { + return initFlowNode({ flags: FlowFlags.BranchLabel, antecedents: undefined }); + } + function createLoopLabel(): FlowLabel { + return initFlowNode({ flags: FlowFlags.LoopLabel, antecedents: undefined }); + } + function setFlowNodeReferenced(flow: FlowNode) { + // On first reference we set the Referenced flag, thereafter we set the Shared flag + flow.flags |= flow.flags & FlowFlags.Referenced ? FlowFlags.Shared : FlowFlags.Referenced; + } + function addAntecedent(label: FlowLabel, antecedent: FlowNode): void { + if (!(antecedent.flags & FlowFlags.Unreachable) && !contains(label.antecedents, antecedent)) { + (label.antecedents || (label.antecedents = [])).push(antecedent); + setFlowNodeReferenced(antecedent); } - - function bindIterativeStatement(node: Statement, breakTarget: FlowLabel, continueTarget: FlowLabel): void { - const saveBreakTarget = currentBreakTarget; - const saveContinueTarget = currentContinueTarget; - currentBreakTarget = breakTarget; - currentContinueTarget = continueTarget; - bind(node); - currentBreakTarget = saveBreakTarget; - currentContinueTarget = saveContinueTarget; + } + function createFlowCondition(flags: FlowFlags, antecedent: FlowNode, expression: Expression | undefined): FlowNode { + if (antecedent.flags & FlowFlags.Unreachable) { + return antecedent; } - - function setContinueTarget(node: Node, target: FlowLabel) { - let label = activeLabelList; - while (label && node.parent.kind === SyntaxKind.LabeledStatement) { - label.continueTarget = target; - label = label.next; - node = node.parent; - } - return target; - } - - function bindWhileStatement(node: WhileStatement): void { - const preWhileLabel = setContinueTarget(node, createLoopLabel()); - const preBodyLabel = createBranchLabel(); - const postWhileLabel = createBranchLabel(); - addAntecedent(preWhileLabel, currentFlow); - currentFlow = preWhileLabel; - bindCondition(node.expression, preBodyLabel, postWhileLabel); - currentFlow = finishFlowLabel(preBodyLabel); - bindIterativeStatement(node.statement, postWhileLabel, preWhileLabel); - addAntecedent(preWhileLabel, currentFlow); - currentFlow = finishFlowLabel(postWhileLabel); - } - - function bindDoStatement(node: DoStatement): void { - const preDoLabel = createLoopLabel(); - const preConditionLabel = setContinueTarget(node, createBranchLabel()); - const postDoLabel = createBranchLabel(); - addAntecedent(preDoLabel, currentFlow); - currentFlow = preDoLabel; - bindIterativeStatement(node.statement, postDoLabel, preConditionLabel); - addAntecedent(preConditionLabel, currentFlow); - currentFlow = finishFlowLabel(preConditionLabel); - bindCondition(node.expression, preDoLabel, postDoLabel); - currentFlow = finishFlowLabel(postDoLabel); - } - - function bindForStatement(node: ForStatement): void { - const preLoopLabel = setContinueTarget(node, createLoopLabel()); - const preBodyLabel = createBranchLabel(); - const postLoopLabel = createBranchLabel(); - bind(node.initializer); - addAntecedent(preLoopLabel, currentFlow); - currentFlow = preLoopLabel; - bindCondition(node.condition, preBodyLabel, postLoopLabel); - currentFlow = finishFlowLabel(preBodyLabel); - bindIterativeStatement(node.statement, postLoopLabel, preLoopLabel); - bind(node.incrementor); - addAntecedent(preLoopLabel, currentFlow); - currentFlow = finishFlowLabel(postLoopLabel); - } - - function bindForInOrForOfStatement(node: ForInOrOfStatement): void { - const preLoopLabel = setContinueTarget(node, createLoopLabel()); - const postLoopLabel = createBranchLabel(); - bind(node.expression); - addAntecedent(preLoopLabel, currentFlow); - currentFlow = preLoopLabel; - if (node.kind === SyntaxKind.ForOfStatement) { - bind(node.awaitModifier); + if (!expression) { + return flags & FlowFlags.TrueCondition ? antecedent : unreachableFlow; + } + if ((expression.kind === SyntaxKind.TrueKeyword && flags & FlowFlags.FalseCondition || + expression.kind === SyntaxKind.FalseKeyword && flags & FlowFlags.TrueCondition) && + !isExpressionOfOptionalChainRoot(expression) && !isNullishCoalesce(expression.parent)) { + return unreachableFlow; + } + if (!isNarrowingExpression(expression)) { + return antecedent; + } + setFlowNodeReferenced(antecedent); + return initFlowNode({ flags, antecedent, node: expression }); + } + function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): FlowNode { + setFlowNodeReferenced(antecedent); + return initFlowNode({ flags: FlowFlags.SwitchClause, antecedent, switchStatement, clauseStart, clauseEnd }); + } + function createFlowMutation(flags: FlowFlags, antecedent: FlowNode, node: Node): FlowNode { + setFlowNodeReferenced(antecedent); + const result = initFlowNode({ flags, antecedent, node }); + if (currentExceptionTarget) { + addAntecedent(currentExceptionTarget, result); + } + return result; + } + function createFlowCall(antecedent: FlowNode, node: CallExpression): FlowNode { + setFlowNodeReferenced(antecedent); + return initFlowNode({ flags: FlowFlags.Call, antecedent, node }); + } + function finishFlowLabel(flow: FlowLabel): FlowNode { + const antecedents = flow.antecedents; + if (!antecedents) { + return unreachableFlow; + } + if (antecedents.length === 1) { + return antecedents[0]; + } + return flow; + } + function isStatementCondition(node: Node) { + const parent = node.parent; + switch (parent.kind) { + case SyntaxKind.IfStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.DoStatement: + return (parent).expression === node; + case SyntaxKind.ForStatement: + case SyntaxKind.ConditionalExpression: + return (parent).condition === node; + } + return false; + } + function isLogicalExpression(node: Node) { + while (true) { + if (node.kind === SyntaxKind.ParenthesizedExpression) { + node = (node).expression; } - addAntecedent(postLoopLabel, currentFlow); - bind(node.initializer); - if (node.initializer.kind !== SyntaxKind.VariableDeclarationList) { - bindAssignmentTargetFlow(node.initializer); + else if (node.kind === SyntaxKind.PrefixUnaryExpression && (node).operator === SyntaxKind.ExclamationToken) { + node = (node).operand; } - bindIterativeStatement(node.statement, postLoopLabel, preLoopLabel); - addAntecedent(preLoopLabel, currentFlow); - currentFlow = finishFlowLabel(postLoopLabel); - } - - function bindIfStatement(node: IfStatement): void { - const thenLabel = createBranchLabel(); - const elseLabel = createBranchLabel(); - const postIfLabel = createBranchLabel(); - bindCondition(node.expression, thenLabel, elseLabel); - currentFlow = finishFlowLabel(thenLabel); - bind(node.thenStatement); - addAntecedent(postIfLabel, currentFlow); - currentFlow = finishFlowLabel(elseLabel); - bind(node.elseStatement); - addAntecedent(postIfLabel, currentFlow); - currentFlow = finishFlowLabel(postIfLabel); - } - - function bindReturnOrThrow(node: ReturnStatement | ThrowStatement): void { - bind(node.expression); - if (node.kind === SyntaxKind.ReturnStatement) { - hasExplicitReturn = true; - if (currentReturnTarget) { - addAntecedent(currentReturnTarget, currentFlow); - } + else { + return node.kind === SyntaxKind.BinaryExpression && ((node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || + (node).operatorToken.kind === SyntaxKind.BarBarToken || + (node).operatorToken.kind === SyntaxKind.QuestionQuestionToken); } - currentFlow = unreachableFlow; } - - function findActiveLabel(name: __String) { - for (let label = activeLabelList; label; label = label.next) { - if (label.name === name) { - return label; - } - } - return undefined; - } - - function bindBreakOrContinueFlow(node: BreakOrContinueStatement, breakTarget: FlowLabel | undefined, continueTarget: FlowLabel | undefined) { - const flowLabel = node.kind === SyntaxKind.BreakStatement ? breakTarget : continueTarget; - if (flowLabel) { - addAntecedent(flowLabel, currentFlow); - currentFlow = unreachableFlow; + } + function isTopLevelLogicalExpression(node: Node): boolean { + while (isParenthesizedExpression(node.parent) || + isPrefixUnaryExpression(node.parent) && node.parent.operator === SyntaxKind.ExclamationToken) { + node = node.parent; + } + return !isStatementCondition(node) && + !isLogicalExpression(node.parent) && + !(isOptionalChain(node.parent) && node.parent.expression === node); + } + function doWithConditionalBranches(action: (value: T) => void, value: T, trueTarget: FlowLabel, falseTarget: FlowLabel) { + const savedTrueTarget = currentTrueTarget; + const savedFalseTarget = currentFalseTarget; + currentTrueTarget = trueTarget; + currentFalseTarget = falseTarget; + action(value); + currentTrueTarget = savedTrueTarget; + currentFalseTarget = savedFalseTarget; + } + function bindCondition(node: Expression | undefined, trueTarget: FlowLabel, falseTarget: FlowLabel) { + doWithConditionalBranches(bind, node, trueTarget, falseTarget); + if (!node || !isLogicalExpression(node) && !(isOptionalChain(node) && isOutermostOptionalChain(node))) { + addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); + addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); + } + } + function bindIterativeStatement(node: Statement, breakTarget: FlowLabel, continueTarget: FlowLabel): void { + const saveBreakTarget = currentBreakTarget; + const saveContinueTarget = currentContinueTarget; + currentBreakTarget = breakTarget; + currentContinueTarget = continueTarget; + bind(node); + currentBreakTarget = saveBreakTarget; + currentContinueTarget = saveContinueTarget; + } + function setContinueTarget(node: Node, target: FlowLabel) { + let label = activeLabelList; + while (label && node.parent.kind === SyntaxKind.LabeledStatement) { + label.continueTarget = target; + label = label.next; + node = node.parent; + } + return target; + } + function bindWhileStatement(node: WhileStatement): void { + const preWhileLabel = setContinueTarget(node, createLoopLabel()); + const preBodyLabel = createBranchLabel(); + const postWhileLabel = createBranchLabel(); + addAntecedent(preWhileLabel, currentFlow); + currentFlow = preWhileLabel; + bindCondition(node.expression, preBodyLabel, postWhileLabel); + currentFlow = finishFlowLabel(preBodyLabel); + bindIterativeStatement(node.statement, postWhileLabel, preWhileLabel); + addAntecedent(preWhileLabel, currentFlow); + currentFlow = finishFlowLabel(postWhileLabel); + } + function bindDoStatement(node: DoStatement): void { + const preDoLabel = createLoopLabel(); + const preConditionLabel = setContinueTarget(node, createBranchLabel()); + const postDoLabel = createBranchLabel(); + addAntecedent(preDoLabel, currentFlow); + currentFlow = preDoLabel; + bindIterativeStatement(node.statement, postDoLabel, preConditionLabel); + addAntecedent(preConditionLabel, currentFlow); + currentFlow = finishFlowLabel(preConditionLabel); + bindCondition(node.expression, preDoLabel, postDoLabel); + currentFlow = finishFlowLabel(postDoLabel); + } + function bindForStatement(node: ForStatement): void { + const preLoopLabel = setContinueTarget(node, createLoopLabel()); + const preBodyLabel = createBranchLabel(); + const postLoopLabel = createBranchLabel(); + bind(node.initializer); + addAntecedent(preLoopLabel, currentFlow); + currentFlow = preLoopLabel; + bindCondition(node.condition, preBodyLabel, postLoopLabel); + currentFlow = finishFlowLabel(preBodyLabel); + bindIterativeStatement(node.statement, postLoopLabel, preLoopLabel); + bind(node.incrementor); + addAntecedent(preLoopLabel, currentFlow); + currentFlow = finishFlowLabel(postLoopLabel); + } + function bindForInOrForOfStatement(node: ForInOrOfStatement): void { + const preLoopLabel = setContinueTarget(node, createLoopLabel()); + const postLoopLabel = createBranchLabel(); + bind(node.expression); + addAntecedent(preLoopLabel, currentFlow); + currentFlow = preLoopLabel; + if (node.kind === SyntaxKind.ForOfStatement) { + bind(node.awaitModifier); + } + addAntecedent(postLoopLabel, currentFlow); + bind(node.initializer); + if (node.initializer.kind !== SyntaxKind.VariableDeclarationList) { + bindAssignmentTargetFlow(node.initializer); + } + bindIterativeStatement(node.statement, postLoopLabel, preLoopLabel); + addAntecedent(preLoopLabel, currentFlow); + currentFlow = finishFlowLabel(postLoopLabel); + } + function bindIfStatement(node: IfStatement): void { + const thenLabel = createBranchLabel(); + const elseLabel = createBranchLabel(); + const postIfLabel = createBranchLabel(); + bindCondition(node.expression, thenLabel, elseLabel); + currentFlow = finishFlowLabel(thenLabel); + bind(node.thenStatement); + addAntecedent(postIfLabel, currentFlow); + currentFlow = finishFlowLabel(elseLabel); + bind(node.elseStatement); + addAntecedent(postIfLabel, currentFlow); + currentFlow = finishFlowLabel(postIfLabel); + } + function bindReturnOrThrow(node: ReturnStatement | ThrowStatement): void { + bind(node.expression); + if (node.kind === SyntaxKind.ReturnStatement) { + hasExplicitReturn = true; + if (currentReturnTarget) { + addAntecedent(currentReturnTarget, currentFlow); } } - - function bindBreakOrContinueStatement(node: BreakOrContinueStatement): void { - bind(node.label); - if (node.label) { - const activeLabel = findActiveLabel(node.label.escapedText); - if (activeLabel) { - activeLabel.referenced = true; - bindBreakOrContinueFlow(node, activeLabel.breakTarget, activeLabel.continueTarget); - } + currentFlow = unreachableFlow; + } + function findActiveLabel(name: __String) { + for (let label = activeLabelList; label; label = label.next) { + if (label.name === name) { + return label; } - else { - bindBreakOrContinueFlow(node, currentBreakTarget, currentContinueTarget); + } + return undefined; + } + function bindBreakOrContinueFlow(node: BreakOrContinueStatement, breakTarget: FlowLabel | undefined, continueTarget: FlowLabel | undefined) { + const flowLabel = node.kind === SyntaxKind.BreakStatement ? breakTarget : continueTarget; + if (flowLabel) { + addAntecedent(flowLabel, currentFlow); + currentFlow = unreachableFlow; + } + } + function bindBreakOrContinueStatement(node: BreakOrContinueStatement): void { + bind(node.label); + if (node.label) { + const activeLabel = findActiveLabel(node.label.escapedText); + if (activeLabel) { + activeLabel.referenced = true; + bindBreakOrContinueFlow(node, activeLabel.breakTarget, activeLabel.continueTarget); } } - - function bindTryStatement(node: TryStatement): void { - const preFinallyLabel = createBranchLabel(); - // We conservatively assume that *any* code in the try block can cause an exception, but we only need - // to track code that causes mutations (because only mutations widen the possible control flow type of - // a variable). The currentExceptionTarget is the target label for control flows that result from - // exceptions. We add all mutation flow nodes as antecedents of this label such that we can analyze them - // as possible antecedents of the start of catch or finally blocks. Furthermore, we add the current - // control flow to represent exceptions that occur before any mutations. - const saveExceptionTarget = currentExceptionTarget; + else { + bindBreakOrContinueFlow(node, currentBreakTarget, currentContinueTarget); + } + } + function bindTryStatement(node: TryStatement): void { + const preFinallyLabel = createBranchLabel(); + // We conservatively assume that *any* code in the try block can cause an exception, but we only need + // to track code that causes mutations (because only mutations widen the possible control flow type of + // a variable). The currentExceptionTarget is the target label for control flows that result from + // exceptions. We add all mutation flow nodes as antecedents of this label such that we can analyze them + // as possible antecedents of the start of catch or finally blocks. Furthermore, we add the current + // control flow to represent exceptions that occur before any mutations. + const saveExceptionTarget = currentExceptionTarget; + currentExceptionTarget = createBranchLabel(); + addAntecedent(currentExceptionTarget, currentFlow); + bind(node.tryBlock); + addAntecedent(preFinallyLabel, currentFlow); + const flowAfterTry = currentFlow; + let flowAfterCatch = unreachableFlow; + if (node.catchClause) { + // Start of catch clause is the target of exceptions from try block. + currentFlow = finishFlowLabel(currentExceptionTarget); + // The currentExceptionTarget now represents control flows from exceptions in the catch clause. + // Effectively, in a try-catch-finally, if an exception occurs in the try block, the catch block + // acts like a second try block. currentExceptionTarget = createBranchLabel(); addAntecedent(currentExceptionTarget, currentFlow); - bind(node.tryBlock); + bind(node.catchClause); addAntecedent(preFinallyLabel, currentFlow); - const flowAfterTry = currentFlow; - let flowAfterCatch = unreachableFlow; - if (node.catchClause) { - // Start of catch clause is the target of exceptions from try block. - currentFlow = finishFlowLabel(currentExceptionTarget); - // The currentExceptionTarget now represents control flows from exceptions in the catch clause. - // Effectively, in a try-catch-finally, if an exception occurs in the try block, the catch block - // acts like a second try block. - currentExceptionTarget = createBranchLabel(); - addAntecedent(currentExceptionTarget, currentFlow); - bind(node.catchClause); - addAntecedent(preFinallyLabel, currentFlow); - flowAfterCatch = currentFlow; - } - const exceptionTarget = finishFlowLabel(currentExceptionTarget); - currentExceptionTarget = saveExceptionTarget; - if (node.finallyBlock) { - // Possible ways control can reach the finally block: - // 1) Normal completion of try block of a try-finally or try-catch-finally - // 2) Normal completion of catch block (following exception in try block) of a try-catch-finally - // 3) Exception in try block of a try-finally - // 4) Exception in catch block of a try-catch-finally - // When analyzing a control flow graph that starts inside a finally block we want to consider all - // four possibilities above. However, when analyzing a control flow graph that starts outside (past) - // the finally block, we only want to consider the first two (if we're past a finally block then it - // must have completed normally). To make this possible, we inject two extra nodes into the control - // flow graph: An after-finally with an antecedent of the control flow at the end of the finally - // block, and a pre-finally with an antecedent that represents all exceptional control flows. The - // 'lock' property of the pre-finally references the after-finally, and the after-finally has a - // boolean 'locked' property that we set to true when analyzing a control flow that contained the - // the after-finally node. When the lock associated with a pre-finally is locked, the antecedent of - // the pre-finally (i.e. the exceptional control flows) are skipped. - const preFinallyFlow: PreFinallyFlow = initFlowNode({ flags: FlowFlags.PreFinally, antecedent: exceptionTarget, lock: {} }); - addAntecedent(preFinallyLabel, preFinallyFlow); - currentFlow = finishFlowLabel(preFinallyLabel); - bind(node.finallyBlock); - // If the end of the finally block is reachable, but the end of the try and catch blocks are not, - // convert the current flow to unreachable. For example, 'try { return 1; } finally { ... }' should - // result in an unreachable current control flow. - if (!(currentFlow.flags & FlowFlags.Unreachable)) { - if ((flowAfterTry.flags & FlowFlags.Unreachable) && (flowAfterCatch.flags & FlowFlags.Unreachable)) { - currentFlow = flowAfterTry === reportedUnreachableFlow || flowAfterCatch === reportedUnreachableFlow - ? reportedUnreachableFlow - : unreachableFlow; - } - } - if (!(currentFlow.flags & FlowFlags.Unreachable)) { - const afterFinallyFlow: AfterFinallyFlow = initFlowNode({ flags: FlowFlags.AfterFinally, antecedent: currentFlow }); - preFinallyFlow.lock = afterFinallyFlow; - currentFlow = afterFinallyFlow; + flowAfterCatch = currentFlow; + } + const exceptionTarget = finishFlowLabel(currentExceptionTarget); + currentExceptionTarget = saveExceptionTarget; + if (node.finallyBlock) { + // Possible ways control can reach the finally block: + // 1) Normal completion of try block of a try-finally or try-catch-finally + // 2) Normal completion of catch block (following exception in try block) of a try-catch-finally + // 3) Exception in try block of a try-finally + // 4) Exception in catch block of a try-catch-finally + // When analyzing a control flow graph that starts inside a finally block we want to consider all + // four possibilities above. However, when analyzing a control flow graph that starts outside (past) + // the finally block, we only want to consider the first two (if we're past a finally block then it + // must have completed normally). To make this possible, we inject two extra nodes into the control + // flow graph: An after-finally with an antecedent of the control flow at the end of the finally + // block, and a pre-finally with an antecedent that represents all exceptional control flows. The + // 'lock' property of the pre-finally references the after-finally, and the after-finally has a + // boolean 'locked' property that we set to true when analyzing a control flow that contained the + // the after-finally node. When the lock associated with a pre-finally is locked, the antecedent of + // the pre-finally (i.e. the exceptional control flows) are skipped. + const preFinallyFlow: PreFinallyFlow = initFlowNode({ flags: FlowFlags.PreFinally, antecedent: exceptionTarget, lock: {} }); + addAntecedent(preFinallyLabel, preFinallyFlow); + currentFlow = finishFlowLabel(preFinallyLabel); + bind(node.finallyBlock); + // If the end of the finally block is reachable, but the end of the try and catch blocks are not, + // convert the current flow to unreachable. For example, 'try { return 1; } finally { ... }' should + // result in an unreachable current control flow. + if (!(currentFlow.flags & FlowFlags.Unreachable)) { + if ((flowAfterTry.flags & FlowFlags.Unreachable) && (flowAfterCatch.flags & FlowFlags.Unreachable)) { + currentFlow = flowAfterTry === reportedUnreachableFlow || flowAfterCatch === reportedUnreachableFlow + ? reportedUnreachableFlow + : unreachableFlow; } } - else { - currentFlow = finishFlowLabel(preFinallyLabel); + if (!(currentFlow.flags & FlowFlags.Unreachable)) { + const afterFinallyFlow: AfterFinallyFlow = initFlowNode({ flags: FlowFlags.AfterFinally, antecedent: currentFlow }); + preFinallyFlow.lock = afterFinallyFlow; + currentFlow = afterFinallyFlow; } } - - function bindSwitchStatement(node: SwitchStatement): void { - const postSwitchLabel = createBranchLabel(); - bind(node.expression); - const saveBreakTarget = currentBreakTarget; - const savePreSwitchCaseFlow = preSwitchCaseFlow; - currentBreakTarget = postSwitchLabel; - preSwitchCaseFlow = currentFlow; - bind(node.caseBlock); - addAntecedent(postSwitchLabel, currentFlow); - const hasDefault = forEach(node.caseBlock.clauses, c => c.kind === SyntaxKind.DefaultClause); - // We mark a switch statement as possibly exhaustive if it has no default clause and if all - // case clauses have unreachable end points (e.g. they all return). Note, we no longer need - // this property in control flow analysis, it's there only for backwards compatibility. - node.possiblyExhaustive = !hasDefault && !postSwitchLabel.antecedents; - if (!hasDefault) { - addAntecedent(postSwitchLabel, createFlowSwitchClause(preSwitchCaseFlow, node, 0, 0)); - } - currentBreakTarget = saveBreakTarget; - preSwitchCaseFlow = savePreSwitchCaseFlow; - currentFlow = finishFlowLabel(postSwitchLabel); + else { + currentFlow = finishFlowLabel(preFinallyLabel); } - - function bindCaseBlock(node: CaseBlock): void { - const savedSubtreeTransformFlags = subtreeTransformFlags; - subtreeTransformFlags = 0; - const clauses = node.clauses; - const isNarrowingSwitch = isNarrowingExpression(node.parent.expression); - let fallthroughFlow = unreachableFlow; - for (let i = 0; i < clauses.length; i++) { - const clauseStart = i; - while (!clauses[i].statements.length && i + 1 < clauses.length) { - bind(clauses[i]); - i++; - } - const preCaseLabel = createBranchLabel(); - addAntecedent(preCaseLabel, isNarrowingSwitch ? createFlowSwitchClause(preSwitchCaseFlow!, node.parent, clauseStart, i + 1) : preSwitchCaseFlow!); - addAntecedent(preCaseLabel, fallthroughFlow); - currentFlow = finishFlowLabel(preCaseLabel); - const clause = clauses[i]; - bind(clause); - fallthroughFlow = currentFlow; - if (!(currentFlow.flags & FlowFlags.Unreachable) && i !== clauses.length - 1 && options.noFallthroughCasesInSwitch) { - clause.fallthroughFlowNode = currentFlow; - } + } + function bindSwitchStatement(node: SwitchStatement): void { + const postSwitchLabel = createBranchLabel(); + bind(node.expression); + const saveBreakTarget = currentBreakTarget; + const savePreSwitchCaseFlow = preSwitchCaseFlow; + currentBreakTarget = postSwitchLabel; + preSwitchCaseFlow = currentFlow; + bind(node.caseBlock); + addAntecedent(postSwitchLabel, currentFlow); + const hasDefault = forEach(node.caseBlock.clauses, c => c.kind === SyntaxKind.DefaultClause); + // We mark a switch statement as possibly exhaustive if it has no default clause and if all + // case clauses have unreachable end points (e.g. they all return). Note, we no longer need + // this property in control flow analysis, it's there only for backwards compatibility. + node.possiblyExhaustive = !hasDefault && !postSwitchLabel.antecedents; + if (!hasDefault) { + addAntecedent(postSwitchLabel, createFlowSwitchClause(preSwitchCaseFlow, node, 0, 0)); + } + currentBreakTarget = saveBreakTarget; + preSwitchCaseFlow = savePreSwitchCaseFlow; + currentFlow = finishFlowLabel(postSwitchLabel); + } + function bindCaseBlock(node: CaseBlock): void { + const savedSubtreeTransformFlags = subtreeTransformFlags; + subtreeTransformFlags = 0; + const clauses = node.clauses; + const isNarrowingSwitch = isNarrowingExpression(node.parent.expression); + let fallthroughFlow = unreachableFlow; + for (let i = 0; i < clauses.length; i++) { + const clauseStart = i; + while (!clauses[i].statements.length && i + 1 < clauses.length) { + bind(clauses[i]); + i++; + } + const preCaseLabel = createBranchLabel(); + addAntecedent(preCaseLabel, isNarrowingSwitch ? createFlowSwitchClause(preSwitchCaseFlow!, node.parent, clauseStart, i + 1) : preSwitchCaseFlow!); + addAntecedent(preCaseLabel, fallthroughFlow); + currentFlow = finishFlowLabel(preCaseLabel); + const clause = clauses[i]; + bind(clause); + fallthroughFlow = currentFlow; + if (!(currentFlow.flags & FlowFlags.Unreachable) && i !== clauses.length - 1 && options.noFallthroughCasesInSwitch) { + clause.fallthroughFlowNode = currentFlow; + } + } + clauses.transformFlags = subtreeTransformFlags | TransformFlags.HasComputedFlags; + subtreeTransformFlags |= savedSubtreeTransformFlags; + } + function bindCaseClause(node: CaseClause): void { + const saveCurrentFlow = currentFlow; + currentFlow = preSwitchCaseFlow!; + bind(node.expression); + currentFlow = saveCurrentFlow; + bindEach(node.statements); + } + function bindExpressionStatement(node: ExpressionStatement): void { + bind(node.expression); + // A top level call expression with a dotted function name and at least one argument + // is potentially an assertion and is therefore included in the control flow. + if (node.expression.kind === SyntaxKind.CallExpression) { + const call = (node.expression); + if (isDottedName(call.expression)) { + currentFlow = createFlowCall(currentFlow, call); } - clauses.transformFlags = subtreeTransformFlags | TransformFlags.HasComputedFlags; - subtreeTransformFlags |= savedSubtreeTransformFlags; } - - function bindCaseClause(node: CaseClause): void { - const saveCurrentFlow = currentFlow; - currentFlow = preSwitchCaseFlow!; - bind(node.expression); - currentFlow = saveCurrentFlow; - bindEach(node.statements); - } - - function bindExpressionStatement(node: ExpressionStatement): void { - bind(node.expression); - // A top level call expression with a dotted function name and at least one argument - // is potentially an assertion and is therefore included in the control flow. - if (node.expression.kind === SyntaxKind.CallExpression) { - const call = node.expression; - if (isDottedName(call.expression)) { - currentFlow = createFlowCall(currentFlow, call); - } - } + } + function bindLabeledStatement(node: LabeledStatement): void { + const postStatementLabel = createBranchLabel(); + activeLabelList = { + next: activeLabelList, + name: node.label.escapedText, + breakTarget: postStatementLabel, + continueTarget: undefined, + referenced: false + }; + bind(node.label); + bind(node.statement); + if (!activeLabelList.referenced && !options.allowUnusedLabels) { + errorOrSuggestionOnNode(unusedLabelIsError(options), node.label, Diagnostics.Unused_label); + } + activeLabelList = activeLabelList.next; + addAntecedent(postStatementLabel, currentFlow); + currentFlow = finishFlowLabel(postStatementLabel); + } + function bindDestructuringTargetFlow(node: Expression) { + if (node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.EqualsToken) { + bindAssignmentTargetFlow((node).left); } - - function bindLabeledStatement(node: LabeledStatement): void { - const postStatementLabel = createBranchLabel(); - activeLabelList = { - next: activeLabelList, - name: node.label.escapedText, - breakTarget: postStatementLabel, - continueTarget: undefined, - referenced: false - }; - bind(node.label); - bind(node.statement); - if (!activeLabelList.referenced && !options.allowUnusedLabels) { - errorOrSuggestionOnNode(unusedLabelIsError(options), node.label, Diagnostics.Unused_label); - } - activeLabelList = activeLabelList.next; - addAntecedent(postStatementLabel, currentFlow); - currentFlow = finishFlowLabel(postStatementLabel); - } - - function bindDestructuringTargetFlow(node: Expression) { - if (node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.EqualsToken) { - bindAssignmentTargetFlow((node).left); - } - else { - bindAssignmentTargetFlow(node); - } + else { + bindAssignmentTargetFlow(node); } - - function bindAssignmentTargetFlow(node: Expression) { - if (isNarrowableReference(node)) { - currentFlow = createFlowMutation(FlowFlags.Assignment, currentFlow, node); - } - else if (node.kind === SyntaxKind.ArrayLiteralExpression) { - for (const e of (node).elements) { - if (e.kind === SyntaxKind.SpreadElement) { - bindAssignmentTargetFlow((e).expression); - } - else { - bindDestructuringTargetFlow(e); - } - } - } - else if (node.kind === SyntaxKind.ObjectLiteralExpression) { - for (const p of (node).properties) { - if (p.kind === SyntaxKind.PropertyAssignment) { - bindDestructuringTargetFlow(p.initializer); - } - else if (p.kind === SyntaxKind.ShorthandPropertyAssignment) { - bindAssignmentTargetFlow(p.name); - } - else if (p.kind === SyntaxKind.SpreadAssignment) { - bindAssignmentTargetFlow(p.expression); - } - } - } + } + function bindAssignmentTargetFlow(node: Expression) { + if (isNarrowableReference(node)) { + currentFlow = createFlowMutation(FlowFlags.Assignment, currentFlow, node); } - - function bindLogicalExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) { - const preRightLabel = createBranchLabel(); - if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { - bindCondition(node.left, preRightLabel, falseTarget); - } - else { - bindCondition(node.left, trueTarget, preRightLabel); - } - currentFlow = finishFlowLabel(preRightLabel); - bind(node.operatorToken); - bindCondition(node.right, trueTarget, falseTarget); - } - - function bindPrefixUnaryExpressionFlow(node: PrefixUnaryExpression) { - if (node.operator === SyntaxKind.ExclamationToken) { - const saveTrueTarget = currentTrueTarget; - currentTrueTarget = currentFalseTarget; - currentFalseTarget = saveTrueTarget; - bindEachChild(node); - currentFalseTarget = currentTrueTarget; - currentTrueTarget = saveTrueTarget; - } - else { - bindEachChild(node); - if (node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) { - bindAssignmentTargetFlow(node.operand); + else if (node.kind === SyntaxKind.ArrayLiteralExpression) { + for (const e of (node).elements) { + if (e.kind === SyntaxKind.SpreadElement) { + bindAssignmentTargetFlow((e).expression); + } + else { + bindDestructuringTargetFlow(e); } } } - - function bindPostfixUnaryExpressionFlow(node: PostfixUnaryExpression) { - bindEachChild(node); - if (node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) { - bindAssignmentTargetFlow(node.operand); - } - } - - function bindBinaryExpressionFlow(node: BinaryExpression) { - const operator = node.operatorToken.kind; - if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) { - if (isTopLevelLogicalExpression(node)) { - const postExpressionLabel = createBranchLabel(); - bindLogicalExpression(node, postExpressionLabel, postExpressionLabel); - currentFlow = finishFlowLabel(postExpressionLabel); + else if (node.kind === SyntaxKind.ObjectLiteralExpression) { + for (const p of (node).properties) { + if (p.kind === SyntaxKind.PropertyAssignment) { + bindDestructuringTargetFlow(p.initializer); } - else { - bindLogicalExpression(node, currentTrueTarget!, currentFalseTarget!); + else if (p.kind === SyntaxKind.ShorthandPropertyAssignment) { + bindAssignmentTargetFlow(p.name); } - } - else { - bindEachChild(node); - if (isAssignmentOperator(operator) && !isAssignmentTarget(node)) { - bindAssignmentTargetFlow(node.left); - if (operator === SyntaxKind.EqualsToken && node.left.kind === SyntaxKind.ElementAccessExpression) { - const elementAccess = node.left; - if (isNarrowableOperand(elementAccess.expression)) { - currentFlow = createFlowMutation(FlowFlags.ArrayMutation, currentFlow, node); - } - } + else if (p.kind === SyntaxKind.SpreadAssignment) { + bindAssignmentTargetFlow(p.expression); } } } - - function bindDeleteExpressionFlow(node: DeleteExpression) { - bindEachChild(node); - if (node.expression.kind === SyntaxKind.PropertyAccessExpression) { - bindAssignmentTargetFlow(node.expression); - } - } - - function bindConditionalExpressionFlow(node: ConditionalExpression) { - const trueLabel = createBranchLabel(); - const falseLabel = createBranchLabel(); - const postExpressionLabel = createBranchLabel(); - bindCondition(node.condition, trueLabel, falseLabel); - currentFlow = finishFlowLabel(trueLabel); - bind(node.questionToken); - bind(node.whenTrue); - addAntecedent(postExpressionLabel, currentFlow); - currentFlow = finishFlowLabel(falseLabel); - bind(node.colonToken); - bind(node.whenFalse); - addAntecedent(postExpressionLabel, currentFlow); - currentFlow = finishFlowLabel(postExpressionLabel); + } + function bindLogicalExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) { + const preRightLabel = createBranchLabel(); + if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { + bindCondition(node.left, preRightLabel, falseTarget); } - - function bindInitializedVariableFlow(node: VariableDeclaration | ArrayBindingElement) { - const name = !isOmittedExpression(node) ? node.name : undefined; - if (isBindingPattern(name)) { - for (const child of name.elements) { - bindInitializedVariableFlow(child); - } - } - else { - currentFlow = createFlowMutation(FlowFlags.Assignment, currentFlow, node); - } + else { + bindCondition(node.left, trueTarget, preRightLabel); } - - function bindVariableDeclarationFlow(node: VariableDeclaration) { + currentFlow = finishFlowLabel(preRightLabel); + bind(node.operatorToken); + bindCondition(node.right, trueTarget, falseTarget); + } + function bindPrefixUnaryExpressionFlow(node: PrefixUnaryExpression) { + if (node.operator === SyntaxKind.ExclamationToken) { + const saveTrueTarget = currentTrueTarget; + currentTrueTarget = currentFalseTarget; + currentFalseTarget = saveTrueTarget; bindEachChild(node); - if (node.initializer || isForInOrOfStatement(node.parent.parent)) { - bindInitializedVariableFlow(node); - } - } - - function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag) { - node.tagName.parent = node; - if (node.kind !== SyntaxKind.JSDocEnumTag && node.fullName) { - setParentPointers(node, node.fullName); - } + currentFalseTarget = currentTrueTarget; + currentTrueTarget = saveTrueTarget; } - - function bindJSDocClassTag(node: JSDocClassTag) { + else { bindEachChild(node); - const host = getHostSignatureFromJSDoc(node); - if (host && host.kind !== SyntaxKind.MethodDeclaration) { - addDeclarationToSymbol(host.symbol, host, SymbolFlags.Class); - } - } - - function bindOptionalExpression(node: Expression, trueTarget: FlowLabel, falseTarget: FlowLabel) { - doWithConditionalBranches(bind, node, trueTarget, falseTarget); - if (!isOptionalChain(node) || isOutermostOptionalChain(node)) { - addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); - addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); - } - } - - function bindOptionalChainRest(node: OptionalChain) { - bind(node.questionDotToken); - switch (node.kind) { - case SyntaxKind.PropertyAccessExpression: - bind(node.name); - break; - case SyntaxKind.ElementAccessExpression: - bind(node.argumentExpression); - break; - case SyntaxKind.CallExpression: - bindEach(node.typeArguments); - bindEach(node.arguments); - break; + if (node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) { + bindAssignmentTargetFlow(node.operand); } } - - function bindOptionalChain(node: OptionalChain, trueTarget: FlowLabel, falseTarget: FlowLabel) { - // For an optional chain, we emulate the behavior of a logical expression: - // - // a?.b -> a && a.b - // a?.b.c -> a && a.b.c - // a?.b?.c -> a && a.b && a.b.c - // a?.[x = 1] -> a && a[x = 1] - // - // To do this we descend through the chain until we reach the root of a chain (the expression with a `?.`) - // and build it's CFA graph as if it were the first condition (`a && ...`). Then we bind the rest - // of the node as part of the "true" branch, and continue to do so as we ascend back up to the outermost - // chain node. We then treat the entire node as the right side of the expression. - const preChainLabel = node.questionDotToken ? createBranchLabel() : undefined; - bindOptionalExpression(node.expression, preChainLabel || trueTarget, falseTarget); - if (preChainLabel) { - currentFlow = finishFlowLabel(preChainLabel); - } - doWithConditionalBranches(bindOptionalChainRest, node, trueTarget, falseTarget); - if (isOutermostOptionalChain(node)) { - addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); - addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); - } + } + function bindPostfixUnaryExpressionFlow(node: PostfixUnaryExpression) { + bindEachChild(node); + if (node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) { + bindAssignmentTargetFlow(node.operand); } - - function bindOptionalChainFlow(node: OptionalChain) { + } + function bindBinaryExpressionFlow(node: BinaryExpression) { + const operator = node.operatorToken.kind; + if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) { if (isTopLevelLogicalExpression(node)) { const postExpressionLabel = createBranchLabel(); - bindOptionalChain(node, postExpressionLabel, postExpressionLabel); + bindLogicalExpression(node, postExpressionLabel, postExpressionLabel); currentFlow = finishFlowLabel(postExpressionLabel); } else { - bindOptionalChain(node, currentTrueTarget!, currentFalseTarget!); - } - } - - function bindAccessExpressionFlow(node: AccessExpression) { - if (isOptionalChain(node)) { - bindOptionalChainFlow(node); - } - else { - bindEachChild(node); + bindLogicalExpression(node, currentTrueTarget!, currentFalseTarget!); } } - - function bindCallExpressionFlow(node: CallExpression) { - if (isOptionalChain(node)) { - bindOptionalChainFlow(node); - } - else { - // If the target of the call expression is a function expression or arrow function we have - // an immediately invoked function expression (IIFE). Initialize the flowNode property to - // the current control flow (which includes evaluation of the IIFE arguments). - const expr = skipParentheses(node.expression); - if (expr.kind === SyntaxKind.FunctionExpression || expr.kind === SyntaxKind.ArrowFunction) { - bindEach(node.typeArguments); - bindEach(node.arguments); - bind(node.expression); - } - else { - bindEachChild(node); - } - } - if (node.expression.kind === SyntaxKind.PropertyAccessExpression) { - const propertyAccess = node.expression; - if (isNarrowableOperand(propertyAccess.expression) && isPushOrUnshiftIdentifier(propertyAccess.name)) { - currentFlow = createFlowMutation(FlowFlags.ArrayMutation, currentFlow, node); + else { + bindEachChild(node); + if (isAssignmentOperator(operator) && !isAssignmentTarget(node)) { + bindAssignmentTargetFlow(node.left); + if (operator === SyntaxKind.EqualsToken && node.left.kind === SyntaxKind.ElementAccessExpression) { + const elementAccess = (node.left); + if (isNarrowableOperand(elementAccess.expression)) { + currentFlow = createFlowMutation(FlowFlags.ArrayMutation, currentFlow, node); + } } } } - - function getContainerFlags(node: Node): ContainerFlags { - switch (node.kind) { - case SyntaxKind.ClassExpression: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.TypeLiteral: - case SyntaxKind.JSDocTypeLiteral: - case SyntaxKind.JsxAttributes: - return ContainerFlags.IsContainer; - - case SyntaxKind.InterfaceDeclaration: - return ContainerFlags.IsContainer | ContainerFlags.IsInterface; - - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.MappedType: - return ContainerFlags.IsContainer | ContainerFlags.HasLocals; - - case SyntaxKind.SourceFile: - return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals; - - case SyntaxKind.MethodDeclaration: - if (isObjectLiteralOrClassExpressionMethod(node)) { - return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike | ContainerFlags.IsObjectLiteralOrClassExpressionMethod; - } - // falls through - case SyntaxKind.Constructor: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.CallSignature: - case SyntaxKind.JSDocSignature: - case SyntaxKind.JSDocFunctionType: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.ConstructorType: - return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike; - - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike | ContainerFlags.IsFunctionExpression; - - case SyntaxKind.ModuleBlock: - return ContainerFlags.IsControlFlowContainer; - case SyntaxKind.PropertyDeclaration: - return (node).initializer ? ContainerFlags.IsControlFlowContainer : 0; - - case SyntaxKind.CatchClause: - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.CaseBlock: - return ContainerFlags.IsBlockScopedContainer; - - case SyntaxKind.Block: - // do not treat blocks directly inside a function as a block-scoped-container. - // Locals that reside in this block should go to the function locals. Otherwise 'x' - // would not appear to be a redeclaration of a block scoped local in the following - // example: - // - // function foo() { - // var x; - // let x; - // } - // - // If we placed 'var x' into the function locals and 'let x' into the locals of - // the block, then there would be no collision. - // - // By not creating a new block-scoped-container here, we ensure that both 'var x' - // and 'let x' go into the Function-container's locals, and we do get a collision - // conflict. - return isFunctionLike(node.parent) ? ContainerFlags.None : ContainerFlags.IsBlockScopedContainer; - } - - return ContainerFlags.None; + } + function bindDeleteExpressionFlow(node: DeleteExpression) { + bindEachChild(node); + if (node.expression.kind === SyntaxKind.PropertyAccessExpression) { + bindAssignmentTargetFlow(node.expression); } - - function addToContainerChain(next: Node) { - if (lastContainer) { - lastContainer.nextContainer = next; - } - - lastContainer = next; - } - - function declareSymbolAndAddToSymbolTable(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): Symbol | undefined { - switch (container.kind) { - // Modules, source files, and classes need specialized handling for how their - // members are declared (for example, a member of a class will go into a specific - // symbol table depending on if it is static or not). We defer to specialized - // handlers to take care of declaring these child members. - case SyntaxKind.ModuleDeclaration: - return declareModuleMember(node, symbolFlags, symbolExcludes); - - case SyntaxKind.SourceFile: - return declareSourceFileMember(node, symbolFlags, symbolExcludes); - - case SyntaxKind.ClassExpression: - case SyntaxKind.ClassDeclaration: - return declareClassMember(node, symbolFlags, symbolExcludes); - - case SyntaxKind.EnumDeclaration: - return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); - - case SyntaxKind.TypeLiteral: - case SyntaxKind.JSDocTypeLiteral: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.JsxAttributes: - // Interface/Object-types always have their children added to the 'members' of - // their container. They are only accessible through an instance of their - // container, and are never in scope otherwise (even inside the body of the - // object / type / interface declaring them). An exception is type parameters, - // which are in scope without qualification (similar to 'locals'). - return declareSymbol(container.symbol.members!, container.symbol, node, symbolFlags, symbolExcludes); - - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.JSDocSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.JSDocFunctionType: - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.MappedType: - // All the children of these container types are never visible through another - // symbol (i.e. through another symbol's 'exports' or 'members'). Instead, - // they're only accessed 'lexically' (i.e. from code that exists underneath - // their container in the tree). To accomplish this, we simply add their declared - // symbol to the 'locals' of the container. These symbols can then be found as - // the type checker walks up the containers, checking them for matching names. - return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); + } + function bindConditionalExpressionFlow(node: ConditionalExpression) { + const trueLabel = createBranchLabel(); + const falseLabel = createBranchLabel(); + const postExpressionLabel = createBranchLabel(); + bindCondition(node.condition, trueLabel, falseLabel); + currentFlow = finishFlowLabel(trueLabel); + bind(node.questionToken); + bind(node.whenTrue); + addAntecedent(postExpressionLabel, currentFlow); + currentFlow = finishFlowLabel(falseLabel); + bind(node.colonToken); + bind(node.whenFalse); + addAntecedent(postExpressionLabel, currentFlow); + currentFlow = finishFlowLabel(postExpressionLabel); + } + function bindInitializedVariableFlow(node: VariableDeclaration | ArrayBindingElement) { + const name = !isOmittedExpression(node) ? node.name : undefined; + if (isBindingPattern(name)) { + for (const child of name.elements) { + bindInitializedVariableFlow(child); } } - - function declareClassMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { - return hasModifier(node, ModifierFlags.Static) - ? declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes) - : declareSymbol(container.symbol.members!, container.symbol, node, symbolFlags, symbolExcludes); - } - - function declareSourceFileMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { - return isExternalModule(file) - ? declareModuleMember(node, symbolFlags, symbolExcludes) - : declareSymbol(file.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); - } - - function hasExportDeclarations(node: ModuleDeclaration | SourceFile): boolean { - const body = isSourceFile(node) ? node : tryCast(node.body, isModuleBlock); - return !!body && body.statements.some(s => isExportDeclaration(s) || isExportAssignment(s)); - } - - function setExportContextFlag(node: ModuleDeclaration | SourceFile) { - // A declaration source file or ambient module declaration that contains no export declarations (but possibly regular - // declarations with export modifiers) is an export context in which declarations are implicitly exported. - if (node.flags & NodeFlags.Ambient && !hasExportDeclarations(node)) { - node.flags |= NodeFlags.ExportContext; - } - else { - node.flags &= ~NodeFlags.ExportContext; - } - } - - function bindModuleDeclaration(node: ModuleDeclaration) { - setExportContextFlag(node); - if (isAmbientModule(node)) { - if (hasModifier(node, ModifierFlags.Export)) { - errorOnFirstToken(node, Diagnostics.export_modifier_cannot_be_applied_to_ambient_modules_and_module_augmentations_since_they_are_always_visible); - } - if (isModuleAugmentationExternal(node)) { - declareModuleSymbol(node); - } - else { - let pattern: Pattern | undefined; - if (node.name.kind === SyntaxKind.StringLiteral) { - const { text } = node.name; - if (hasZeroOrOneAsteriskCharacter(text)) { - pattern = tryParsePattern(text); - } - else { - errorOnFirstToken(node.name, Diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, text); - } - } - - const symbol = declareSymbolAndAddToSymbolTable(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes)!; - file.patternAmbientModules = append(file.patternAmbientModules, pattern && { pattern, symbol }); - } - } - else { - const state = declareModuleSymbol(node); - if (state !== ModuleInstanceState.NonInstantiated) { - const { symbol } = node; - // if module was already merged with some function, class or non-const enum, treat it as non-const-enum-only - symbol.constEnumOnlyModule = (!(symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.RegularEnum))) - // Current must be `const enum` only - && state === ModuleInstanceState.ConstEnumOnly - // Can't have been set to 'false' in a previous merged symbol. ('undefined' OK) - && symbol.constEnumOnlyModule !== false; - } - } + else { + currentFlow = createFlowMutation(FlowFlags.Assignment, currentFlow, node); } - - function declareModuleSymbol(node: ModuleDeclaration): ModuleInstanceState { - const state = getModuleInstanceState(node); - const instantiated = state !== ModuleInstanceState.NonInstantiated; - declareSymbolAndAddToSymbolTable(node, - instantiated ? SymbolFlags.ValueModule : SymbolFlags.NamespaceModule, - instantiated ? SymbolFlags.ValueModuleExcludes : SymbolFlags.NamespaceModuleExcludes); - return state; + } + function bindVariableDeclarationFlow(node: VariableDeclaration) { + bindEachChild(node); + if (node.initializer || isForInOrOfStatement(node.parent.parent)) { + bindInitializedVariableFlow(node); } - - function bindFunctionOrConstructorType(node: SignatureDeclaration | JSDocSignature): void { - // For a given function symbol "<...>(...) => T" we want to generate a symbol identical - // to the one we would get for: { <...>(...): T } - // - // We do that by making an anonymous type literal symbol, and then setting the function - // symbol as its sole member. To the rest of the system, this symbol will be indistinguishable - // from an actual type literal symbol you would have gotten had you used the long form. - const symbol = createSymbol(SymbolFlags.Signature, getDeclarationName(node)!); // TODO: GH#18217 - addDeclarationToSymbol(symbol, node, SymbolFlags.Signature); - - const typeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); - addDeclarationToSymbol(typeLiteralSymbol, node, SymbolFlags.TypeLiteral); - typeLiteralSymbol.members = createSymbolTable(); - typeLiteralSymbol.members.set(symbol.escapedName, symbol); - } - - function bindObjectLiteralExpression(node: ObjectLiteralExpression) { - const enum ElementKind { - Property = 1, - Accessor = 2 - } - - if (inStrictMode) { - const seen = createUnderscoreEscapedMap(); - - for (const prop of node.properties) { - if (prop.kind === SyntaxKind.SpreadAssignment || prop.name.kind !== SyntaxKind.Identifier) { - continue; - } - - const identifier = prop.name; - - // ECMA-262 11.1.5 Object Initializer - // If previous is not undefined then throw a SyntaxError exception if any of the following conditions are true - // a.This production is contained in strict code and IsDataDescriptor(previous) is true and - // IsDataDescriptor(propId.descriptor) is true. - // b.IsDataDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true. - // c.IsAccessorDescriptor(previous) is true and IsDataDescriptor(propId.descriptor) is true. - // d.IsAccessorDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true - // and either both previous and propId.descriptor have[[Get]] fields or both previous and propId.descriptor have[[Set]] fields - const currentKind = prop.kind === SyntaxKind.PropertyAssignment || prop.kind === SyntaxKind.ShorthandPropertyAssignment || prop.kind === SyntaxKind.MethodDeclaration - ? ElementKind.Property - : ElementKind.Accessor; - - const existingKind = seen.get(identifier.escapedText); - if (!existingKind) { - seen.set(identifier.escapedText, currentKind); - continue; - } - - if (currentKind === ElementKind.Property && existingKind === ElementKind.Property) { - const span = getErrorSpanForNode(file, identifier); - file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, - Diagnostics.An_object_literal_cannot_have_multiple_properties_with_the_same_name_in_strict_mode)); - } - } - } - - return bindAnonymousDeclaration(node, SymbolFlags.ObjectLiteral, InternalSymbolName.Object); - } - - function bindJsxAttributes(node: JsxAttributes) { - return bindAnonymousDeclaration(node, SymbolFlags.ObjectLiteral, InternalSymbolName.JSXAttributes); - } - - function bindJsxAttribute(node: JsxAttribute, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { - return declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes); - } - - function bindAnonymousDeclaration(node: Declaration, symbolFlags: SymbolFlags, name: __String) { - const symbol = createSymbol(symbolFlags, name); - if (symbolFlags & (SymbolFlags.EnumMember | SymbolFlags.ClassMember)) { - symbol.parent = container.symbol; - } - addDeclarationToSymbol(symbol, node, symbolFlags); - return symbol; + } + function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag) { + node.tagName.parent = node; + if (node.kind !== SyntaxKind.JSDocEnumTag && node.fullName) { + setParentPointers(node, node.fullName); } - - function bindBlockScopedDeclaration(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { - switch (blockScopeContainer.kind) { - case SyntaxKind.ModuleDeclaration: - declareModuleMember(node, symbolFlags, symbolExcludes); - break; - case SyntaxKind.SourceFile: - if (isExternalOrCommonJsModule(container)) { - declareModuleMember(node, symbolFlags, symbolExcludes); - break; - } - // falls through - default: - if (!blockScopeContainer.locals) { - blockScopeContainer.locals = createSymbolTable(); - addToContainerChain(blockScopeContainer); - } - declareSymbol(blockScopeContainer.locals, /*parent*/ undefined, node, symbolFlags, symbolExcludes); - } + } + function bindJSDocClassTag(node: JSDocClassTag) { + bindEachChild(node); + const host = getHostSignatureFromJSDoc(node); + if (host && host.kind !== SyntaxKind.MethodDeclaration) { + addDeclarationToSymbol(host.symbol, host, SymbolFlags.Class); } - - function delayedBindJSDocTypedefTag() { - if (!delayedTypeAliases) { - return; - } - const saveContainer = container; - const saveLastContainer = lastContainer; - const saveBlockScopeContainer = blockScopeContainer; - const saveParent = parent; - const saveCurrentFlow = currentFlow; - for (const typeAlias of delayedTypeAliases) { - const host = getJSDocHost(typeAlias); - container = findAncestor(host.parent, n => !!(getContainerFlags(n) & ContainerFlags.IsContainer)) || file; - blockScopeContainer = getEnclosingBlockScopeContainer(host) || file; - currentFlow = initFlowNode({ flags: FlowFlags.Start }); - parent = typeAlias; - bind(typeAlias.typeExpression); - const declName = getNameOfDeclaration(typeAlias); - if ((isJSDocEnumTag(typeAlias) || !typeAlias.fullName) && declName && isPropertyAccessEntityNameExpression(declName.parent)) { - // typedef anchored to an A.B.C assignment - we need to bind into B's namespace under name C - const isTopLevel = isTopLevelNamespaceAssignment(declName.parent); - if (isTopLevel) { - bindPotentiallyMissingNamespaces(file.symbol, declName.parent, isTopLevel, - !!findAncestor(declName, d => isPropertyAccessExpression(d) && d.name.escapedText === "prototype"), /*containerIsClass*/ false); - const oldContainer = container; - switch (getAssignmentDeclarationPropertyAccessKind(declName.parent)) { - case AssignmentDeclarationKind.ExportsProperty: - case AssignmentDeclarationKind.ModuleExports: - if (!isExternalOrCommonJsModule(file)) { - container = undefined!; - } - else { - container = file; - } - break; - case AssignmentDeclarationKind.ThisProperty: - container = declName.parent.expression; - break; - case AssignmentDeclarationKind.PrototypeProperty: - container = (declName.parent.expression as PropertyAccessExpression).name; - break; - case AssignmentDeclarationKind.Property: - container = isPropertyAccessExpression(declName.parent.expression) ? declName.parent.expression.name : declName.parent.expression; - break; - case AssignmentDeclarationKind.None: - return Debug.fail("Shouldn't have detected typedef or enum on non-assignment declaration"); - } - if (container) { - declareModuleMember(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); - } - container = oldContainer; - } - } - else if (isJSDocEnumTag(typeAlias) || !typeAlias.fullName || typeAlias.fullName.kind === SyntaxKind.Identifier) { - parent = typeAlias.parent; - bindBlockScopedDeclaration(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); - } - else { - bind(typeAlias.fullName); - } - } - container = saveContainer; - lastContainer = saveLastContainer; - blockScopeContainer = saveBlockScopeContainer; - parent = saveParent; - currentFlow = saveCurrentFlow; - } - - // The binder visits every node in the syntax tree so it is a convenient place to perform a single localized - // check for reserved words used as identifiers in strict mode code. - function checkStrictModeIdentifier(node: Identifier) { - if (inStrictMode && - node.originalKeywordKind! >= SyntaxKind.FirstFutureReservedWord && - node.originalKeywordKind! <= SyntaxKind.LastFutureReservedWord && - !isIdentifierName(node) && - !(node.flags & NodeFlags.Ambient) && - !(node.flags & NodeFlags.JSDoc)) { - - // Report error only if there are no parse errors in file - if (!file.parseDiagnostics.length) { - file.bindDiagnostics.push(createDiagnosticForNode(node, - getStrictModeIdentifierMessage(node), declarationNameToString(node))); - } - } + } + function bindOptionalExpression(node: Expression, trueTarget: FlowLabel, falseTarget: FlowLabel) { + doWithConditionalBranches(bind, node, trueTarget, falseTarget); + if (!isOptionalChain(node) || isOutermostOptionalChain(node)) { + addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); + addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); } - - function getStrictModeIdentifierMessage(node: Node) { - // Provide specialized messages to help the user understand why we think they're in - // strict mode. - if (getContainingClass(node)) { - return Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode_Class_definitions_are_automatically_in_strict_mode; - } - - if (file.externalModuleIndicator) { - return Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode_Modules_are_automatically_in_strict_mode; - } - - return Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode; - } - - function checkStrictModeBinaryExpression(node: BinaryExpression) { - if (inStrictMode && isLeftHandSideExpression(node.left) && isAssignmentOperator(node.operatorToken.kind)) { - // ECMA 262 (Annex C) The identifier eval or arguments may not appear as the LeftHandSideExpression of an - // Assignment operator(11.13) or of a PostfixExpression(11.3) - checkStrictModeEvalOrArguments(node, node.left); - } + } + function bindOptionalChainRest(node: OptionalChain) { + bind(node.questionDotToken); + switch (node.kind) { + case SyntaxKind.PropertyAccessExpression: + bind(node.name); + break; + case SyntaxKind.ElementAccessExpression: + bind(node.argumentExpression); + break; + case SyntaxKind.CallExpression: + bindEach(node.typeArguments); + bindEach(node.arguments); + break; } - - function checkStrictModeCatchClause(node: CatchClause) { - // It is a SyntaxError if a TryStatement with a Catch occurs within strict code and the Identifier of the - // Catch production is eval or arguments - if (inStrictMode && node.variableDeclaration) { - checkStrictModeEvalOrArguments(node, node.variableDeclaration.name); - } + } + function bindOptionalChain(node: OptionalChain, trueTarget: FlowLabel, falseTarget: FlowLabel) { + // For an optional chain, we emulate the behavior of a logical expression: + // + // a?.b -> a && a.b + // a?.b.c -> a && a.b.c + // a?.b?.c -> a && a.b && a.b.c + // a?.[x = 1] -> a && a[x = 1] + // + // To do this we descend through the chain until we reach the root of a chain (the expression with a `?.`) + // and build it's CFA graph as if it were the first condition (`a && ...`). Then we bind the rest + // of the node as part of the "true" branch, and continue to do so as we ascend back up to the outermost + // chain node. We then treat the entire node as the right side of the expression. + const preChainLabel = node.questionDotToken ? createBranchLabel() : undefined; + bindOptionalExpression(node.expression, preChainLabel || trueTarget, falseTarget); + if (preChainLabel) { + currentFlow = finishFlowLabel(preChainLabel); + } + doWithConditionalBranches(bindOptionalChainRest, node, trueTarget, falseTarget); + if (isOutermostOptionalChain(node)) { + addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); + addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); } - - function checkStrictModeDeleteExpression(node: DeleteExpression) { - // Grammar checking - if (inStrictMode && node.expression.kind === SyntaxKind.Identifier) { - // When a delete operator occurs within strict mode code, a SyntaxError is thrown if its - // UnaryExpression is a direct reference to a variable, function argument, or function name - const span = getErrorSpanForNode(file, node.expression); - file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, Diagnostics.delete_cannot_be_called_on_an_identifier_in_strict_mode)); - } + } + function bindOptionalChainFlow(node: OptionalChain) { + if (isTopLevelLogicalExpression(node)) { + const postExpressionLabel = createBranchLabel(); + bindOptionalChain(node, postExpressionLabel, postExpressionLabel); + currentFlow = finishFlowLabel(postExpressionLabel); } - - function isEvalOrArgumentsIdentifier(node: Node): boolean { - return isIdentifier(node) && (node.escapedText === "eval" || node.escapedText === "arguments"); - } - - function checkStrictModeEvalOrArguments(contextNode: Node, name: Node | undefined) { - if (name && name.kind === SyntaxKind.Identifier) { - const identifier = name; - if (isEvalOrArgumentsIdentifier(identifier)) { - // We check first if the name is inside class declaration or class expression; if so give explicit message - // otherwise report generic error message. - const span = getErrorSpanForNode(file, name); - file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, - getStrictModeEvalOrArgumentsMessage(contextNode), idText(identifier))); - } - } + else { + bindOptionalChain(node, currentTrueTarget!, currentFalseTarget!); } - - function getStrictModeEvalOrArgumentsMessage(node: Node) { - // Provide specialized messages to help the user understand why we think they're in - // strict mode. - if (getContainingClass(node)) { - return Diagnostics.Invalid_use_of_0_Class_definitions_are_automatically_in_strict_mode; - } - - if (file.externalModuleIndicator) { - return Diagnostics.Invalid_use_of_0_Modules_are_automatically_in_strict_mode; - } - - return Diagnostics.Invalid_use_of_0_in_strict_mode; - } - - function checkStrictModeFunctionName(node: FunctionLikeDeclaration) { - if (inStrictMode) { - // It is a SyntaxError if the identifier eval or arguments appears within a FormalParameterList of a strict mode FunctionDeclaration or FunctionExpression (13.1)) - checkStrictModeEvalOrArguments(node, node.name); - } + } + function bindAccessExpressionFlow(node: AccessExpression) { + if (isOptionalChain(node)) { + bindOptionalChainFlow(node); } - - function getStrictModeBlockScopeFunctionDeclarationMessage(node: Node) { - // Provide specialized messages to help the user understand why we think they're in - // strict mode. - if (getContainingClass(node)) { - return Diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES3_or_ES5_Class_definitions_are_automatically_in_strict_mode; - } - - if (file.externalModuleIndicator) { - return Diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES3_or_ES5_Modules_are_automatically_in_strict_mode; - } - - return Diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES3_or_ES5; - } - - function checkStrictModeFunctionDeclaration(node: FunctionDeclaration) { - if (languageVersion < ScriptTarget.ES2015) { - // Report error if function is not top level function declaration - if (blockScopeContainer.kind !== SyntaxKind.SourceFile && - blockScopeContainer.kind !== SyntaxKind.ModuleDeclaration && - !isFunctionLike(blockScopeContainer)) { - // We check first if the name is inside class declaration or class expression; if so give explicit message - // otherwise report generic error message. - const errorSpan = getErrorSpanForNode(file, node); - file.bindDiagnostics.push(createFileDiagnostic(file, errorSpan.start, errorSpan.length, - getStrictModeBlockScopeFunctionDeclarationMessage(node))); - } - } + else { + bindEachChild(node); } - - function checkStrictModeNumericLiteral(node: NumericLiteral) { - if (inStrictMode && node.numericLiteralFlags & TokenFlags.Octal) { - file.bindDiagnostics.push(createDiagnosticForNode(node, Diagnostics.Octal_literals_are_not_allowed_in_strict_mode)); - } + } + function bindCallExpressionFlow(node: CallExpression) { + if (isOptionalChain(node)) { + bindOptionalChainFlow(node); } - - function checkStrictModePostfixUnaryExpression(node: PostfixUnaryExpression) { - // Grammar checking - // The identifier eval or arguments may not appear as the LeftHandSideExpression of an - // Assignment operator(11.13) or of a PostfixExpression(11.3) or as the UnaryExpression - // operated upon by a Prefix Increment(11.4.4) or a Prefix Decrement(11.4.5) operator. - if (inStrictMode) { - checkStrictModeEvalOrArguments(node, node.operand); + else { + // If the target of the call expression is a function expression or arrow function we have + // an immediately invoked function expression (IIFE). Initialize the flowNode property to + // the current control flow (which includes evaluation of the IIFE arguments). + const expr = skipParentheses(node.expression); + if (expr.kind === SyntaxKind.FunctionExpression || expr.kind === SyntaxKind.ArrowFunction) { + bindEach(node.typeArguments); + bindEach(node.arguments); + bind(node.expression); } - } - - function checkStrictModePrefixUnaryExpression(node: PrefixUnaryExpression) { - // Grammar checking - if (inStrictMode) { - if (node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) { - checkStrictModeEvalOrArguments(node, node.operand); - } + else { + bindEachChild(node); } } - - function checkStrictModeWithStatement(node: WithStatement) { - // Grammar checking for withStatement - if (inStrictMode) { - errorOnFirstToken(node, Diagnostics.with_statements_are_not_allowed_in_strict_mode); + if (node.expression.kind === SyntaxKind.PropertyAccessExpression) { + const propertyAccess = (node.expression); + if (isNarrowableOperand(propertyAccess.expression) && isPushOrUnshiftIdentifier(propertyAccess.name)) { + currentFlow = createFlowMutation(FlowFlags.ArrayMutation, currentFlow, node); } } - - function checkStrictModeLabeledStatement(node: LabeledStatement) { - // Grammar checking for labeledStatement - if (inStrictMode && options.target! >= ScriptTarget.ES2015) { - if (isDeclarationStatement(node.statement) || isVariableStatement(node.statement)) { - errorOnFirstToken(node.label, Diagnostics.A_label_is_not_allowed_here); + } + function getContainerFlags(node: Node): ContainerFlags { + switch (node.kind) { + case SyntaxKind.ClassExpression: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: + case SyntaxKind.JsxAttributes: + return ContainerFlags.IsContainer; + case SyntaxKind.InterfaceDeclaration: + return ContainerFlags.IsContainer | ContainerFlags.IsInterface; + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.MappedType: + return ContainerFlags.IsContainer | ContainerFlags.HasLocals; + case SyntaxKind.SourceFile: + return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals; + case SyntaxKind.MethodDeclaration: + if (isObjectLiteralOrClassExpressionMethod(node)) { + return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike | ContainerFlags.IsObjectLiteralOrClassExpressionMethod; } - } + // falls through + case SyntaxKind.Constructor: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.CallSignature: + case SyntaxKind.JSDocSignature: + case SyntaxKind.JSDocFunctionType: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.ConstructorType: + return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike; + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike | ContainerFlags.IsFunctionExpression; + case SyntaxKind.ModuleBlock: + return ContainerFlags.IsControlFlowContainer; + case SyntaxKind.PropertyDeclaration: + return (node).initializer ? ContainerFlags.IsControlFlowContainer : 0; + case SyntaxKind.CatchClause: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.CaseBlock: + return ContainerFlags.IsBlockScopedContainer; + case SyntaxKind.Block: + // do not treat blocks directly inside a function as a block-scoped-container. + // Locals that reside in this block should go to the function locals. Otherwise 'x' + // would not appear to be a redeclaration of a block scoped local in the following + // example: + // + // function foo() { + // var x; + // let x; + // } + // + // If we placed 'var x' into the function locals and 'let x' into the locals of + // the block, then there would be no collision. + // + // By not creating a new block-scoped-container here, we ensure that both 'var x' + // and 'let x' go into the Function-container's locals, and we do get a collision + // conflict. + return isFunctionLike(node.parent) ? ContainerFlags.None : ContainerFlags.IsBlockScopedContainer; } - - function errorOnFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any) { - const span = getSpanOfTokenAtPosition(file, node.pos); - file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, message, arg0, arg1, arg2)); + return ContainerFlags.None; + } + function addToContainerChain(next: Node) { + if (lastContainer) { + lastContainer.nextContainer = next; } - - function errorOrSuggestionOnNode(isError: boolean, node: Node, message: DiagnosticMessage): void { - errorOrSuggestionOnRange(isError, node, node, message); + lastContainer = next; + } + function declareSymbolAndAddToSymbolTable(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): ts.Symbol | undefined { + switch (container.kind) { + // Modules, source files, and classes need specialized handling for how their + // members are declared (for example, a member of a class will go into a specific + // symbol table depending on if it is static or not). We defer to specialized + // handlers to take care of declaring these child members. + case SyntaxKind.ModuleDeclaration: + return declareModuleMember(node, symbolFlags, symbolExcludes); + case SyntaxKind.SourceFile: + return declareSourceFileMember(node, symbolFlags, symbolExcludes); + case SyntaxKind.ClassExpression: + case SyntaxKind.ClassDeclaration: + return declareClassMember(node, symbolFlags, symbolExcludes); + case SyntaxKind.EnumDeclaration: + return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); + case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.JsxAttributes: + // Interface/Object-types always have their children added to the 'members' of + // their container. They are only accessible through an instance of their + // container, and are never in scope otherwise (even inside the body of the + // object / type / interface declaring them). An exception is type parameters, + // which are in scope without qualification (similar to 'locals'). + return declareSymbol(container.symbol.members!, container.symbol, node, symbolFlags, symbolExcludes); + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.JSDocSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.JSDocFunctionType: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.MappedType: + // All the children of these container types are never visible through another + // symbol (i.e. through another symbol's 'exports' or 'members'). Instead, + // they're only accessed 'lexically' (i.e. from code that exists underneath + // their container in the tree). To accomplish this, we simply add their declared + // symbol to the 'locals' of the container. These symbols can then be found as + // the type checker walks up the containers, checking them for matching names. + return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); } - - function errorOrSuggestionOnRange(isError: boolean, startNode: Node, endNode: Node, message: DiagnosticMessage): void { - addErrorOrSuggestionDiagnostic(isError, { pos: getTokenPosOfNode(startNode, file), end: endNode.end }, message); + } + function declareClassMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { + return hasModifier(node, ModifierFlags.Static) + ? declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes) + : declareSymbol(container.symbol.members!, container.symbol, node, symbolFlags, symbolExcludes); + } + function declareSourceFileMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { + return isExternalModule(file) + ? declareModuleMember(node, symbolFlags, symbolExcludes) + : declareSymbol(file.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); + } + function hasExportDeclarations(node: ModuleDeclaration | SourceFile): boolean { + const body = isSourceFile(node) ? node : tryCast(node.body, isModuleBlock); + return !!body && body.statements.some(s => isExportDeclaration(s) || isExportAssignment(s)); + } + function setExportContextFlag(node: ModuleDeclaration | SourceFile) { + // A declaration source file or ambient module declaration that contains no export declarations (but possibly regular + // declarations with export modifiers) is an export context in which declarations are implicitly exported. + if (node.flags & NodeFlags.Ambient && !hasExportDeclarations(node)) { + node.flags |= NodeFlags.ExportContext; } - - function addErrorOrSuggestionDiagnostic(isError: boolean, range: TextRange, message: DiagnosticMessage): void { - const diag = createFileDiagnostic(file, range.pos, range.end - range.pos, message); - if (isError) { - file.bindDiagnostics.push(diag); - } - else { - file.bindSuggestionDiagnostics = append(file.bindSuggestionDiagnostics, { ...diag, category: DiagnosticCategory.Suggestion }); - } + else { + node.flags &= ~NodeFlags.ExportContext; } - - function bind(node: Node | undefined): void { - if (!node) { - return; - } - node.parent = parent; - const saveInStrictMode = inStrictMode; - - // Even though in the AST the jsdoc @typedef node belongs to the current node, - // its symbol might be in the same scope with the current node's symbol. Consider: - // - // /** @typedef {string | number} MyType */ - // function foo(); - // - // Here the current node is "foo", which is a container, but the scope of "MyType" should - // not be inside "foo". Therefore we always bind @typedef before bind the parent node, - // and skip binding this tag later when binding all the other jsdoc tags. - - // First we bind declaration nodes to a symbol if possible. We'll both create a symbol - // and then potentially add the symbol to an appropriate symbol table. Possible - // destination symbol tables are: - // - // 1) The 'exports' table of the current container's symbol. - // 2) The 'members' table of the current container's symbol. - // 3) The 'locals' table of the current container. - // - // However, not all symbols will end up in any of these tables. 'Anonymous' symbols - // (like TypeLiterals for example) will not be put in any table. - bindWorker(node); - // Then we recurse into the children of the node to bind them as well. For certain - // symbols we do specialized work when we recurse. For example, we'll keep track of - // the current 'container' node when it changes. This helps us know which symbol table - // a local should go into for example. Since terminal nodes are known not to have - // children, as an optimization we don't process those. - if (node.kind > SyntaxKind.LastToken) { - const saveParent = parent; - parent = node; - const containerFlags = getContainerFlags(node); - if (containerFlags === ContainerFlags.None) { - bindChildren(node); - } - else { - bindContainer(node, containerFlags); - } - parent = saveParent; + } + function bindModuleDeclaration(node: ModuleDeclaration) { + setExportContextFlag(node); + if (isAmbientModule(node)) { + if (hasModifier(node, ModifierFlags.Export)) { + errorOnFirstToken(node, Diagnostics.export_modifier_cannot_be_applied_to_ambient_modules_and_module_augmentations_since_they_are_always_visible); } - else if (!skipTransformFlagAggregation && (node.transformFlags & TransformFlags.HasComputedFlags) === 0) { - subtreeTransformFlags |= computeTransformFlagsForNode(node, 0); - const saveParent = parent; - if (node.kind === SyntaxKind.EndOfFileToken) parent = node; - bindJSDoc(node); - parent = saveParent; + if (isModuleAugmentationExternal(node)) { + declareModuleSymbol(node); } - inStrictMode = saveInStrictMode; - } - - function bindJSDoc(node: Node) { - if (hasJSDocNodes(node)) { - if (isInJSFile(node)) { - for (const j of node.jsDoc!) { - bind(j); + else { + let pattern: Pattern | undefined; + if (node.name.kind === SyntaxKind.StringLiteral) { + const { text } = node.name; + if (hasZeroOrOneAsteriskCharacter(text)) { + pattern = tryParsePattern(text); } - } - else { - for (const j of node.jsDoc!) { - setParentPointers(node, j); + else { + errorOnFirstToken(node.name, Diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, text); } } + const symbol = (declareSymbolAndAddToSymbolTable(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes)!); + file.patternAmbientModules = append(file.patternAmbientModules, pattern && { pattern, symbol }); } } - - function updateStrictModeStatementList(statements: NodeArray) { - if (!inStrictMode) { - for (const statement of statements) { - if (!isPrologueDirective(statement)) { - return; - } - - if (isUseStrictPrologueDirective(statement)) { - inStrictMode = true; - return; - } - } + else { + const state = declareModuleSymbol(node); + if (state !== ModuleInstanceState.NonInstantiated) { + const { symbol } = node; + // if module was already merged with some function, class or non-const enum, treat it as non-const-enum-only + symbol.constEnumOnlyModule = (!(symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.RegularEnum))) + // Current must be `const enum` only + && state === ModuleInstanceState.ConstEnumOnly + // Can't have been set to 'false' in a previous merged symbol. ('undefined' OK) + && symbol.constEnumOnlyModule !== false; } } - - /// Should be called only on prologue directives (isPrologueDirective(node) should be true) - function isUseStrictPrologueDirective(node: ExpressionStatement): boolean { - const nodeText = getSourceTextOfNodeFromSourceFile(file, node.expression); - - // Note: the node text must be exactly "use strict" or 'use strict'. It is not ok for the - // string to contain unicode escapes (as per ES5). - return nodeText === '"use strict"' || nodeText === "'use strict'"; - } - - function bindWorker(node: Node) { - switch (node.kind) { - /* Strict mode checks */ - case SyntaxKind.Identifier: - // for typedef type names with namespaces, bind the new jsdoc type symbol here - // because it requires all containing namespaces to be in effect, namely the - // current "blockScopeContainer" needs to be set to its immediate namespace parent. - if ((node).isInJSDocNamespace) { - let parentNode = node.parent; - while (parentNode && !isJSDocTypeAlias(parentNode)) { - parentNode = parentNode.parent; - } - bindBlockScopedDeclaration(parentNode as Declaration, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); - break; - } - // falls through - case SyntaxKind.ThisKeyword: - if (currentFlow && (isExpression(node) || parent.kind === SyntaxKind.ShorthandPropertyAssignment)) { - node.flowNode = currentFlow; - } - return checkStrictModeIdentifier(node); - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - const expr = node as PropertyAccessExpression | ElementAccessExpression; - if (currentFlow && isNarrowableReference(expr)) { - expr.flowNode = currentFlow; - } - if (isSpecialPropertyDeclaration(expr)) { - bindSpecialPropertyDeclaration(expr); - } - if (isInJSFile(expr) && - file.commonJsModuleIndicator && - isModuleExportsAccessExpression(expr) && - !lookupSymbolForNameWorker(blockScopeContainer, "module" as __String)) { - declareSymbol(file.locals!, /*parent*/ undefined, expr.expression, - SymbolFlags.FunctionScopedVariable | SymbolFlags.ModuleExports, SymbolFlags.FunctionScopedVariableExcludes); - } + } + function declareModuleSymbol(node: ModuleDeclaration): ModuleInstanceState { + const state = getModuleInstanceState(node); + const instantiated = state !== ModuleInstanceState.NonInstantiated; + declareSymbolAndAddToSymbolTable(node, instantiated ? SymbolFlags.ValueModule : SymbolFlags.NamespaceModule, instantiated ? SymbolFlags.ValueModuleExcludes : SymbolFlags.NamespaceModuleExcludes); + return state; + } + function bindFunctionOrConstructorType(node: SignatureDeclaration | JSDocSignature): void { + // For a given function symbol "<...>(...) => T" we want to generate a symbol identical + // to the one we would get for: { <...>(...): T } + // + // We do that by making an anonymous type literal symbol, and then setting the function + // symbol as its sole member. To the rest of the system, this symbol will be indistinguishable + // from an actual type literal symbol you would have gotten had you used the long form. + const symbol = createSymbol(SymbolFlags.Signature, (getDeclarationName(node)!)); // TODO: GH#18217 + addDeclarationToSymbol(symbol, node, SymbolFlags.Signature); + const typeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); + addDeclarationToSymbol(typeLiteralSymbol, node, SymbolFlags.TypeLiteral); + typeLiteralSymbol.members = createSymbolTable(); + typeLiteralSymbol.members.set(symbol.escapedName, symbol); + } + function bindObjectLiteralExpression(node: ObjectLiteralExpression) { + const enum ElementKind { + Property = 1, + Accessor = 2 + } + if (inStrictMode) { + const seen = createUnderscoreEscapedMap(); + for (const prop of node.properties) { + if (prop.kind === SyntaxKind.SpreadAssignment || prop.name.kind !== SyntaxKind.Identifier) { + continue; + } + const identifier = prop.name; + // ECMA-262 11.1.5 Object Initializer + // If previous is not undefined then throw a SyntaxError exception if any of the following conditions are true + // a.This production is contained in strict code and IsDataDescriptor(previous) is true and + // IsDataDescriptor(propId.descriptor) is true. + // b.IsDataDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true. + // c.IsAccessorDescriptor(previous) is true and IsDataDescriptor(propId.descriptor) is true. + // d.IsAccessorDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true + // and either both previous and propId.descriptor have[[Get]] fields or both previous and propId.descriptor have[[Set]] fields + const currentKind = prop.kind === SyntaxKind.PropertyAssignment || prop.kind === SyntaxKind.ShorthandPropertyAssignment || prop.kind === SyntaxKind.MethodDeclaration + ? ElementKind.Property + : ElementKind.Accessor; + const existingKind = seen.get(identifier.escapedText); + if (!existingKind) { + seen.set(identifier.escapedText, currentKind); + continue; + } + if (currentKind === ElementKind.Property && existingKind === ElementKind.Property) { + const span = getErrorSpanForNode(file, identifier); + file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, Diagnostics.An_object_literal_cannot_have_multiple_properties_with_the_same_name_in_strict_mode)); + } + } + } + return bindAnonymousDeclaration(node, SymbolFlags.ObjectLiteral, InternalSymbolName.Object); + } + function bindJsxAttributes(node: JsxAttributes) { + return bindAnonymousDeclaration(node, SymbolFlags.ObjectLiteral, InternalSymbolName.JSXAttributes); + } + function bindJsxAttribute(node: JsxAttribute, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { + return declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes); + } + function bindAnonymousDeclaration(node: Declaration, symbolFlags: SymbolFlags, name: __String) { + const symbol = createSymbol(symbolFlags, name); + if (symbolFlags & (SymbolFlags.EnumMember | SymbolFlags.ClassMember)) { + symbol.parent = container.symbol; + } + addDeclarationToSymbol(symbol, node, symbolFlags); + return symbol; + } + function bindBlockScopedDeclaration(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { + switch (blockScopeContainer.kind) { + case SyntaxKind.ModuleDeclaration: + declareModuleMember(node, symbolFlags, symbolExcludes); + break; + case SyntaxKind.SourceFile: + if (isExternalOrCommonJsModule((container))) { + declareModuleMember(node, symbolFlags, symbolExcludes); break; - case SyntaxKind.BinaryExpression: - const specialKind = getAssignmentDeclarationKind(node as BinaryExpression); - switch (specialKind) { + } + // falls through + default: + if (!blockScopeContainer.locals) { + blockScopeContainer.locals = createSymbolTable(); + addToContainerChain(blockScopeContainer); + } + declareSymbol(blockScopeContainer.locals, /*parent*/ undefined, node, symbolFlags, symbolExcludes); + } + } + function delayedBindJSDocTypedefTag() { + if (!delayedTypeAliases) { + return; + } + const saveContainer = container; + const saveLastContainer = lastContainer; + const saveBlockScopeContainer = blockScopeContainer; + const saveParent = parent; + const saveCurrentFlow = currentFlow; + for (const typeAlias of delayedTypeAliases) { + const host = getJSDocHost(typeAlias); + container = findAncestor(host.parent, n => !!(getContainerFlags(n) & ContainerFlags.IsContainer)) || file; + blockScopeContainer = getEnclosingBlockScopeContainer(host) || file; + currentFlow = initFlowNode({ flags: FlowFlags.Start }); + parent = typeAlias; + bind(typeAlias.typeExpression); + const declName = getNameOfDeclaration(typeAlias); + if ((isJSDocEnumTag(typeAlias) || !typeAlias.fullName) && declName && isPropertyAccessEntityNameExpression(declName.parent)) { + // typedef anchored to an A.B.C assignment - we need to bind into B's namespace under name C + const isTopLevel = isTopLevelNamespaceAssignment(declName.parent); + if (isTopLevel) { + bindPotentiallyMissingNamespaces(file.symbol, declName.parent, isTopLevel, !!findAncestor(declName, d => isPropertyAccessExpression(d) && d.name.escapedText === "prototype"), /*containerIsClass*/ false); + const oldContainer = container; + switch (getAssignmentDeclarationPropertyAccessKind(declName.parent)) { case AssignmentDeclarationKind.ExportsProperty: - bindExportsPropertyAssignment(node as BindableStaticPropertyAssignmentExpression); - break; case AssignmentDeclarationKind.ModuleExports: - bindModuleExportsAssignment(node as BindablePropertyAssignmentExpression); - break; - case AssignmentDeclarationKind.PrototypeProperty: - bindPrototypePropertyAssignment((node as BindableStaticPropertyAssignmentExpression).left, node); - break; - case AssignmentDeclarationKind.Prototype: - bindPrototypeAssignment(node as BindableStaticPropertyAssignmentExpression); + if (!isExternalOrCommonJsModule(file)) { + container = undefined!; + } + else { + container = file; + } break; case AssignmentDeclarationKind.ThisProperty: - bindThisPropertyAssignment(node as BindablePropertyAssignmentExpression); + container = declName.parent.expression; break; - case AssignmentDeclarationKind.Property: - bindSpecialPropertyAssignment(node as BindablePropertyAssignmentExpression); + case AssignmentDeclarationKind.PrototypeProperty: + container = (declName.parent.expression as PropertyAccessExpression).name; break; - case AssignmentDeclarationKind.None: - // Nothing to do + case AssignmentDeclarationKind.Property: + container = isPropertyAccessExpression(declName.parent.expression) ? declName.parent.expression.name : declName.parent.expression; break; - default: - Debug.fail("Unknown binary expression special property assignment kind"); - } - return checkStrictModeBinaryExpression(node); - case SyntaxKind.CatchClause: - return checkStrictModeCatchClause(node); - case SyntaxKind.DeleteExpression: - return checkStrictModeDeleteExpression(node); - case SyntaxKind.NumericLiteral: - return checkStrictModeNumericLiteral(node); - case SyntaxKind.PostfixUnaryExpression: - return checkStrictModePostfixUnaryExpression(node); - case SyntaxKind.PrefixUnaryExpression: - return checkStrictModePrefixUnaryExpression(node); - case SyntaxKind.WithStatement: - return checkStrictModeWithStatement(node); - case SyntaxKind.LabeledStatement: - return checkStrictModeLabeledStatement(node); - case SyntaxKind.ThisType: - seenThisKeyword = true; - return; - case SyntaxKind.TypePredicate: - break; // Binding the children will handle everything - case SyntaxKind.TypeParameter: - return bindTypeParameter(node as TypeParameterDeclaration); - case SyntaxKind.Parameter: - return bindParameter(node); - case SyntaxKind.VariableDeclaration: - return bindVariableDeclarationOrBindingElement(node); - case SyntaxKind.BindingElement: - node.flowNode = currentFlow; - return bindVariableDeclarationOrBindingElement(node); - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - return bindPropertyWorker(node as PropertyDeclaration | PropertySignature); - case SyntaxKind.PropertyAssignment: - case SyntaxKind.ShorthandPropertyAssignment: - return bindPropertyOrMethodOrAccessor(node, SymbolFlags.Property, SymbolFlags.PropertyExcludes); - case SyntaxKind.EnumMember: - return bindPropertyOrMethodOrAccessor(node, SymbolFlags.EnumMember, SymbolFlags.EnumMemberExcludes); - - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: - return declareSymbolAndAddToSymbolTable(node, SymbolFlags.Signature, SymbolFlags.None); - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - // If this is an ObjectLiteralExpression method, then it sits in the same space - // as other properties in the object literal. So we use SymbolFlags.PropertyExcludes - // so that it will conflict with any other object literal members with the same - // name. - return bindPropertyOrMethodOrAccessor(node, SymbolFlags.Method | ((node).questionToken ? SymbolFlags.Optional : SymbolFlags.None), - isObjectLiteralMethod(node) ? SymbolFlags.PropertyExcludes : SymbolFlags.MethodExcludes); - case SyntaxKind.FunctionDeclaration: - return bindFunctionDeclaration(node); - case SyntaxKind.Constructor: - return declareSymbolAndAddToSymbolTable(node, SymbolFlags.Constructor, /*symbolExcludes:*/ SymbolFlags.None); - case SyntaxKind.GetAccessor: - return bindPropertyOrMethodOrAccessor(node, SymbolFlags.GetAccessor, SymbolFlags.GetAccessorExcludes); - case SyntaxKind.SetAccessor: - return bindPropertyOrMethodOrAccessor(node, SymbolFlags.SetAccessor, SymbolFlags.SetAccessorExcludes); - case SyntaxKind.FunctionType: - case SyntaxKind.JSDocFunctionType: - case SyntaxKind.JSDocSignature: - case SyntaxKind.ConstructorType: - return bindFunctionOrConstructorType(node); - case SyntaxKind.TypeLiteral: - case SyntaxKind.JSDocTypeLiteral: - case SyntaxKind.MappedType: - return bindAnonymousTypeWorker(node as TypeLiteralNode | MappedTypeNode | JSDocTypeLiteral); - case SyntaxKind.JSDocClassTag: - return bindJSDocClassTag(node as JSDocClassTag); - case SyntaxKind.ObjectLiteralExpression: - return bindObjectLiteralExpression(node); - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return bindFunctionExpression(node); - - case SyntaxKind.CallExpression: - const assignmentKind = getAssignmentDeclarationKind(node as CallExpression); - switch (assignmentKind) { - case AssignmentDeclarationKind.ObjectDefinePropertyValue: - return bindObjectDefinePropertyAssignment(node as BindableObjectDefinePropertyCall); - case AssignmentDeclarationKind.ObjectDefinePropertyExports: - return bindObjectDefinePropertyExport(node as BindableObjectDefinePropertyCall); - case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: - return bindObjectDefinePrototypeProperty(node as BindableObjectDefinePropertyCall); case AssignmentDeclarationKind.None: - break; // Nothing to do - default: - return Debug.fail("Unknown call expression assignment declaration kind"); - } - if (isInJSFile(node)) { - bindCallExpression(node); + return Debug.fail("Shouldn't have detected typedef or enum on non-assignment declaration"); } - break; - - // Members of classes, interfaces, and modules - case SyntaxKind.ClassExpression: - case SyntaxKind.ClassDeclaration: - // All classes are automatically in strict mode in ES6. - inStrictMode = true; - return bindClassLikeDeclaration(node); - case SyntaxKind.InterfaceDeclaration: - return bindBlockScopedDeclaration(node, SymbolFlags.Interface, SymbolFlags.InterfaceExcludes); - case SyntaxKind.TypeAliasDeclaration: - return bindBlockScopedDeclaration(node, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); - case SyntaxKind.EnumDeclaration: - return bindEnumDeclaration(node); - case SyntaxKind.ModuleDeclaration: - return bindModuleDeclaration(node); - // Jsx-attributes - case SyntaxKind.JsxAttributes: - return bindJsxAttributes(node); - case SyntaxKind.JsxAttribute: - return bindJsxAttribute(node, SymbolFlags.Property, SymbolFlags.PropertyExcludes); - - // Imports and exports - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.NamespaceImport: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: - return declareSymbolAndAddToSymbolTable(node, SymbolFlags.Alias, SymbolFlags.AliasExcludes); - case SyntaxKind.NamespaceExportDeclaration: - return bindNamespaceExportDeclaration(node); - case SyntaxKind.ImportClause: - return bindImportClause(node); - case SyntaxKind.ExportDeclaration: - return bindExportDeclaration(node); - case SyntaxKind.ExportAssignment: - return bindExportAssignment(node); - case SyntaxKind.SourceFile: - updateStrictModeStatementList((node).statements); - return bindSourceFileIfExternalModule(); - case SyntaxKind.Block: - if (!isFunctionLike(node.parent)) { - return; - } - // falls through - case SyntaxKind.ModuleBlock: - return updateStrictModeStatementList((node).statements); - - case SyntaxKind.JSDocParameterTag: - if (node.parent.kind === SyntaxKind.JSDocSignature) { - return bindParameter(node as JSDocParameterTag); + if (container) { + declareModuleMember(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); } - if (node.parent.kind !== SyntaxKind.JSDocTypeLiteral) { - break; - } - // falls through - case SyntaxKind.JSDocPropertyTag: - const propTag = node as JSDocPropertyLikeTag; - const flags = propTag.isBracketed || propTag.typeExpression && propTag.typeExpression.type.kind === SyntaxKind.JSDocOptionalType ? - SymbolFlags.Property | SymbolFlags.Optional : - SymbolFlags.Property; - return declareSymbolAndAddToSymbolTable(propTag, flags, SymbolFlags.PropertyExcludes); - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocEnumTag: - return (delayedTypeAliases || (delayedTypeAliases = [])).push(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag); - } - } - - function bindPropertyWorker(node: PropertyDeclaration | PropertySignature) { - return bindPropertyOrMethodOrAccessor(node, SymbolFlags.Property | (node.questionToken ? SymbolFlags.Optional : SymbolFlags.None), SymbolFlags.PropertyExcludes); - } - - function bindAnonymousTypeWorker(node: TypeLiteralNode | MappedTypeNode | JSDocTypeLiteral) { - return bindAnonymousDeclaration(node, SymbolFlags.TypeLiteral, InternalSymbolName.Type); - } - - function bindSourceFileIfExternalModule() { - setExportContextFlag(file); - if (isExternalModule(file)) { - bindSourceFileAsExternalModule(); - } - else if (isJsonSourceFile(file)) { - bindSourceFileAsExternalModule(); - // Create symbol equivalent for the module.exports = {} - const originalSymbol = file.symbol; - declareSymbol(file.symbol.exports!, file.symbol, file, SymbolFlags.Property, SymbolFlags.All); - file.symbol = originalSymbol; - } - } - - function bindSourceFileAsExternalModule() { - bindAnonymousDeclaration(file, SymbolFlags.ValueModule, `"${removeFileExtension(file.fileName)}"` as __String); - } - - function bindExportAssignment(node: ExportAssignment) { - if (!container.symbol || !container.symbol.exports) { - // Export assignment in some sort of block construct - bindAnonymousDeclaration(node, SymbolFlags.Alias, getDeclarationName(node)!); - } - else { - const flags = exportAssignmentIsAlias(node) - // An export default clause with an EntityNameExpression or a class expression exports all meanings of that identifier or expression; - ? SymbolFlags.Alias - // An export default clause with any other expression exports a value - : SymbolFlags.Property; - // If there is an `export default x;` alias declaration, can't `export default` anything else. - // (In contrast, you can still have `export default function f() {}` and `export default interface I {}`.) - const symbol = declareSymbol(container.symbol.exports, container.symbol, node, flags, SymbolFlags.All); - - if (node.isExportEquals) { - // Will be an error later, since the module already has other exports. Just make sure this has a valueDeclaration set. - setValueDeclaration(symbol, node); + container = oldContainer; } } - } - - function bindNamespaceExportDeclaration(node: NamespaceExportDeclaration) { - if (node.modifiers && node.modifiers.length) { - file.bindDiagnostics.push(createDiagnosticForNode(node, Diagnostics.Modifiers_cannot_appear_here)); - } - const diag = !isSourceFile(node.parent) ? Diagnostics.Global_module_exports_may_only_appear_at_top_level - : !isExternalModule(node.parent) ? Diagnostics.Global_module_exports_may_only_appear_in_module_files - : !node.parent.isDeclarationFile ? Diagnostics.Global_module_exports_may_only_appear_in_declaration_files - : undefined; - if (diag) { - file.bindDiagnostics.push(createDiagnosticForNode(node, diag)); + else if (isJSDocEnumTag(typeAlias) || !typeAlias.fullName || typeAlias.fullName.kind === SyntaxKind.Identifier) { + parent = typeAlias.parent; + bindBlockScopedDeclaration(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); } else { - file.symbol.globalExports = file.symbol.globalExports || createSymbolTable(); - declareSymbol(file.symbol.globalExports, file.symbol, node, SymbolFlags.Alias, SymbolFlags.AliasExcludes); + bind(typeAlias.fullName); } } - - function bindExportDeclaration(node: ExportDeclaration) { - if (!container.symbol || !container.symbol.exports) { - // Export * in some sort of block construct - bindAnonymousDeclaration(node, SymbolFlags.ExportStar, getDeclarationName(node)!); - } - else if (!node.exportClause) { - // All export * declarations are collected in an __export symbol - declareSymbol(container.symbol.exports, container.symbol, node, SymbolFlags.ExportStar, SymbolFlags.None); + container = saveContainer; + lastContainer = saveLastContainer; + blockScopeContainer = saveBlockScopeContainer; + parent = saveParent; + currentFlow = saveCurrentFlow; + } + // The binder visits every node in the syntax tree so it is a convenient place to perform a single localized + // check for reserved words used as identifiers in strict mode code. + function checkStrictModeIdentifier(node: Identifier) { + if (inStrictMode && + (node.originalKeywordKind!) >= SyntaxKind.FirstFutureReservedWord && + (node.originalKeywordKind!) <= SyntaxKind.LastFutureReservedWord && + !isIdentifierName(node) && + !(node.flags & NodeFlags.Ambient) && + !(node.flags & NodeFlags.JSDoc)) { + // Report error only if there are no parse errors in file + if (!file.parseDiagnostics.length) { + file.bindDiagnostics.push(createDiagnosticForNode(node, getStrictModeIdentifierMessage(node), declarationNameToString(node))); } } - - function bindImportClause(node: ImportClause) { - if (node.name) { - declareSymbolAndAddToSymbolTable(node, SymbolFlags.Alias, SymbolFlags.AliasExcludes); - } + } + function getStrictModeIdentifierMessage(node: Node) { + // Provide specialized messages to help the user understand why we think they're in + // strict mode. + if (getContainingClass(node)) { + return Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode_Class_definitions_are_automatically_in_strict_mode; } - - function setCommonJsModuleIndicator(node: Node) { - if (file.externalModuleIndicator) { - return false; - } - if (!file.commonJsModuleIndicator) { - file.commonJsModuleIndicator = node; - bindSourceFileAsExternalModule(); - } - return true; + if (file.externalModuleIndicator) { + return Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode_Modules_are_automatically_in_strict_mode; } - - function bindObjectDefinePropertyExport(node: BindableObjectDefinePropertyCall) { - if (!setCommonJsModuleIndicator(node)) { - return; - } - const symbol = forEachIdentifierInEntityName(node.arguments[0], /*parent*/ undefined, (id, symbol) => { - if (symbol) { - addDeclarationToSymbol(symbol, id, SymbolFlags.Module | SymbolFlags.Assignment); - } - return symbol; - }); - if (symbol) { - const flags = SymbolFlags.Property | SymbolFlags.ExportValue; - declareSymbol(symbol.exports!, symbol, node, flags, SymbolFlags.None); - } + return Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode; + } + function checkStrictModeBinaryExpression(node: BinaryExpression) { + if (inStrictMode && isLeftHandSideExpression(node.left) && isAssignmentOperator(node.operatorToken.kind)) { + // ECMA 262 (Annex C) The identifier eval or arguments may not appear as the LeftHandSideExpression of an + // Assignment operator(11.13) or of a PostfixExpression(11.3) + checkStrictModeEvalOrArguments(node, (node.left)); } - - function bindExportsPropertyAssignment(node: BindableStaticPropertyAssignmentExpression) { - // When we create a property via 'exports.foo = bar', the 'exports.foo' property access - // expression is the declaration - if (!setCommonJsModuleIndicator(node)) { - return; - } - const symbol = forEachIdentifierInEntityName(node.left.expression, /*parent*/ undefined, (id, symbol) => { - if (symbol) { - addDeclarationToSymbol(symbol, id, SymbolFlags.Module | SymbolFlags.Assignment); - } - return symbol; - }); - if (symbol) { - const flags = isClassExpression(node.right) ? - SymbolFlags.Property | SymbolFlags.ExportValue | SymbolFlags.Class : - SymbolFlags.Property | SymbolFlags.ExportValue; - declareSymbol(symbol.exports!, symbol, node.left, flags, SymbolFlags.None); - } + } + function checkStrictModeCatchClause(node: CatchClause) { + // It is a SyntaxError if a TryStatement with a Catch occurs within strict code and the Identifier of the + // Catch production is eval or arguments + if (inStrictMode && node.variableDeclaration) { + checkStrictModeEvalOrArguments(node, node.variableDeclaration.name); } - - function bindModuleExportsAssignment(node: BindablePropertyAssignmentExpression) { - // A common practice in node modules is to set 'export = module.exports = {}', this ensures that 'exports' - // is still pointing to 'module.exports'. - // We do not want to consider this as 'export=' since a module can have only one of these. - // Similarly we do not want to treat 'module.exports = exports' as an 'export='. - if (!setCommonJsModuleIndicator(node)) { - return; - } - const assignedExpression = getRightMostAssignedExpression(node.right); - if (isEmptyObjectLiteral(assignedExpression) || container === file && isExportsOrModuleExportsOrAlias(file, assignedExpression)) { - return; - } - - // 'module.exports = expr' assignment - const flags = exportAssignmentIsAlias(node) - ? SymbolFlags.Alias // An export= with an EntityNameExpression or a ClassExpression exports all meanings of that identifier or class - : SymbolFlags.Property | SymbolFlags.ExportValue | SymbolFlags.ValueModule; - const symbol = declareSymbol(file.symbol.exports!, file.symbol, node, flags | SymbolFlags.Assignment, SymbolFlags.None); - setValueDeclaration(symbol, node); + } + function checkStrictModeDeleteExpression(node: DeleteExpression) { + // Grammar checking + if (inStrictMode && node.expression.kind === SyntaxKind.Identifier) { + // When a delete operator occurs within strict mode code, a SyntaxError is thrown if its + // UnaryExpression is a direct reference to a variable, function argument, or function name + const span = getErrorSpanForNode(file, node.expression); + file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, Diagnostics.delete_cannot_be_called_on_an_identifier_in_strict_mode)); } - - function bindThisPropertyAssignment(node: BindablePropertyAssignmentExpression | PropertyAccessExpression | LiteralLikeElementAccessExpression) { - Debug.assert(isInJSFile(node)); - const thisContainer = getThisContainer(node, /*includeArrowFunctions*/ false); - switch (thisContainer.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - let constructorSymbol: Symbol | undefined = thisContainer.symbol; - // For `f.prototype.m = function() { this.x = 0; }`, `this.x = 0` should modify `f`'s members, not the function expression. - if (isBinaryExpression(thisContainer.parent) && thisContainer.parent.operatorToken.kind === SyntaxKind.EqualsToken) { - const l = thisContainer.parent.left; - if (isBindableStaticAccessExpression(l) && isPrototypeAccess(l.expression)) { - constructorSymbol = lookupSymbolForPropertyAccess(l.expression.expression, thisParentContainer); - } - } - - if (constructorSymbol && constructorSymbol.valueDeclaration) { - // Declare a 'member' if the container is an ES5 class or ES6 constructor - constructorSymbol.members = constructorSymbol.members || createSymbolTable(); - // It's acceptable for multiple 'this' assignments of the same identifier to occur - if (hasDynamicName(node)) { - bindDynamicallyNamedThisPropertyAssignment(node, constructorSymbol); - } - else { - declareSymbol(constructorSymbol.members, constructorSymbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); - } - addDeclarationToSymbol(constructorSymbol, constructorSymbol.valueDeclaration, SymbolFlags.Class); - } - break; - - case SyntaxKind.Constructor: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - // this.foo assignment in a JavaScript class - // Bind this property to the containing class - const containingClass = thisContainer.parent; - const symbolTable = hasModifier(thisContainer, ModifierFlags.Static) ? containingClass.symbol.exports! : containingClass.symbol.members!; - if (hasDynamicName(node)) { - bindDynamicallyNamedThisPropertyAssignment(node, containingClass.symbol); - } - else { - declareSymbol(symbolTable, containingClass.symbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.None, /*isReplaceableByMethod*/ true); - } - break; - case SyntaxKind.SourceFile: - // this.property = assignment in a source file -- declare symbol in exports for a module, in locals for a script - if (hasDynamicName(node)) { - break; - } - else if ((thisContainer as SourceFile).commonJsModuleIndicator) { - declareSymbol(thisContainer.symbol.exports!, thisContainer.symbol, node, SymbolFlags.Property | SymbolFlags.ExportValue, SymbolFlags.None); - } - else { - declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.FunctionScopedVariableExcludes); - } - break; - - default: - Debug.failBadSyntaxKind(thisContainer); + } + function isEvalOrArgumentsIdentifier(node: Node): boolean { + return isIdentifier(node) && (node.escapedText === "eval" || node.escapedText === "arguments"); + } + function checkStrictModeEvalOrArguments(contextNode: Node, name: Node | undefined) { + if (name && name.kind === SyntaxKind.Identifier) { + const identifier = (name); + if (isEvalOrArgumentsIdentifier(identifier)) { + // We check first if the name is inside class declaration or class expression; if so give explicit message + // otherwise report generic error message. + const span = getErrorSpanForNode(file, name); + file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, getStrictModeEvalOrArgumentsMessage(contextNode), idText(identifier))); } } - - function bindDynamicallyNamedThisPropertyAssignment(node: BinaryExpression | DynamicNamedDeclaration, symbol: Symbol) { - bindAnonymousDeclaration(node, SymbolFlags.Property, InternalSymbolName.Computed); - addLateBoundAssignmentDeclarationToSymbol(node, symbol); + } + function getStrictModeEvalOrArgumentsMessage(node: Node) { + // Provide specialized messages to help the user understand why we think they're in + // strict mode. + if (getContainingClass(node)) { + return Diagnostics.Invalid_use_of_0_Class_definitions_are_automatically_in_strict_mode; } - - function addLateBoundAssignmentDeclarationToSymbol(node: BinaryExpression | DynamicNamedDeclaration, symbol: Symbol | undefined) { - if (symbol) { - const members = symbol.assignmentDeclarationMembers || (symbol.assignmentDeclarationMembers = createMap()); - members.set("" + getNodeId(node), node); - } + if (file.externalModuleIndicator) { + return Diagnostics.Invalid_use_of_0_Modules_are_automatically_in_strict_mode; } - - function bindSpecialPropertyDeclaration(node: PropertyAccessExpression | LiteralLikeElementAccessExpression) { - if (node.expression.kind === SyntaxKind.ThisKeyword) { - bindThisPropertyAssignment(node); - } - else if (isBindableStaticAccessExpression(node) && node.parent.parent.kind === SyntaxKind.SourceFile) { - if (isPrototypeAccess(node.expression)) { - bindPrototypePropertyAssignment(node, node.parent); - } - else { - bindStaticPropertyAssignment(node); - } - } + return Diagnostics.Invalid_use_of_0_in_strict_mode; + } + function checkStrictModeFunctionName(node: FunctionLikeDeclaration) { + if (inStrictMode) { + // It is a SyntaxError if the identifier eval or arguments appears within a FormalParameterList of a strict mode FunctionDeclaration or FunctionExpression (13.1)) + checkStrictModeEvalOrArguments(node, node.name); } - - /** For `x.prototype = { p, ... }`, declare members p,... if `x` is function/class/{}, or not declared. */ - function bindPrototypeAssignment(node: BindableStaticPropertyAssignmentExpression) { - node.left.parent = node; - node.right.parent = node; - bindPropertyAssignment(node.left.expression, node.left, /*isPrototypeProperty*/ false, /*containerIsClass*/ true); - } - - function bindObjectDefinePrototypeProperty(node: BindableObjectDefinePropertyCall) { - const namespaceSymbol = lookupSymbolForPropertyAccess((node.arguments[0] as PropertyAccessExpression).expression as EntityNameExpression); - if (namespaceSymbol && namespaceSymbol.valueDeclaration) { - // Ensure the namespace symbol becomes class-like - addDeclarationToSymbol(namespaceSymbol, namespaceSymbol.valueDeclaration, SymbolFlags.Class); - } - bindPotentiallyNewExpandoMemberToNamespace(node, namespaceSymbol, /*isPrototypeProperty*/ true); - } - - /** - * For `x.prototype.y = z`, declare a member `y` on `x` if `x` is a function or class, or not declared. - * Note that jsdoc preceding an ExpressionStatement like `x.prototype.y;` is also treated as a declaration. - */ - function bindPrototypePropertyAssignment(lhs: BindableStaticAccessExpression, parent: Node) { - // Look up the function in the local scope, since prototype assignments should - // follow the function declaration - const classPrototype = lhs.expression as BindableStaticAccessExpression; - const constructorFunction = classPrototype.expression; - - // Fix up parent pointers since we're going to use these nodes before we bind into them - lhs.parent = parent; - constructorFunction.parent = classPrototype; - classPrototype.parent = lhs; - - bindPropertyAssignment(constructorFunction, lhs, /*isPrototypeProperty*/ true, /*containerIsClass*/ true); - } - - function bindObjectDefinePropertyAssignment(node: BindableObjectDefinePropertyCall) { - let namespaceSymbol = lookupSymbolForPropertyAccess(node.arguments[0]); - const isToplevel = node.parent.parent.kind === SyntaxKind.SourceFile; - namespaceSymbol = bindPotentiallyMissingNamespaces(namespaceSymbol, node.arguments[0], isToplevel, /*isPrototypeProperty*/ false, /*containerIsClass*/ false); - bindPotentiallyNewExpandoMemberToNamespace(node, namespaceSymbol, /*isPrototypeProperty*/ false); - } - - function bindSpecialPropertyAssignment(node: BindablePropertyAssignmentExpression) { - // Class declarations in Typescript do not allow property declarations - const parentSymbol = lookupSymbolForPropertyAccess(node.left.expression); - if (!isInJSFile(node) && !isFunctionSymbol(parentSymbol)) { - return; - } - // Fix up parent pointers since we're going to use these nodes before we bind into them - node.left.parent = node; - node.right.parent = node; - if (isIdentifier(node.left.expression) && container === file && isExportsOrModuleExportsOrAlias(file, node.left.expression)) { - // This can be an alias for the 'exports' or 'module.exports' names, e.g. - // var util = module.exports; - // util.property = function ... - bindExportsPropertyAssignment(node as BindableStaticPropertyAssignmentExpression); - } - else { - if (hasDynamicName(node)) { - bindAnonymousDeclaration(node, SymbolFlags.Property | SymbolFlags.Assignment, InternalSymbolName.Computed); - const sym = bindPotentiallyMissingNamespaces(parentSymbol, node.left.expression, isTopLevelNamespaceAssignment(node.left), /*isPrototype*/ false, /*containerIsClass*/ false); - addLateBoundAssignmentDeclarationToSymbol(node, sym); - } - else { - bindStaticPropertyAssignment(cast(node.left, isBindableStaticAccessExpression)); - } - } + } + function getStrictModeBlockScopeFunctionDeclarationMessage(node: Node) { + // Provide specialized messages to help the user understand why we think they're in + // strict mode. + if (getContainingClass(node)) { + return Diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES3_or_ES5_Class_definitions_are_automatically_in_strict_mode; } - - /** - * For nodes like `x.y = z`, declare a member 'y' on 'x' if x is a function (or IIFE) or class or {}, or not declared. - * Also works for expression statements preceded by JSDoc, like / ** @type number * / x.y; - */ - function bindStaticPropertyAssignment(node: BindableStaticAccessExpression) { - node.expression.parent = node; - bindPropertyAssignment(node.expression, node, /*isPrototypeProperty*/ false, /*containerIsClass*/ false); - } - - function bindPotentiallyMissingNamespaces(namespaceSymbol: Symbol | undefined, entityName: BindableStaticNameExpression, isToplevel: boolean, isPrototypeProperty: boolean, containerIsClass: boolean) { - if (isToplevel && !isPrototypeProperty) { - // make symbols or add declarations for intermediate containers - const flags = SymbolFlags.Module | SymbolFlags.Assignment; - const excludeFlags = SymbolFlags.ValueModuleExcludes & ~SymbolFlags.Assignment; - namespaceSymbol = forEachIdentifierInEntityName(entityName, namespaceSymbol, (id, symbol, parent) => { - if (symbol) { - addDeclarationToSymbol(symbol, id, flags); - return symbol; - } - else { - const table = parent ? parent.exports! : - file.jsGlobalAugmentations || (file.jsGlobalAugmentations = createSymbolTable()); - return declareSymbol(table, parent, id, flags, excludeFlags); - } - }); - } - if (containerIsClass && namespaceSymbol && namespaceSymbol.valueDeclaration) { - addDeclarationToSymbol(namespaceSymbol, namespaceSymbol.valueDeclaration, SymbolFlags.Class); - } - return namespaceSymbol; + if (file.externalModuleIndicator) { + return Diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES3_or_ES5_Modules_are_automatically_in_strict_mode; } - - function bindPotentiallyNewExpandoMemberToNamespace(declaration: BindableStaticAccessExpression | CallExpression, namespaceSymbol: Symbol | undefined, isPrototypeProperty: boolean) { - if (!namespaceSymbol || !isExpandoSymbol(namespaceSymbol)) { - return; - } - - // Set up the members collection if it doesn't exist already - const symbolTable = isPrototypeProperty ? - (namespaceSymbol.members || (namespaceSymbol.members = createSymbolTable())) : - (namespaceSymbol.exports || (namespaceSymbol.exports = createSymbolTable())); - - let includes = SymbolFlags.None; - let excludes = SymbolFlags.None; - // Method-like - if (isFunctionLikeDeclaration(getAssignedExpandoInitializer(declaration)!)) { - includes = SymbolFlags.Method; - excludes = SymbolFlags.MethodExcludes; - } - // Maybe accessor-like - else if (isCallExpression(declaration) && isBindableObjectDefinePropertyCall(declaration)) { - if (some(declaration.arguments[2].properties, p => { - const id = getNameOfDeclaration(p); - return !!id && isIdentifier(id) && idText(id) === "set"; - })) { - // We mix in `SymbolFLags.Property` so in the checker `getTypeOfVariableParameterOrProperty` is used for this - // symbol, instead of `getTypeOfAccessor` (which will assert as there is no real accessor declaration) - includes |= SymbolFlags.SetAccessor | SymbolFlags.Property; - excludes |= SymbolFlags.SetAccessorExcludes; - } - if (some(declaration.arguments[2].properties, p => { - const id = getNameOfDeclaration(p); - return !!id && isIdentifier(id) && idText(id) === "get"; - })) { - includes |= SymbolFlags.GetAccessor | SymbolFlags.Property; - excludes |= SymbolFlags.GetAccessorExcludes; - } - } - - if (includes === SymbolFlags.None) { - includes = SymbolFlags.Property; - excludes = SymbolFlags.PropertyExcludes; - } - - declareSymbol(symbolTable, namespaceSymbol, declaration, includes | SymbolFlags.Assignment, excludes & ~SymbolFlags.Assignment); - } - - function isTopLevelNamespaceAssignment(propertyAccess: BindableAccessExpression) { - return isBinaryExpression(propertyAccess.parent) - ? getParentOfBinaryExpression(propertyAccess.parent).parent.kind === SyntaxKind.SourceFile - : propertyAccess.parent.parent.kind === SyntaxKind.SourceFile; - } - - function bindPropertyAssignment(name: BindableStaticNameExpression, propertyAccess: BindableStaticAccessExpression, isPrototypeProperty: boolean, containerIsClass: boolean) { - let namespaceSymbol = lookupSymbolForPropertyAccess(name); - const isToplevel = isTopLevelNamespaceAssignment(propertyAccess); - namespaceSymbol = bindPotentiallyMissingNamespaces(namespaceSymbol, propertyAccess.expression, isToplevel, isPrototypeProperty, containerIsClass); - bindPotentiallyNewExpandoMemberToNamespace(propertyAccess, namespaceSymbol, isPrototypeProperty); - } - - /** - * Javascript expando values are: - * - Functions - * - classes - * - namespaces - * - variables initialized with function expressions - * - with class expressions - * - with empty object literals - * - with non-empty object literals if assigned to the prototype property - */ - function isExpandoSymbol(symbol: Symbol): boolean { - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.NamespaceModule)) { - return true; - } - const node = symbol.valueDeclaration; - if (node && isCallExpression(node)) { - return !!getAssignedExpandoInitializer(node); - } - let init = !node ? undefined : - isVariableDeclaration(node) ? node.initializer : - isBinaryExpression(node) ? node.right : - isPropertyAccessExpression(node) && isBinaryExpression(node.parent) ? node.parent.right : - undefined; - init = init && getRightMostAssignedExpression(init); - if (init) { - const isPrototypeAssignment = isPrototypeAccess(isVariableDeclaration(node) ? node.name : isBinaryExpression(node) ? node.left : node); - return !!getExpandoInitializer(isBinaryExpression(init) && (init.operatorToken.kind === SyntaxKind.BarBarToken || init.operatorToken.kind === SyntaxKind.QuestionQuestionToken) ? init.right : init, isPrototypeAssignment); + return Diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES3_or_ES5; + } + function checkStrictModeFunctionDeclaration(node: FunctionDeclaration) { + if (languageVersion < ScriptTarget.ES2015) { + // Report error if function is not top level function declaration + if (blockScopeContainer.kind !== SyntaxKind.SourceFile && + blockScopeContainer.kind !== SyntaxKind.ModuleDeclaration && + !isFunctionLike(blockScopeContainer)) { + // We check first if the name is inside class declaration or class expression; if so give explicit message + // otherwise report generic error message. + const errorSpan = getErrorSpanForNode(file, node); + file.bindDiagnostics.push(createFileDiagnostic(file, errorSpan.start, errorSpan.length, getStrictModeBlockScopeFunctionDeclarationMessage(node))); } - return false; } - - function getParentOfBinaryExpression(expr: Node) { - while (isBinaryExpression(expr.parent)) { - expr = expr.parent; - } - return expr.parent; + } + function checkStrictModeNumericLiteral(node: NumericLiteral) { + if (inStrictMode && node.numericLiteralFlags & TokenFlags.Octal) { + file.bindDiagnostics.push(createDiagnosticForNode(node, Diagnostics.Octal_literals_are_not_allowed_in_strict_mode)); } - - function lookupSymbolForPropertyAccess(node: BindableStaticNameExpression, lookupContainer: Node = container): Symbol | undefined { - if (isIdentifier(node)) { - return lookupSymbolForNameWorker(lookupContainer, node.escapedText); - } - else { - const symbol = lookupSymbolForPropertyAccess(node.expression); - return symbol && symbol.exports && symbol.exports.get(getElementOrPropertyAccessName(node)); + } + function checkStrictModePostfixUnaryExpression(node: PostfixUnaryExpression) { + // Grammar checking + // The identifier eval or arguments may not appear as the LeftHandSideExpression of an + // Assignment operator(11.13) or of a PostfixExpression(11.3) or as the UnaryExpression + // operated upon by a Prefix Increment(11.4.4) or a Prefix Decrement(11.4.5) operator. + if (inStrictMode) { + checkStrictModeEvalOrArguments(node, (node.operand)); + } + } + function checkStrictModePrefixUnaryExpression(node: PrefixUnaryExpression) { + // Grammar checking + if (inStrictMode) { + if (node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) { + checkStrictModeEvalOrArguments(node, (node.operand)); } } - - function forEachIdentifierInEntityName(e: BindableStaticNameExpression, parent: Symbol | undefined, action: (e: Declaration, symbol: Symbol | undefined, parent: Symbol | undefined) => Symbol | undefined): Symbol | undefined { - if (isExportsOrModuleExportsOrAlias(file, e)) { - return file.symbol; + } + function checkStrictModeWithStatement(node: WithStatement) { + // Grammar checking for withStatement + if (inStrictMode) { + errorOnFirstToken(node, Diagnostics.with_statements_are_not_allowed_in_strict_mode); + } + } + function checkStrictModeLabeledStatement(node: LabeledStatement) { + // Grammar checking for labeledStatement + if (inStrictMode && (options.target!) >= ScriptTarget.ES2015) { + if (isDeclarationStatement(node.statement) || isVariableStatement(node.statement)) { + errorOnFirstToken(node.label, Diagnostics.A_label_is_not_allowed_here); } - else if (isIdentifier(e)) { - return action(e, lookupSymbolForPropertyAccess(e), parent); + } + } + function errorOnFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any) { + const span = getSpanOfTokenAtPosition(file, node.pos); + file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, message, arg0, arg1, arg2)); + } + function errorOrSuggestionOnNode(isError: boolean, node: Node, message: DiagnosticMessage): void { + errorOrSuggestionOnRange(isError, node, node, message); + } + function errorOrSuggestionOnRange(isError: boolean, startNode: Node, endNode: Node, message: DiagnosticMessage): void { + addErrorOrSuggestionDiagnostic(isError, { pos: getTokenPosOfNode(startNode, file), end: endNode.end }, message); + } + function addErrorOrSuggestionDiagnostic(isError: boolean, range: TextRange, message: DiagnosticMessage): void { + const diag = createFileDiagnostic(file, range.pos, range.end - range.pos, message); + if (isError) { + file.bindDiagnostics.push(diag); + } + else { + file.bindSuggestionDiagnostics = append(file.bindSuggestionDiagnostics, { ...diag, category: DiagnosticCategory.Suggestion }); + } + } + function bind(node: Node | undefined): void { + if (!node) { + return; + } + node.parent = parent; + const saveInStrictMode = inStrictMode; + // Even though in the AST the jsdoc @typedef node belongs to the current node, + // its symbol might be in the same scope with the current node's symbol. Consider: + // + // /** @typedef {string | number} MyType */ + // function foo(); + // + // Here the current node is "foo", which is a container, but the scope of "MyType" should + // not be inside "foo". Therefore we always bind @typedef before bind the parent node, + // and skip binding this tag later when binding all the other jsdoc tags. + // First we bind declaration nodes to a symbol if possible. We'll both create a symbol + // and then potentially add the symbol to an appropriate symbol table. Possible + // destination symbol tables are: + // + // 1) The 'exports' table of the current container's symbol. + // 2) The 'members' table of the current container's symbol. + // 3) The 'locals' table of the current container. + // + // However, not all symbols will end up in any of these tables. 'Anonymous' symbols + // (like TypeLiterals for example) will not be put in any table. + bindWorker(node); + // Then we recurse into the children of the node to bind them as well. For certain + // symbols we do specialized work when we recurse. For example, we'll keep track of + // the current 'container' node when it changes. This helps us know which symbol table + // a local should go into for example. Since terminal nodes are known not to have + // children, as an optimization we don't process those. + if (node.kind > SyntaxKind.LastToken) { + const saveParent = parent; + parent = node; + const containerFlags = getContainerFlags(node); + if (containerFlags === ContainerFlags.None) { + bindChildren(node); } else { - const s = forEachIdentifierInEntityName(e.expression, parent, action); - return action(getNameOrArgument(e), s && s.exports && s.exports.get(getElementOrPropertyAccessName(e)), s); + bindContainer(node, containerFlags); } + parent = saveParent; } - - function bindCallExpression(node: CallExpression) { - // We're only inspecting call expressions to detect CommonJS modules, so we can skip - // this check if we've already seen the module indicator - if (!file.commonJsModuleIndicator && isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ false)) { - setCommonJsModuleIndicator(node); - } + else if (!skipTransformFlagAggregation && (node.transformFlags & TransformFlags.HasComputedFlags) === 0) { + subtreeTransformFlags |= computeTransformFlagsForNode(node, 0); + const saveParent = parent; + if (node.kind === SyntaxKind.EndOfFileToken) + parent = node; + bindJSDoc(node); + parent = saveParent; } - - function bindClassLikeDeclaration(node: ClassLikeDeclaration) { - if (node.kind === SyntaxKind.ClassDeclaration) { - bindBlockScopedDeclaration(node, SymbolFlags.Class, SymbolFlags.ClassExcludes); + inStrictMode = saveInStrictMode; + } + function bindJSDoc(node: Node) { + if (hasJSDocNodes(node)) { + if (isInJSFile(node)) { + for (const j of node.jsDoc!) { + bind(j); + } } else { - const bindingName = node.name ? node.name.escapedText : InternalSymbolName.Class; - bindAnonymousDeclaration(node, SymbolFlags.Class, bindingName); - // Add name of class expression into the map for semantic classifier - if (node.name) { - classifiableNames.set(node.name.escapedText, true); + for (const j of node.jsDoc!) { + setParentPointers(node, j); } } - - const { symbol } = node; - - // TypeScript 1.0 spec (April 2014): 8.4 - // Every class automatically contains a static property member named 'prototype', the - // type of which is an instantiation of the class type with type Any supplied as a type - // argument for each type parameter. It is an error to explicitly declare a static - // property member with the name 'prototype'. - // - // Note: we check for this here because this class may be merging into a module. The - // module might have an exported variable called 'prototype'. We can't allow that as - // that would clash with the built-in 'prototype' for the class. - const prototypeSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Prototype, "prototype" as __String); - const symbolExport = symbol.exports!.get(prototypeSymbol.escapedName); - if (symbolExport) { - if (node.name) { - node.name.parent = node; + } + } + function updateStrictModeStatementList(statements: NodeArray) { + if (!inStrictMode) { + for (const statement of statements) { + if (!isPrologueDirective(statement)) { + return; + } + if (isUseStrictPrologueDirective((statement))) { + inStrictMode = true; + return; } - file.bindDiagnostics.push(createDiagnosticForNode(symbolExport.declarations[0], Diagnostics.Duplicate_identifier_0, symbolName(prototypeSymbol))); - } - symbol.exports!.set(prototypeSymbol.escapedName, prototypeSymbol); - prototypeSymbol.parent = symbol; - } - - function bindEnumDeclaration(node: EnumDeclaration) { - return isEnumConst(node) - ? bindBlockScopedDeclaration(node, SymbolFlags.ConstEnum, SymbolFlags.ConstEnumExcludes) - : bindBlockScopedDeclaration(node, SymbolFlags.RegularEnum, SymbolFlags.RegularEnumExcludes); - } - - function bindVariableDeclarationOrBindingElement(node: VariableDeclaration | BindingElement) { - if (inStrictMode) { - checkStrictModeEvalOrArguments(node, node.name); } - - if (!isBindingPattern(node.name)) { - if (isBlockOrCatchScoped(node)) { - bindBlockScopedDeclaration(node, SymbolFlags.BlockScopedVariable, SymbolFlags.BlockScopedVariableExcludes); + } + } + /// Should be called only on prologue directives (isPrologueDirective(node) should be true) + function isUseStrictPrologueDirective(node: ExpressionStatement): boolean { + const nodeText = getSourceTextOfNodeFromSourceFile(file, node.expression); + // Note: the node text must be exactly "use strict" or 'use strict'. It is not ok for the + // string to contain unicode escapes (as per ES5). + return nodeText === '"use strict"' || nodeText === "'use strict'"; + } + function bindWorker(node: Node) { + switch (node.kind) { + /* Strict mode checks */ + case SyntaxKind.Identifier: + // for typedef type names with namespaces, bind the new jsdoc type symbol here + // because it requires all containing namespaces to be in effect, namely the + // current "blockScopeContainer" needs to be set to its immediate namespace parent. + if ((node).isInJSDocNamespace) { + let parentNode = node.parent; + while (parentNode && !isJSDocTypeAlias(parentNode)) { + parentNode = parentNode.parent; + } + bindBlockScopedDeclaration((parentNode as Declaration), SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); + break; } - else if (isParameterDeclaration(node)) { - // It is safe to walk up parent chain to find whether the node is a destructuring parameter declaration - // because its parent chain has already been set up, since parents are set before descending into children. - // - // If node is a binding element in parameter declaration, we need to use ParameterExcludes. - // Using ParameterExcludes flag allows the compiler to report an error on duplicate identifiers in Parameter Declaration - // For example: - // function foo([a,a]) {} // Duplicate Identifier error - // function bar(a,a) {} // Duplicate Identifier error, parameter declaration in this case is handled in bindParameter - // // which correctly set excluded symbols - declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes); + // falls through + case SyntaxKind.ThisKeyword: + if (currentFlow && (isExpression(node) || parent.kind === SyntaxKind.ShorthandPropertyAssignment)) { + node.flowNode = currentFlow; } - else { - declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.FunctionScopedVariableExcludes); + return checkStrictModeIdentifier((node)); + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + const expr = (node as PropertyAccessExpression | ElementAccessExpression); + if (currentFlow && isNarrowableReference(expr)) { + expr.flowNode = currentFlow; } - } - } - - function bindParameter(node: ParameterDeclaration | JSDocParameterTag) { - if (node.kind === SyntaxKind.JSDocParameterTag && container.kind !== SyntaxKind.JSDocSignature) { - return; - } - if (inStrictMode && !(node.flags & NodeFlags.Ambient)) { - // It is a SyntaxError if the identifier eval or arguments appears within a FormalParameterList of a - // strict mode FunctionLikeDeclaration or FunctionExpression(13.1) - checkStrictModeEvalOrArguments(node, node.name); - } - - if (isBindingPattern(node.name)) { - bindAnonymousDeclaration(node, SymbolFlags.FunctionScopedVariable, "__" + (node as ParameterDeclaration).parent.parameters.indexOf(node as ParameterDeclaration) as __String); - } - else { - declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes); - } - - // If this is a property-parameter, then also declare the property symbol into the - // containing class. - if (isParameterPropertyDeclaration(node, node.parent)) { - const classDeclaration = node.parent.parent; - declareSymbol(classDeclaration.symbol.members!, classDeclaration.symbol, node, SymbolFlags.Property | (node.questionToken ? SymbolFlags.Optional : SymbolFlags.None), SymbolFlags.PropertyExcludes); - } - } - - function bindFunctionDeclaration(node: FunctionDeclaration) { - if (!file.isDeclarationFile && !(node.flags & NodeFlags.Ambient)) { - if (isAsyncFunction(node)) { - emitFlags |= NodeFlags.HasAsyncFunctions; + if (isSpecialPropertyDeclaration(expr)) { + bindSpecialPropertyDeclaration(expr); } - } - - checkStrictModeFunctionName(node); - if (inStrictMode) { - checkStrictModeFunctionDeclaration(node); - bindBlockScopedDeclaration(node, SymbolFlags.Function, SymbolFlags.FunctionExcludes); - } - else { - declareSymbolAndAddToSymbolTable(node, SymbolFlags.Function, SymbolFlags.FunctionExcludes); - } - } - - function bindFunctionExpression(node: FunctionExpression) { - if (!file.isDeclarationFile && !(node.flags & NodeFlags.Ambient)) { - if (isAsyncFunction(node)) { - emitFlags |= NodeFlags.HasAsyncFunctions; + if (isInJSFile(expr) && + file.commonJsModuleIndicator && + isModuleExportsAccessExpression(expr) && + !lookupSymbolForNameWorker(blockScopeContainer, ("module" as __String))) { + declareSymbol((file.locals!), /*parent*/ undefined, expr.expression, SymbolFlags.FunctionScopedVariable | SymbolFlags.ModuleExports, SymbolFlags.FunctionScopedVariableExcludes); } - } - if (currentFlow) { - node.flowNode = currentFlow; - } - checkStrictModeFunctionName(node); - const bindingName = node.name ? node.name.escapedText : InternalSymbolName.Function; - return bindAnonymousDeclaration(node, SymbolFlags.Function, bindingName); - } - - function bindPropertyOrMethodOrAccessor(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { - if (!file.isDeclarationFile && !(node.flags & NodeFlags.Ambient) && isAsyncFunction(node)) { - emitFlags |= NodeFlags.HasAsyncFunctions; - } - - if (currentFlow && isObjectLiteralOrClassExpressionMethod(node)) { + break; + case SyntaxKind.BinaryExpression: + const specialKind = getAssignmentDeclarationKind((node as BinaryExpression)); + switch (specialKind) { + case AssignmentDeclarationKind.ExportsProperty: + bindExportsPropertyAssignment((node as BindableStaticPropertyAssignmentExpression)); + break; + case AssignmentDeclarationKind.ModuleExports: + bindModuleExportsAssignment((node as BindablePropertyAssignmentExpression)); + break; + case AssignmentDeclarationKind.PrototypeProperty: + bindPrototypePropertyAssignment((node as BindableStaticPropertyAssignmentExpression).left, node); + break; + case AssignmentDeclarationKind.Prototype: + bindPrototypeAssignment((node as BindableStaticPropertyAssignmentExpression)); + break; + case AssignmentDeclarationKind.ThisProperty: + bindThisPropertyAssignment((node as BindablePropertyAssignmentExpression)); + break; + case AssignmentDeclarationKind.Property: + bindSpecialPropertyAssignment((node as BindablePropertyAssignmentExpression)); + break; + case AssignmentDeclarationKind.None: + // Nothing to do + break; + default: + Debug.fail("Unknown binary expression special property assignment kind"); + } + return checkStrictModeBinaryExpression((node)); + case SyntaxKind.CatchClause: + return checkStrictModeCatchClause((node)); + case SyntaxKind.DeleteExpression: + return checkStrictModeDeleteExpression((node)); + case SyntaxKind.NumericLiteral: + return checkStrictModeNumericLiteral((node)); + case SyntaxKind.PostfixUnaryExpression: + return checkStrictModePostfixUnaryExpression((node)); + case SyntaxKind.PrefixUnaryExpression: + return checkStrictModePrefixUnaryExpression((node)); + case SyntaxKind.WithStatement: + return checkStrictModeWithStatement((node)); + case SyntaxKind.LabeledStatement: + return checkStrictModeLabeledStatement((node)); + case SyntaxKind.ThisType: + seenThisKeyword = true; + return; + case SyntaxKind.TypePredicate: + break; // Binding the children will handle everything + case SyntaxKind.TypeParameter: + return bindTypeParameter((node as TypeParameterDeclaration)); + case SyntaxKind.Parameter: + return bindParameter((node)); + case SyntaxKind.VariableDeclaration: + return bindVariableDeclarationOrBindingElement((node)); + case SyntaxKind.BindingElement: node.flowNode = currentFlow; - } - - return hasDynamicName(node) - ? bindAnonymousDeclaration(node, symbolFlags, InternalSymbolName.Computed) - : declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes); - } - - function getInferTypeContainer(node: Node): ConditionalTypeNode | undefined { - const extendsType = findAncestor(node, n => n.parent && isConditionalTypeNode(n.parent) && n.parent.extendsType === n); - return extendsType && extendsType.parent as ConditionalTypeNode; - } - - function bindTypeParameter(node: TypeParameterDeclaration) { - if (isJSDocTemplateTag(node.parent)) { - const container = find((node.parent.parent as JSDoc).tags!, isJSDocTypeAlias) || getHostSignatureFromJSDoc(node.parent); // TODO: GH#18217 - if (container) { - if (!container.locals) { - container.locals = createSymbolTable(); - } - declareSymbol(container.locals, /*parent*/ undefined, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); + return bindVariableDeclarationOrBindingElement((node)); + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + return bindPropertyWorker((node as PropertyDeclaration | PropertySignature)); + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + return bindPropertyOrMethodOrAccessor((node), SymbolFlags.Property, SymbolFlags.PropertyExcludes); + case SyntaxKind.EnumMember: + return bindPropertyOrMethodOrAccessor((node), SymbolFlags.EnumMember, SymbolFlags.EnumMemberExcludes); + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + return declareSymbolAndAddToSymbolTable((node), SymbolFlags.Signature, SymbolFlags.None); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + // If this is an ObjectLiteralExpression method, then it sits in the same space + // as other properties in the object literal. So we use SymbolFlags.PropertyExcludes + // so that it will conflict with any other object literal members with the same + // name. + return bindPropertyOrMethodOrAccessor((node), SymbolFlags.Method | ((node).questionToken ? SymbolFlags.Optional : SymbolFlags.None), isObjectLiteralMethod(node) ? SymbolFlags.PropertyExcludes : SymbolFlags.MethodExcludes); + case SyntaxKind.FunctionDeclaration: + return bindFunctionDeclaration((node)); + case SyntaxKind.Constructor: + return declareSymbolAndAddToSymbolTable((node), SymbolFlags.Constructor, /*symbolExcludes:*/ SymbolFlags.None); + case SyntaxKind.GetAccessor: + return bindPropertyOrMethodOrAccessor((node), SymbolFlags.GetAccessor, SymbolFlags.GetAccessorExcludes); + case SyntaxKind.SetAccessor: + return bindPropertyOrMethodOrAccessor((node), SymbolFlags.SetAccessor, SymbolFlags.SetAccessorExcludes); + case SyntaxKind.FunctionType: + case SyntaxKind.JSDocFunctionType: + case SyntaxKind.JSDocSignature: + case SyntaxKind.ConstructorType: + return bindFunctionOrConstructorType((node)); + case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: + case SyntaxKind.MappedType: + return bindAnonymousTypeWorker((node as TypeLiteralNode | MappedTypeNode | JSDocTypeLiteral)); + case SyntaxKind.JSDocClassTag: + return bindJSDocClassTag((node as JSDocClassTag)); + case SyntaxKind.ObjectLiteralExpression: + return bindObjectLiteralExpression((node)); + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return bindFunctionExpression((node)); + case SyntaxKind.CallExpression: + const assignmentKind = getAssignmentDeclarationKind((node as CallExpression)); + switch (assignmentKind) { + case AssignmentDeclarationKind.ObjectDefinePropertyValue: + return bindObjectDefinePropertyAssignment((node as BindableObjectDefinePropertyCall)); + case AssignmentDeclarationKind.ObjectDefinePropertyExports: + return bindObjectDefinePropertyExport((node as BindableObjectDefinePropertyCall)); + case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: + return bindObjectDefinePrototypeProperty((node as BindableObjectDefinePropertyCall)); + case AssignmentDeclarationKind.None: + break; // Nothing to do + default: + return Debug.fail("Unknown call expression assignment declaration kind"); } - else { - declareSymbolAndAddToSymbolTable(node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); + if (isInJSFile(node)) { + bindCallExpression((node)); } - } - else if (node.parent.kind === SyntaxKind.InferType) { - const container = getInferTypeContainer(node.parent); - if (container) { - if (!container.locals) { - container.locals = createSymbolTable(); - } - declareSymbol(container.locals, /*parent*/ undefined, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); + break; + // Members of classes, interfaces, and modules + case SyntaxKind.ClassExpression: + case SyntaxKind.ClassDeclaration: + // All classes are automatically in strict mode in ES6. + inStrictMode = true; + return bindClassLikeDeclaration((node)); + case SyntaxKind.InterfaceDeclaration: + return bindBlockScopedDeclaration((node), SymbolFlags.Interface, SymbolFlags.InterfaceExcludes); + case SyntaxKind.TypeAliasDeclaration: + return bindBlockScopedDeclaration((node), SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); + case SyntaxKind.EnumDeclaration: + return bindEnumDeclaration((node)); + case SyntaxKind.ModuleDeclaration: + return bindModuleDeclaration((node)); + // Jsx-attributes + case SyntaxKind.JsxAttributes: + return bindJsxAttributes((node)); + case SyntaxKind.JsxAttribute: + return bindJsxAttribute((node), SymbolFlags.Property, SymbolFlags.PropertyExcludes); + // Imports and exports + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: + return declareSymbolAndAddToSymbolTable((node), SymbolFlags.Alias, SymbolFlags.AliasExcludes); + case SyntaxKind.NamespaceExportDeclaration: + return bindNamespaceExportDeclaration((node)); + case SyntaxKind.ImportClause: + return bindImportClause((node)); + case SyntaxKind.ExportDeclaration: + return bindExportDeclaration((node)); + case SyntaxKind.ExportAssignment: + return bindExportAssignment((node)); + case SyntaxKind.SourceFile: + updateStrictModeStatementList((node).statements); + return bindSourceFileIfExternalModule(); + case SyntaxKind.Block: + if (!isFunctionLike(node.parent)) { + return; } - else { - bindAnonymousDeclaration(node, SymbolFlags.TypeParameter, getDeclarationName(node)!); // TODO: GH#18217 + // falls through + case SyntaxKind.ModuleBlock: + return updateStrictModeStatementList((node).statements); + case SyntaxKind.JSDocParameterTag: + if (node.parent.kind === SyntaxKind.JSDocSignature) { + return bindParameter((node as JSDocParameterTag)); } - } - else { - declareSymbolAndAddToSymbolTable(node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); - } + if (node.parent.kind !== SyntaxKind.JSDocTypeLiteral) { + break; + } + // falls through + case SyntaxKind.JSDocPropertyTag: + const propTag = (node as JSDocPropertyLikeTag); + const flags = propTag.isBracketed || propTag.typeExpression && propTag.typeExpression.type.kind === SyntaxKind.JSDocOptionalType ? + SymbolFlags.Property | SymbolFlags.Optional : + SymbolFlags.Property; + return declareSymbolAndAddToSymbolTable(propTag, flags, SymbolFlags.PropertyExcludes); + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocEnumTag: + return (delayedTypeAliases || (delayedTypeAliases = [])).push((node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag)); } - - // reachability checks - - function shouldReportErrorOnModuleDeclaration(node: ModuleDeclaration): boolean { - const instanceState = getModuleInstanceState(node); - return instanceState === ModuleInstanceState.Instantiated || (instanceState === ModuleInstanceState.ConstEnumOnly && !!options.preserveConstEnums); + } + function bindPropertyWorker(node: PropertyDeclaration | PropertySignature) { + return bindPropertyOrMethodOrAccessor(node, SymbolFlags.Property | (node.questionToken ? SymbolFlags.Optional : SymbolFlags.None), SymbolFlags.PropertyExcludes); + } + function bindAnonymousTypeWorker(node: TypeLiteralNode | MappedTypeNode | JSDocTypeLiteral) { + return bindAnonymousDeclaration((node), SymbolFlags.TypeLiteral, InternalSymbolName.Type); + } + function bindSourceFileIfExternalModule() { + setExportContextFlag(file); + if (isExternalModule(file)) { + bindSourceFileAsExternalModule(); + } + else if (isJsonSourceFile(file)) { + bindSourceFileAsExternalModule(); + // Create symbol equivalent for the module.exports = {} + const originalSymbol = file.symbol; + declareSymbol((file.symbol.exports!), file.symbol, file, SymbolFlags.Property, SymbolFlags.All); + file.symbol = originalSymbol; } - - function checkUnreachable(node: Node): boolean { - if (!(currentFlow.flags & FlowFlags.Unreachable)) { - return false; - } - if (currentFlow === unreachableFlow) { - const reportError = - // report error on all statements except empty ones - (isStatementButNotDeclaration(node) && node.kind !== SyntaxKind.EmptyStatement) || - // report error on class declarations - node.kind === SyntaxKind.ClassDeclaration || - // report error on instantiated modules or const-enums only modules if preserveConstEnums is set - (node.kind === SyntaxKind.ModuleDeclaration && shouldReportErrorOnModuleDeclaration(node)); - - if (reportError) { - currentFlow = reportedUnreachableFlow; - - if (!options.allowUnreachableCode) { - // unreachable code is reported if - // - user has explicitly asked about it AND - // - statement is in not ambient context (statements in ambient context is already an error - // so we should not report extras) AND - // - node is not variable statement OR - // - node is block scoped variable statement OR - // - node is not block scoped variable statement and at least one variable declaration has initializer - // Rationale: we don't want to report errors on non-initialized var's since they are hoisted - // On the other side we do want to report errors on non-initialized 'lets' because of TDZ - const isError = - unreachableCodeIsError(options) && - !(node.flags & NodeFlags.Ambient) && - ( - !isVariableStatement(node) || - !!(getCombinedNodeFlags(node.declarationList) & NodeFlags.BlockScoped) || - node.declarationList.declarations.some(d => !!d.initializer) - ); - - eachUnreachableRange(node, (start, end) => errorOrSuggestionOnRange(isError, start, end, Diagnostics.Unreachable_code_detected)); - } - } + } + function bindSourceFileAsExternalModule() { + bindAnonymousDeclaration(file, SymbolFlags.ValueModule, (`"${removeFileExtension(file.fileName)}"` as __String)); + } + function bindExportAssignment(node: ExportAssignment) { + if (!container.symbol || !container.symbol.exports) { + // Export assignment in some sort of block construct + bindAnonymousDeclaration(node, SymbolFlags.Alias, (getDeclarationName(node)!)); + } + else { + const flags = exportAssignmentIsAlias(node) + // An export default clause with an EntityNameExpression or a class expression exports all meanings of that identifier or expression; + ? SymbolFlags.Alias + // An export default clause with any other expression exports a value + : SymbolFlags.Property; + // If there is an `export default x;` alias declaration, can't `export default` anything else. + // (In contrast, you can still have `export default function f() {}` and `export default interface I {}`.) + const symbol = declareSymbol(container.symbol.exports, container.symbol, node, flags, SymbolFlags.All); + if (node.isExportEquals) { + // Will be an error later, since the module already has other exports. Just make sure this has a valueDeclaration set. + setValueDeclaration(symbol, node); } - return true; } } - - function eachUnreachableRange(node: Node, cb: (start: Node, last: Node) => void): void { - if (isStatement(node) && isExecutableStatement(node) && isBlock(node.parent)) { - const { statements } = node.parent; - const slice = sliceAfter(statements, node); - getRangesWhere(slice, isExecutableStatement, (start, afterEnd) => cb(slice[start], slice[afterEnd - 1])); + function bindNamespaceExportDeclaration(node: NamespaceExportDeclaration) { + if (node.modifiers && node.modifiers.length) { + file.bindDiagnostics.push(createDiagnosticForNode(node, Diagnostics.Modifiers_cannot_appear_here)); + } + const diag = !isSourceFile(node.parent) ? Diagnostics.Global_module_exports_may_only_appear_at_top_level + : !isExternalModule(node.parent) ? Diagnostics.Global_module_exports_may_only_appear_in_module_files + : !node.parent.isDeclarationFile ? Diagnostics.Global_module_exports_may_only_appear_in_declaration_files + : undefined; + if (diag) { + file.bindDiagnostics.push(createDiagnosticForNode(node, diag)); } else { - cb(node, node); + file.symbol.globalExports = file.symbol.globalExports || createSymbolTable(); + declareSymbol(file.symbol.globalExports, file.symbol, node, SymbolFlags.Alias, SymbolFlags.AliasExcludes); } } - // As opposed to a pure declaration like an `interface` - function isExecutableStatement(s: Statement): boolean { - // Don't remove statements that can validly be used before they appear. - return !isFunctionDeclaration(s) && !isPurelyTypeDeclaration(s) && !isEnumDeclaration(s) && - // `var x;` may declare a variable used above - !(isVariableStatement(s) && !(getCombinedNodeFlags(s) & (NodeFlags.Let | NodeFlags.Const)) && s.declarationList.declarations.some(d => !d.initializer)); + function bindExportDeclaration(node: ExportDeclaration) { + if (!container.symbol || !container.symbol.exports) { + // Export * in some sort of block construct + bindAnonymousDeclaration(node, SymbolFlags.ExportStar, (getDeclarationName(node)!)); + } + else if (!node.exportClause) { + // All export * declarations are collected in an __export symbol + declareSymbol(container.symbol.exports, container.symbol, node, SymbolFlags.ExportStar, SymbolFlags.None); + } } - - function isPurelyTypeDeclaration(s: Statement): boolean { - switch (s.kind) { - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - return true; - case SyntaxKind.ModuleDeclaration: - return getModuleInstanceState(s as ModuleDeclaration) !== ModuleInstanceState.Instantiated; - case SyntaxKind.EnumDeclaration: - return hasModifier(s, ModifierFlags.Const); - default: - return false; - } - } - - export function isExportsOrModuleExportsOrAlias(sourceFile: SourceFile, node: Expression): boolean { - let i = 0; - const q = [node]; - while (q.length && i < 100) { - i++; - node = q.shift()!; - if (isExportsIdentifier(node) || isModuleExportsAccessExpression(node)) { - return true; - } - else if (isIdentifier(node)) { - const symbol = lookupSymbolForNameWorker(sourceFile, node.escapedText); - if (!!symbol && !!symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && !!symbol.valueDeclaration.initializer) { - const init = symbol.valueDeclaration.initializer; - q.push(init); - if (isAssignmentExpression(init, /*excludeCompoundAssignment*/ true)) { - q.push(init.left); - q.push(init.right); - } - } - } + function bindImportClause(node: ImportClause) { + if (node.name) { + declareSymbolAndAddToSymbolTable(node, SymbolFlags.Alias, SymbolFlags.AliasExcludes); } - return false; } - - function lookupSymbolForNameWorker(container: Node, name: __String): Symbol | undefined { - const local = container.locals && container.locals.get(name); - if (local) { - return local.exportSymbol || local; + function setCommonJsModuleIndicator(node: Node) { + if (file.externalModuleIndicator) { + return false; } - if (isSourceFile(container) && container.jsGlobalAugmentations && container.jsGlobalAugmentations.has(name)) { - return container.jsGlobalAugmentations.get(name); + if (!file.commonJsModuleIndicator) { + file.commonJsModuleIndicator = node; + bindSourceFileAsExternalModule(); } - return container.symbol && container.symbol.exports && container.symbol.exports.get(name); + return true; } - - /** - * Computes the transform flags for a node, given the transform flags of its subtree - * - * @param node The node to analyze - * @param subtreeFlags Transform flags computed for this node's subtree - */ - export function computeTransformFlagsForNode(node: Node, subtreeFlags: TransformFlags): TransformFlags { - const kind = node.kind; - switch (kind) { - case SyntaxKind.CallExpression: - return computeCallExpression(node, subtreeFlags); - - case SyntaxKind.NewExpression: - return computeNewExpression(node, subtreeFlags); - - case SyntaxKind.ModuleDeclaration: - return computeModuleDeclaration(node, subtreeFlags); - - case SyntaxKind.ParenthesizedExpression: - return computeParenthesizedExpression(node, subtreeFlags); - - case SyntaxKind.BinaryExpression: - return computeBinaryExpression(node, subtreeFlags); - - case SyntaxKind.ExpressionStatement: - return computeExpressionStatement(node, subtreeFlags); - - case SyntaxKind.Parameter: - return computeParameter(node, subtreeFlags); - - case SyntaxKind.ArrowFunction: - return computeArrowFunction(node, subtreeFlags); - - case SyntaxKind.FunctionExpression: - return computeFunctionExpression(node, subtreeFlags); - + function bindObjectDefinePropertyExport(node: BindableObjectDefinePropertyCall) { + if (!setCommonJsModuleIndicator(node)) { + return; + } + const symbol = forEachIdentifierInEntityName(node.arguments[0], /*parent*/ undefined, (id, symbol) => { + if (symbol) { + addDeclarationToSymbol(symbol, id, SymbolFlags.Module | SymbolFlags.Assignment); + } + return symbol; + }); + if (symbol) { + const flags = SymbolFlags.Property | SymbolFlags.ExportValue; + declareSymbol((symbol.exports!), symbol, node, flags, SymbolFlags.None); + } + } + function bindExportsPropertyAssignment(node: BindableStaticPropertyAssignmentExpression) { + // When we create a property via 'exports.foo = bar', the 'exports.foo' property access + // expression is the declaration + if (!setCommonJsModuleIndicator(node)) { + return; + } + const symbol = forEachIdentifierInEntityName(node.left.expression, /*parent*/ undefined, (id, symbol) => { + if (symbol) { + addDeclarationToSymbol(symbol, id, SymbolFlags.Module | SymbolFlags.Assignment); + } + return symbol; + }); + if (symbol) { + const flags = isClassExpression(node.right) ? + SymbolFlags.Property | SymbolFlags.ExportValue | SymbolFlags.Class : + SymbolFlags.Property | SymbolFlags.ExportValue; + declareSymbol((symbol.exports!), symbol, node.left, flags, SymbolFlags.None); + } + } + function bindModuleExportsAssignment(node: BindablePropertyAssignmentExpression) { + // A common practice in node modules is to set 'export = module.exports = {}', this ensures that 'exports' + // is still pointing to 'module.exports'. + // We do not want to consider this as 'export=' since a module can have only one of these. + // Similarly we do not want to treat 'module.exports = exports' as an 'export='. + if (!setCommonJsModuleIndicator(node)) { + return; + } + const assignedExpression = getRightMostAssignedExpression(node.right); + if (isEmptyObjectLiteral(assignedExpression) || container === file && isExportsOrModuleExportsOrAlias(file, assignedExpression)) { + return; + } + // 'module.exports = expr' assignment + const flags = exportAssignmentIsAlias(node) + ? SymbolFlags.Alias // An export= with an EntityNameExpression or a ClassExpression exports all meanings of that identifier or class + : SymbolFlags.Property | SymbolFlags.ExportValue | SymbolFlags.ValueModule; + const symbol = declareSymbol((file.symbol.exports!), file.symbol, node, flags | SymbolFlags.Assignment, SymbolFlags.None); + setValueDeclaration(symbol, node); + } + function bindThisPropertyAssignment(node: BindablePropertyAssignmentExpression | PropertyAccessExpression | LiteralLikeElementAccessExpression) { + Debug.assert(isInJSFile(node)); + const thisContainer = getThisContainer(node, /*includeArrowFunctions*/ false); + switch (thisContainer.kind) { case SyntaxKind.FunctionDeclaration: - return computeFunctionDeclaration(node, subtreeFlags); - - case SyntaxKind.VariableDeclaration: - return computeVariableDeclaration(node, subtreeFlags); - - case SyntaxKind.VariableDeclarationList: - return computeVariableDeclarationList(node, subtreeFlags); - - case SyntaxKind.VariableStatement: - return computeVariableStatement(node, subtreeFlags); - - case SyntaxKind.LabeledStatement: - return computeLabeledStatement(node, subtreeFlags); - - case SyntaxKind.ClassDeclaration: - return computeClassDeclaration(node, subtreeFlags); - - case SyntaxKind.ClassExpression: - return computeClassExpression(node, subtreeFlags); - - case SyntaxKind.HeritageClause: - return computeHeritageClause(node, subtreeFlags); - - case SyntaxKind.CatchClause: - return computeCatchClause(node, subtreeFlags); - - case SyntaxKind.ExpressionWithTypeArguments: - return computeExpressionWithTypeArguments(node, subtreeFlags); - + case SyntaxKind.FunctionExpression: + let constructorSymbol: ts.Symbol | undefined = thisContainer.symbol; + // For `f.prototype.m = function() { this.x = 0; }`, `this.x = 0` should modify `f`'s members, not the function expression. + if (isBinaryExpression(thisContainer.parent) && thisContainer.parent.operatorToken.kind === SyntaxKind.EqualsToken) { + const l = thisContainer.parent.left; + if (isBindableStaticAccessExpression(l) && isPrototypeAccess(l.expression)) { + constructorSymbol = lookupSymbolForPropertyAccess(l.expression.expression, thisParentContainer); + } + } + if (constructorSymbol && constructorSymbol.valueDeclaration) { + // Declare a 'member' if the container is an ES5 class or ES6 constructor + constructorSymbol.members = constructorSymbol.members || createSymbolTable(); + // It's acceptable for multiple 'this' assignments of the same identifier to occur + if (hasDynamicName(node)) { + bindDynamicallyNamedThisPropertyAssignment(node, constructorSymbol); + } + else { + declareSymbol(constructorSymbol.members, constructorSymbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); + } + addDeclarationToSymbol(constructorSymbol, constructorSymbol.valueDeclaration, SymbolFlags.Class); + } + break; case SyntaxKind.Constructor: - return computeConstructor(node, subtreeFlags); - case SyntaxKind.PropertyDeclaration: - return computePropertyDeclaration(node, subtreeFlags); - case SyntaxKind.MethodDeclaration: - return computeMethod(node, subtreeFlags); - case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: - return computeAccessor(node, subtreeFlags); - - case SyntaxKind.ImportEqualsDeclaration: - return computeImportEquals(node, subtreeFlags); - - case SyntaxKind.PropertyAccessExpression: - return computePropertyAccess(node, subtreeFlags); - - case SyntaxKind.ElementAccessExpression: - return computeElementAccess(node, subtreeFlags); - + // this.foo assignment in a JavaScript class + // Bind this property to the containing class + const containingClass = thisContainer.parent; + const symbolTable = hasModifier(thisContainer, ModifierFlags.Static) ? containingClass.symbol.exports! : containingClass.symbol.members!; + if (hasDynamicName(node)) { + bindDynamicallyNamedThisPropertyAssignment(node, containingClass.symbol); + } + else { + declareSymbol(symbolTable, containingClass.symbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.None, /*isReplaceableByMethod*/ true); + } + break; + case SyntaxKind.SourceFile: + // this.property = assignment in a source file -- declare symbol in exports for a module, in locals for a script + if (hasDynamicName(node)) { + break; + } + else if ((thisContainer as SourceFile).commonJsModuleIndicator) { + declareSymbol((thisContainer.symbol.exports!), thisContainer.symbol, node, SymbolFlags.Property | SymbolFlags.ExportValue, SymbolFlags.None); + } + else { + declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.FunctionScopedVariableExcludes); + } + break; default: - return computeOther(node, kind, subtreeFlags); + Debug.failBadSyntaxKind(thisContainer); } } - - function computeCallExpression(node: CallExpression, subtreeFlags: TransformFlags) { - let transformFlags = subtreeFlags; - const callee = skipOuterExpressions(node.expression); - const expression = node.expression; - - if (node.flags & NodeFlags.OptionalChain) { - transformFlags |= TransformFlags.ContainsESNext; + function bindDynamicallyNamedThisPropertyAssignment(node: BinaryExpression | DynamicNamedDeclaration, symbol: ts.Symbol) { + bindAnonymousDeclaration(node, SymbolFlags.Property, InternalSymbolName.Computed); + addLateBoundAssignmentDeclarationToSymbol(node, symbol); + } + function addLateBoundAssignmentDeclarationToSymbol(node: BinaryExpression | DynamicNamedDeclaration, symbol: ts.Symbol | undefined) { + if (symbol) { + const members = symbol.assignmentDeclarationMembers || (symbol.assignmentDeclarationMembers = createMap()); + members.set("" + getNodeId(node), node); } - - if (node.typeArguments) { - transformFlags |= TransformFlags.AssertTypeScript; + } + function bindSpecialPropertyDeclaration(node: PropertyAccessExpression | LiteralLikeElementAccessExpression) { + if (node.expression.kind === SyntaxKind.ThisKeyword) { + bindThisPropertyAssignment(node); } - - if (subtreeFlags & TransformFlags.ContainsRestOrSpread || isSuperOrSuperProperty(callee)) { - // If the this node contains a SpreadExpression, or is a super call, then it is an ES6 - // node. - transformFlags |= TransformFlags.AssertES2015; - if (isSuperProperty(callee)) { - transformFlags |= TransformFlags.ContainsLexicalThis; + else if (isBindableStaticAccessExpression(node) && node.parent.parent.kind === SyntaxKind.SourceFile) { + if (isPrototypeAccess(node.expression)) { + bindPrototypePropertyAssignment(node, node.parent); + } + else { + bindStaticPropertyAssignment(node); } } - - if (expression.kind === SyntaxKind.ImportKeyword) { - transformFlags |= TransformFlags.ContainsDynamicImport; + } + /** For `x.prototype = { p, ... }`, declare members p,... if `x` is function/class/{}, or not declared. */ + function bindPrototypeAssignment(node: BindableStaticPropertyAssignmentExpression) { + node.left.parent = node; + node.right.parent = node; + bindPropertyAssignment(node.left.expression, node.left, /*isPrototypeProperty*/ false, /*containerIsClass*/ true); + } + function bindObjectDefinePrototypeProperty(node: BindableObjectDefinePropertyCall) { + const namespaceSymbol = lookupSymbolForPropertyAccess(((node.arguments[0] as PropertyAccessExpression).expression as EntityNameExpression)); + if (namespaceSymbol && namespaceSymbol.valueDeclaration) { + // Ensure the namespace symbol becomes class-like + addDeclarationToSymbol(namespaceSymbol, namespaceSymbol.valueDeclaration, SymbolFlags.Class); } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.ArrayLiteralOrCallOrNewExcludes; + bindPotentiallyNewExpandoMemberToNamespace(node, namespaceSymbol, /*isPrototypeProperty*/ true); } - - function computeNewExpression(node: NewExpression, subtreeFlags: TransformFlags) { - let transformFlags = subtreeFlags; - if (node.typeArguments) { - transformFlags |= TransformFlags.AssertTypeScript; + /** + * For `x.prototype.y = z`, declare a member `y` on `x` if `x` is a function or class, or not declared. + * Note that jsdoc preceding an ExpressionStatement like `x.prototype.y;` is also treated as a declaration. + */ + function bindPrototypePropertyAssignment(lhs: BindableStaticAccessExpression, parent: Node) { + // Look up the function in the local scope, since prototype assignments should + // follow the function declaration + const classPrototype = (lhs.expression as BindableStaticAccessExpression); + const constructorFunction = classPrototype.expression; + // Fix up parent pointers since we're going to use these nodes before we bind into them + lhs.parent = parent; + constructorFunction.parent = classPrototype; + classPrototype.parent = lhs; + bindPropertyAssignment(constructorFunction, lhs, /*isPrototypeProperty*/ true, /*containerIsClass*/ true); + } + function bindObjectDefinePropertyAssignment(node: BindableObjectDefinePropertyCall) { + let namespaceSymbol = lookupSymbolForPropertyAccess(node.arguments[0]); + const isToplevel = node.parent.parent.kind === SyntaxKind.SourceFile; + namespaceSymbol = bindPotentiallyMissingNamespaces(namespaceSymbol, node.arguments[0], isToplevel, /*isPrototypeProperty*/ false, /*containerIsClass*/ false); + bindPotentiallyNewExpandoMemberToNamespace(node, namespaceSymbol, /*isPrototypeProperty*/ false); + } + function bindSpecialPropertyAssignment(node: BindablePropertyAssignmentExpression) { + // Class declarations in Typescript do not allow property declarations + const parentSymbol = lookupSymbolForPropertyAccess(node.left.expression); + if (!isInJSFile(node) && !isFunctionSymbol(parentSymbol)) { + return; + } + // Fix up parent pointers since we're going to use these nodes before we bind into them + node.left.parent = node; + node.right.parent = node; + if (isIdentifier(node.left.expression) && container === file && isExportsOrModuleExportsOrAlias(file, node.left.expression)) { + // This can be an alias for the 'exports' or 'module.exports' names, e.g. + // var util = module.exports; + // util.property = function ... + bindExportsPropertyAssignment((node as BindableStaticPropertyAssignmentExpression)); } - if (subtreeFlags & TransformFlags.ContainsRestOrSpread) { - // If the this node contains a SpreadElementExpression then it is an ES6 - // node. - transformFlags |= TransformFlags.AssertES2015; + else { + if (hasDynamicName(node)) { + bindAnonymousDeclaration(node, SymbolFlags.Property | SymbolFlags.Assignment, InternalSymbolName.Computed); + const sym = bindPotentiallyMissingNamespaces(parentSymbol, node.left.expression, isTopLevelNamespaceAssignment(node.left), /*isPrototype*/ false, /*containerIsClass*/ false); + addLateBoundAssignmentDeclarationToSymbol(node, sym); + } + else { + bindStaticPropertyAssignment(cast(node.left, isBindableStaticAccessExpression)); + } } - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.ArrayLiteralOrCallOrNewExcludes; } - - function computeBinaryExpression(node: BinaryExpression, subtreeFlags: TransformFlags) { - let transformFlags = subtreeFlags; - const operatorTokenKind = node.operatorToken.kind; - const leftKind = node.left.kind; - - if (operatorTokenKind === SyntaxKind.QuestionQuestionToken) { - transformFlags |= TransformFlags.AssertESNext; + /** + * For nodes like `x.y = z`, declare a member 'y' on 'x' if x is a function (or IIFE) or class or {}, or not declared. + * Also works for expression statements preceded by JSDoc, like / ** @type number * / x.y; + */ + function bindStaticPropertyAssignment(node: BindableStaticAccessExpression) { + node.expression.parent = node; + bindPropertyAssignment(node.expression, node, /*isPrototypeProperty*/ false, /*containerIsClass*/ false); + } + function bindPotentiallyMissingNamespaces(namespaceSymbol: ts.Symbol | undefined, entityName: BindableStaticNameExpression, isToplevel: boolean, isPrototypeProperty: boolean, containerIsClass: boolean) { + if (isToplevel && !isPrototypeProperty) { + // make symbols or add declarations for intermediate containers + const flags = SymbolFlags.Module | SymbolFlags.Assignment; + const excludeFlags = SymbolFlags.ValueModuleExcludes & ~SymbolFlags.Assignment; + namespaceSymbol = forEachIdentifierInEntityName(entityName, namespaceSymbol, (id, symbol, parent) => { + if (symbol) { + addDeclarationToSymbol(symbol, id, flags); + return symbol; + } + else { + const table = parent ? parent.exports! : + file.jsGlobalAugmentations || (file.jsGlobalAugmentations = createSymbolTable()); + return declareSymbol(table, parent, id, flags, excludeFlags); + } + }); } - else if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ObjectLiteralExpression) { - // Destructuring object assignments with are ES2015 syntax - // and possibly ES2018 if they contain rest - transformFlags |= TransformFlags.AssertES2018 | TransformFlags.AssertES2015 | TransformFlags.AssertDestructuringAssignment; - } - else if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ArrayLiteralExpression) { - // Destructuring assignments are ES2015 syntax. - transformFlags |= TransformFlags.AssertES2015 | TransformFlags.AssertDestructuringAssignment; - } - else if (operatorTokenKind === SyntaxKind.AsteriskAsteriskToken - || operatorTokenKind === SyntaxKind.AsteriskAsteriskEqualsToken) { - // Exponentiation is ES2016 syntax. - transformFlags |= TransformFlags.AssertES2016; - } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.NodeExcludes; - } - - function computeParameter(node: ParameterDeclaration, subtreeFlags: TransformFlags) { - let transformFlags = subtreeFlags; - const name = node.name; - const initializer = node.initializer; - const dotDotDotToken = node.dotDotDotToken; - - // The '?' token, type annotations, decorators, and 'this' parameters are TypeSCript - // syntax. - if (node.questionToken - || node.type - || (subtreeFlags & TransformFlags.ContainsTypeScriptClassSyntax && some(node.decorators)) - || isThisIdentifier(name)) { - transformFlags |= TransformFlags.AssertTypeScript; + if (containerIsClass && namespaceSymbol && namespaceSymbol.valueDeclaration) { + addDeclarationToSymbol(namespaceSymbol, namespaceSymbol.valueDeclaration, SymbolFlags.Class); } - - // If a parameter has an accessibility modifier, then it is TypeScript syntax. - if (hasModifier(node, ModifierFlags.ParameterPropertyModifier)) { - transformFlags |= TransformFlags.AssertTypeScript | TransformFlags.ContainsTypeScriptClassSyntax; + return namespaceSymbol; + } + function bindPotentiallyNewExpandoMemberToNamespace(declaration: BindableStaticAccessExpression | CallExpression, namespaceSymbol: ts.Symbol | undefined, isPrototypeProperty: boolean) { + if (!namespaceSymbol || !isExpandoSymbol(namespaceSymbol)) { + return; + } + // Set up the members collection if it doesn't exist already + const symbolTable = isPrototypeProperty ? + (namespaceSymbol.members || (namespaceSymbol.members = createSymbolTable())) : + (namespaceSymbol.exports || (namespaceSymbol.exports = createSymbolTable())); + let includes = SymbolFlags.None; + let excludes = SymbolFlags.None; + // Method-like + if (isFunctionLikeDeclaration((getAssignedExpandoInitializer(declaration)!))) { + includes = SymbolFlags.Method; + excludes = SymbolFlags.MethodExcludes; + } + // Maybe accessor-like + else if (isCallExpression(declaration) && isBindableObjectDefinePropertyCall(declaration)) { + if (some(declaration.arguments[2].properties, p => { + const id = getNameOfDeclaration(p); + return !!id && isIdentifier(id) && idText(id) === "set"; + })) { + // We mix in `SymbolFLags.Property` so in the checker `getTypeOfVariableParameterOrProperty` is used for this + // symbol, instead of `getTypeOfAccessor` (which will assert as there is no real accessor declaration) + includes |= SymbolFlags.SetAccessor | SymbolFlags.Property; + excludes |= SymbolFlags.SetAccessorExcludes; + } + if (some(declaration.arguments[2].properties, p => { + const id = getNameOfDeclaration(p); + return !!id && isIdentifier(id) && idText(id) === "get"; + })) { + includes |= SymbolFlags.GetAccessor | SymbolFlags.Property; + excludes |= SymbolFlags.GetAccessorExcludes; + } + } + if (includes === SymbolFlags.None) { + includes = SymbolFlags.Property; + excludes = SymbolFlags.PropertyExcludes; + } + declareSymbol(symbolTable, namespaceSymbol, declaration, includes | SymbolFlags.Assignment, excludes & ~SymbolFlags.Assignment); + } + function isTopLevelNamespaceAssignment(propertyAccess: BindableAccessExpression) { + return isBinaryExpression(propertyAccess.parent) + ? getParentOfBinaryExpression(propertyAccess.parent).parent.kind === SyntaxKind.SourceFile + : propertyAccess.parent.parent.kind === SyntaxKind.SourceFile; + } + function bindPropertyAssignment(name: BindableStaticNameExpression, propertyAccess: BindableStaticAccessExpression, isPrototypeProperty: boolean, containerIsClass: boolean) { + let namespaceSymbol = lookupSymbolForPropertyAccess(name); + const isToplevel = isTopLevelNamespaceAssignment(propertyAccess); + namespaceSymbol = bindPotentiallyMissingNamespaces(namespaceSymbol, propertyAccess.expression, isToplevel, isPrototypeProperty, containerIsClass); + bindPotentiallyNewExpandoMemberToNamespace(propertyAccess, namespaceSymbol, isPrototypeProperty); + } + /** + * Javascript expando values are: + * - Functions + * - classes + * - namespaces + * - variables initialized with function expressions + * - with class expressions + * - with empty object literals + * - with non-empty object literals if assigned to the prototype property + */ + function isExpandoSymbol(symbol: ts.Symbol): boolean { + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.NamespaceModule)) { + return true; } - - // parameters with object rest destructuring are ES2018 syntax - if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) { - transformFlags |= TransformFlags.AssertES2018; + const node = symbol.valueDeclaration; + if (node && isCallExpression(node)) { + return !!getAssignedExpandoInitializer(node); } - - // If a parameter has an initializer, a binding pattern or a dotDotDot token, then - // it is ES6 syntax and its container must emit default value assignments or parameter destructuring downlevel. - if (subtreeFlags & TransformFlags.ContainsBindingPattern || initializer || dotDotDotToken) { - transformFlags |= TransformFlags.AssertES2015; + let init = !node ? undefined : + isVariableDeclaration(node) ? node.initializer : + isBinaryExpression(node) ? node.right : + isPropertyAccessExpression(node) && isBinaryExpression(node.parent) ? node.parent.right : + undefined; + init = init && getRightMostAssignedExpression(init); + if (init) { + const isPrototypeAssignment = isPrototypeAccess(isVariableDeclaration(node) ? node.name : isBinaryExpression(node) ? node.left : node); + return !!getExpandoInitializer(isBinaryExpression(init) && (init.operatorToken.kind === SyntaxKind.BarBarToken || init.operatorToken.kind === SyntaxKind.QuestionQuestionToken) ? init.right : init, isPrototypeAssignment); } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.ParameterExcludes; - } - - function computeParenthesizedExpression(node: ParenthesizedExpression, subtreeFlags: TransformFlags) { - let transformFlags = subtreeFlags; - const expression = node.expression; - const expressionKind = expression.kind; - - // If the node is synthesized, it means the emitter put the parentheses there, - // not the user. If we didn't want them, the emitter would not have put them - // there. - if (expressionKind === SyntaxKind.AsExpression - || expressionKind === SyntaxKind.TypeAssertionExpression) { - transformFlags |= TransformFlags.AssertTypeScript; + return false; + } + function getParentOfBinaryExpression(expr: Node) { + while (isBinaryExpression(expr.parent)) { + expr = expr.parent; } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.OuterExpressionExcludes; + return expr.parent; } - - function computeClassDeclaration(node: ClassDeclaration, subtreeFlags: TransformFlags) { - let transformFlags: TransformFlags; - - if (hasModifier(node, ModifierFlags.Ambient)) { - // An ambient declaration is TypeScript syntax. - transformFlags = TransformFlags.AssertTypeScript; + function lookupSymbolForPropertyAccess(node: BindableStaticNameExpression, lookupContainer: Node = container): ts.Symbol | undefined { + if (isIdentifier(node)) { + return lookupSymbolForNameWorker(lookupContainer, node.escapedText); } else { - // A ClassDeclaration is ES6 syntax. - transformFlags = subtreeFlags | TransformFlags.AssertES2015; - - // A class with a parameter property assignment or decorator is TypeScript syntax. - // An exported declaration may be TypeScript syntax, but is handled by the visitor - // for a namespace declaration. - if ((subtreeFlags & TransformFlags.ContainsTypeScriptClassSyntax) - || node.typeParameters) { - transformFlags |= TransformFlags.AssertTypeScript; - } + const symbol = lookupSymbolForPropertyAccess(node.expression); + return symbol && symbol.exports && symbol.exports.get(getElementOrPropertyAccessName(node)); } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.ClassExcludes; } - - function computeClassExpression(node: ClassExpression, subtreeFlags: TransformFlags) { - // A ClassExpression is ES6 syntax. - let transformFlags = subtreeFlags | TransformFlags.AssertES2015; - - // A class with a parameter property assignment or decorator is TypeScript syntax. - if (subtreeFlags & TransformFlags.ContainsTypeScriptClassSyntax - || node.typeParameters) { - transformFlags |= TransformFlags.AssertTypeScript; + function forEachIdentifierInEntityName(e: BindableStaticNameExpression, parent: ts.Symbol | undefined, action: (e: Declaration, symbol: ts.Symbol | undefined, parent: ts.Symbol | undefined) => ts.Symbol | undefined): ts.Symbol | undefined { + if (isExportsOrModuleExportsOrAlias(file, e)) { + return file.symbol; } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.ClassExcludes; - } - - function computeHeritageClause(node: HeritageClause, subtreeFlags: TransformFlags) { - let transformFlags = subtreeFlags; - - switch (node.token) { - case SyntaxKind.ExtendsKeyword: - // An `extends` HeritageClause is ES6 syntax. - transformFlags |= TransformFlags.AssertES2015; - break; - - case SyntaxKind.ImplementsKeyword: - // An `implements` HeritageClause is TypeScript syntax. - transformFlags |= TransformFlags.AssertTypeScript; - break; - - default: - Debug.fail("Unexpected token for heritage clause"); - break; + else if (isIdentifier(e)) { + return action(e, lookupSymbolForPropertyAccess(e), parent); + } + else { + const s = forEachIdentifierInEntityName(e.expression, parent, action); + return action(getNameOrArgument(e), s && s.exports && s.exports.get(getElementOrPropertyAccessName(e)), s); } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.NodeExcludes; } - - function computeCatchClause(node: CatchClause, subtreeFlags: TransformFlags) { - let transformFlags = subtreeFlags; - - if (!node.variableDeclaration) { - transformFlags |= TransformFlags.AssertES2019; + function bindCallExpression(node: CallExpression) { + // We're only inspecting call expressions to detect CommonJS modules, so we can skip + // this check if we've already seen the module indicator + if (!file.commonJsModuleIndicator && isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ false)) { + setCommonJsModuleIndicator(node); } - else if (isBindingPattern(node.variableDeclaration.name)) { - transformFlags |= TransformFlags.AssertES2015; + } + function bindClassLikeDeclaration(node: ClassLikeDeclaration) { + if (node.kind === SyntaxKind.ClassDeclaration) { + bindBlockScopedDeclaration(node, SymbolFlags.Class, SymbolFlags.ClassExcludes); } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.CatchClauseExcludes; - } - - function computeExpressionWithTypeArguments(node: ExpressionWithTypeArguments, subtreeFlags: TransformFlags) { - // An ExpressionWithTypeArguments is ES6 syntax, as it is used in the - // extends clause of a class. - let transformFlags = subtreeFlags | TransformFlags.AssertES2015; - - // If an ExpressionWithTypeArguments contains type arguments, then it - // is TypeScript syntax. - if (node.typeArguments) { - transformFlags |= TransformFlags.AssertTypeScript; + else { + const bindingName = node.name ? node.name.escapedText : InternalSymbolName.Class; + bindAnonymousDeclaration(node, SymbolFlags.Class, bindingName); + // Add name of class expression into the map for semantic classifier + if (node.name) { + classifiableNames.set(node.name.escapedText, true); + } } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.NodeExcludes; - } - - function computeConstructor(node: ConstructorDeclaration, subtreeFlags: TransformFlags) { - let transformFlags = subtreeFlags; - - // TypeScript-specific modifiers and overloads are TypeScript syntax - if (hasModifier(node, ModifierFlags.TypeScriptModifier) - || !node.body) { - transformFlags |= TransformFlags.AssertTypeScript; + const { symbol } = node; + // TypeScript 1.0 spec (April 2014): 8.4 + // Every class automatically contains a static property member named 'prototype', the + // type of which is an instantiation of the class type with type Any supplied as a type + // argument for each type parameter. It is an error to explicitly declare a static + // property member with the name 'prototype'. + // + // Note: we check for this here because this class may be merging into a module. The + // module might have an exported variable called 'prototype'. We can't allow that as + // that would clash with the built-in 'prototype' for the class. + const prototypeSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Prototype, ("prototype" as __String)); + const symbolExport = symbol.exports!.get(prototypeSymbol.escapedName); + if (symbolExport) { + if (node.name) { + node.name.parent = node; + } + file.bindDiagnostics.push(createDiagnosticForNode(symbolExport.declarations[0], Diagnostics.Duplicate_identifier_0, symbolName(prototypeSymbol))); } - - // function declarations with object rest destructuring are ES2018 syntax - if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) { - transformFlags |= TransformFlags.AssertES2018; + symbol.exports!.set(prototypeSymbol.escapedName, prototypeSymbol); + prototypeSymbol.parent = symbol; + } + function bindEnumDeclaration(node: EnumDeclaration) { + return isEnumConst(node) + ? bindBlockScopedDeclaration(node, SymbolFlags.ConstEnum, SymbolFlags.ConstEnumExcludes) + : bindBlockScopedDeclaration(node, SymbolFlags.RegularEnum, SymbolFlags.RegularEnumExcludes); + } + function bindVariableDeclarationOrBindingElement(node: VariableDeclaration | BindingElement) { + if (inStrictMode) { + checkStrictModeEvalOrArguments(node, node.name); } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.ConstructorExcludes; - } - - function computeMethod(node: MethodDeclaration, subtreeFlags: TransformFlags) { - // A MethodDeclaration is ES6 syntax. - let transformFlags = subtreeFlags | TransformFlags.AssertES2015; - - // Decorators, TypeScript-specific modifiers, type parameters, type annotations, and - // overloads are TypeScript syntax. - if (node.decorators - || hasModifier(node, ModifierFlags.TypeScriptModifier) - || node.typeParameters - || node.type - || !node.body - || node.questionToken) { - transformFlags |= TransformFlags.AssertTypeScript; + if (!isBindingPattern(node.name)) { + if (isBlockOrCatchScoped(node)) { + bindBlockScopedDeclaration(node, SymbolFlags.BlockScopedVariable, SymbolFlags.BlockScopedVariableExcludes); + } + else if (isParameterDeclaration(node)) { + // It is safe to walk up parent chain to find whether the node is a destructuring parameter declaration + // because its parent chain has already been set up, since parents are set before descending into children. + // + // If node is a binding element in parameter declaration, we need to use ParameterExcludes. + // Using ParameterExcludes flag allows the compiler to report an error on duplicate identifiers in Parameter Declaration + // For example: + // function foo([a,a]) {} // Duplicate Identifier error + // function bar(a,a) {} // Duplicate Identifier error, parameter declaration in this case is handled in bindParameter + // // which correctly set excluded symbols + declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes); + } + else { + declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.FunctionScopedVariableExcludes); + } } - - // function declarations with object rest destructuring are ES2018 syntax - if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) { - transformFlags |= TransformFlags.AssertES2018; + } + function bindParameter(node: ParameterDeclaration | JSDocParameterTag) { + if (node.kind === SyntaxKind.JSDocParameterTag && container.kind !== SyntaxKind.JSDocSignature) { + return; } - - // An async method declaration is ES2017 syntax. - if (hasModifier(node, ModifierFlags.Async)) { - transformFlags |= node.asteriskToken ? TransformFlags.AssertES2018 : TransformFlags.AssertES2017; + if (inStrictMode && !(node.flags & NodeFlags.Ambient)) { + // It is a SyntaxError if the identifier eval or arguments appears within a FormalParameterList of a + // strict mode FunctionLikeDeclaration or FunctionExpression(13.1) + checkStrictModeEvalOrArguments(node, node.name); } - - if (node.asteriskToken) { - transformFlags |= TransformFlags.AssertGenerator; + if (isBindingPattern(node.name)) { + bindAnonymousDeclaration(node, SymbolFlags.FunctionScopedVariable, ("__" + (node as ParameterDeclaration).parent.parameters.indexOf((node as ParameterDeclaration)) as __String)); } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return propagatePropertyNameFlags(node.name, transformFlags & ~TransformFlags.MethodOrAccessorExcludes); - } - - function computeAccessor(node: AccessorDeclaration, subtreeFlags: TransformFlags) { - let transformFlags = subtreeFlags; - - // Decorators, TypeScript-specific modifiers, type annotations, and overloads are - // TypeScript syntax. - if (node.decorators - || hasModifier(node, ModifierFlags.TypeScriptModifier) - || node.type - || !node.body) { - transformFlags |= TransformFlags.AssertTypeScript; + else { + declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes); } - - // function declarations with object rest destructuring are ES2018 syntax - if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) { - transformFlags |= TransformFlags.AssertES2018; + // If this is a property-parameter, then also declare the property symbol into the + // containing class. + if (isParameterPropertyDeclaration(node, node.parent)) { + const classDeclaration = (node.parent.parent); + declareSymbol((classDeclaration.symbol.members!), classDeclaration.symbol, node, SymbolFlags.Property | (node.questionToken ? SymbolFlags.Optional : SymbolFlags.None), SymbolFlags.PropertyExcludes); } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return propagatePropertyNameFlags(node.name, transformFlags & ~TransformFlags.MethodOrAccessorExcludes); } - - function computePropertyDeclaration(node: PropertyDeclaration, subtreeFlags: TransformFlags) { - let transformFlags = subtreeFlags | TransformFlags.ContainsClassFields; - - // Decorators, TypeScript-specific modifiers, and type annotations are TypeScript syntax. - if (some(node.decorators) || hasModifier(node, ModifierFlags.TypeScriptModifier) || node.type || node.questionToken || node.exclamationToken) { - transformFlags |= TransformFlags.AssertTypeScript; + function bindFunctionDeclaration(node: FunctionDeclaration) { + if (!file.isDeclarationFile && !(node.flags & NodeFlags.Ambient)) { + if (isAsyncFunction(node)) { + emitFlags |= NodeFlags.HasAsyncFunctions; + } } - - // Hoisted variables related to class properties should live within the TypeScript class wrapper. - if (isComputedPropertyName(node.name) || (hasStaticModifier(node) && node.initializer)) { - transformFlags |= TransformFlags.ContainsTypeScriptClassSyntax; - } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return propagatePropertyNameFlags(node.name, transformFlags & ~TransformFlags.PropertyExcludes); - } - - function computeFunctionDeclaration(node: FunctionDeclaration, subtreeFlags: TransformFlags) { - let transformFlags: TransformFlags; - const modifierFlags = getModifierFlags(node); - const body = node.body; - - if (!body || (modifierFlags & ModifierFlags.Ambient)) { - // An ambient declaration is TypeScript syntax. - // A FunctionDeclaration without a body is an overload and is TypeScript syntax. - transformFlags = TransformFlags.AssertTypeScript; + checkStrictModeFunctionName(node); + if (inStrictMode) { + checkStrictModeFunctionDeclaration(node); + bindBlockScopedDeclaration(node, SymbolFlags.Function, SymbolFlags.FunctionExcludes); } else { - transformFlags = subtreeFlags | TransformFlags.ContainsHoistedDeclarationOrCompletion; - - // TypeScript-specific modifiers, type parameters, and type annotations are TypeScript - // syntax. - if (modifierFlags & ModifierFlags.TypeScriptModifier - || node.typeParameters - || node.type) { - transformFlags |= TransformFlags.AssertTypeScript; + declareSymbolAndAddToSymbolTable(node, SymbolFlags.Function, SymbolFlags.FunctionExcludes); + } + } + function bindFunctionExpression(node: FunctionExpression) { + if (!file.isDeclarationFile && !(node.flags & NodeFlags.Ambient)) { + if (isAsyncFunction(node)) { + emitFlags |= NodeFlags.HasAsyncFunctions; } - - // An async function declaration is ES2017 syntax. - if (modifierFlags & ModifierFlags.Async) { - transformFlags |= node.asteriskToken ? TransformFlags.AssertES2018 : TransformFlags.AssertES2017; + } + if (currentFlow) { + node.flowNode = currentFlow; + } + checkStrictModeFunctionName(node); + const bindingName = node.name ? node.name.escapedText : InternalSymbolName.Function; + return bindAnonymousDeclaration(node, SymbolFlags.Function, bindingName); + } + function bindPropertyOrMethodOrAccessor(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { + if (!file.isDeclarationFile && !(node.flags & NodeFlags.Ambient) && isAsyncFunction(node)) { + emitFlags |= NodeFlags.HasAsyncFunctions; + } + if (currentFlow && isObjectLiteralOrClassExpressionMethod(node)) { + node.flowNode = currentFlow; + } + return hasDynamicName(node) + ? bindAnonymousDeclaration(node, symbolFlags, InternalSymbolName.Computed) + : declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes); + } + function getInferTypeContainer(node: Node): ConditionalTypeNode | undefined { + const extendsType = findAncestor(node, n => n.parent && isConditionalTypeNode(n.parent) && n.parent.extendsType === n); + return extendsType && (extendsType.parent as ConditionalTypeNode); + } + function bindTypeParameter(node: TypeParameterDeclaration) { + if (isJSDocTemplateTag(node.parent)) { + const container = find(((node.parent.parent as JSDoc).tags!), isJSDocTypeAlias) || getHostSignatureFromJSDoc(node.parent); // TODO: GH#18217 + if (container) { + if (!container.locals) { + container.locals = createSymbolTable(); + } + declareSymbol(container.locals, /*parent*/ undefined, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); } - - // function declarations with object rest destructuring are ES2018 syntax - if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) { - transformFlags |= TransformFlags.AssertES2018; + else { + declareSymbolAndAddToSymbolTable(node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); + } + } + else if (node.parent.kind === SyntaxKind.InferType) { + const container = getInferTypeContainer(node.parent); + if (container) { + if (!container.locals) { + container.locals = createSymbolTable(); + } + declareSymbol(container.locals, /*parent*/ undefined, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); } - - // If a FunctionDeclaration is generator function and is the body of a - // transformed async function, then this node can be transformed to a - // down-level generator. - // Currently we do not support transforming any other generator functions - // down level. - if (node.asteriskToken) { - transformFlags |= TransformFlags.AssertGenerator; + else { + bindAnonymousDeclaration(node, SymbolFlags.TypeParameter, (getDeclarationName(node)!)); // TODO: GH#18217 + } + } + else { + declareSymbolAndAddToSymbolTable(node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); + } + } + // reachability checks + function shouldReportErrorOnModuleDeclaration(node: ModuleDeclaration): boolean { + const instanceState = getModuleInstanceState(node); + return instanceState === ModuleInstanceState.Instantiated || (instanceState === ModuleInstanceState.ConstEnumOnly && !!options.preserveConstEnums); + } + function checkUnreachable(node: Node): boolean { + if (!(currentFlow.flags & FlowFlags.Unreachable)) { + return false; + } + if (currentFlow === unreachableFlow) { + const reportError = + // report error on all statements except empty ones + (isStatementButNotDeclaration(node) && node.kind !== SyntaxKind.EmptyStatement) || + // report error on class declarations + node.kind === SyntaxKind.ClassDeclaration || + // report error on instantiated modules or const-enums only modules if preserveConstEnums is set + (node.kind === SyntaxKind.ModuleDeclaration && shouldReportErrorOnModuleDeclaration((node))); + if (reportError) { + currentFlow = reportedUnreachableFlow; + if (!options.allowUnreachableCode) { + // unreachable code is reported if + // - user has explicitly asked about it AND + // - statement is in not ambient context (statements in ambient context is already an error + // so we should not report extras) AND + // - node is not variable statement OR + // - node is block scoped variable statement OR + // - node is not block scoped variable statement and at least one variable declaration has initializer + // Rationale: we don't want to report errors on non-initialized var's since they are hoisted + // On the other side we do want to report errors on non-initialized 'lets' because of TDZ + const isError = unreachableCodeIsError(options) && + !(node.flags & NodeFlags.Ambient) && + (!isVariableStatement(node) || + !!(getCombinedNodeFlags(node.declarationList) & NodeFlags.BlockScoped) || + node.declarationList.declarations.some(d => !!d.initializer)); + eachUnreachableRange(node, (start, end) => errorOrSuggestionOnRange(isError, start, end, Diagnostics.Unreachable_code_detected)); + } + } + } + return true; + } +} +/* @internal */ +function eachUnreachableRange(node: Node, cb: (start: Node, last: Node) => void): void { + if (isStatement(node) && isExecutableStatement(node) && isBlock(node.parent)) { + const { statements } = node.parent; + const slice = sliceAfter(statements, node); + getRangesWhere(slice, isExecutableStatement, (start, afterEnd) => cb(slice[start], slice[afterEnd - 1])); + } + else { + cb(node, node); + } +} +// As opposed to a pure declaration like an `interface` +/* @internal */ +function isExecutableStatement(s: Statement): boolean { + // Don't remove statements that can validly be used before they appear. + return !isFunctionDeclaration(s) && !isPurelyTypeDeclaration(s) && !isEnumDeclaration(s) && + // `var x;` may declare a variable used above + !(isVariableStatement(s) && !(getCombinedNodeFlags(s) & (NodeFlags.Let | NodeFlags.Const)) && s.declarationList.declarations.some(d => !d.initializer)); +} +/* @internal */ +function isPurelyTypeDeclaration(s: Statement): boolean { + switch (s.kind) { + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + return true; + case SyntaxKind.ModuleDeclaration: + return getModuleInstanceState((s as ModuleDeclaration)) !== ModuleInstanceState.Instantiated; + case SyntaxKind.EnumDeclaration: + return hasModifier(s, ModifierFlags.Const); + default: + return false; + } +} +/* @internal */ +export function isExportsOrModuleExportsOrAlias(sourceFile: SourceFile, node: Expression): boolean { + let i = 0; + const q = [node]; + while (q.length && i < 100) { + i++; + node = q.shift()!; + if (isExportsIdentifier(node) || isModuleExportsAccessExpression(node)) { + return true; + } + else if (isIdentifier(node)) { + const symbol = lookupSymbolForNameWorker(sourceFile, node.escapedText); + if (!!symbol && !!symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && !!symbol.valueDeclaration.initializer) { + const init = symbol.valueDeclaration.initializer; + q.push(init); + if (isAssignmentExpression(init, /*excludeCompoundAssignment*/ true)) { + q.push(init.left); + q.push(init.right); + } } } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.FunctionExcludes; } - - function computeFunctionExpression(node: FunctionExpression, subtreeFlags: TransformFlags) { - let transformFlags = subtreeFlags; - + return false; +} +/* @internal */ +function lookupSymbolForNameWorker(container: Node, name: __String): Symbol | undefined { + const local = container.locals && container.locals.get(name); + if (local) { + return local.exportSymbol || local; + } + if (isSourceFile(container) && container.jsGlobalAugmentations && container.jsGlobalAugmentations.has(name)) { + return container.jsGlobalAugmentations.get(name); + } + return container.symbol && container.symbol.exports && container.symbol.exports.get(name); +} +/** + * Computes the transform flags for a node, given the transform flags of its subtree + * + * @param node The node to analyze + * @param subtreeFlags Transform flags computed for this node's subtree + */ +/* @internal */ +export function computeTransformFlagsForNode(node: Node, subtreeFlags: TransformFlags): TransformFlags { + const kind = node.kind; + switch (kind) { + case SyntaxKind.CallExpression: + return computeCallExpression((node), subtreeFlags); + case SyntaxKind.NewExpression: + return computeNewExpression((node), subtreeFlags); + case SyntaxKind.ModuleDeclaration: + return computeModuleDeclaration((node), subtreeFlags); + case SyntaxKind.ParenthesizedExpression: + return computeParenthesizedExpression((node), subtreeFlags); + case SyntaxKind.BinaryExpression: + return computeBinaryExpression((node), subtreeFlags); + case SyntaxKind.ExpressionStatement: + return computeExpressionStatement((node), subtreeFlags); + case SyntaxKind.Parameter: + return computeParameter((node), subtreeFlags); + case SyntaxKind.ArrowFunction: + return computeArrowFunction((node), subtreeFlags); + case SyntaxKind.FunctionExpression: + return computeFunctionExpression((node), subtreeFlags); + case SyntaxKind.FunctionDeclaration: + return computeFunctionDeclaration((node), subtreeFlags); + case SyntaxKind.VariableDeclaration: + return computeVariableDeclaration((node), subtreeFlags); + case SyntaxKind.VariableDeclarationList: + return computeVariableDeclarationList((node), subtreeFlags); + case SyntaxKind.VariableStatement: + return computeVariableStatement((node), subtreeFlags); + case SyntaxKind.LabeledStatement: + return computeLabeledStatement((node), subtreeFlags); + case SyntaxKind.ClassDeclaration: + return computeClassDeclaration((node), subtreeFlags); + case SyntaxKind.ClassExpression: + return computeClassExpression((node), subtreeFlags); + case SyntaxKind.HeritageClause: + return computeHeritageClause((node), subtreeFlags); + case SyntaxKind.CatchClause: + return computeCatchClause((node), subtreeFlags); + case SyntaxKind.ExpressionWithTypeArguments: + return computeExpressionWithTypeArguments((node), subtreeFlags); + case SyntaxKind.Constructor: + return computeConstructor((node), subtreeFlags); + case SyntaxKind.PropertyDeclaration: + return computePropertyDeclaration((node), subtreeFlags); + case SyntaxKind.MethodDeclaration: + return computeMethod((node), subtreeFlags); + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return computeAccessor((node), subtreeFlags); + case SyntaxKind.ImportEqualsDeclaration: + return computeImportEquals((node), subtreeFlags); + case SyntaxKind.PropertyAccessExpression: + return computePropertyAccess((node), subtreeFlags); + case SyntaxKind.ElementAccessExpression: + return computeElementAccess((node), subtreeFlags); + default: + return computeOther(node, kind, subtreeFlags); + } +} +/* @internal */ +function computeCallExpression(node: CallExpression, subtreeFlags: TransformFlags) { + let transformFlags = subtreeFlags; + const callee = skipOuterExpressions(node.expression); + const expression = node.expression; + if (node.flags & NodeFlags.OptionalChain) { + transformFlags |= TransformFlags.ContainsESNext; + } + if (node.typeArguments) { + transformFlags |= TransformFlags.AssertTypeScript; + } + if (subtreeFlags & TransformFlags.ContainsRestOrSpread || isSuperOrSuperProperty(callee)) { + // If the this node contains a SpreadExpression, or is a super call, then it is an ES6 + // node. + transformFlags |= TransformFlags.AssertES2015; + if (isSuperProperty(callee)) { + transformFlags |= TransformFlags.ContainsLexicalThis; + } + } + if (expression.kind === SyntaxKind.ImportKeyword) { + transformFlags |= TransformFlags.ContainsDynamicImport; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.ArrayLiteralOrCallOrNewExcludes; +} +/* @internal */ +function computeNewExpression(node: NewExpression, subtreeFlags: TransformFlags) { + let transformFlags = subtreeFlags; + if (node.typeArguments) { + transformFlags |= TransformFlags.AssertTypeScript; + } + if (subtreeFlags & TransformFlags.ContainsRestOrSpread) { + // If the this node contains a SpreadElementExpression then it is an ES6 + // node. + transformFlags |= TransformFlags.AssertES2015; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.ArrayLiteralOrCallOrNewExcludes; +} +/* @internal */ +function computeBinaryExpression(node: BinaryExpression, subtreeFlags: TransformFlags) { + let transformFlags = subtreeFlags; + const operatorTokenKind = node.operatorToken.kind; + const leftKind = node.left.kind; + if (operatorTokenKind === SyntaxKind.QuestionQuestionToken) { + transformFlags |= TransformFlags.AssertESNext; + } + else if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ObjectLiteralExpression) { + // Destructuring object assignments with are ES2015 syntax + // and possibly ES2018 if they contain rest + transformFlags |= TransformFlags.AssertES2018 | TransformFlags.AssertES2015 | TransformFlags.AssertDestructuringAssignment; + } + else if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ArrayLiteralExpression) { + // Destructuring assignments are ES2015 syntax. + transformFlags |= TransformFlags.AssertES2015 | TransformFlags.AssertDestructuringAssignment; + } + else if (operatorTokenKind === SyntaxKind.AsteriskAsteriskToken + || operatorTokenKind === SyntaxKind.AsteriskAsteriskEqualsToken) { + // Exponentiation is ES2016 syntax. + transformFlags |= TransformFlags.AssertES2016; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.NodeExcludes; +} +/* @internal */ +function computeParameter(node: ParameterDeclaration, subtreeFlags: TransformFlags) { + let transformFlags = subtreeFlags; + const name = node.name; + const initializer = node.initializer; + const dotDotDotToken = node.dotDotDotToken; + // The '?' token, type annotations, decorators, and 'this' parameters are TypeSCript + // syntax. + if (node.questionToken + || node.type + || (subtreeFlags & TransformFlags.ContainsTypeScriptClassSyntax && some(node.decorators)) + || isThisIdentifier(name)) { + transformFlags |= TransformFlags.AssertTypeScript; + } + // If a parameter has an accessibility modifier, then it is TypeScript syntax. + if (hasModifier(node, ModifierFlags.ParameterPropertyModifier)) { + transformFlags |= TransformFlags.AssertTypeScript | TransformFlags.ContainsTypeScriptClassSyntax; + } + // parameters with object rest destructuring are ES2018 syntax + if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) { + transformFlags |= TransformFlags.AssertES2018; + } + // If a parameter has an initializer, a binding pattern or a dotDotDot token, then + // it is ES6 syntax and its container must emit default value assignments or parameter destructuring downlevel. + if (subtreeFlags & TransformFlags.ContainsBindingPattern || initializer || dotDotDotToken) { + transformFlags |= TransformFlags.AssertES2015; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.ParameterExcludes; +} +/* @internal */ +function computeParenthesizedExpression(node: ParenthesizedExpression, subtreeFlags: TransformFlags) { + let transformFlags = subtreeFlags; + const expression = node.expression; + const expressionKind = expression.kind; + // If the node is synthesized, it means the emitter put the parentheses there, + // not the user. If we didn't want them, the emitter would not have put them + // there. + if (expressionKind === SyntaxKind.AsExpression + || expressionKind === SyntaxKind.TypeAssertionExpression) { + transformFlags |= TransformFlags.AssertTypeScript; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.OuterExpressionExcludes; +} +/* @internal */ +function computeClassDeclaration(node: ClassDeclaration, subtreeFlags: TransformFlags) { + let transformFlags: TransformFlags; + if (hasModifier(node, ModifierFlags.Ambient)) { + // An ambient declaration is TypeScript syntax. + transformFlags = TransformFlags.AssertTypeScript; + } + else { + // A ClassDeclaration is ES6 syntax. + transformFlags = subtreeFlags | TransformFlags.AssertES2015; + // A class with a parameter property assignment or decorator is TypeScript syntax. + // An exported declaration may be TypeScript syntax, but is handled by the visitor + // for a namespace declaration. + if ((subtreeFlags & TransformFlags.ContainsTypeScriptClassSyntax) + || node.typeParameters) { + transformFlags |= TransformFlags.AssertTypeScript; + } + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.ClassExcludes; +} +/* @internal */ +function computeClassExpression(node: ClassExpression, subtreeFlags: TransformFlags) { + // A ClassExpression is ES6 syntax. + let transformFlags = subtreeFlags | TransformFlags.AssertES2015; + // A class with a parameter property assignment or decorator is TypeScript syntax. + if (subtreeFlags & TransformFlags.ContainsTypeScriptClassSyntax + || node.typeParameters) { + transformFlags |= TransformFlags.AssertTypeScript; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.ClassExcludes; +} +/* @internal */ +function computeHeritageClause(node: HeritageClause, subtreeFlags: TransformFlags) { + let transformFlags = subtreeFlags; + switch (node.token) { + case SyntaxKind.ExtendsKeyword: + // An `extends` HeritageClause is ES6 syntax. + transformFlags |= TransformFlags.AssertES2015; + break; + case SyntaxKind.ImplementsKeyword: + // An `implements` HeritageClause is TypeScript syntax. + transformFlags |= TransformFlags.AssertTypeScript; + break; + default: + Debug.fail("Unexpected token for heritage clause"); + break; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.NodeExcludes; +} +/* @internal */ +function computeCatchClause(node: CatchClause, subtreeFlags: TransformFlags) { + let transformFlags = subtreeFlags; + if (!node.variableDeclaration) { + transformFlags |= TransformFlags.AssertES2019; + } + else if (isBindingPattern(node.variableDeclaration.name)) { + transformFlags |= TransformFlags.AssertES2015; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.CatchClauseExcludes; +} +/* @internal */ +function computeExpressionWithTypeArguments(node: ExpressionWithTypeArguments, subtreeFlags: TransformFlags) { + // An ExpressionWithTypeArguments is ES6 syntax, as it is used in the + // extends clause of a class. + let transformFlags = subtreeFlags | TransformFlags.AssertES2015; + // If an ExpressionWithTypeArguments contains type arguments, then it + // is TypeScript syntax. + if (node.typeArguments) { + transformFlags |= TransformFlags.AssertTypeScript; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.NodeExcludes; +} +/* @internal */ +function computeConstructor(node: ConstructorDeclaration, subtreeFlags: TransformFlags) { + let transformFlags = subtreeFlags; + // TypeScript-specific modifiers and overloads are TypeScript syntax + if (hasModifier(node, ModifierFlags.TypeScriptModifier) + || !node.body) { + transformFlags |= TransformFlags.AssertTypeScript; + } + // function declarations with object rest destructuring are ES2018 syntax + if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) { + transformFlags |= TransformFlags.AssertES2018; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.ConstructorExcludes; +} +/* @internal */ +function computeMethod(node: MethodDeclaration, subtreeFlags: TransformFlags) { + // A MethodDeclaration is ES6 syntax. + let transformFlags = subtreeFlags | TransformFlags.AssertES2015; + // Decorators, TypeScript-specific modifiers, type parameters, type annotations, and + // overloads are TypeScript syntax. + if (node.decorators + || hasModifier(node, ModifierFlags.TypeScriptModifier) + || node.typeParameters + || node.type + || !node.body + || node.questionToken) { + transformFlags |= TransformFlags.AssertTypeScript; + } + // function declarations with object rest destructuring are ES2018 syntax + if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) { + transformFlags |= TransformFlags.AssertES2018; + } + // An async method declaration is ES2017 syntax. + if (hasModifier(node, ModifierFlags.Async)) { + transformFlags |= node.asteriskToken ? TransformFlags.AssertES2018 : TransformFlags.AssertES2017; + } + if (node.asteriskToken) { + transformFlags |= TransformFlags.AssertGenerator; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return propagatePropertyNameFlags(node.name, transformFlags & ~TransformFlags.MethodOrAccessorExcludes); +} +/* @internal */ +function computeAccessor(node: AccessorDeclaration, subtreeFlags: TransformFlags) { + let transformFlags = subtreeFlags; + // Decorators, TypeScript-specific modifiers, type annotations, and overloads are + // TypeScript syntax. + if (node.decorators + || hasModifier(node, ModifierFlags.TypeScriptModifier) + || node.type + || !node.body) { + transformFlags |= TransformFlags.AssertTypeScript; + } + // function declarations with object rest destructuring are ES2018 syntax + if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) { + transformFlags |= TransformFlags.AssertES2018; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return propagatePropertyNameFlags(node.name, transformFlags & ~TransformFlags.MethodOrAccessorExcludes); +} +/* @internal */ +function computePropertyDeclaration(node: PropertyDeclaration, subtreeFlags: TransformFlags) { + let transformFlags = subtreeFlags | TransformFlags.ContainsClassFields; + // Decorators, TypeScript-specific modifiers, and type annotations are TypeScript syntax. + if (some(node.decorators) || hasModifier(node, ModifierFlags.TypeScriptModifier) || node.type || node.questionToken || node.exclamationToken) { + transformFlags |= TransformFlags.AssertTypeScript; + } + // Hoisted variables related to class properties should live within the TypeScript class wrapper. + if (isComputedPropertyName(node.name) || (hasStaticModifier(node) && node.initializer)) { + transformFlags |= TransformFlags.ContainsTypeScriptClassSyntax; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return propagatePropertyNameFlags(node.name, transformFlags & ~TransformFlags.PropertyExcludes); +} +/* @internal */ +function computeFunctionDeclaration(node: FunctionDeclaration, subtreeFlags: TransformFlags) { + let transformFlags: TransformFlags; + const modifierFlags = getModifierFlags(node); + const body = node.body; + if (!body || (modifierFlags & ModifierFlags.Ambient)) { + // An ambient declaration is TypeScript syntax. + // A FunctionDeclaration without a body is an overload and is TypeScript syntax. + transformFlags = TransformFlags.AssertTypeScript; + } + else { + transformFlags = subtreeFlags | TransformFlags.ContainsHoistedDeclarationOrCompletion; // TypeScript-specific modifiers, type parameters, and type annotations are TypeScript // syntax. - if (hasModifier(node, ModifierFlags.TypeScriptModifier) + if (modifierFlags & ModifierFlags.TypeScriptModifier || node.typeParameters || node.type) { transformFlags |= TransformFlags.AssertTypeScript; } - - // An async function expression is ES2017 syntax. - if (hasModifier(node, ModifierFlags.Async)) { + // An async function declaration is ES2017 syntax. + if (modifierFlags & ModifierFlags.Async) { transformFlags |= node.asteriskToken ? TransformFlags.AssertES2018 : TransformFlags.AssertES2017; } - - // function expressions with object rest destructuring are ES2018 syntax + // function declarations with object rest destructuring are ES2018 syntax if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) { transformFlags |= TransformFlags.AssertES2018; } - - // If a FunctionExpression is generator function and is the body of a + // If a FunctionDeclaration is generator function and is the body of a // transformed async function, then this node can be transformed to a // down-level generator. + // Currently we do not support transforming any other generator functions + // down level. if (node.asteriskToken) { transformFlags |= TransformFlags.AssertGenerator; } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.FunctionExcludes; } - - function computeArrowFunction(node: ArrowFunction, subtreeFlags: TransformFlags) { - // An ArrowFunction is ES6 syntax, and excludes markers that should not escape the scope of an ArrowFunction. - let transformFlags = subtreeFlags | TransformFlags.AssertES2015; - - // TypeScript-specific modifiers, type parameters, and type annotations are TypeScript - // syntax. - if (hasModifier(node, ModifierFlags.TypeScriptModifier) - || node.typeParameters - || node.type) { - transformFlags |= TransformFlags.AssertTypeScript; - } - - // An async arrow function is ES2017 syntax. - if (hasModifier(node, ModifierFlags.Async)) { - transformFlags |= TransformFlags.AssertES2017; - } - - // arrow functions with object rest destructuring are ES2018 syntax - if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) { - transformFlags |= TransformFlags.AssertES2018; - } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.ArrowFunctionExcludes; - } - - function computePropertyAccess(node: PropertyAccessExpression, subtreeFlags: TransformFlags) { - let transformFlags = subtreeFlags; - - if (node.flags & NodeFlags.OptionalChain) { - transformFlags |= TransformFlags.ContainsESNext; - } - - // If a PropertyAccessExpression starts with a super keyword, then it is - // ES6 syntax, and requires a lexical `this` binding. - if (node.expression.kind === SyntaxKind.SuperKeyword) { - // super inside of an async function requires hoisting the super access (ES2017). - // same for super inside of an async generator, which is ES2018. - transformFlags |= TransformFlags.ContainsES2017 | TransformFlags.ContainsES2018; - } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.PropertyAccessExcludes; - } - - function computeElementAccess(node: ElementAccessExpression, subtreeFlags: TransformFlags) { - let transformFlags = subtreeFlags; - - if (node.flags & NodeFlags.OptionalChain) { - transformFlags |= TransformFlags.ContainsESNext; - } - - // If an ElementAccessExpression starts with a super keyword, then it is - // ES6 syntax, and requires a lexical `this` binding. - if (node.expression.kind === SyntaxKind.SuperKeyword) { - // super inside of an async function requires hoisting the super access (ES2017). - // same for super inside of an async generator, which is ES2018. - transformFlags |= TransformFlags.ContainsES2017 | TransformFlags.ContainsES2018; - } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.PropertyAccessExcludes; - } - - function computeVariableDeclaration(node: VariableDeclaration, subtreeFlags: TransformFlags) { - let transformFlags = subtreeFlags; - transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern; // TODO(rbuckton): Why are these set unconditionally? - - // A VariableDeclaration containing ObjectRest is ES2018 syntax - if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) { - transformFlags |= TransformFlags.AssertES2018; - } - - // Type annotations are TypeScript syntax. - if (node.type || node.exclamationToken) { - transformFlags |= TransformFlags.AssertTypeScript; - } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.NodeExcludes; + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.FunctionExcludes; +} +/* @internal */ +function computeFunctionExpression(node: FunctionExpression, subtreeFlags: TransformFlags) { + let transformFlags = subtreeFlags; + // TypeScript-specific modifiers, type parameters, and type annotations are TypeScript + // syntax. + if (hasModifier(node, ModifierFlags.TypeScriptModifier) + || node.typeParameters + || node.type) { + transformFlags |= TransformFlags.AssertTypeScript; } - - function computeVariableStatement(node: VariableStatement, subtreeFlags: TransformFlags) { - let transformFlags: TransformFlags; - const declarationListTransformFlags = node.declarationList.transformFlags; - - // An ambient declaration is TypeScript syntax. - if (hasModifier(node, ModifierFlags.Ambient)) { - transformFlags = TransformFlags.AssertTypeScript; - } - else { - transformFlags = subtreeFlags; - - if (declarationListTransformFlags & TransformFlags.ContainsBindingPattern) { - transformFlags |= TransformFlags.AssertES2015; - } - } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.NodeExcludes; - } - - function computeLabeledStatement(node: LabeledStatement, subtreeFlags: TransformFlags) { - let transformFlags = subtreeFlags; - - // A labeled statement containing a block scoped binding *may* need to be transformed from ES6. - if (subtreeFlags & TransformFlags.ContainsBlockScopedBinding - && isIterationStatement(node, /*lookInLabeledStatements*/ true)) { + // An async function expression is ES2017 syntax. + if (hasModifier(node, ModifierFlags.Async)) { + transformFlags |= node.asteriskToken ? TransformFlags.AssertES2018 : TransformFlags.AssertES2017; + } + // function expressions with object rest destructuring are ES2018 syntax + if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) { + transformFlags |= TransformFlags.AssertES2018; + } + // If a FunctionExpression is generator function and is the body of a + // transformed async function, then this node can be transformed to a + // down-level generator. + if (node.asteriskToken) { + transformFlags |= TransformFlags.AssertGenerator; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.FunctionExcludes; +} +/* @internal */ +function computeArrowFunction(node: ArrowFunction, subtreeFlags: TransformFlags) { + // An ArrowFunction is ES6 syntax, and excludes markers that should not escape the scope of an ArrowFunction. + let transformFlags = subtreeFlags | TransformFlags.AssertES2015; + // TypeScript-specific modifiers, type parameters, and type annotations are TypeScript + // syntax. + if (hasModifier(node, ModifierFlags.TypeScriptModifier) + || node.typeParameters + || node.type) { + transformFlags |= TransformFlags.AssertTypeScript; + } + // An async arrow function is ES2017 syntax. + if (hasModifier(node, ModifierFlags.Async)) { + transformFlags |= TransformFlags.AssertES2017; + } + // arrow functions with object rest destructuring are ES2018 syntax + if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) { + transformFlags |= TransformFlags.AssertES2018; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.ArrowFunctionExcludes; +} +/* @internal */ +function computePropertyAccess(node: PropertyAccessExpression, subtreeFlags: TransformFlags) { + let transformFlags = subtreeFlags; + if (node.flags & NodeFlags.OptionalChain) { + transformFlags |= TransformFlags.ContainsESNext; + } + // If a PropertyAccessExpression starts with a super keyword, then it is + // ES6 syntax, and requires a lexical `this` binding. + if (node.expression.kind === SyntaxKind.SuperKeyword) { + // super inside of an async function requires hoisting the super access (ES2017). + // same for super inside of an async generator, which is ES2018. + transformFlags |= TransformFlags.ContainsES2017 | TransformFlags.ContainsES2018; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.PropertyAccessExcludes; +} +/* @internal */ +function computeElementAccess(node: ElementAccessExpression, subtreeFlags: TransformFlags) { + let transformFlags = subtreeFlags; + if (node.flags & NodeFlags.OptionalChain) { + transformFlags |= TransformFlags.ContainsESNext; + } + // If an ElementAccessExpression starts with a super keyword, then it is + // ES6 syntax, and requires a lexical `this` binding. + if (node.expression.kind === SyntaxKind.SuperKeyword) { + // super inside of an async function requires hoisting the super access (ES2017). + // same for super inside of an async generator, which is ES2018. + transformFlags |= TransformFlags.ContainsES2017 | TransformFlags.ContainsES2018; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.PropertyAccessExcludes; +} +/* @internal */ +function computeVariableDeclaration(node: VariableDeclaration, subtreeFlags: TransformFlags) { + let transformFlags = subtreeFlags; + transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern; // TODO(rbuckton): Why are these set unconditionally? + // A VariableDeclaration containing ObjectRest is ES2018 syntax + if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) { + transformFlags |= TransformFlags.AssertES2018; + } + // Type annotations are TypeScript syntax. + if (node.type || node.exclamationToken) { + transformFlags |= TransformFlags.AssertTypeScript; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.NodeExcludes; +} +/* @internal */ +function computeVariableStatement(node: VariableStatement, subtreeFlags: TransformFlags) { + let transformFlags: TransformFlags; + const declarationListTransformFlags = node.declarationList.transformFlags; + // An ambient declaration is TypeScript syntax. + if (hasModifier(node, ModifierFlags.Ambient)) { + transformFlags = TransformFlags.AssertTypeScript; + } + else { + transformFlags = subtreeFlags; + if (declarationListTransformFlags & TransformFlags.ContainsBindingPattern) { transformFlags |= TransformFlags.AssertES2015; } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.NodeExcludes; } - - function computeImportEquals(node: ImportEqualsDeclaration, subtreeFlags: TransformFlags) { - let transformFlags = subtreeFlags; - - // An ImportEqualsDeclaration with a namespace reference is TypeScript. - if (!isExternalModuleImportEqualsDeclaration(node)) { + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.NodeExcludes; +} +/* @internal */ +function computeLabeledStatement(node: LabeledStatement, subtreeFlags: TransformFlags) { + let transformFlags = subtreeFlags; + // A labeled statement containing a block scoped binding *may* need to be transformed from ES6. + if (subtreeFlags & TransformFlags.ContainsBlockScopedBinding + && isIterationStatement(node, /*lookInLabeledStatements*/ true)) { + transformFlags |= TransformFlags.AssertES2015; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.NodeExcludes; +} +/* @internal */ +function computeImportEquals(node: ImportEqualsDeclaration, subtreeFlags: TransformFlags) { + let transformFlags = subtreeFlags; + // An ImportEqualsDeclaration with a namespace reference is TypeScript. + if (!isExternalModuleImportEqualsDeclaration(node)) { + transformFlags |= TransformFlags.AssertTypeScript; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.NodeExcludes; +} +/* @internal */ +function computeExpressionStatement(node: ExpressionStatement, subtreeFlags: TransformFlags) { + const transformFlags = subtreeFlags; + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.NodeExcludes; +} +/* @internal */ +function computeModuleDeclaration(node: ModuleDeclaration, subtreeFlags: TransformFlags) { + let transformFlags = TransformFlags.AssertTypeScript; + const modifierFlags = getModifierFlags(node); + if ((modifierFlags & ModifierFlags.Ambient) === 0) { + transformFlags |= subtreeFlags; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.ModuleExcludes; +} +/* @internal */ +function computeVariableDeclarationList(node: VariableDeclarationList, subtreeFlags: TransformFlags) { + let transformFlags = subtreeFlags | TransformFlags.ContainsHoistedDeclarationOrCompletion; + if (subtreeFlags & TransformFlags.ContainsBindingPattern) { + transformFlags |= TransformFlags.AssertES2015; + } + // If a VariableDeclarationList is `let` or `const`, then it is ES6 syntax. + if (node.flags & NodeFlags.BlockScoped) { + transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBlockScopedBinding; + } + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.VariableDeclarationListExcludes; +} +/* @internal */ +function computeOther(node: Node, kind: SyntaxKind, subtreeFlags: TransformFlags) { + // Mark transformations needed for each node + let transformFlags = subtreeFlags; + let excludeFlags = TransformFlags.NodeExcludes; + switch (kind) { + case SyntaxKind.AsyncKeyword: + case SyntaxKind.AwaitExpression: + // async/await is ES2017 syntax, but may be ES2018 syntax (for async generators) + transformFlags |= TransformFlags.AssertES2018 | TransformFlags.AssertES2017; + break; + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + case SyntaxKind.PartiallyEmittedExpression: + // These nodes are TypeScript syntax. transformFlags |= TransformFlags.AssertTypeScript; - } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.NodeExcludes; - } - - function computeExpressionStatement(node: ExpressionStatement, subtreeFlags: TransformFlags) { - const transformFlags = subtreeFlags; - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.NodeExcludes; - } - - function computeModuleDeclaration(node: ModuleDeclaration, subtreeFlags: TransformFlags) { - let transformFlags = TransformFlags.AssertTypeScript; - const modifierFlags = getModifierFlags(node); - - if ((modifierFlags & ModifierFlags.Ambient) === 0) { - transformFlags |= subtreeFlags; - } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.ModuleExcludes; - } - - function computeVariableDeclarationList(node: VariableDeclarationList, subtreeFlags: TransformFlags) { - let transformFlags = subtreeFlags | TransformFlags.ContainsHoistedDeclarationOrCompletion; - - if (subtreeFlags & TransformFlags.ContainsBindingPattern) { + excludeFlags = TransformFlags.OuterExpressionExcludes; + break; + case SyntaxKind.PublicKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.AbstractKeyword: + case SyntaxKind.DeclareKeyword: + case SyntaxKind.ConstKeyword: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.EnumMember: + case SyntaxKind.NonNullExpression: + case SyntaxKind.ReadonlyKeyword: + // These nodes are TypeScript syntax. + transformFlags |= TransformFlags.AssertTypeScript; + break; + case SyntaxKind.JsxElement: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxText: + case SyntaxKind.JsxClosingElement: + case SyntaxKind.JsxFragment: + case SyntaxKind.JsxOpeningFragment: + case SyntaxKind.JsxClosingFragment: + case SyntaxKind.JsxAttribute: + case SyntaxKind.JsxAttributes: + case SyntaxKind.JsxSpreadAttribute: + case SyntaxKind.JsxExpression: + // These nodes are Jsx syntax. + transformFlags |= TransformFlags.AssertJsx; + break; + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TemplateHead: + case SyntaxKind.TemplateMiddle: + case SyntaxKind.TemplateTail: + case SyntaxKind.TemplateExpression: + case SyntaxKind.TaggedTemplateExpression: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.StaticKeyword: + case SyntaxKind.MetaProperty: + // These nodes are ES6 syntax. transformFlags |= TransformFlags.AssertES2015; - } - - // If a VariableDeclarationList is `let` or `const`, then it is ES6 syntax. - if (node.flags & NodeFlags.BlockScoped) { - transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBlockScopedBinding; - } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.VariableDeclarationListExcludes; - } - - function computeOther(node: Node, kind: SyntaxKind, subtreeFlags: TransformFlags) { - // Mark transformations needed for each node - let transformFlags = subtreeFlags; - let excludeFlags = TransformFlags.NodeExcludes; - - switch (kind) { - case SyntaxKind.AsyncKeyword: - case SyntaxKind.AwaitExpression: - // async/await is ES2017 syntax, but may be ES2018 syntax (for async generators) - transformFlags |= TransformFlags.AssertES2018 | TransformFlags.AssertES2017; - break; - - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.AsExpression: - case SyntaxKind.PartiallyEmittedExpression: - // These nodes are TypeScript syntax. - transformFlags |= TransformFlags.AssertTypeScript; - excludeFlags = TransformFlags.OuterExpressionExcludes; - break; - case SyntaxKind.PublicKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.ProtectedKeyword: - case SyntaxKind.AbstractKeyword: - case SyntaxKind.DeclareKeyword: - case SyntaxKind.ConstKeyword: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.EnumMember: - case SyntaxKind.NonNullExpression: - case SyntaxKind.ReadonlyKeyword: - // These nodes are TypeScript syntax. - transformFlags |= TransformFlags.AssertTypeScript; - break; - - case SyntaxKind.JsxElement: - case SyntaxKind.JsxSelfClosingElement: - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxText: - case SyntaxKind.JsxClosingElement: - case SyntaxKind.JsxFragment: - case SyntaxKind.JsxOpeningFragment: - case SyntaxKind.JsxClosingFragment: - case SyntaxKind.JsxAttribute: - case SyntaxKind.JsxAttributes: - case SyntaxKind.JsxSpreadAttribute: - case SyntaxKind.JsxExpression: - // These nodes are Jsx syntax. - transformFlags |= TransformFlags.AssertJsx; - break; - - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.TemplateHead: - case SyntaxKind.TemplateMiddle: - case SyntaxKind.TemplateTail: - case SyntaxKind.TemplateExpression: - case SyntaxKind.TaggedTemplateExpression: - case SyntaxKind.ShorthandPropertyAssignment: - case SyntaxKind.StaticKeyword: - case SyntaxKind.MetaProperty: - // These nodes are ES6 syntax. + break; + case SyntaxKind.StringLiteral: + if ((node).hasExtendedUnicodeEscape) { transformFlags |= TransformFlags.AssertES2015; - break; - - case SyntaxKind.StringLiteral: - if ((node).hasExtendedUnicodeEscape) { - transformFlags |= TransformFlags.AssertES2015; - } - break; - - case SyntaxKind.NumericLiteral: - if ((node).numericLiteralFlags & TokenFlags.BinaryOrOctalSpecifier) { - transformFlags |= TransformFlags.AssertES2015; - } - break; - - case SyntaxKind.BigIntLiteral: - transformFlags |= TransformFlags.AssertESNext; - break; - - case SyntaxKind.ForOfStatement: - // This node is either ES2015 syntax or ES2017 syntax (if it is a for-await-of). - if ((node).awaitModifier) { - transformFlags |= TransformFlags.AssertES2018; - } + } + break; + case SyntaxKind.NumericLiteral: + if ((node).numericLiteralFlags & TokenFlags.BinaryOrOctalSpecifier) { transformFlags |= TransformFlags.AssertES2015; - break; - - case SyntaxKind.YieldExpression: - // This node is either ES2015 syntax (in a generator) or ES2017 syntax (in an async - // generator). - transformFlags |= TransformFlags.AssertES2018 | TransformFlags.AssertES2015 | TransformFlags.ContainsYield; - break; - - case SyntaxKind.AnyKeyword: - case SyntaxKind.NumberKeyword: - case SyntaxKind.BigIntKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.ObjectKeyword: - case SyntaxKind.StringKeyword: - case SyntaxKind.BooleanKeyword: - case SyntaxKind.SymbolKeyword: - case SyntaxKind.VoidKeyword: - case SyntaxKind.TypeParameter: - case SyntaxKind.PropertySignature: - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.TypePredicate: - case SyntaxKind.TypeReference: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.TypeQuery: - case SyntaxKind.TypeLiteral: - case SyntaxKind.ArrayType: - case SyntaxKind.TupleType: - case SyntaxKind.OptionalType: - case SyntaxKind.RestType: - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: - case SyntaxKind.ConditionalType: - case SyntaxKind.InferType: - case SyntaxKind.ParenthesizedType: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.ThisType: - case SyntaxKind.TypeOperator: - case SyntaxKind.IndexedAccessType: - case SyntaxKind.MappedType: - case SyntaxKind.LiteralType: - case SyntaxKind.NamespaceExportDeclaration: - // Types and signatures are TypeScript syntax, and exclude all other facts. - transformFlags = TransformFlags.AssertTypeScript; - excludeFlags = TransformFlags.TypeExcludes; - break; - - case SyntaxKind.ComputedPropertyName: - // Even though computed property names are ES6, we don't treat them as such. - // This is so that they can flow through PropertyName transforms unaffected. - // Instead, we mark the container as ES6, so that it can properly handle the transform. - transformFlags |= TransformFlags.ContainsComputedPropertyName; - break; - - case SyntaxKind.SpreadElement: - transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsRestOrSpread; - break; - - case SyntaxKind.SpreadAssignment: + } + break; + case SyntaxKind.BigIntLiteral: + transformFlags |= TransformFlags.AssertESNext; + break; + case SyntaxKind.ForOfStatement: + // This node is either ES2015 syntax or ES2017 syntax (if it is a for-await-of). + if ((node).awaitModifier) { + transformFlags |= TransformFlags.AssertES2018; + } + transformFlags |= TransformFlags.AssertES2015; + break; + case SyntaxKind.YieldExpression: + // This node is either ES2015 syntax (in a generator) or ES2017 syntax (in an async + // generator). + transformFlags |= TransformFlags.AssertES2018 | TransformFlags.AssertES2015 | TransformFlags.ContainsYield; + break; + case SyntaxKind.AnyKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.BigIntKeyword: + case SyntaxKind.NeverKeyword: + case SyntaxKind.ObjectKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.SymbolKeyword: + case SyntaxKind.VoidKeyword: + case SyntaxKind.TypeParameter: + case SyntaxKind.PropertySignature: + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.TypePredicate: + case SyntaxKind.TypeReference: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.TypeQuery: + case SyntaxKind.TypeLiteral: + case SyntaxKind.ArrayType: + case SyntaxKind.TupleType: + case SyntaxKind.OptionalType: + case SyntaxKind.RestType: + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + case SyntaxKind.ConditionalType: + case SyntaxKind.InferType: + case SyntaxKind.ParenthesizedType: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.ThisType: + case SyntaxKind.TypeOperator: + case SyntaxKind.IndexedAccessType: + case SyntaxKind.MappedType: + case SyntaxKind.LiteralType: + case SyntaxKind.NamespaceExportDeclaration: + // Types and signatures are TypeScript syntax, and exclude all other facts. + transformFlags = TransformFlags.AssertTypeScript; + excludeFlags = TransformFlags.TypeExcludes; + break; + case SyntaxKind.ComputedPropertyName: + // Even though computed property names are ES6, we don't treat them as such. + // This is so that they can flow through PropertyName transforms unaffected. + // Instead, we mark the container as ES6, so that it can properly handle the transform. + transformFlags |= TransformFlags.ContainsComputedPropertyName; + break; + case SyntaxKind.SpreadElement: + transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsRestOrSpread; + break; + case SyntaxKind.SpreadAssignment: + transformFlags |= TransformFlags.AssertES2018 | TransformFlags.ContainsObjectRestOrSpread; + break; + case SyntaxKind.SuperKeyword: + // This node is ES6 syntax. + transformFlags |= TransformFlags.AssertES2015; + excludeFlags = TransformFlags.OuterExpressionExcludes; // must be set to persist `Super` + break; + case SyntaxKind.ThisKeyword: + // Mark this node and its ancestors as containing a lexical `this` keyword. + transformFlags |= TransformFlags.ContainsLexicalThis; + break; + case SyntaxKind.ObjectBindingPattern: + transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern; + if (subtreeFlags & TransformFlags.ContainsRestOrSpread) { transformFlags |= TransformFlags.AssertES2018 | TransformFlags.ContainsObjectRestOrSpread; - break; - - case SyntaxKind.SuperKeyword: - // This node is ES6 syntax. + } + excludeFlags = TransformFlags.BindingPatternExcludes; + break; + case SyntaxKind.ArrayBindingPattern: + transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern; + excludeFlags = TransformFlags.BindingPatternExcludes; + break; + case SyntaxKind.BindingElement: + transformFlags |= TransformFlags.AssertES2015; + if ((node).dotDotDotToken) { + transformFlags |= TransformFlags.ContainsRestOrSpread; + } + break; + case SyntaxKind.Decorator: + // This node is TypeScript syntax, and marks its container as also being TypeScript syntax. + transformFlags |= TransformFlags.AssertTypeScript | TransformFlags.ContainsTypeScriptClassSyntax; + break; + case SyntaxKind.ObjectLiteralExpression: + excludeFlags = TransformFlags.ObjectLiteralExcludes; + if (subtreeFlags & TransformFlags.ContainsComputedPropertyName) { + // If an ObjectLiteralExpression contains a ComputedPropertyName, then it + // is an ES6 node. transformFlags |= TransformFlags.AssertES2015; - excludeFlags = TransformFlags.OuterExpressionExcludes; // must be set to persist `Super` - break; - - case SyntaxKind.ThisKeyword: - // Mark this node and its ancestors as containing a lexical `this` keyword. - transformFlags |= TransformFlags.ContainsLexicalThis; - break; - - case SyntaxKind.ObjectBindingPattern: - transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern; - if (subtreeFlags & TransformFlags.ContainsRestOrSpread) { - transformFlags |= TransformFlags.AssertES2018 | TransformFlags.ContainsObjectRestOrSpread; - } - excludeFlags = TransformFlags.BindingPatternExcludes; - break; - - case SyntaxKind.ArrayBindingPattern: - transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern; - excludeFlags = TransformFlags.BindingPatternExcludes; - break; - - case SyntaxKind.BindingElement: + } + if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) { + // If an ObjectLiteralExpression contains a spread element, then it + // is an ES2018 node. + transformFlags |= TransformFlags.AssertES2018; + } + break; + case SyntaxKind.ArrayLiteralExpression: + excludeFlags = TransformFlags.ArrayLiteralOrCallOrNewExcludes; + break; + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + // A loop containing a block scoped binding *may* need to be transformed from ES6. + if (subtreeFlags & TransformFlags.ContainsBlockScopedBinding) { transformFlags |= TransformFlags.AssertES2015; - if ((node).dotDotDotToken) { - transformFlags |= TransformFlags.ContainsRestOrSpread; - } - break; - - case SyntaxKind.Decorator: - // This node is TypeScript syntax, and marks its container as also being TypeScript syntax. - transformFlags |= TransformFlags.AssertTypeScript | TransformFlags.ContainsTypeScriptClassSyntax; - break; - - case SyntaxKind.ObjectLiteralExpression: - excludeFlags = TransformFlags.ObjectLiteralExcludes; - if (subtreeFlags & TransformFlags.ContainsComputedPropertyName) { - // If an ObjectLiteralExpression contains a ComputedPropertyName, then it - // is an ES6 node. - transformFlags |= TransformFlags.AssertES2015; - } - - if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) { - // If an ObjectLiteralExpression contains a spread element, then it - // is an ES2018 node. - transformFlags |= TransformFlags.AssertES2018; - } - - break; - - case SyntaxKind.ArrayLiteralExpression: - excludeFlags = TransformFlags.ArrayLiteralOrCallOrNewExcludes; - break; - - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - // A loop containing a block scoped binding *may* need to be transformed from ES6. - if (subtreeFlags & TransformFlags.ContainsBlockScopedBinding) { - transformFlags |= TransformFlags.AssertES2015; - } - - break; - - case SyntaxKind.SourceFile: - break; - - case SyntaxKind.ReturnStatement: - // Return statements may require an `await` in ES2018. - transformFlags |= TransformFlags.ContainsHoistedDeclarationOrCompletion | TransformFlags.AssertES2018; - break; - - case SyntaxKind.ContinueStatement: - case SyntaxKind.BreakStatement: - transformFlags |= TransformFlags.ContainsHoistedDeclarationOrCompletion; - break; - } - - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~excludeFlags; + } + break; + case SyntaxKind.SourceFile: + break; + case SyntaxKind.ReturnStatement: + // Return statements may require an `await` in ES2018. + transformFlags |= TransformFlags.ContainsHoistedDeclarationOrCompletion | TransformFlags.AssertES2018; + break; + case SyntaxKind.ContinueStatement: + case SyntaxKind.BreakStatement: + transformFlags |= TransformFlags.ContainsHoistedDeclarationOrCompletion; + break; } - - function propagatePropertyNameFlags(node: PropertyName, transformFlags: TransformFlags) { - return transformFlags | (node.transformFlags & TransformFlags.PropertyNamePropagatingFlags); + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~excludeFlags; +} +/* @internal */ +function propagatePropertyNameFlags(node: PropertyName, transformFlags: TransformFlags) { + return transformFlags | (node.transformFlags & TransformFlags.PropertyNamePropagatingFlags); +} +/** + * Gets the transform flags to exclude when unioning the transform flags of a subtree. + * + * NOTE: This needs to be kept up-to-date with the exclusions used in `computeTransformFlagsForNode`. + * For performance reasons, `computeTransformFlagsForNode` uses local constant values rather + * than calling this function. + */ +/* @internal */ +export function getTransformFlagsSubtreeExclusions(kind: SyntaxKind) { + if (kind >= SyntaxKind.FirstTypeNode && kind <= SyntaxKind.LastTypeNode) { + return TransformFlags.TypeExcludes; } - - /** - * Gets the transform flags to exclude when unioning the transform flags of a subtree. - * - * NOTE: This needs to be kept up-to-date with the exclusions used in `computeTransformFlagsForNode`. - * For performance reasons, `computeTransformFlagsForNode` uses local constant values rather - * than calling this function. - */ - export function getTransformFlagsSubtreeExclusions(kind: SyntaxKind) { - if (kind >= SyntaxKind.FirstTypeNode && kind <= SyntaxKind.LastTypeNode) { + switch (kind) { + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + case SyntaxKind.ArrayLiteralExpression: + return TransformFlags.ArrayLiteralOrCallOrNewExcludes; + case SyntaxKind.ModuleDeclaration: + return TransformFlags.ModuleExcludes; + case SyntaxKind.Parameter: + return TransformFlags.ParameterExcludes; + case SyntaxKind.ArrowFunction: + return TransformFlags.ArrowFunctionExcludes; + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + return TransformFlags.FunctionExcludes; + case SyntaxKind.VariableDeclarationList: + return TransformFlags.VariableDeclarationListExcludes; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + return TransformFlags.ClassExcludes; + case SyntaxKind.Constructor: + return TransformFlags.ConstructorExcludes; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return TransformFlags.MethodOrAccessorExcludes; + case SyntaxKind.AnyKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.BigIntKeyword: + case SyntaxKind.NeverKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.ObjectKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.SymbolKeyword: + case SyntaxKind.VoidKeyword: + case SyntaxKind.TypeParameter: + case SyntaxKind.PropertySignature: + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: return TransformFlags.TypeExcludes; - } - - switch (kind) { - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - case SyntaxKind.ArrayLiteralExpression: - return TransformFlags.ArrayLiteralOrCallOrNewExcludes; - case SyntaxKind.ModuleDeclaration: - return TransformFlags.ModuleExcludes; - case SyntaxKind.Parameter: - return TransformFlags.ParameterExcludes; - case SyntaxKind.ArrowFunction: - return TransformFlags.ArrowFunctionExcludes; - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - return TransformFlags.FunctionExcludes; - case SyntaxKind.VariableDeclarationList: - return TransformFlags.VariableDeclarationListExcludes; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - return TransformFlags.ClassExcludes; - case SyntaxKind.Constructor: - return TransformFlags.ConstructorExcludes; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return TransformFlags.MethodOrAccessorExcludes; - case SyntaxKind.AnyKeyword: - case SyntaxKind.NumberKeyword: - case SyntaxKind.BigIntKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.StringKeyword: - case SyntaxKind.ObjectKeyword: - case SyntaxKind.BooleanKeyword: - case SyntaxKind.SymbolKeyword: - case SyntaxKind.VoidKeyword: - case SyntaxKind.TypeParameter: - case SyntaxKind.PropertySignature: - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - return TransformFlags.TypeExcludes; - case SyntaxKind.ObjectLiteralExpression: - return TransformFlags.ObjectLiteralExcludes; - case SyntaxKind.CatchClause: - return TransformFlags.CatchClauseExcludes; - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ArrayBindingPattern: - return TransformFlags.BindingPatternExcludes; - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.AsExpression: - case SyntaxKind.PartiallyEmittedExpression: - case SyntaxKind.ParenthesizedExpression: - case SyntaxKind.SuperKeyword: - return TransformFlags.OuterExpressionExcludes; - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - return TransformFlags.PropertyAccessExcludes; - default: - return TransformFlags.NodeExcludes; - } - } - - /** - * "Binds" JSDoc nodes in TypeScript code. - * Since we will never create symbols for JSDoc, we just set parent pointers instead. - */ - function setParentPointers(parent: Node, child: Node): void { - child.parent = parent; - forEachChild(child, grandchild => setParentPointers(child, grandchild)); + case SyntaxKind.ObjectLiteralExpression: + return TransformFlags.ObjectLiteralExcludes; + case SyntaxKind.CatchClause: + return TransformFlags.CatchClauseExcludes; + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + return TransformFlags.BindingPatternExcludes; + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + case SyntaxKind.PartiallyEmittedExpression: + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.SuperKeyword: + return TransformFlags.OuterExpressionExcludes; + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return TransformFlags.PropertyAccessExcludes; + default: + return TransformFlags.NodeExcludes; } } +/** + * "Binds" JSDoc nodes in TypeScript code. + * Since we will never create symbols for JSDoc, we just set parent pointers instead. + */ +/* @internal */ +function setParentPointers(parent: Node, child: Node): void { + child.parent = parent; + forEachChild(child, grandchild => setParentPointers(child, grandchild)); +} diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index a5750544d3af2..4449f9efffeec 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -1,1199 +1,1092 @@ +import { DiagnosticCategory, DiagnosticMessageChain, ReusableBuilderState, Diagnostic, SourceFile, Path, BuilderState, Program, CompilerOptions, forEachKey, GetCanonicalFileName, createMap, compilerOptionsAffectSemanticDiagnostics, Debug, copyEntries, cloneMapOrUndefined, compilerOptionsAffectEmit, emptyArray, getDirectoryPath, getNormalizedAbsolutePath, getTsBuildInfoEmitOutputFilePath, DiagnosticRelatedInformation, cloneMap, CancellationToken, forEach, skipTypeChecking, getEmitDeclarations, forEachEntry, addToSeen, AffectedFileResult, EmitResult, concatenate, MapLike, arrayFrom, ensurePathIsNonModuleName, getRelativePathFromDirectory, getOptionsNameMap, hasProperty, CompilerOptionsValue, CommandLineOption, BuilderProgramHost, BuilderProgram, CompilerHost, ProjectReference, isArray, createProgram, SemanticDiagnosticsBuilderProgram, EmitAndSemanticDiagnosticsBuilderProgram, createGetCanonicalFileName, generateDjb2Hash, notImplemented, WriteFileCallback, CustomTransformers, maybeBind, some, handleNoEmitOptions, SourceMapEmitResult, addRange, arrayToSet, ReadBuildProgramHost, convertToOptionsWithAbsolutePaths, arrayToMap, isString, noop, returnUndefined } from "./ts"; +import * as ts from "./ts"; /*@internal*/ -namespace ts { - export interface ReusableDiagnostic extends ReusableDiagnosticRelatedInformation { - /** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */ - reportsUnnecessary?: {}; - source?: string; - relatedInformation?: ReusableDiagnosticRelatedInformation[]; - } - - export interface ReusableDiagnosticRelatedInformation { - category: DiagnosticCategory; - code: number; - file: string | undefined; - start: number | undefined; - length: number | undefined; - messageText: string | ReusableDiagnosticMessageChain; - } - - export type ReusableDiagnosticMessageChain = DiagnosticMessageChain; - - export interface ReusableBuilderProgramState extends ReusableBuilderState { - /** - * Cache of bind and check diagnostics for files with their Path being the key - */ - semanticDiagnosticsPerFile?: ReadonlyMap | undefined; - /** - * The map has key by source file's path that has been changed - */ - changedFilesSet?: ReadonlyMap; - /** - * Set of affected files being iterated - */ - affectedFiles?: readonly SourceFile[] | undefined; - /** - * Current changed file for iterating over affected files - */ - currentChangedFilePath?: Path | undefined; - /** - * Map of file signatures, with key being file path, calculated while getting current changed file's affected files - * These will be committed whenever the iteration through affected files of current changed file is complete - */ - currentAffectedFilesSignatures?: ReadonlyMap | undefined; - /** - * Newly computed visible to outside referencedSet - */ - currentAffectedFilesExportedModulesMap?: Readonly | undefined; - /** - * True if the semantic diagnostics were copied from the old state - */ - semanticDiagnosticsFromOldState?: Map; - /** - * program corresponding to this state - */ - program?: Program | undefined; - /** - * compilerOptions for the program - */ - compilerOptions: CompilerOptions; - /** - * Files pending to be emitted - */ - affectedFilesPendingEmit?: readonly Path[] | undefined; - /** - * Files pending to be emitted kind. - */ - affectedFilesPendingEmitKind?: ReadonlyMap | undefined; - /** - * Current index to retrieve pending affected file - */ - affectedFilesPendingEmitIndex?: number | undefined; - /* - * true if semantic diagnostics are ReusableDiagnostic instead of Diagnostic - */ - hasReusableDiagnostic?: true; - } - - export const enum BuilderFileEmit { - DtsOnly, - Full - } - +export interface ReusableDiagnostic extends ReusableDiagnosticRelatedInformation { + /** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */ + reportsUnnecessary?: {}; + source?: string; + relatedInformation?: ReusableDiagnosticRelatedInformation[]; +} +/* @internal */ +export interface ReusableDiagnosticRelatedInformation { + category: DiagnosticCategory; + code: number; + file: string | undefined; + start: number | undefined; + length: number | undefined; + messageText: string | ReusableDiagnosticMessageChain; +} +/* @internal */ +export type ReusableDiagnosticMessageChain = DiagnosticMessageChain; +/* @internal */ +export interface ReusableBuilderProgramState extends ReusableBuilderState { /** - * State to store the changed files, affected files and cache semantic diagnostics + * Cache of bind and check diagnostics for files with their Path being the key */ - // TODO: GH#18217 Properties of this interface are frequently asserted to be defined. - export interface BuilderProgramState extends BuilderState { - /** - * Cache of bind and check diagnostics for files with their Path being the key - */ - semanticDiagnosticsPerFile: Map | undefined; - /** - * The map has key by source file's path that has been changed - */ - changedFilesSet: Map; - /** - * Set of affected files being iterated - */ - affectedFiles: readonly SourceFile[] | undefined; - /** - * Current index to retrieve affected file from - */ - affectedFilesIndex: number | undefined; - /** - * Current changed file for iterating over affected files - */ - currentChangedFilePath: Path | undefined; - /** - * Map of file signatures, with key being file path, calculated while getting current changed file's affected files - * These will be committed whenever the iteration through affected files of current changed file is complete - */ - currentAffectedFilesSignatures: Map | undefined; - /** - * Newly computed visible to outside referencedSet - */ - currentAffectedFilesExportedModulesMap: BuilderState.ComputingExportedModulesMap | undefined; - /** - * Already seen affected files - */ - seenAffectedFiles: Map | undefined; - /** - * whether this program has cleaned semantic diagnostics cache for lib files - */ - cleanedDiagnosticsOfLibFiles?: boolean; - /** - * True if the semantic diagnostics were copied from the old state - */ - semanticDiagnosticsFromOldState?: Map; - /** - * program corresponding to this state - */ - program: Program | undefined; - /** - * compilerOptions for the program - */ - compilerOptions: CompilerOptions; - /** - * Files pending to be emitted - */ - affectedFilesPendingEmit: Path[] | undefined; - /** - * Files pending to be emitted kind. - */ - affectedFilesPendingEmitKind: Map | undefined; - /** - * Current index to retrieve pending affected file - */ - affectedFilesPendingEmitIndex: number | undefined; - /** - * true if build info is emitted - */ - emittedBuildInfo?: boolean; - /** - * Already seen emitted files - */ - seenEmittedFiles: Map | undefined; - /** - * true if program has been emitted - */ - programEmitComplete?: true; - } - - function hasSameKeys(map1: ReadonlyMap | undefined, map2: ReadonlyMap | undefined): boolean { - // Has same size and every key is present in both maps - return map1 as ReadonlyMap === map2 || map1 !== undefined && map2 !== undefined && map1.size === map2.size && !forEachKey(map1, key => !map2.has(key)); - } - + semanticDiagnosticsPerFile?: ts.ReadonlyMap | undefined; /** - * Create the state so that we can iterate on changedFiles/affected files + * The map has key by source file's path that has been changed */ - function createBuilderProgramState(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly): BuilderProgramState { - const state = BuilderState.create(newProgram, getCanonicalFileName, oldState) as BuilderProgramState; - state.program = newProgram; - const compilerOptions = newProgram.getCompilerOptions(); - state.compilerOptions = compilerOptions; - // With --out or --outFile, any change affects all semantic diagnostics so no need to cache them - if (!compilerOptions.outFile && !compilerOptions.out) { - state.semanticDiagnosticsPerFile = createMap(); - } - state.changedFilesSet = createMap(); - - const useOldState = BuilderState.canReuseOldState(state.referencedMap, oldState); - const oldCompilerOptions = useOldState ? oldState!.compilerOptions : undefined; - const canCopySemanticDiagnostics = useOldState && oldState!.semanticDiagnosticsPerFile && !!state.semanticDiagnosticsPerFile && - !compilerOptionsAffectSemanticDiagnostics(compilerOptions, oldCompilerOptions!); - if (useOldState) { - // Verify the sanity of old state - if (!oldState!.currentChangedFilePath) { - const affectedSignatures = oldState!.currentAffectedFilesSignatures; - Debug.assert(!oldState!.affectedFiles && (!affectedSignatures || !affectedSignatures.size), "Cannot reuse if only few affected files of currentChangedFile were iterated"); - } - const changedFilesSet = oldState!.changedFilesSet; - if (canCopySemanticDiagnostics) { - Debug.assert(!changedFilesSet || !forEachKey(changedFilesSet, path => oldState!.semanticDiagnosticsPerFile!.has(path)), "Semantic diagnostics shouldnt be available for changed files"); - } - - // Copy old state's changed files set - if (changedFilesSet) { - copyEntries(changedFilesSet, state.changedFilesSet); - } - if (!compilerOptions.outFile && !compilerOptions.out && oldState!.affectedFilesPendingEmit) { - state.affectedFilesPendingEmit = oldState!.affectedFilesPendingEmit.slice(); - state.affectedFilesPendingEmitKind = cloneMapOrUndefined(oldState!.affectedFilesPendingEmitKind); - state.affectedFilesPendingEmitIndex = oldState!.affectedFilesPendingEmitIndex; - } - } - - // Update changed files and copy semantic diagnostics if we can - const referencedMap = state.referencedMap; - const oldReferencedMap = useOldState ? oldState!.referencedMap : undefined; - const copyDeclarationFileDiagnostics = canCopySemanticDiagnostics && !compilerOptions.skipLibCheck === !oldCompilerOptions!.skipLibCheck; - const copyLibFileDiagnostics = copyDeclarationFileDiagnostics && !compilerOptions.skipDefaultLibCheck === !oldCompilerOptions!.skipDefaultLibCheck; - state.fileInfos.forEach((info, sourceFilePath) => { - let oldInfo: Readonly | undefined; - let newReferences: BuilderState.ReferencedSet | undefined; - - // if not using old state, every file is changed - if (!useOldState || - // File wasnt present in old state - !(oldInfo = oldState!.fileInfos.get(sourceFilePath)) || - // versions dont match - oldInfo.version !== info.version || - // Referenced files changed - !hasSameKeys(newReferences = referencedMap && referencedMap.get(sourceFilePath), oldReferencedMap && oldReferencedMap.get(sourceFilePath)) || - // Referenced file was deleted in the new program - newReferences && forEachKey(newReferences, path => !state.fileInfos.has(path) && oldState!.fileInfos.has(path))) { - // Register file as changed file and do not copy semantic diagnostics, since all changed files need to be re-evaluated - state.changedFilesSet.set(sourceFilePath, true); - } - else if (canCopySemanticDiagnostics) { - const sourceFile = newProgram.getSourceFileByPath(sourceFilePath as Path)!; - - if (sourceFile.isDeclarationFile && !copyDeclarationFileDiagnostics) { return; } - if (sourceFile.hasNoDefaultLib && !copyLibFileDiagnostics) { return; } - - // Unchanged file copy diagnostics - const diagnostics = oldState!.semanticDiagnosticsPerFile!.get(sourceFilePath); - if (diagnostics) { - state.semanticDiagnosticsPerFile!.set(sourceFilePath, oldState!.hasReusableDiagnostic ? convertToDiagnostics(diagnostics as readonly ReusableDiagnostic[], newProgram, getCanonicalFileName) : diagnostics as readonly Diagnostic[]); - if (!state.semanticDiagnosticsFromOldState) { - state.semanticDiagnosticsFromOldState = createMap(); - } - state.semanticDiagnosticsFromOldState.set(sourceFilePath, true); - } - } - }); - - if (oldCompilerOptions && compilerOptionsAffectEmit(compilerOptions, oldCompilerOptions)) { - // Add all files to affectedFilesPendingEmit since emit changed - newProgram.getSourceFiles().forEach(f => addToAffectedFilesPendingEmit(state, f.path, BuilderFileEmit.Full)); - Debug.assert(state.seenAffectedFiles === undefined); - state.seenAffectedFiles = createMap(); - } - - state.emittedBuildInfo = !state.changedFilesSet.size && !state.affectedFilesPendingEmit; - return state; - } - - function convertToDiagnostics(diagnostics: readonly ReusableDiagnostic[], newProgram: Program, getCanonicalFileName: GetCanonicalFileName): readonly Diagnostic[] { - if (!diagnostics.length) return emptyArray; - const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(newProgram.getCompilerOptions())!, newProgram.getCurrentDirectory())); - return diagnostics.map(diagnostic => { - const result: Diagnostic = convertToDiagnosticRelatedInformation(diagnostic, newProgram, toPath); - result.reportsUnnecessary = diagnostic.reportsUnnecessary; - result.source = diagnostic.source; - const { relatedInformation } = diagnostic; - result.relatedInformation = relatedInformation ? - relatedInformation.length ? - relatedInformation.map(r => convertToDiagnosticRelatedInformation(r, newProgram, toPath)) : - emptyArray : - undefined; - return result; - }); - - function toPath(path: string) { - return ts.toPath(path, buildInfoDirectory, getCanonicalFileName); - } - } - - function convertToDiagnosticRelatedInformation(diagnostic: ReusableDiagnosticRelatedInformation, newProgram: Program, toPath: (path: string) => Path): DiagnosticRelatedInformation { - const { file } = diagnostic; - return { - ...diagnostic, - file: file ? newProgram.getSourceFileByPath(toPath(file)) : undefined - }; - } - + changedFilesSet?: ts.ReadonlyMap; /** - * Releases program and other related not needed properties + * Set of affected files being iterated */ - function releaseCache(state: BuilderProgramState) { - BuilderState.releaseCache(state); - state.program = undefined; - } - + affectedFiles?: readonly SourceFile[] | undefined; /** - * Creates a clone of the state + * Current changed file for iterating over affected files */ - function cloneBuilderProgramState(state: Readonly): BuilderProgramState { - const newState = BuilderState.clone(state) as BuilderProgramState; - newState.semanticDiagnosticsPerFile = cloneMapOrUndefined(state.semanticDiagnosticsPerFile); - newState.changedFilesSet = cloneMap(state.changedFilesSet); - newState.affectedFiles = state.affectedFiles; - newState.affectedFilesIndex = state.affectedFilesIndex; - newState.currentChangedFilePath = state.currentChangedFilePath; - newState.currentAffectedFilesSignatures = cloneMapOrUndefined(state.currentAffectedFilesSignatures); - newState.currentAffectedFilesExportedModulesMap = cloneMapOrUndefined(state.currentAffectedFilesExportedModulesMap); - newState.seenAffectedFiles = cloneMapOrUndefined(state.seenAffectedFiles); - newState.cleanedDiagnosticsOfLibFiles = state.cleanedDiagnosticsOfLibFiles; - newState.semanticDiagnosticsFromOldState = cloneMapOrUndefined(state.semanticDiagnosticsFromOldState); - newState.program = state.program; - newState.compilerOptions = state.compilerOptions; - newState.affectedFilesPendingEmit = state.affectedFilesPendingEmit && state.affectedFilesPendingEmit.slice(); - newState.affectedFilesPendingEmitKind = cloneMapOrUndefined(state.affectedFilesPendingEmitKind); - newState.affectedFilesPendingEmitIndex = state.affectedFilesPendingEmitIndex; - newState.seenEmittedFiles = cloneMapOrUndefined(state.seenEmittedFiles); - newState.programEmitComplete = state.programEmitComplete; - return newState; - } - + currentChangedFilePath?: Path | undefined; /** - * Verifies that source file is ok to be used in calls that arent handled by next + * Map of file signatures, with key being file path, calculated while getting current changed file's affected files + * These will be committed whenever the iteration through affected files of current changed file is complete */ - function assertSourceFileOkWithoutNextAffectedCall(state: BuilderProgramState, sourceFile: SourceFile | undefined) { - Debug.assert(!sourceFile || !state.affectedFiles || state.affectedFiles[state.affectedFilesIndex! - 1] !== sourceFile || !state.semanticDiagnosticsPerFile!.has(sourceFile.path)); - } - + currentAffectedFilesSignatures?: ts.ReadonlyMap | undefined; /** - * This function returns the next affected file to be processed. - * Note that until doneAffected is called it would keep reporting same result - * This is to allow the callers to be able to actually remove affected file only when the operation is complete - * eg. if during diagnostics check cancellation token ends up cancelling the request, the affected file should be retained + * Newly computed visible to outside referencedSet */ - function getNextAffectedFile(state: BuilderProgramState, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash): SourceFile | Program | undefined { - while (true) { - const { affectedFiles } = state; - if (affectedFiles) { - const seenAffectedFiles = state.seenAffectedFiles!; - let affectedFilesIndex = state.affectedFilesIndex!; // TODO: GH#18217 - while (affectedFilesIndex < affectedFiles.length) { - const affectedFile = affectedFiles[affectedFilesIndex]; - if (!seenAffectedFiles.has(affectedFile.path)) { - // Set the next affected file as seen and remove the cached semantic diagnostics - state.affectedFilesIndex = affectedFilesIndex; - handleDtsMayChangeOfAffectedFile(state, affectedFile, cancellationToken, computeHash); - return affectedFile; - } - affectedFilesIndex++; - } - - // Remove the changed file from the change set - state.changedFilesSet.delete(state.currentChangedFilePath!); - state.currentChangedFilePath = undefined; - // Commit the changes in file signature - BuilderState.updateSignaturesFromCache(state, state.currentAffectedFilesSignatures!); - state.currentAffectedFilesSignatures!.clear(); - BuilderState.updateExportedFilesMapFromCache(state, state.currentAffectedFilesExportedModulesMap); - state.affectedFiles = undefined; - } - - // Get next changed file - const nextKey = state.changedFilesSet.keys().next(); - if (nextKey.done) { - // Done - return undefined; - } - - // With --out or --outFile all outputs go into single file - // so operations are performed directly on program, return program - const program = Debug.assertDefined(state.program); - const compilerOptions = program.getCompilerOptions(); - if (compilerOptions.outFile || compilerOptions.out) { - Debug.assert(!state.semanticDiagnosticsPerFile); - return program; - } - - // Get next batch of affected files - state.currentAffectedFilesSignatures = state.currentAffectedFilesSignatures || createMap(); - if (state.exportedModulesMap) { - state.currentAffectedFilesExportedModulesMap = state.currentAffectedFilesExportedModulesMap || createMap(); - } - state.affectedFiles = BuilderState.getFilesAffectedBy(state, program, nextKey.value as Path, cancellationToken, computeHash, state.currentAffectedFilesSignatures, state.currentAffectedFilesExportedModulesMap); - state.currentChangedFilePath = nextKey.value as Path; - state.affectedFilesIndex = 0; - state.seenAffectedFiles = state.seenAffectedFiles || createMap(); - } - } - + currentAffectedFilesExportedModulesMap?: Readonly | undefined; /** - * Returns next file to be emitted from files that retrieved semantic diagnostics but did not emit yet + * True if the semantic diagnostics were copied from the old state */ - function getNextAffectedFilePendingEmit(state: BuilderProgramState) { - const { affectedFilesPendingEmit } = state; - if (affectedFilesPendingEmit) { - const seenEmittedFiles = state.seenEmittedFiles || (state.seenEmittedFiles = createMap()); - for (let i = state.affectedFilesPendingEmitIndex!; i < affectedFilesPendingEmit.length; i++) { - const affectedFile = Debug.assertDefined(state.program).getSourceFileByPath(affectedFilesPendingEmit[i]); - if (affectedFile) { - const seenKind = seenEmittedFiles.get(affectedFile.path); - const emitKind = Debug.assertDefined(Debug.assertDefined(state.affectedFilesPendingEmitKind).get(affectedFile.path)); - if (seenKind === undefined || seenKind < emitKind) { - // emit this file - state.affectedFilesPendingEmitIndex = i; - return { affectedFile, emitKind }; - } - } - } - state.affectedFilesPendingEmit = undefined; - state.affectedFilesPendingEmitKind = undefined; - state.affectedFilesPendingEmitIndex = undefined; - } - return undefined; - } - + semanticDiagnosticsFromOldState?: ts.Map; /** - * Handles semantic diagnostics and dts emit for affectedFile and files, that are referencing modules that export entities from affected file - * This is because even though js emit doesnt change, dts emit / type used can change resulting in need for dts emit and js change + * program corresponding to this state */ - function handleDtsMayChangeOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash) { - removeSemanticDiagnosticsOf(state, affectedFile.path); - - // If affected files is everything except default library, then nothing more to do - if (state.allFilesExcludingDefaultLibraryFile === state.affectedFiles) { - if (!state.cleanedDiagnosticsOfLibFiles) { - state.cleanedDiagnosticsOfLibFiles = true; - const program = Debug.assertDefined(state.program); - const options = program.getCompilerOptions(); - forEach(program.getSourceFiles(), f => - program.isSourceFileDefaultLibrary(f) && - !skipTypeChecking(f, options, program) && - removeSemanticDiagnosticsOf(state, f.path) - ); - } - return; - } - - forEachReferencingModulesOfExportOfAffectedFile(state, affectedFile, (state, path) => handleDtsMayChangeOf(state, path, cancellationToken, computeHash)); - } - + program?: Program | undefined; /** - * Handle the dts may change, so they need to be added to pending emit if dts emit is enabled, - * Also we need to make sure signature is updated for these files + * compilerOptions for the program */ - function handleDtsMayChangeOf(state: BuilderProgramState, path: Path, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash) { - removeSemanticDiagnosticsOf(state, path); - - if (!state.changedFilesSet.has(path)) { - const program = Debug.assertDefined(state.program); - const sourceFile = program.getSourceFileByPath(path); - if (sourceFile) { - // Even though the js emit doesnt change and we are already handling dts emit and semantic diagnostics - // we need to update the signature to reflect correctness of the signature(which is output d.ts emit) of this file - // This ensures that we dont later during incremental builds considering wrong signature. - // Eg where this also is needed to ensure that .tsbuildinfo generated by incremental build should be same as if it was first fresh build - BuilderState.updateShapeSignature( - state, - program, - sourceFile, - Debug.assertDefined(state.currentAffectedFilesSignatures), - cancellationToken, - computeHash, - state.currentAffectedFilesExportedModulesMap - ); - // If not dts emit, nothing more to do - if (getEmitDeclarations(state.compilerOptions)) { - addToAffectedFilesPendingEmit(state, path, BuilderFileEmit.DtsOnly); - } - } - } - - return false; - } - + compilerOptions: CompilerOptions; /** - * Removes semantic diagnostics for path and - * returns true if there are no more semantic diagnostics from the old state + * Files pending to be emitted */ - function removeSemanticDiagnosticsOf(state: BuilderProgramState, path: Path) { - if (!state.semanticDiagnosticsFromOldState) { - return true; - } - state.semanticDiagnosticsFromOldState.delete(path); - state.semanticDiagnosticsPerFile!.delete(path); - return !state.semanticDiagnosticsFromOldState.size; - } - - function isChangedSignagure(state: BuilderProgramState, path: Path) { - const newSignature = Debug.assertDefined(state.currentAffectedFilesSignatures).get(path); - const oldSignagure = Debug.assertDefined(state.fileInfos.get(path)).signature; - return newSignature !== oldSignagure; - } - + affectedFilesPendingEmit?: readonly Path[] | undefined; /** - * Iterate on referencing modules that export entities from affected file + * Files pending to be emitted kind. */ - function forEachReferencingModulesOfExportOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile, fn: (state: BuilderProgramState, filePath: Path) => boolean) { - // If there was change in signature (dts output) for the changed file, - // then only we need to handle pending file emit - if (!state.exportedModulesMap || !state.changedFilesSet.has(affectedFile.path)) { - return; - } - - if (!isChangedSignagure(state, affectedFile.path)) return; - - // Since isolated modules dont change js files, files affected by change in signature is itself - // But we need to cleanup semantic diagnostics and queue dts emit for affected files - if (state.compilerOptions.isolatedModules) { - const seenFileNamesMap = createMap(); - seenFileNamesMap.set(affectedFile.path, true); - const queue = BuilderState.getReferencedByPaths(state, affectedFile.resolvedPath); - while (queue.length > 0) { - const currentPath = queue.pop()!; - if (!seenFileNamesMap.has(currentPath)) { - seenFileNamesMap.set(currentPath, true); - const result = fn(state, currentPath); - if (result && isChangedSignagure(state, currentPath)) { - const currentSourceFile = Debug.assertDefined(state.program).getSourceFileByPath(currentPath)!; - queue.push(...BuilderState.getReferencedByPaths(state, currentSourceFile.resolvedPath)); - } - } - } - } - - Debug.assert(!!state.currentAffectedFilesExportedModulesMap); - const seenFileAndExportsOfFile = createMap(); - // Go through exported modules from cache first - // If exported modules has path, all files referencing file exported from are affected - if (forEachEntry(state.currentAffectedFilesExportedModulesMap!, (exportedModules, exportedFromPath) => - exportedModules && - exportedModules.has(affectedFile.path) && - forEachFilesReferencingPath(state, exportedFromPath as Path, seenFileAndExportsOfFile, fn) - )) { - return; - } - - // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected - forEachEntry(state.exportedModulesMap, (exportedModules, exportedFromPath) => - !state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it - exportedModules.has(affectedFile.path) && - forEachFilesReferencingPath(state, exportedFromPath as Path, seenFileAndExportsOfFile, fn) - ); - } - + affectedFilesPendingEmitKind?: ts.ReadonlyMap | undefined; /** - * Iterate on files referencing referencedPath + * Current index to retrieve pending affected file */ - function forEachFilesReferencingPath(state: BuilderProgramState, referencedPath: Path, seenFileAndExportsOfFile: Map, fn: (state: BuilderProgramState, filePath: Path) => boolean) { - return forEachEntry(state.referencedMap!, (referencesInFile, filePath) => - referencesInFile.has(referencedPath) && forEachFileAndExportsOfFile(state, filePath as Path, seenFileAndExportsOfFile, fn) - ); - } - + affectedFilesPendingEmitIndex?: number | undefined; + /* + * true if semantic diagnostics are ReusableDiagnostic instead of Diagnostic + */ + hasReusableDiagnostic?: true; +} +/* @internal */ +export const enum BuilderFileEmit { + DtsOnly, + Full +} +/** + * State to store the changed files, affected files and cache semantic diagnostics + */ +// TODO: GH#18217 Properties of this interface are frequently asserted to be defined. +/* @internal */ +export interface BuilderProgramState extends BuilderState { + /** + * Cache of bind and check diagnostics for files with their Path being the key + */ + semanticDiagnosticsPerFile: ts.Map | undefined; + /** + * The map has key by source file's path that has been changed + */ + changedFilesSet: ts.Map; + /** + * Set of affected files being iterated + */ + affectedFiles: readonly SourceFile[] | undefined; + /** + * Current index to retrieve affected file from + */ + affectedFilesIndex: number | undefined; + /** + * Current changed file for iterating over affected files + */ + currentChangedFilePath: Path | undefined; + /** + * Map of file signatures, with key being file path, calculated while getting current changed file's affected files + * These will be committed whenever the iteration through affected files of current changed file is complete + */ + currentAffectedFilesSignatures: ts.Map | undefined; + /** + * Newly computed visible to outside referencedSet + */ + currentAffectedFilesExportedModulesMap: BuilderState.ComputingExportedModulesMap | undefined; + /** + * Already seen affected files + */ + seenAffectedFiles: ts.Map | undefined; + /** + * whether this program has cleaned semantic diagnostics cache for lib files + */ + cleanedDiagnosticsOfLibFiles?: boolean; + /** + * True if the semantic diagnostics were copied from the old state + */ + semanticDiagnosticsFromOldState?: ts.Map; + /** + * program corresponding to this state + */ + program: Program | undefined; + /** + * compilerOptions for the program + */ + compilerOptions: CompilerOptions; /** - * fn on file and iterate on anything that exports this file + * Files pending to be emitted */ - function forEachFileAndExportsOfFile(state: BuilderProgramState, filePath: Path, seenFileAndExportsOfFile: Map, fn: (state: BuilderProgramState, filePath: Path) => boolean): boolean { - if (!addToSeen(seenFileAndExportsOfFile, filePath)) { - return false; + affectedFilesPendingEmit: Path[] | undefined; + /** + * Files pending to be emitted kind. + */ + affectedFilesPendingEmitKind: ts.Map | undefined; + /** + * Current index to retrieve pending affected file + */ + affectedFilesPendingEmitIndex: number | undefined; + /** + * true if build info is emitted + */ + emittedBuildInfo?: boolean; + /** + * Already seen emitted files + */ + seenEmittedFiles: ts.Map | undefined; + /** + * true if program has been emitted + */ + programEmitComplete?: true; +} +/* @internal */ +function hasSameKeys(map1: ts.ReadonlyMap | undefined, map2: ts.ReadonlyMap | undefined): boolean { + // Has same size and every key is present in both maps + return (map1 as ts.ReadonlyMap) === map2 || map1 !== undefined && map2 !== undefined && map1.size === map2.size && !forEachKey(map1, key => !map2.has(key)); +} +/** + * Create the state so that we can iterate on changedFiles/affected files + */ +/* @internal */ +function createBuilderProgramState(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly): BuilderProgramState { + const state = (BuilderState.create(newProgram, getCanonicalFileName, oldState) as BuilderProgramState); + state.program = newProgram; + const compilerOptions = newProgram.getCompilerOptions(); + state.compilerOptions = compilerOptions; + // With --out or --outFile, any change affects all semantic diagnostics so no need to cache them + if (!compilerOptions.outFile && !compilerOptions.out) { + state.semanticDiagnosticsPerFile = createMap(); + } + state.changedFilesSet = createMap(); + const useOldState = BuilderState.canReuseOldState(state.referencedMap, oldState); + const oldCompilerOptions = useOldState ? oldState!.compilerOptions : undefined; + const canCopySemanticDiagnostics = useOldState && oldState!.semanticDiagnosticsPerFile && !!state.semanticDiagnosticsPerFile && + !compilerOptionsAffectSemanticDiagnostics(compilerOptions, (oldCompilerOptions!)); + if (useOldState) { + // Verify the sanity of old state + if (!oldState!.currentChangedFilePath) { + const affectedSignatures = oldState!.currentAffectedFilesSignatures; + Debug.assert(!oldState!.affectedFiles && (!affectedSignatures || !affectedSignatures.size), "Cannot reuse if only few affected files of currentChangedFile were iterated"); } - - if (fn(state, filePath)) { - // If there are no more diagnostics from old cache, done - return true; + const changedFilesSet = oldState!.changedFilesSet; + if (canCopySemanticDiagnostics) { + Debug.assert(!changedFilesSet || !forEachKey(changedFilesSet, path => oldState!.semanticDiagnosticsPerFile!.has(path)), "Semantic diagnostics shouldnt be available for changed files"); } - - Debug.assert(!!state.currentAffectedFilesExportedModulesMap); - // Go through exported modules from cache first - // If exported modules has path, all files referencing file exported from are affected - if (forEachEntry(state.currentAffectedFilesExportedModulesMap!, (exportedModules, exportedFromPath) => - exportedModules && - exportedModules.has(filePath) && - forEachFileAndExportsOfFile(state, exportedFromPath as Path, seenFileAndExportsOfFile, fn) - )) { - return true; + // Copy old state's changed files set + if (changedFilesSet) { + copyEntries(changedFilesSet, state.changedFilesSet); } - - // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected - if (forEachEntry(state.exportedModulesMap!, (exportedModules, exportedFromPath) => - !state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it - exportedModules.has(filePath) && - forEachFileAndExportsOfFile(state, exportedFromPath as Path, seenFileAndExportsOfFile, fn) - )) { - return true; + if (!compilerOptions.outFile && !compilerOptions.out && oldState!.affectedFilesPendingEmit) { + state.affectedFilesPendingEmit = oldState!.affectedFilesPendingEmit.slice(); + state.affectedFilesPendingEmitKind = cloneMapOrUndefined(oldState!.affectedFilesPendingEmitKind); + state.affectedFilesPendingEmitIndex = oldState!.affectedFilesPendingEmitIndex; } - - // Remove diagnostics of files that import this file (without going to exports of referencing files) - return !!forEachEntry(state.referencedMap!, (referencesInFile, referencingFilePath) => - referencesInFile.has(filePath) && - !seenFileAndExportsOfFile.has(referencingFilePath) && // Not already removed diagnostic file - fn(state, referencingFilePath as Path) // Dont add to seen since this is not yet done with the export removal - ); } - - - /** - * This is called after completing operation on the next affected file. - * The operations here are postponed to ensure that cancellation during the iteration is handled correctly - */ - function doneWithAffectedFile( - state: BuilderProgramState, - affected: SourceFile | Program, - emitKind?: BuilderFileEmit, - isPendingEmit?: boolean, - isBuildInfoEmit?: boolean - ) { - if (isBuildInfoEmit) { - state.emittedBuildInfo = true; - } - else if (affected === state.program) { - state.changedFilesSet.clear(); - state.programEmitComplete = true; + // Update changed files and copy semantic diagnostics if we can + const referencedMap = state.referencedMap; + const oldReferencedMap = useOldState ? oldState!.referencedMap : undefined; + const copyDeclarationFileDiagnostics = canCopySemanticDiagnostics && !compilerOptions.skipLibCheck === !oldCompilerOptions!.skipLibCheck; + const copyLibFileDiagnostics = copyDeclarationFileDiagnostics && !compilerOptions.skipDefaultLibCheck === !oldCompilerOptions!.skipDefaultLibCheck; + state.fileInfos.forEach((info, sourceFilePath) => { + let oldInfo: Readonly | undefined; + let newReferences: BuilderState.ReferencedSet | undefined; + // if not using old state, every file is changed + if (!useOldState || + // File wasnt present in old state + !(oldInfo = oldState!.fileInfos.get(sourceFilePath)) || + // versions dont match + oldInfo.version !== info.version || + // Referenced files changed + !hasSameKeys(newReferences = referencedMap && referencedMap.get(sourceFilePath), oldReferencedMap && oldReferencedMap.get(sourceFilePath)) || + // Referenced file was deleted in the new program + newReferences && forEachKey(newReferences, path => !state.fileInfos.has(path) && oldState!.fileInfos.has(path))) { + // Register file as changed file and do not copy semantic diagnostics, since all changed files need to be re-evaluated + state.changedFilesSet.set(sourceFilePath, true); } - else { - state.seenAffectedFiles!.set((affected as SourceFile).path, true); - if (emitKind !== undefined) { - (state.seenEmittedFiles || (state.seenEmittedFiles = createMap())).set((affected as SourceFile).path, emitKind); + else if (canCopySemanticDiagnostics) { + const sourceFile = (newProgram.getSourceFileByPath((sourceFilePath as Path))!); + if (sourceFile.isDeclarationFile && !copyDeclarationFileDiagnostics) { + return; } - if (isPendingEmit) { - state.affectedFilesPendingEmitIndex!++; + if (sourceFile.hasNoDefaultLib && !copyLibFileDiagnostics) { + return; } - else { - state.affectedFilesIndex!++; + // Unchanged file copy diagnostics + const diagnostics = oldState!.semanticDiagnosticsPerFile!.get(sourceFilePath); + if (diagnostics) { + state.semanticDiagnosticsPerFile!.set(sourceFilePath, oldState!.hasReusableDiagnostic ? convertToDiagnostics(diagnostics as readonly ReusableDiagnostic[], newProgram, getCanonicalFileName) : diagnostics as readonly Diagnostic[]); + if (!state.semanticDiagnosticsFromOldState) { + state.semanticDiagnosticsFromOldState = createMap(); + } + state.semanticDiagnosticsFromOldState.set(sourceFilePath, true); } } + }); + if (oldCompilerOptions && compilerOptionsAffectEmit(compilerOptions, oldCompilerOptions)) { + // Add all files to affectedFilesPendingEmit since emit changed + newProgram.getSourceFiles().forEach(f => addToAffectedFilesPendingEmit(state, f.path, BuilderFileEmit.Full)); + Debug.assert(state.seenAffectedFiles === undefined); + state.seenAffectedFiles = createMap(); } - - /** - * Returns the result with affected file - */ - function toAffectedFileResult(state: BuilderProgramState, result: T, affected: SourceFile | Program): AffectedFileResult { - doneWithAffectedFile(state, affected); - return { result, affected }; - } - - /** - * Returns the result with affected file - */ - function toAffectedFileEmitResult( - state: BuilderProgramState, - result: EmitResult, - affected: SourceFile | Program, - emitKind: BuilderFileEmit, - isPendingEmit?: boolean, - isBuildInfoEmit?: boolean - ): AffectedFileResult { - doneWithAffectedFile(state, affected, emitKind, isPendingEmit, isBuildInfoEmit); - return { result, affected }; - } - - /** - * Gets semantic diagnostics for the file which are - * bindAndCheckDiagnostics (from cache) and program diagnostics - */ - function getSemanticDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { - return concatenate( - getBinderAndCheckerDiagnosticsOfFile(state, sourceFile, cancellationToken), - Debug.assertDefined(state.program).getProgramDiagnostics(sourceFile) - ); + state.emittedBuildInfo = !state.changedFilesSet.size && !state.affectedFilesPendingEmit; + return state; +} +/* @internal */ +function convertToDiagnostics(diagnostics: readonly ReusableDiagnostic[], newProgram: Program, getCanonicalFileName: GetCanonicalFileName): readonly Diagnostic[] { + if (!diagnostics.length) + return emptyArray; + const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath((getTsBuildInfoEmitOutputFilePath(newProgram.getCompilerOptions())!), newProgram.getCurrentDirectory())); + return diagnostics.map(diagnostic => { + const result: Diagnostic = convertToDiagnosticRelatedInformation(diagnostic, newProgram, toPath); + result.reportsUnnecessary = diagnostic.reportsUnnecessary; + result.source = diagnostic.source; + const { relatedInformation } = diagnostic; + result.relatedInformation = relatedInformation ? + relatedInformation.length ? + relatedInformation.map(r => convertToDiagnosticRelatedInformation(r, newProgram, toPath)) : emptyArray : + undefined; + return result; + }); + function toPath(path: string) { + return ts.toPath(path, buildInfoDirectory, getCanonicalFileName); } - - /** - * Gets the binder and checker diagnostics either from cache if present, or otherwise from program and caches it - * Note that it is assumed that when asked about binder and checker diagnostics, the file has been taken out of affected files/changed file set - */ - function getBinderAndCheckerDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { - const path = sourceFile.path; - if (state.semanticDiagnosticsPerFile) { - const cachedDiagnostics = state.semanticDiagnosticsPerFile.get(path); - // Report the bind and check diagnostics from the cache if we already have those diagnostics present - if (cachedDiagnostics) { - return cachedDiagnostics; +} +/* @internal */ +function convertToDiagnosticRelatedInformation(diagnostic: ReusableDiagnosticRelatedInformation, newProgram: Program, toPath: (path: string) => Path): DiagnosticRelatedInformation { + const { file } = diagnostic; + return { + ...diagnostic, + file: file ? newProgram.getSourceFileByPath(toPath(file)) : undefined + }; +} +/** + * Releases program and other related not needed properties + */ +/* @internal */ +function releaseCache(state: BuilderProgramState) { + BuilderState.releaseCache(state); + state.program = undefined; +} +/** + * Creates a clone of the state + */ +/* @internal */ +function cloneBuilderProgramState(state: Readonly): BuilderProgramState { + const newState = (BuilderState.clone(state) as BuilderProgramState); + newState.semanticDiagnosticsPerFile = cloneMapOrUndefined(state.semanticDiagnosticsPerFile); + newState.changedFilesSet = cloneMap(state.changedFilesSet); + newState.affectedFiles = state.affectedFiles; + newState.affectedFilesIndex = state.affectedFilesIndex; + newState.currentChangedFilePath = state.currentChangedFilePath; + newState.currentAffectedFilesSignatures = cloneMapOrUndefined(state.currentAffectedFilesSignatures); + newState.currentAffectedFilesExportedModulesMap = cloneMapOrUndefined(state.currentAffectedFilesExportedModulesMap); + newState.seenAffectedFiles = cloneMapOrUndefined(state.seenAffectedFiles); + newState.cleanedDiagnosticsOfLibFiles = state.cleanedDiagnosticsOfLibFiles; + newState.semanticDiagnosticsFromOldState = cloneMapOrUndefined(state.semanticDiagnosticsFromOldState); + newState.program = state.program; + newState.compilerOptions = state.compilerOptions; + newState.affectedFilesPendingEmit = state.affectedFilesPendingEmit && state.affectedFilesPendingEmit.slice(); + newState.affectedFilesPendingEmitKind = cloneMapOrUndefined(state.affectedFilesPendingEmitKind); + newState.affectedFilesPendingEmitIndex = state.affectedFilesPendingEmitIndex; + newState.seenEmittedFiles = cloneMapOrUndefined(state.seenEmittedFiles); + newState.programEmitComplete = state.programEmitComplete; + return newState; +} +/** + * Verifies that source file is ok to be used in calls that arent handled by next + */ +/* @internal */ +function assertSourceFileOkWithoutNextAffectedCall(state: BuilderProgramState, sourceFile: SourceFile | undefined) { + Debug.assert(!sourceFile || !state.affectedFiles || state.affectedFiles[state.affectedFilesIndex! - 1] !== sourceFile || !state.semanticDiagnosticsPerFile!.has(sourceFile.path)); +} +/** + * This function returns the next affected file to be processed. + * Note that until doneAffected is called it would keep reporting same result + * This is to allow the callers to be able to actually remove affected file only when the operation is complete + * eg. if during diagnostics check cancellation token ends up cancelling the request, the affected file should be retained + */ +/* @internal */ +function getNextAffectedFile(state: BuilderProgramState, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash): SourceFile | Program | undefined { + while (true) { + const { affectedFiles } = state; + if (affectedFiles) { + const seenAffectedFiles = state.seenAffectedFiles!; + let affectedFilesIndex = state.affectedFilesIndex!; // TODO: GH#18217 + while (affectedFilesIndex < affectedFiles.length) { + const affectedFile = affectedFiles[affectedFilesIndex]; + if (!seenAffectedFiles.has(affectedFile.path)) { + // Set the next affected file as seen and remove the cached semantic diagnostics + state.affectedFilesIndex = affectedFilesIndex; + handleDtsMayChangeOfAffectedFile(state, affectedFile, cancellationToken, computeHash); + return affectedFile; + } + affectedFilesIndex++; } + // Remove the changed file from the change set + state.changedFilesSet.delete(state.currentChangedFilePath!); + state.currentChangedFilePath = undefined; + // Commit the changes in file signature + BuilderState.updateSignaturesFromCache(state, (state.currentAffectedFilesSignatures!)); + state.currentAffectedFilesSignatures!.clear(); + BuilderState.updateExportedFilesMapFromCache(state, state.currentAffectedFilesExportedModulesMap); + state.affectedFiles = undefined; } - - // Diagnostics werent cached, get them from program, and cache the result - const diagnostics = Debug.assertDefined(state.program).getBindAndCheckDiagnostics(sourceFile, cancellationToken); - if (state.semanticDiagnosticsPerFile) { - state.semanticDiagnosticsPerFile.set(path, diagnostics); + // Get next changed file + const nextKey = state.changedFilesSet.keys().next(); + if (nextKey.done) { + // Done + return undefined; } - return diagnostics; - } - - export type ProgramBuildInfoDiagnostic = string | [string, readonly ReusableDiagnostic[]]; - export interface ProgramBuildInfo { - fileInfos: MapLike; - options: CompilerOptions; - referencedMap?: MapLike; - exportedModulesMap?: MapLike; - semanticDiagnosticsPerFile?: ProgramBuildInfoDiagnostic[]; - } - - /** - * Gets the program information to be emitted in buildInfo so that we can use it to create new program - */ - function getProgramBuildInfo(state: Readonly, getCanonicalFileName: GetCanonicalFileName): ProgramBuildInfo | undefined { - if (state.compilerOptions.outFile || state.compilerOptions.out) return undefined; - const currentDirectory = Debug.assertDefined(state.program).getCurrentDirectory(); - const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(state.compilerOptions)!, currentDirectory)); - const fileInfos: MapLike = {}; - state.fileInfos.forEach((value, key) => { - const signature = state.currentAffectedFilesSignatures && state.currentAffectedFilesSignatures.get(key); - fileInfos[relativeToBuildInfo(key)] = signature === undefined ? value : { version: value.version, signature }; - }); - - const result: ProgramBuildInfo = { - fileInfos, - options: convertToReusableCompilerOptions(state.compilerOptions, relativeToBuildInfoEnsuringAbsolutePath) - }; - if (state.referencedMap) { - const referencedMap: MapLike = {}; - state.referencedMap.forEach((value, key) => { - referencedMap[relativeToBuildInfo(key)] = arrayFrom(value.keys(), relativeToBuildInfo); - }); - result.referencedMap = referencedMap; + // With --out or --outFile all outputs go into single file + // so operations are performed directly on program, return program + const program = Debug.assertDefined(state.program); + const compilerOptions = program.getCompilerOptions(); + if (compilerOptions.outFile || compilerOptions.out) { + Debug.assert(!state.semanticDiagnosticsPerFile); + return program; } - + // Get next batch of affected files + state.currentAffectedFilesSignatures = state.currentAffectedFilesSignatures || createMap(); if (state.exportedModulesMap) { - const exportedModulesMap: MapLike = {}; - state.exportedModulesMap.forEach((value, key) => { - const newValue = state.currentAffectedFilesExportedModulesMap && state.currentAffectedFilesExportedModulesMap.get(key); - // Not in temporary cache, use existing value - if (newValue === undefined) exportedModulesMap[relativeToBuildInfo(key)] = arrayFrom(value.keys(), relativeToBuildInfo); - // Value in cache and has updated value map, use that - else if (newValue) exportedModulesMap[relativeToBuildInfo(key)] = arrayFrom(newValue.keys(), relativeToBuildInfo); - }); - result.exportedModulesMap = exportedModulesMap; - } - - if (state.semanticDiagnosticsPerFile) { - const semanticDiagnosticsPerFile: ProgramBuildInfoDiagnostic[] = []; - // Currently not recording actual errors since those mean no emit for tsc --build - state.semanticDiagnosticsPerFile.forEach((value, key) => semanticDiagnosticsPerFile.push( - value.length ? - [ - relativeToBuildInfo(key), - state.hasReusableDiagnostic ? - value as readonly ReusableDiagnostic[] : - convertToReusableDiagnostics(value as readonly Diagnostic[], relativeToBuildInfo) - ] : - relativeToBuildInfo(key) - )); - result.semanticDiagnosticsPerFile = semanticDiagnosticsPerFile; + state.currentAffectedFilesExportedModulesMap = state.currentAffectedFilesExportedModulesMap || createMap(); } - - return result; - - function relativeToBuildInfoEnsuringAbsolutePath(path: string) { - return relativeToBuildInfo(getNormalizedAbsolutePath(path, currentDirectory)); + state.affectedFiles = BuilderState.getFilesAffectedBy(state, program, (nextKey.value as Path), cancellationToken, computeHash, state.currentAffectedFilesSignatures, state.currentAffectedFilesExportedModulesMap); + state.currentChangedFilePath = (nextKey.value as Path); + state.affectedFilesIndex = 0; + state.seenAffectedFiles = state.seenAffectedFiles || createMap(); + } +} +/** + * Returns next file to be emitted from files that retrieved semantic diagnostics but did not emit yet + */ +/* @internal */ +function getNextAffectedFilePendingEmit(state: BuilderProgramState) { + const { affectedFilesPendingEmit } = state; + if (affectedFilesPendingEmit) { + const seenEmittedFiles = state.seenEmittedFiles || (state.seenEmittedFiles = createMap()); + for (let i = state.affectedFilesPendingEmitIndex!; i < affectedFilesPendingEmit.length; i++) { + const affectedFile = Debug.assertDefined(state.program).getSourceFileByPath(affectedFilesPendingEmit[i]); + if (affectedFile) { + const seenKind = seenEmittedFiles.get(affectedFile.path); + const emitKind = Debug.assertDefined(Debug.assertDefined(state.affectedFilesPendingEmitKind).get(affectedFile.path)); + if (seenKind === undefined || seenKind < emitKind) { + // emit this file + state.affectedFilesPendingEmitIndex = i; + return { affectedFile, emitKind }; + } + } } - - function relativeToBuildInfo(path: string) { - return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory, path, getCanonicalFileName)); + state.affectedFilesPendingEmit = undefined; + state.affectedFilesPendingEmitKind = undefined; + state.affectedFilesPendingEmitIndex = undefined; + } + return undefined; +} +/** + * Handles semantic diagnostics and dts emit for affectedFile and files, that are referencing modules that export entities from affected file + * This is because even though js emit doesnt change, dts emit / type used can change resulting in need for dts emit and js change + */ +/* @internal */ +function handleDtsMayChangeOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash) { + removeSemanticDiagnosticsOf(state, affectedFile.path); + // If affected files is everything except default library, then nothing more to do + if (state.allFilesExcludingDefaultLibraryFile === state.affectedFiles) { + if (!state.cleanedDiagnosticsOfLibFiles) { + state.cleanedDiagnosticsOfLibFiles = true; + const program = Debug.assertDefined(state.program); + const options = program.getCompilerOptions(); + forEach(program.getSourceFiles(), f => program.isSourceFileDefaultLibrary(f) && + !skipTypeChecking(f, options, program) && + removeSemanticDiagnosticsOf(state, f.path)); } + return; } - - function convertToReusableCompilerOptions(options: CompilerOptions, relativeToBuildInfo: (path: string) => string) { - const result: CompilerOptions = {}; - const { optionsNameMap } = getOptionsNameMap(); - - for (const name in options) { - if (hasProperty(options, name)) { - result[name] = convertToReusableCompilerOptionValue( - optionsNameMap.get(name.toLowerCase()), - options[name] as CompilerOptionsValue, - relativeToBuildInfo - ); + forEachReferencingModulesOfExportOfAffectedFile(state, affectedFile, (state, path) => handleDtsMayChangeOf(state, path, cancellationToken, computeHash)); +} +/** + * Handle the dts may change, so they need to be added to pending emit if dts emit is enabled, + * Also we need to make sure signature is updated for these files + */ +/* @internal */ +function handleDtsMayChangeOf(state: BuilderProgramState, path: Path, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash) { + removeSemanticDiagnosticsOf(state, path); + if (!state.changedFilesSet.has(path)) { + const program = Debug.assertDefined(state.program); + const sourceFile = program.getSourceFileByPath(path); + if (sourceFile) { + // Even though the js emit doesnt change and we are already handling dts emit and semantic diagnostics + // we need to update the signature to reflect correctness of the signature(which is output d.ts emit) of this file + // This ensures that we dont later during incremental builds considering wrong signature. + // Eg where this also is needed to ensure that .tsbuildinfo generated by incremental build should be same as if it was first fresh build + BuilderState.updateShapeSignature(state, program, sourceFile, Debug.assertDefined(state.currentAffectedFilesSignatures), cancellationToken, computeHash, state.currentAffectedFilesExportedModulesMap); + // If not dts emit, nothing more to do + if (getEmitDeclarations(state.compilerOptions)) { + addToAffectedFilesPendingEmit(state, path, BuilderFileEmit.DtsOnly); } } - if (result.configFilePath) { - result.configFilePath = relativeToBuildInfo(result.configFilePath); - } - return result; } - - function convertToReusableCompilerOptionValue(option: CommandLineOption | undefined, value: CompilerOptionsValue, relativeToBuildInfo: (path: string) => string) { - if (option) { - if (option.type === "list") { - const values = value as readonly (string | number)[]; - if (option.element.isFilePath && values.length) { - return values.map(relativeToBuildInfo); + return false; +} +/** + * Removes semantic diagnostics for path and + * returns true if there are no more semantic diagnostics from the old state + */ +/* @internal */ +function removeSemanticDiagnosticsOf(state: BuilderProgramState, path: Path) { + if (!state.semanticDiagnosticsFromOldState) { + return true; + } + state.semanticDiagnosticsFromOldState.delete(path); + state.semanticDiagnosticsPerFile!.delete(path); + return !state.semanticDiagnosticsFromOldState.size; +} +/* @internal */ +function isChangedSignagure(state: BuilderProgramState, path: Path) { + const newSignature = Debug.assertDefined(state.currentAffectedFilesSignatures).get(path); + const oldSignagure = Debug.assertDefined(state.fileInfos.get(path)).signature; + return newSignature !== oldSignagure; +} +/** + * Iterate on referencing modules that export entities from affected file + */ +/* @internal */ +function forEachReferencingModulesOfExportOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile, fn: (state: BuilderProgramState, filePath: Path) => boolean) { + // If there was change in signature (dts output) for the changed file, + // then only we need to handle pending file emit + if (!state.exportedModulesMap || !state.changedFilesSet.has(affectedFile.path)) { + return; + } + if (!isChangedSignagure(state, affectedFile.path)) + return; + // Since isolated modules dont change js files, files affected by change in signature is itself + // But we need to cleanup semantic diagnostics and queue dts emit for affected files + if (state.compilerOptions.isolatedModules) { + const seenFileNamesMap = createMap(); + seenFileNamesMap.set(affectedFile.path, true); + const queue = BuilderState.getReferencedByPaths(state, affectedFile.resolvedPath); + while (queue.length > 0) { + const currentPath = queue.pop()!; + if (!seenFileNamesMap.has(currentPath)) { + seenFileNamesMap.set(currentPath, true); + const result = fn(state, currentPath); + if (result && isChangedSignagure(state, currentPath)) { + const currentSourceFile = (Debug.assertDefined(state.program).getSourceFileByPath(currentPath)!); + queue.push(...BuilderState.getReferencedByPaths(state, currentSourceFile.resolvedPath)); } } - else if (option.isFilePath) { - return relativeToBuildInfo(value as string); - } } - return value; } - - function convertToReusableDiagnostics(diagnostics: readonly Diagnostic[], relativeToBuildInfo: (path: string) => string): readonly ReusableDiagnostic[] { - Debug.assert(!!diagnostics.length); - return diagnostics.map(diagnostic => { - const result: ReusableDiagnostic = convertToReusableDiagnosticRelatedInformation(diagnostic, relativeToBuildInfo); - result.reportsUnnecessary = diagnostic.reportsUnnecessary; - result.source = diagnostic.source; - const { relatedInformation } = diagnostic; - result.relatedInformation = relatedInformation ? - relatedInformation.length ? - relatedInformation.map(r => convertToReusableDiagnosticRelatedInformation(r, relativeToBuildInfo)) : - emptyArray : - undefined; - return result; - }); + Debug.assert(!!state.currentAffectedFilesExportedModulesMap); + const seenFileAndExportsOfFile = createMap(); + // Go through exported modules from cache first + // If exported modules has path, all files referencing file exported from are affected + if (forEachEntry((state.currentAffectedFilesExportedModulesMap!), (exportedModules, exportedFromPath) => exportedModules && + exportedModules.has(affectedFile.path) && + forEachFilesReferencingPath(state, (exportedFromPath as Path), seenFileAndExportsOfFile, fn))) { + return; } - - function convertToReusableDiagnosticRelatedInformation(diagnostic: DiagnosticRelatedInformation, relativeToBuildInfo: (path: string) => string): ReusableDiagnosticRelatedInformation { - const { file } = diagnostic; - return { - ...diagnostic, - file: file ? relativeToBuildInfo(file.path) : undefined - }; + // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected + forEachEntry(state.exportedModulesMap, (exportedModules, exportedFromPath) => !state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it + exportedModules.has(affectedFile.path) && + forEachFilesReferencingPath(state, (exportedFromPath as Path), seenFileAndExportsOfFile, fn)); +} +/** + * Iterate on files referencing referencedPath + */ +/* @internal */ +function forEachFilesReferencingPath(state: BuilderProgramState, referencedPath: Path, seenFileAndExportsOfFile: ts.Map, fn: (state: BuilderProgramState, filePath: Path) => boolean) { + return forEachEntry((state.referencedMap!), (referencesInFile, filePath) => referencesInFile.has(referencedPath) && forEachFileAndExportsOfFile(state, (filePath as Path), seenFileAndExportsOfFile, fn)); +} +/** + * fn on file and iterate on anything that exports this file + */ +/* @internal */ +function forEachFileAndExportsOfFile(state: BuilderProgramState, filePath: Path, seenFileAndExportsOfFile: ts.Map, fn: (state: BuilderProgramState, filePath: Path) => boolean): boolean { + if (!addToSeen(seenFileAndExportsOfFile, filePath)) { + return false; + } + if (fn(state, filePath)) { + // If there are no more diagnostics from old cache, done + return true; + } + Debug.assert(!!state.currentAffectedFilesExportedModulesMap); + // Go through exported modules from cache first + // If exported modules has path, all files referencing file exported from are affected + if (forEachEntry((state.currentAffectedFilesExportedModulesMap!), (exportedModules, exportedFromPath) => exportedModules && + exportedModules.has(filePath) && + forEachFileAndExportsOfFile(state, (exportedFromPath as Path), seenFileAndExportsOfFile, fn))) { + return true; + } + // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected + if (forEachEntry((state.exportedModulesMap!), (exportedModules, exportedFromPath) => !state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it + exportedModules.has(filePath) && + forEachFileAndExportsOfFile(state, (exportedFromPath as Path), seenFileAndExportsOfFile, fn))) { + return true; } - - export enum BuilderProgramKind { - SemanticDiagnosticsBuilderProgram, - EmitAndSemanticDiagnosticsBuilderProgram + // Remove diagnostics of files that import this file (without going to exports of referencing files) + return !!forEachEntry((state.referencedMap!), (referencesInFile, referencingFilePath) => referencesInFile.has(filePath) && + !seenFileAndExportsOfFile.has(referencingFilePath) && // Not already removed diagnostic file + fn(state, (referencingFilePath as Path)) // Dont add to seen since this is not yet done with the export removal + ); +} +/** + * This is called after completing operation on the next affected file. + * The operations here are postponed to ensure that cancellation during the iteration is handled correctly + */ +/* @internal */ +function doneWithAffectedFile(state: BuilderProgramState, affected: SourceFile | Program, emitKind?: BuilderFileEmit, isPendingEmit?: boolean, isBuildInfoEmit?: boolean) { + if (isBuildInfoEmit) { + state.emittedBuildInfo = true; } - - export interface BuilderCreationParameters { - newProgram: Program; - host: BuilderProgramHost; - oldProgram: BuilderProgram | undefined; - configFileParsingDiagnostics: readonly Diagnostic[]; + else if (affected === state.program) { + state.changedFilesSet.clear(); + state.programEmitComplete = true; } - - export function getBuilderCreationParameters(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: BuilderProgram | CompilerHost, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderCreationParameters { - let host: BuilderProgramHost; - let newProgram: Program; - let oldProgram: BuilderProgram; - if (newProgramOrRootNames === undefined) { - Debug.assert(hostOrOptions === undefined); - host = oldProgramOrHost as CompilerHost; - oldProgram = configFileParsingDiagnosticsOrOldProgram as BuilderProgram; - Debug.assert(!!oldProgram); - newProgram = oldProgram.getProgram(); + else { + state.seenAffectedFiles!.set((affected as SourceFile).path, true); + if (emitKind !== undefined) { + (state.seenEmittedFiles || (state.seenEmittedFiles = createMap())).set((affected as SourceFile).path, emitKind); } - else if (isArray(newProgramOrRootNames)) { - oldProgram = configFileParsingDiagnosticsOrOldProgram as BuilderProgram; - newProgram = createProgram({ - rootNames: newProgramOrRootNames, - options: hostOrOptions as CompilerOptions, - host: oldProgramOrHost as CompilerHost, - oldProgram: oldProgram && oldProgram.getProgramOrUndefined(), - configFileParsingDiagnostics, - projectReferences - }); - host = oldProgramOrHost as CompilerHost; + if (isPendingEmit) { + state.affectedFilesPendingEmitIndex!++; } else { - newProgram = newProgramOrRootNames; - host = hostOrOptions as BuilderProgramHost; - oldProgram = oldProgramOrHost as BuilderProgram; - configFileParsingDiagnostics = configFileParsingDiagnosticsOrOldProgram as readonly Diagnostic[]; + state.affectedFilesIndex!++; } - return { host, newProgram, oldProgram, configFileParsingDiagnostics: configFileParsingDiagnostics || emptyArray }; } - - export function createBuilderProgram(kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): SemanticDiagnosticsBuilderProgram; - export function createBuilderProgram(kind: BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): EmitAndSemanticDiagnosticsBuilderProgram; - export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, host, oldProgram, configFileParsingDiagnostics }: BuilderCreationParameters) { - // Return same program if underlying program doesnt change - let oldState = oldProgram && oldProgram.getState(); - if (oldState && newProgram === oldState.program && configFileParsingDiagnostics === newProgram.getConfigFileParsingDiagnostics()) { - newProgram = undefined!; // TODO: GH#18217 - oldState = undefined; - return oldProgram; +} +/** + * Returns the result with affected file + */ +/* @internal */ +function toAffectedFileResult(state: BuilderProgramState, result: T, affected: SourceFile | Program): AffectedFileResult { + doneWithAffectedFile(state, affected); + return { result, affected }; +} +/** + * Returns the result with affected file + */ +/* @internal */ +function toAffectedFileEmitResult(state: BuilderProgramState, result: EmitResult, affected: SourceFile | Program, emitKind: BuilderFileEmit, isPendingEmit?: boolean, isBuildInfoEmit?: boolean): AffectedFileResult { + doneWithAffectedFile(state, affected, emitKind, isPendingEmit, isBuildInfoEmit); + return { result, affected }; +} +/** + * Gets semantic diagnostics for the file which are + * bindAndCheckDiagnostics (from cache) and program diagnostics + */ +/* @internal */ +function getSemanticDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { + return concatenate(getBinderAndCheckerDiagnosticsOfFile(state, sourceFile, cancellationToken), Debug.assertDefined(state.program).getProgramDiagnostics(sourceFile)); +} +/** + * Gets the binder and checker diagnostics either from cache if present, or otherwise from program and caches it + * Note that it is assumed that when asked about binder and checker diagnostics, the file has been taken out of affected files/changed file set + */ +/* @internal */ +function getBinderAndCheckerDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { + const path = sourceFile.path; + if (state.semanticDiagnosticsPerFile) { + const cachedDiagnostics = state.semanticDiagnosticsPerFile.get(path); + // Report the bind and check diagnostics from the cache if we already have those diagnostics present + if (cachedDiagnostics) { + return cachedDiagnostics; } - - /** - * Create the canonical file name for identity - */ - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); - /** - * Computing hash to for signature verification - */ - const computeHash = host.createHash || generateDjb2Hash; - let state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState); - let backupState: BuilderProgramState | undefined; - newProgram.getProgramBuildInfo = () => getProgramBuildInfo(state, getCanonicalFileName); - - // To ensure that we arent storing any references to old program or new program without state - newProgram = undefined!; // TODO: GH#18217 - oldProgram = undefined; - oldState = undefined; - - const builderProgram = createRedirectedBuilderProgram(state, configFileParsingDiagnostics); - builderProgram.getState = () => state; - builderProgram.backupState = () => { - Debug.assert(backupState === undefined); - backupState = cloneBuilderProgramState(state); - }; - builderProgram.restoreState = () => { - state = Debug.assertDefined(backupState); - backupState = undefined; - }; - builderProgram.getAllDependencies = sourceFile => BuilderState.getAllDependencies(state, Debug.assertDefined(state.program), sourceFile); - builderProgram.getSemanticDiagnostics = getSemanticDiagnostics; - builderProgram.emit = emit; - builderProgram.releaseProgram = () => { - releaseCache(state); - backupState = undefined; - }; - - if (kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram) { - (builderProgram as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile; + } + // Diagnostics werent cached, get them from program, and cache the result + const diagnostics = Debug.assertDefined(state.program).getBindAndCheckDiagnostics(sourceFile, cancellationToken); + if (state.semanticDiagnosticsPerFile) { + state.semanticDiagnosticsPerFile.set(path, diagnostics); + } + return diagnostics; +} +/* @internal */ +export type ProgramBuildInfoDiagnostic = string | [string, readonly ReusableDiagnostic[]]; +/* @internal */ +export interface ProgramBuildInfo { + fileInfos: MapLike; + options: CompilerOptions; + referencedMap?: MapLike; + exportedModulesMap?: MapLike; + semanticDiagnosticsPerFile?: ProgramBuildInfoDiagnostic[]; +} +/** + * Gets the program information to be emitted in buildInfo so that we can use it to create new program + */ +/* @internal */ +function getProgramBuildInfo(state: Readonly, getCanonicalFileName: GetCanonicalFileName): ProgramBuildInfo | undefined { + if (state.compilerOptions.outFile || state.compilerOptions.out) + return undefined; + const currentDirectory = Debug.assertDefined(state.program).getCurrentDirectory(); + const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath((getTsBuildInfoEmitOutputFilePath(state.compilerOptions)!), currentDirectory)); + const fileInfos: MapLike = {}; + state.fileInfos.forEach((value, key) => { + const signature = state.currentAffectedFilesSignatures && state.currentAffectedFilesSignatures.get(key); + fileInfos[relativeToBuildInfo(key)] = signature === undefined ? value : { version: value.version, signature }; + }); + const result: ProgramBuildInfo = { + fileInfos, + options: convertToReusableCompilerOptions(state.compilerOptions, relativeToBuildInfoEnsuringAbsolutePath) + }; + if (state.referencedMap) { + const referencedMap: MapLike = {}; + state.referencedMap.forEach((value, key) => { + referencedMap[relativeToBuildInfo(key)] = arrayFrom(value.keys(), relativeToBuildInfo); + }); + result.referencedMap = referencedMap; + } + if (state.exportedModulesMap) { + const exportedModulesMap: MapLike = {}; + state.exportedModulesMap.forEach((value, key) => { + const newValue = state.currentAffectedFilesExportedModulesMap && state.currentAffectedFilesExportedModulesMap.get(key); + // Not in temporary cache, use existing value + if (newValue === undefined) + exportedModulesMap[relativeToBuildInfo(key)] = arrayFrom(value.keys(), relativeToBuildInfo); + // Value in cache and has updated value map, use that + else if (newValue) + exportedModulesMap[relativeToBuildInfo(key)] = arrayFrom(newValue.keys(), relativeToBuildInfo); + }); + result.exportedModulesMap = exportedModulesMap; + } + if (state.semanticDiagnosticsPerFile) { + const semanticDiagnosticsPerFile: ProgramBuildInfoDiagnostic[] = []; + // Currently not recording actual errors since those mean no emit for tsc --build + state.semanticDiagnosticsPerFile.forEach((value, key) => semanticDiagnosticsPerFile.push(value.length ? + [ + relativeToBuildInfo(key), + state.hasReusableDiagnostic ? + value as readonly ReusableDiagnostic[] : + convertToReusableDiagnostics((value as readonly Diagnostic[]), relativeToBuildInfo) + ] : + relativeToBuildInfo(key))); + result.semanticDiagnosticsPerFile = semanticDiagnosticsPerFile; + } + return result; + function relativeToBuildInfoEnsuringAbsolutePath(path: string) { + return relativeToBuildInfo(getNormalizedAbsolutePath(path, currentDirectory)); + } + function relativeToBuildInfo(path: string) { + return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory, path, getCanonicalFileName)); + } +} +/* @internal */ +function convertToReusableCompilerOptions(options: CompilerOptions, relativeToBuildInfo: (path: string) => string) { + const result: CompilerOptions = {}; + const { optionsNameMap } = getOptionsNameMap(); + for (const name in options) { + if (hasProperty(options, name)) { + result[name] = convertToReusableCompilerOptionValue(optionsNameMap.get(name.toLowerCase()), (options[name] as CompilerOptionsValue), relativeToBuildInfo); } - else if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { - (builderProgram as EmitAndSemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile; - (builderProgram as EmitAndSemanticDiagnosticsBuilderProgram).emitNextAffectedFile = emitNextAffectedFile; + } + if (result.configFilePath) { + result.configFilePath = relativeToBuildInfo(result.configFilePath); + } + return result; +} +/* @internal */ +function convertToReusableCompilerOptionValue(option: CommandLineOption | undefined, value: CompilerOptionsValue, relativeToBuildInfo: (path: string) => string) { + if (option) { + if (option.type === "list") { + const values = value as readonly (string | number)[]; + if (option.element.isFilePath && values.length) { + return values.map(relativeToBuildInfo); + } } - else { - notImplemented(); + else if (option.isFilePath) { + return relativeToBuildInfo(value as string); } - - return builderProgram; - - /** - * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete - * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host - * in that order would be used to write the files - */ - function emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult { - let affected = getNextAffectedFile(state, cancellationToken, computeHash); - let emitKind = BuilderFileEmit.Full; - let isPendingEmitFile = false; - if (!affected) { - if (!state.compilerOptions.out && !state.compilerOptions.outFile) { - const pendingAffectedFile = getNextAffectedFilePendingEmit(state); - if (!pendingAffectedFile) { - if (state.emittedBuildInfo) { - return undefined; - } - - const affected = Debug.assertDefined(state.program); - return toAffectedFileEmitResult( - state, - // When whole program is affected, do emit only once (eg when --out or --outFile is specified) - // Otherwise just affected file - affected.emitBuildInfo(writeFile || maybeBind(host, host.writeFile), cancellationToken), - affected, - /*emitKind*/ BuilderFileEmit.Full, - /*isPendingEmitFile*/ false, - /*isBuildInfoEmit*/ true - ); - } - ({ affectedFile: affected, emitKind } = pendingAffectedFile); - isPendingEmitFile = true; - } - else { - const program = Debug.assertDefined(state.program); - // Check if program uses any prepend project references, if thats the case we cant track of the js files of those, so emit even though there are no changes - if (state.programEmitComplete || !some(program.getProjectReferences(), ref => !!ref.prepend)) { - state.programEmitComplete = true; + } + return value; +} +/* @internal */ +function convertToReusableDiagnostics(diagnostics: readonly Diagnostic[], relativeToBuildInfo: (path: string) => string): readonly ReusableDiagnostic[] { + Debug.assert(!!diagnostics.length); + return diagnostics.map(diagnostic => { + const result: ReusableDiagnostic = convertToReusableDiagnosticRelatedInformation(diagnostic, relativeToBuildInfo); + result.reportsUnnecessary = diagnostic.reportsUnnecessary; + result.source = diagnostic.source; + const { relatedInformation } = diagnostic; + result.relatedInformation = relatedInformation ? + relatedInformation.length ? + relatedInformation.map(r => convertToReusableDiagnosticRelatedInformation(r, relativeToBuildInfo)) : emptyArray : + undefined; + return result; + }); +} +/* @internal */ +function convertToReusableDiagnosticRelatedInformation(diagnostic: DiagnosticRelatedInformation, relativeToBuildInfo: (path: string) => string): ReusableDiagnosticRelatedInformation { + const { file } = diagnostic; + return { + ...diagnostic, + file: file ? relativeToBuildInfo(file.path) : undefined + }; +} +/* @internal */ +export enum BuilderProgramKind { + SemanticDiagnosticsBuilderProgram, + EmitAndSemanticDiagnosticsBuilderProgram +} +/* @internal */ +export interface BuilderCreationParameters { + newProgram: Program; + host: BuilderProgramHost; + oldProgram: BuilderProgram | undefined; + configFileParsingDiagnostics: readonly Diagnostic[]; +} +/* @internal */ +export function getBuilderCreationParameters(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: BuilderProgram | CompilerHost, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderCreationParameters { + let host: BuilderProgramHost; + let newProgram: Program; + let oldProgram: BuilderProgram; + if (newProgramOrRootNames === undefined) { + Debug.assert(hostOrOptions === undefined); + host = (oldProgramOrHost as CompilerHost); + oldProgram = (configFileParsingDiagnosticsOrOldProgram as BuilderProgram); + Debug.assert(!!oldProgram); + newProgram = oldProgram.getProgram(); + } + else if (isArray(newProgramOrRootNames)) { + oldProgram = (configFileParsingDiagnosticsOrOldProgram as BuilderProgram); + newProgram = createProgram({ + rootNames: newProgramOrRootNames, + options: (hostOrOptions as CompilerOptions), + host: (oldProgramOrHost as CompilerHost), + oldProgram: oldProgram && oldProgram.getProgramOrUndefined(), + configFileParsingDiagnostics, + projectReferences + }); + host = (oldProgramOrHost as CompilerHost); + } + else { + newProgram = newProgramOrRootNames; + host = (hostOrOptions as BuilderProgramHost); + oldProgram = (oldProgramOrHost as BuilderProgram); + configFileParsingDiagnostics = (configFileParsingDiagnosticsOrOldProgram as readonly Diagnostic[]); + } + return { host, newProgram, oldProgram, configFileParsingDiagnostics: configFileParsingDiagnostics || emptyArray }; +} +/* @internal */ +export function createBuilderProgram(kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): SemanticDiagnosticsBuilderProgram; +/* @internal */ +export function createBuilderProgram(kind: BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): EmitAndSemanticDiagnosticsBuilderProgram; +/* @internal */ +export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, host, oldProgram, configFileParsingDiagnostics }: BuilderCreationParameters) { + // Return same program if underlying program doesnt change + let oldState = oldProgram && oldProgram.getState(); + if (oldState && newProgram === oldState.program && configFileParsingDiagnostics === newProgram.getConfigFileParsingDiagnostics()) { + newProgram = undefined!; // TODO: GH#18217 + oldState = undefined; + return oldProgram; + } + /** + * Create the canonical file name for identity + */ + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); + /** + * Computing hash to for signature verification + */ + const computeHash = host.createHash || generateDjb2Hash; + let state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState); + let backupState: BuilderProgramState | undefined; + newProgram.getProgramBuildInfo = () => getProgramBuildInfo(state, getCanonicalFileName); + // To ensure that we arent storing any references to old program or new program without state + newProgram = undefined!; // TODO: GH#18217 + oldProgram = undefined; + oldState = undefined; + const builderProgram = createRedirectedBuilderProgram(state, configFileParsingDiagnostics); + builderProgram.getState = () => state; + builderProgram.backupState = () => { + Debug.assert(backupState === undefined); + backupState = cloneBuilderProgramState(state); + }; + builderProgram.restoreState = () => { + state = Debug.assertDefined(backupState); + backupState = undefined; + }; + builderProgram.getAllDependencies = sourceFile => BuilderState.getAllDependencies(state, Debug.assertDefined(state.program), sourceFile); + builderProgram.getSemanticDiagnostics = getSemanticDiagnostics; + builderProgram.emit = emit; + builderProgram.releaseProgram = () => { + releaseCache(state); + backupState = undefined; + }; + if (kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram) { + (builderProgram as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile; + } + else if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { + (builderProgram as EmitAndSemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile; + (builderProgram as EmitAndSemanticDiagnosticsBuilderProgram).emitNextAffectedFile = emitNextAffectedFile; + } + else { + notImplemented(); + } + return builderProgram; + /** + * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files + */ + function emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult { + let affected = getNextAffectedFile(state, cancellationToken, computeHash); + let emitKind = BuilderFileEmit.Full; + let isPendingEmitFile = false; + if (!affected) { + if (!state.compilerOptions.out && !state.compilerOptions.outFile) { + const pendingAffectedFile = getNextAffectedFilePendingEmit(state); + if (!pendingAffectedFile) { + if (state.emittedBuildInfo) { return undefined; } - affected = program; + const affected = Debug.assertDefined(state.program); + return toAffectedFileEmitResult(state, + // When whole program is affected, do emit only once (eg when --out or --outFile is specified) + // Otherwise just affected file + affected.emitBuildInfo(writeFile || maybeBind(host, host.writeFile), cancellationToken), affected, + /*emitKind*/ BuilderFileEmit.Full, + /*isPendingEmitFile*/ false, + /*isBuildInfoEmit*/ true); } + ({ affectedFile: affected, emitKind } = pendingAffectedFile); + isPendingEmitFile = true; } - - return toAffectedFileEmitResult( - state, - // When whole program is affected, do emit only once (eg when --out or --outFile is specified) - // Otherwise just affected file - Debug.assertDefined(state.program).emit( - affected === state.program ? undefined : affected as SourceFile, - writeFile || maybeBind(host, host.writeFile), - cancellationToken, - emitOnlyDtsFiles || emitKind === BuilderFileEmit.DtsOnly, - customTransformers - ), - affected, - emitKind, - isPendingEmitFile, - ); - } - - /** - * Emits the JavaScript and declaration files. - * When targetSource file is specified, emits the files corresponding to that source file, - * otherwise for the whole program. - * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, - * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, - * it will only emit all the affected files instead of whole program - * - * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host - * in that order would be used to write the files - */ - function emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult { - if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { - assertSourceFileOkWithoutNextAffectedCall(state, targetSourceFile); - const result = handleNoEmitOptions(builderProgram, targetSourceFile, cancellationToken); - if (result) return result; - if (!targetSourceFile) { - // Emit and report any errors we ran into. - let sourceMaps: SourceMapEmitResult[] = []; - let emitSkipped = false; - let diagnostics: Diagnostic[] | undefined; - let emittedFiles: string[] = []; - - let affectedEmitResult: AffectedFileResult; - while (affectedEmitResult = emitNextAffectedFile(writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers)) { - emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped; - diagnostics = addRange(diagnostics, affectedEmitResult.result.diagnostics); - emittedFiles = addRange(emittedFiles, affectedEmitResult.result.emittedFiles); - sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps); - } - return { - emitSkipped, - diagnostics: diagnostics || emptyArray, - emittedFiles, - sourceMaps - }; + else { + const program = Debug.assertDefined(state.program); + // Check if program uses any prepend project references, if thats the case we cant track of the js files of those, so emit even though there are no changes + if (state.programEmitComplete || !some(program.getProjectReferences(), ref => !!ref.prepend)) { + state.programEmitComplete = true; + return undefined; } + affected = program; } - return Debug.assertDefined(state.program).emit(targetSourceFile, writeFile || maybeBind(host, host.writeFile), cancellationToken, emitOnlyDtsFiles, customTransformers); } - - /** - * Return the semantic diagnostics for the next affected file or undefined if iteration is complete - * If provided ignoreSourceFile would be called before getting the diagnostics and would ignore the sourceFile if the returned value was true - */ - function getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult { - while (true) { - const affected = getNextAffectedFile(state, cancellationToken, computeHash); - if (!affected) { - // Done - return undefined; - } - else if (affected === state.program) { - // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified) - return toAffectedFileResult( - state, - state.program.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken), - affected - ); - } - - // Add file to affected file pending emit to handle for later emit time - if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { - addToAffectedFilesPendingEmit(state, (affected as SourceFile).path, BuilderFileEmit.Full); - } - - // Get diagnostics for the affected file if its not ignored - if (ignoreSourceFile && ignoreSourceFile(affected as SourceFile)) { - // Get next affected file - doneWithAffectedFile(state, affected); - continue; + return toAffectedFileEmitResult(state, + // When whole program is affected, do emit only once (eg when --out or --outFile is specified) + // Otherwise just affected file + Debug.assertDefined(state.program).emit(affected === state.program ? undefined : affected as SourceFile, writeFile || maybeBind(host, host.writeFile), cancellationToken, emitOnlyDtsFiles || emitKind === BuilderFileEmit.DtsOnly, customTransformers), affected, emitKind, isPendingEmitFile); + } + /** + * Emits the JavaScript and declaration files. + * When targetSource file is specified, emits the files corresponding to that source file, + * otherwise for the whole program. + * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, + * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, + * it will only emit all the affected files instead of whole program + * + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files + */ + function emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult { + if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { + assertSourceFileOkWithoutNextAffectedCall(state, targetSourceFile); + const result = handleNoEmitOptions(builderProgram, targetSourceFile, cancellationToken); + if (result) + return result; + if (!targetSourceFile) { + // Emit and report any errors we ran into. + let sourceMaps: SourceMapEmitResult[] = []; + let emitSkipped = false; + let diagnostics: Diagnostic[] | undefined; + let emittedFiles: string[] = []; + let affectedEmitResult: AffectedFileResult; + while (affectedEmitResult = emitNextAffectedFile(writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers)) { + emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped; + diagnostics = addRange(diagnostics, affectedEmitResult.result.diagnostics); + emittedFiles = addRange(emittedFiles, affectedEmitResult.result.emittedFiles); + sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps); } - - return toAffectedFileResult( - state, - getSemanticDiagnosticsOfFile(state, affected as SourceFile, cancellationToken), - affected - ); + return { + emitSkipped, + diagnostics: diagnostics || emptyArray, + emittedFiles, + sourceMaps + }; } } - - /** - * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program - * The semantic diagnostics are cached and managed here - * Note that it is assumed that when asked about semantic diagnostics through this API, - * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics - * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, - * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics - */ - function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { - assertSourceFileOkWithoutNextAffectedCall(state, sourceFile); - const compilerOptions = Debug.assertDefined(state.program).getCompilerOptions(); - if (compilerOptions.outFile || compilerOptions.out) { - Debug.assert(!state.semanticDiagnosticsPerFile); - // We dont need to cache the diagnostics just return them from program - return Debug.assertDefined(state.program).getSemanticDiagnostics(sourceFile, cancellationToken); + return Debug.assertDefined(state.program).emit(targetSourceFile, writeFile || maybeBind(host, host.writeFile), cancellationToken, emitOnlyDtsFiles, customTransformers); + } + /** + * Return the semantic diagnostics for the next affected file or undefined if iteration is complete + * If provided ignoreSourceFile would be called before getting the diagnostics and would ignore the sourceFile if the returned value was true + */ + function getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult { + while (true) { + const affected = getNextAffectedFile(state, cancellationToken, computeHash); + if (!affected) { + // Done + return undefined; } - - if (sourceFile) { - return getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken); + else if (affected === state.program) { + // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified) + return toAffectedFileResult(state, state.program.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken), affected); } - - // When semantic builder asks for diagnostics of the whole program, - // ensure that all the affected files are handled - // eslint-disable-next-line no-empty - while (getSemanticDiagnosticsOfNextAffectedFile(cancellationToken)) { + // Add file to affected file pending emit to handle for later emit time + if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { + addToAffectedFilesPendingEmit(state, (affected as SourceFile).path, BuilderFileEmit.Full); } - - let diagnostics: Diagnostic[] | undefined; - for (const sourceFile of Debug.assertDefined(state.program).getSourceFiles()) { - diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken)); + // Get diagnostics for the affected file if its not ignored + if (ignoreSourceFile && ignoreSourceFile((affected as SourceFile))) { + // Get next affected file + doneWithAffectedFile(state, affected); + continue; } - return diagnostics || emptyArray; + return toAffectedFileResult(state, getSemanticDiagnosticsOfFile(state, (affected as SourceFile), cancellationToken), affected); } } - - function addToAffectedFilesPendingEmit(state: BuilderProgramState, affectedFilePendingEmit: Path, kind: BuilderFileEmit) { - if (!state.affectedFilesPendingEmit) state.affectedFilesPendingEmit = []; - if (!state.affectedFilesPendingEmitKind) state.affectedFilesPendingEmitKind = createMap(); - - const existingKind = state.affectedFilesPendingEmitKind.get(affectedFilePendingEmit); - state.affectedFilesPendingEmit.push(affectedFilePendingEmit); - state.affectedFilesPendingEmitKind.set(affectedFilePendingEmit, existingKind || kind); - - // affectedFilesPendingEmitIndex === undefined - // - means the emit state.affectedFilesPendingEmit was undefined before adding current affected files - // so start from 0 as array would be affectedFilesPendingEmit - // else, continue to iterate from existing index, the current set is appended to existing files - if (state.affectedFilesPendingEmitIndex === undefined) { - state.affectedFilesPendingEmitIndex = 0; + /** + * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program + * The semantic diagnostics are cached and managed here + * Note that it is assumed that when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics + * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, + * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics + */ + function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { + assertSourceFileOkWithoutNextAffectedCall(state, sourceFile); + const compilerOptions = Debug.assertDefined(state.program).getCompilerOptions(); + if (compilerOptions.outFile || compilerOptions.out) { + Debug.assert(!state.semanticDiagnosticsPerFile); + // We dont need to cache the diagnostics just return them from program + return Debug.assertDefined(state.program).getSemanticDiagnostics(sourceFile, cancellationToken); } - } - - function getMapOfReferencedSet(mapLike: MapLike | undefined, toPath: (path: string) => Path): ReadonlyMap | undefined { - if (!mapLike) return undefined; - const map = createMap(); - // Copies keys/values from template. Note that for..in will not throw if - // template is undefined, and instead will just exit the loop. - for (const key in mapLike) { - if (hasProperty(mapLike, key)) { - map.set(toPath(key), arrayToSet(mapLike[key], toPath)); - } + if (sourceFile) { + return getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken); } - return map; - } - - export function createBuildProgramUsingProgramBuildInfo(program: ProgramBuildInfo, buildInfoPath: string, host: ReadBuildProgramHost): EmitAndSemanticDiagnosticsBuilderProgram { - const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory())); - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); - - const fileInfos = createMap(); - for (const key in program.fileInfos) { - if (hasProperty(program.fileInfos, key)) { - fileInfos.set(toPath(key), program.fileInfos[key]); - } + // When semantic builder asks for diagnostics of the whole program, + // ensure that all the affected files are handled + // eslint-disable-next-line no-empty + while (getSemanticDiagnosticsOfNextAffectedFile(cancellationToken)) { } - - const state: ReusableBuilderProgramState = { - fileInfos, - compilerOptions: convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath), - referencedMap: getMapOfReferencedSet(program.referencedMap, toPath), - exportedModulesMap: getMapOfReferencedSet(program.exportedModulesMap, toPath), - semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => toPath(isString(value) ? value : value[0]), value => isString(value) ? emptyArray : value[1]), - hasReusableDiagnostic: true - }; - return { - getState: () => state, - backupState: noop, - restoreState: noop, - getProgram: notImplemented, - getProgramOrUndefined: returnUndefined, - releaseProgram: noop, - getCompilerOptions: () => state.compilerOptions, - getSourceFile: notImplemented, - getSourceFiles: notImplemented, - getOptionsDiagnostics: notImplemented, - getGlobalDiagnostics: notImplemented, - getConfigFileParsingDiagnostics: notImplemented, - getSyntacticDiagnostics: notImplemented, - getDeclarationDiagnostics: notImplemented, - getSemanticDiagnostics: notImplemented, - emit: notImplemented, - getAllDependencies: notImplemented, - getCurrentDirectory: notImplemented, - emitNextAffectedFile: notImplemented, - getSemanticDiagnosticsOfNextAffectedFile: notImplemented, - }; - - function toPath(path: string) { - return ts.toPath(path, buildInfoDirectory, getCanonicalFileName); + let diagnostics: Diagnostic[] | undefined; + for (const sourceFile of Debug.assertDefined(state.program).getSourceFiles()) { + diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken)); } - - function toAbsolutePath(path: string) { - return getNormalizedAbsolutePath(path, buildInfoDirectory); + return diagnostics || emptyArray; + } +} +/* @internal */ +function addToAffectedFilesPendingEmit(state: BuilderProgramState, affectedFilePendingEmit: Path, kind: BuilderFileEmit) { + if (!state.affectedFilesPendingEmit) + state.affectedFilesPendingEmit = []; + if (!state.affectedFilesPendingEmitKind) + state.affectedFilesPendingEmitKind = createMap(); + const existingKind = state.affectedFilesPendingEmitKind.get(affectedFilePendingEmit); + state.affectedFilesPendingEmit.push(affectedFilePendingEmit); + state.affectedFilesPendingEmitKind.set(affectedFilePendingEmit, existingKind || kind); + // affectedFilesPendingEmitIndex === undefined + // - means the emit state.affectedFilesPendingEmit was undefined before adding current affected files + // so start from 0 as array would be affectedFilesPendingEmit + // else, continue to iterate from existing index, the current set is appended to existing files + if (state.affectedFilesPendingEmitIndex === undefined) { + state.affectedFilesPendingEmitIndex = 0; + } +} +/* @internal */ +function getMapOfReferencedSet(mapLike: MapLike | undefined, toPath: (path: string) => Path): ts.ReadonlyMap | undefined { + if (!mapLike) + return undefined; + const map = createMap(); + // Copies keys/values from template. Note that for..in will not throw if + // template is undefined, and instead will just exit the loop. + for (const key in mapLike) { + if (hasProperty(mapLike, key)) { + map.set(toPath(key), arrayToSet(mapLike[key], toPath)); } } - - export function createRedirectedBuilderProgram(state: { program: Program | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: readonly Diagnostic[]): BuilderProgram { - return { - getState: notImplemented, - backupState: noop, - restoreState: noop, - getProgram, - getProgramOrUndefined: () => state.program, - releaseProgram: () => state.program = undefined, - getCompilerOptions: () => state.compilerOptions, - getSourceFile: fileName => getProgram().getSourceFile(fileName), - getSourceFiles: () => getProgram().getSourceFiles(), - getOptionsDiagnostics: cancellationToken => getProgram().getOptionsDiagnostics(cancellationToken), - getGlobalDiagnostics: cancellationToken => getProgram().getGlobalDiagnostics(cancellationToken), - getConfigFileParsingDiagnostics: () => configFileParsingDiagnostics, - getSyntacticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSyntacticDiagnostics(sourceFile, cancellationToken), - getDeclarationDiagnostics: (sourceFile, cancellationToken) => getProgram().getDeclarationDiagnostics(sourceFile, cancellationToken), - getSemanticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSemanticDiagnostics(sourceFile, cancellationToken), - emit: (sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers) => getProgram().emit(sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers), - getAllDependencies: notImplemented, - getCurrentDirectory: () => getProgram().getCurrentDirectory(), - }; - - function getProgram() { - return Debug.assertDefined(state.program); + return map; +} +/* @internal */ +export function createBuildProgramUsingProgramBuildInfo(program: ProgramBuildInfo, buildInfoPath: string, host: ReadBuildProgramHost): EmitAndSemanticDiagnosticsBuilderProgram { + const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory())); + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); + const fileInfos = createMap(); + for (const key in program.fileInfos) { + if (hasProperty(program.fileInfos, key)) { + fileInfos.set(toPath(key), program.fileInfos[key]); } } + const state: ReusableBuilderProgramState = { + fileInfos, + compilerOptions: convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath), + referencedMap: getMapOfReferencedSet(program.referencedMap, toPath), + exportedModulesMap: getMapOfReferencedSet(program.exportedModulesMap, toPath), + semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => toPath(isString(value) ? value : value[0]), value => isString(value) ? emptyArray : value[1]), + hasReusableDiagnostic: true + }; + return { + getState: () => state, + backupState: noop, + restoreState: noop, + getProgram: notImplemented, + getProgramOrUndefined: returnUndefined, + releaseProgram: noop, + getCompilerOptions: () => state.compilerOptions, + getSourceFile: notImplemented, + getSourceFiles: notImplemented, + getOptionsDiagnostics: notImplemented, + getGlobalDiagnostics: notImplemented, + getConfigFileParsingDiagnostics: notImplemented, + getSyntacticDiagnostics: notImplemented, + getDeclarationDiagnostics: notImplemented, + getSemanticDiagnostics: notImplemented, + emit: notImplemented, + getAllDependencies: notImplemented, + getCurrentDirectory: notImplemented, + emitNextAffectedFile: notImplemented, + getSemanticDiagnosticsOfNextAffectedFile: notImplemented, + }; + function toPath(path: string) { + return ts.toPath(path, buildInfoDirectory, getCanonicalFileName); + } + function toAbsolutePath(path: string) { + return getNormalizedAbsolutePath(path, buildInfoDirectory); + } +} +/* @internal */ +export function createRedirectedBuilderProgram(state: { + program: Program | undefined; + compilerOptions: CompilerOptions; +}, configFileParsingDiagnostics: readonly Diagnostic[]): BuilderProgram { + return { + getState: notImplemented, + backupState: noop, + restoreState: noop, + getProgram, + getProgramOrUndefined: () => state.program, + releaseProgram: () => state.program = undefined, + getCompilerOptions: () => state.compilerOptions, + getSourceFile: fileName => getProgram().getSourceFile(fileName), + getSourceFiles: () => getProgram().getSourceFiles(), + getOptionsDiagnostics: cancellationToken => getProgram().getOptionsDiagnostics(cancellationToken), + getGlobalDiagnostics: cancellationToken => getProgram().getGlobalDiagnostics(cancellationToken), + getConfigFileParsingDiagnostics: () => configFileParsingDiagnostics, + getSyntacticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSyntacticDiagnostics(sourceFile, cancellationToken), + getDeclarationDiagnostics: (sourceFile, cancellationToken) => getProgram().getDeclarationDiagnostics(sourceFile, cancellationToken), + getSemanticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSemanticDiagnostics(sourceFile, cancellationToken), + emit: (sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers) => getProgram().emit(sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers), + getAllDependencies: notImplemented, + getCurrentDirectory: () => getProgram().getCurrentDirectory(), + }; + function getProgram() { + return Debug.assertDefined(state.program); + } } diff --git a/src/compiler/builderPublic.ts b/src/compiler/builderPublic.ts index 72e02941731a4..8b4aef86da7aa 100644 --- a/src/compiler/builderPublic.ts +++ b/src/compiler/builderPublic.ts @@ -1,160 +1,154 @@ -namespace ts { - export type AffectedFileResult = { result: T; affected: SourceFile | Program; } | undefined; - - export interface BuilderProgramHost { - /** - * return true if file names are treated with case sensitivity - */ - useCaseSensitiveFileNames(): boolean; - /** - * If provided this would be used this hash instead of actual file shape text for detecting changes - */ - createHash?: (data: string) => string; - /** - * When emit or emitNextAffectedFile are called without writeFile, - * this callback if present would be used to write files - */ - writeFile?: WriteFileCallback; - } - - /** - * Builder to manage the program state changes - */ - export interface BuilderProgram { - /*@internal*/ - getState(): ReusableBuilderProgramState; - /*@internal*/ - backupState(): void; - /*@internal*/ - restoreState(): void; - /** - * Returns current program - */ - getProgram(): Program; - /** - * Returns current program that could be undefined if the program was released - */ - /*@internal*/ - getProgramOrUndefined(): Program | undefined; - /** - * Releases reference to the program, making all the other operations that need program to fail. - */ - /*@internal*/ - releaseProgram(): void; - /** - * Get compiler options of the program - */ - getCompilerOptions(): CompilerOptions; - /** - * Get the source file in the program with file name - */ - getSourceFile(fileName: string): SourceFile | undefined; - /** - * Get a list of files in the program - */ - getSourceFiles(): readonly SourceFile[]; - /** - * Get the diagnostics for compiler options - */ - getOptionsDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; - /** - * Get the diagnostics that dont belong to any file - */ - getGlobalDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; - /** - * Get the diagnostics from config file parsing - */ - getConfigFileParsingDiagnostics(): readonly Diagnostic[]; - /** - * Get the syntax diagnostics, for all source files if source file is not supplied - */ - getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; - /** - * Get the declaration diagnostics, for all source files if source file is not supplied - */ - getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[]; - /** - * Get all the dependencies of the file - */ - getAllDependencies(sourceFile: SourceFile): readonly string[]; - - /** - * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program - * The semantic diagnostics are cached and managed here - * Note that it is assumed that when asked about semantic diagnostics through this API, - * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics - * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, - * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics - */ - getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; - /** - * Emits the JavaScript and declaration files. - * When targetSource file is specified, emits the files corresponding to that source file, - * otherwise for the whole program. - * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, - * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, - * it will only emit all the affected files instead of whole program - * - * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host - * in that order would be used to write the files - */ - emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; - /** - * Get the current directory of the program - */ - getCurrentDirectory(): string; - } - - /** - * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files - */ - export interface SemanticDiagnosticsBuilderProgram extends BuilderProgram { - /** - * Gets the semantic diagnostics from the program for the next affected file and caches it - * Returns undefined if the iteration is complete - */ - getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult; - } - - /** - * The builder that can handle the changes in program and iterate through changed file to emit the files - * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files - */ - export interface EmitAndSemanticDiagnosticsBuilderProgram extends SemanticDiagnosticsBuilderProgram { - /** - * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete - * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host - * in that order would be used to write the files - */ - emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult; - } - - /** - * Create the builder to manage semantic diagnostics and cache them - */ - export function createSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): SemanticDiagnosticsBuilderProgram; - export function createSemanticDiagnosticsBuilderProgram(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): SemanticDiagnosticsBuilderProgram; - export function createSemanticDiagnosticsBuilderProgram(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | SemanticDiagnosticsBuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]) { - return createBuilderProgram(BuilderProgramKind.SemanticDiagnosticsBuilderProgram, getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences)); - } - - /** - * Create the builder that can handle the changes in program and iterate through changed files - * to emit the those files and manage semantic diagnostics cache as well - */ - export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): EmitAndSemanticDiagnosticsBuilderProgram; - export function createEmitAndSemanticDiagnosticsBuilderProgram(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): EmitAndSemanticDiagnosticsBuilderProgram; - export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]) { - return createBuilderProgram(BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences)); - } - - /** - * Creates a builder thats just abstraction over program and can be used with watch - */ - export function createAbstractBuilder(newProgram: Program, host: BuilderProgramHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): BuilderProgram; - export function createAbstractBuilder(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderProgram; - export function createAbstractBuilder(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | BuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderProgram { - const { newProgram, configFileParsingDiagnostics: newConfigFileParsingDiagnostics } = getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences); - return createRedirectedBuilderProgram({ program: newProgram, compilerOptions: newProgram.getCompilerOptions() }, newConfigFileParsingDiagnostics); - } +import { SourceFile, Program, WriteFileCallback, ReusableBuilderProgramState, CompilerOptions, CancellationToken, Diagnostic, DiagnosticWithLocation, CustomTransformers, EmitResult, CompilerHost, ProjectReference, createBuilderProgram, BuilderProgramKind, getBuilderCreationParameters, createRedirectedBuilderProgram } from "./ts"; +export type AffectedFileResult = { + result: T; + affected: SourceFile | Program; +} | undefined; +export interface BuilderProgramHost { + /** + * return true if file names are treated with case sensitivity + */ + useCaseSensitiveFileNames(): boolean; + /** + * If provided this would be used this hash instead of actual file shape text for detecting changes + */ + createHash?: (data: string) => string; + /** + * When emit or emitNextAffectedFile are called without writeFile, + * this callback if present would be used to write files + */ + writeFile?: WriteFileCallback; +} +/** + * Builder to manage the program state changes + */ +export interface BuilderProgram { + /*@internal*/ + getState(): ReusableBuilderProgramState; + /*@internal*/ + backupState(): void; + /*@internal*/ + restoreState(): void; + /** + * Returns current program + */ + getProgram(): Program; + /** + * Returns current program that could be undefined if the program was released + */ + /*@internal*/ + getProgramOrUndefined(): Program | undefined; + /** + * Releases reference to the program, making all the other operations that need program to fail. + */ + /*@internal*/ + releaseProgram(): void; + /** + * Get compiler options of the program + */ + getCompilerOptions(): CompilerOptions; + /** + * Get the source file in the program with file name + */ + getSourceFile(fileName: string): SourceFile | undefined; + /** + * Get a list of files in the program + */ + getSourceFiles(): readonly SourceFile[]; + /** + * Get the diagnostics for compiler options + */ + getOptionsDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; + /** + * Get the diagnostics that dont belong to any file + */ + getGlobalDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; + /** + * Get the diagnostics from config file parsing + */ + getConfigFileParsingDiagnostics(): readonly Diagnostic[]; + /** + * Get the syntax diagnostics, for all source files if source file is not supplied + */ + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; + /** + * Get the declaration diagnostics, for all source files if source file is not supplied + */ + getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[]; + /** + * Get all the dependencies of the file + */ + getAllDependencies(sourceFile: SourceFile): readonly string[]; + /** + * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program + * The semantic diagnostics are cached and managed here + * Note that it is assumed that when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics + * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, + * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics + */ + getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; + /** + * Emits the JavaScript and declaration files. + * When targetSource file is specified, emits the files corresponding to that source file, + * otherwise for the whole program. + * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, + * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, + * it will only emit all the affected files instead of whole program + * + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files + */ + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; + /** + * Get the current directory of the program + */ + getCurrentDirectory(): string; +} +/** + * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files + */ +export interface SemanticDiagnosticsBuilderProgram extends BuilderProgram { + /** + * Gets the semantic diagnostics from the program for the next affected file and caches it + * Returns undefined if the iteration is complete + */ + getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult; +} +/** + * The builder that can handle the changes in program and iterate through changed file to emit the files + * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files + */ +export interface EmitAndSemanticDiagnosticsBuilderProgram extends SemanticDiagnosticsBuilderProgram { + /** + * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files + */ + emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult; +} +/** + * Create the builder to manage semantic diagnostics and cache them + */ +export function createSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): SemanticDiagnosticsBuilderProgram; +export function createSemanticDiagnosticsBuilderProgram(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): SemanticDiagnosticsBuilderProgram; +export function createSemanticDiagnosticsBuilderProgram(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | SemanticDiagnosticsBuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]) { + return createBuilderProgram(BuilderProgramKind.SemanticDiagnosticsBuilderProgram, getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences)); +} +/** + * Create the builder that can handle the changes in program and iterate through changed files + * to emit the those files and manage semantic diagnostics cache as well + */ +export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): EmitAndSemanticDiagnosticsBuilderProgram; +export function createEmitAndSemanticDiagnosticsBuilderProgram(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): EmitAndSemanticDiagnosticsBuilderProgram; +export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]) { + return createBuilderProgram(BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences)); +} +/** + * Creates a builder thats just abstraction over program and can be used with watch + */ +export function createAbstractBuilder(newProgram: Program, host: BuilderProgramHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): BuilderProgram; +export function createAbstractBuilder(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderProgram; +export function createAbstractBuilder(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | BuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderProgram { + const { newProgram, configFileParsingDiagnostics: newConfigFileParsingDiagnostics } = getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences); + return createRedirectedBuilderProgram({ program: newProgram, compilerOptions: newProgram.getCompilerOptions() }, newConfigFileParsingDiagnostics); } diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index bb2272cff8e58..5bdd63d5c3fb2 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -1,566 +1,510 @@ +import { Program, SourceFile, CancellationToken, CustomTransformers, EmitOutput, OutputFile, Symbol, getSourceFileOfNode, TypeChecker, StringLiteralLike, Path, GetCanonicalFileName, toPath, getDirectoryPath, isStringLiteral, createMap, ModuleKind, Debug, cloneMap, emptyArray, fileExtensionIs, Extension, getAnyExtensionFromPath, ExportedModulesFromDeclarationEmit, arrayFrom, mapDefinedIterator, isModuleWithStringLiteralName, some, isGlobalScopeAugmentation, ModuleDeclaration, isExternalModule } from "./ts"; +import * as ts from "./ts"; /*@internal*/ -namespace ts { - export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, - cancellationToken?: CancellationToken, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitOutput { - const outputFiles: OutputFile[] = []; - const emitResult = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers, forceDtsEmit); - return { outputFiles, emitSkipped: emitResult.emitSkipped, exportedModulesFromDeclarationEmit: emitResult.exportedModulesFromDeclarationEmit }; - - function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) { - outputFiles.push({ name: fileName, writeByteOrderMark, text }); +export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitOutput { + const outputFiles: OutputFile[] = []; + const emitResult = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers, forceDtsEmit); + return { outputFiles, emitSkipped: emitResult.emitSkipped, exportedModulesFromDeclarationEmit: emitResult.exportedModulesFromDeclarationEmit }; + function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) { + outputFiles.push({ name: fileName, writeByteOrderMark, text }); + } +} +/* @internal */ +export interface ReusableBuilderState { + /** + * Information of the file eg. its version, signature etc + */ + fileInfos: ts.ReadonlyMap; + /** + * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled + * Otherwise undefined + * Thus non undefined value indicates, module emit + */ + readonly referencedMap?: ts.ReadonlyMap | undefined; + /** + * Contains the map of exported modules ReferencedSet=exported module files from the file if module emit is enabled + * Otherwise undefined + */ + readonly exportedModulesMap?: ts.ReadonlyMap | undefined; +} +/* @internal */ +export interface BuilderState { + /** + * Information of the file eg. its version, signature etc + */ + fileInfos: ts.Map; + /** + * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled + * Otherwise undefined + * Thus non undefined value indicates, module emit + */ + readonly referencedMap: ts.ReadonlyMap | undefined; + /** + * Contains the map of exported modules ReferencedSet=exported module files from the file if module emit is enabled + * Otherwise undefined + */ + readonly exportedModulesMap: ts.Map | undefined; + /** + * Map of files that have already called update signature. + * That means hence forth these files are assumed to have + * no change in their signature for this version of the program + */ + hasCalledUpdateShapeSignature: ts.Map; + /** + * Cache of all files excluding default library file for the current program + */ + allFilesExcludingDefaultLibraryFile?: readonly SourceFile[]; + /** + * Cache of all the file names + */ + allFileNames?: readonly string[]; +} +/* @internal */ +export namespace BuilderState { + /** + * Information about the source file: Its version and optional signature from last emit + */ + export interface FileInfo { + readonly version: string; + signature: string | undefined; + } + /** + * Referenced files with values for the keys as referenced file's path to be true + */ + export type ReferencedSet = ts.ReadonlyMap; + /** + * Compute the hash to store the shape of the file + */ + export type ComputeHash = (data: string) => string; + /** + * Exported modules to from declaration emit being computed. + * This can contain false in the affected file path to specify that there are no exported module(types from other modules) for this file + */ + export type ComputingExportedModulesMap = ts.Map; + /** + * Get the referencedFile from the imported module symbol + */ + function getReferencedFileFromImportedModuleSymbol(symbol: Symbol) { + if (symbol.declarations && symbol.declarations[0]) { + const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]); + return declarationSourceFile && declarationSourceFile.resolvedPath; } } - - export interface ReusableBuilderState { - /** - * Information of the file eg. its version, signature etc - */ - fileInfos: ReadonlyMap; - /** - * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled - * Otherwise undefined - * Thus non undefined value indicates, module emit - */ - readonly referencedMap?: ReadonlyMap | undefined; - /** - * Contains the map of exported modules ReferencedSet=exported module files from the file if module emit is enabled - * Otherwise undefined - */ - readonly exportedModulesMap?: ReadonlyMap | undefined; + /** + * Get the referencedFile from the import name node from file + */ + function getReferencedFileFromImportLiteral(checker: TypeChecker, importName: StringLiteralLike) { + const symbol = checker.getSymbolAtLocation(importName); + return symbol && getReferencedFileFromImportedModuleSymbol(symbol); } - - export interface BuilderState { - /** - * Information of the file eg. its version, signature etc - */ - fileInfos: Map; - /** - * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled - * Otherwise undefined - * Thus non undefined value indicates, module emit - */ - readonly referencedMap: ReadonlyMap | undefined; - /** - * Contains the map of exported modules ReferencedSet=exported module files from the file if module emit is enabled - * Otherwise undefined - */ - readonly exportedModulesMap: Map | undefined; - /** - * Map of files that have already called update signature. - * That means hence forth these files are assumed to have - * no change in their signature for this version of the program - */ - hasCalledUpdateShapeSignature: Map; - /** - * Cache of all files excluding default library file for the current program - */ - allFilesExcludingDefaultLibraryFile?: readonly SourceFile[]; - /** - * Cache of all the file names - */ - allFileNames?: readonly string[]; + /** + * Gets the path to reference file from file name, it could be resolvedPath if present otherwise path + */ + function getReferencedFileFromFileName(program: Program, fileName: string, sourceFileDirectory: Path, getCanonicalFileName: GetCanonicalFileName): Path { + return toPath(program.getProjectReferenceRedirect(fileName) || fileName, sourceFileDirectory, getCanonicalFileName); } - - export namespace BuilderState { - /** - * Information about the source file: Its version and optional signature from last emit - */ - export interface FileInfo { - readonly version: string; - signature: string | undefined; - } - /** - * Referenced files with values for the keys as referenced file's path to be true - */ - export type ReferencedSet = ReadonlyMap; - /** - * Compute the hash to store the shape of the file - */ - export type ComputeHash = (data: string) => string; - - /** - * Exported modules to from declaration emit being computed. - * This can contain false in the affected file path to specify that there are no exported module(types from other modules) for this file - */ - export type ComputingExportedModulesMap = Map; - - /** - * Get the referencedFile from the imported module symbol - */ - function getReferencedFileFromImportedModuleSymbol(symbol: Symbol) { - if (symbol.declarations && symbol.declarations[0]) { - const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]); - return declarationSourceFile && declarationSourceFile.resolvedPath; + /** + * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true + */ + function getReferencedFiles(program: Program, sourceFile: SourceFile, getCanonicalFileName: GetCanonicalFileName): ts.Map | undefined { + let referencedFiles: ts.Map | undefined; + // We need to use a set here since the code can contain the same import twice, + // but that will only be one dependency. + // To avoid invernal conversion, the key of the referencedFiles map must be of type Path + if (sourceFile.imports && sourceFile.imports.length > 0) { + const checker: TypeChecker = program.getTypeChecker(); + for (const importName of sourceFile.imports) { + const declarationSourceFilePath = getReferencedFileFromImportLiteral(checker, importName); + if (declarationSourceFilePath) { + addReferencedFile(declarationSourceFilePath); + } } } - - /** - * Get the referencedFile from the import name node from file - */ - function getReferencedFileFromImportLiteral(checker: TypeChecker, importName: StringLiteralLike) { - const symbol = checker.getSymbolAtLocation(importName); - return symbol && getReferencedFileFromImportedModuleSymbol(symbol); - } - - /** - * Gets the path to reference file from file name, it could be resolvedPath if present otherwise path - */ - function getReferencedFileFromFileName(program: Program, fileName: string, sourceFileDirectory: Path, getCanonicalFileName: GetCanonicalFileName): Path { - return toPath(program.getProjectReferenceRedirect(fileName) || fileName, sourceFileDirectory, getCanonicalFileName); - } - - /** - * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true - */ - function getReferencedFiles(program: Program, sourceFile: SourceFile, getCanonicalFileName: GetCanonicalFileName): Map | undefined { - let referencedFiles: Map | undefined; - - // We need to use a set here since the code can contain the same import twice, - // but that will only be one dependency. - // To avoid invernal conversion, the key of the referencedFiles map must be of type Path - if (sourceFile.imports && sourceFile.imports.length > 0) { - const checker: TypeChecker = program.getTypeChecker(); - for (const importName of sourceFile.imports) { - const declarationSourceFilePath = getReferencedFileFromImportLiteral(checker, importName); - if (declarationSourceFilePath) { - addReferencedFile(declarationSourceFilePath); - } - } + const sourceFileDirectory = getDirectoryPath(sourceFile.path); + // Handle triple slash references + if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { + for (const referencedFile of sourceFile.referencedFiles) { + const referencedPath = getReferencedFileFromFileName(program, referencedFile.fileName, sourceFileDirectory, getCanonicalFileName); + addReferencedFile(referencedPath); } - - const sourceFileDirectory = getDirectoryPath(sourceFile.path); - // Handle triple slash references - if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { - for (const referencedFile of sourceFile.referencedFiles) { - const referencedPath = getReferencedFileFromFileName(program, referencedFile.fileName, sourceFileDirectory, getCanonicalFileName); - addReferencedFile(referencedPath); + } + // Handle type reference directives + if (sourceFile.resolvedTypeReferenceDirectiveNames) { + sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => { + if (!resolvedTypeReferenceDirective) { + return; } - } - - // Handle type reference directives - if (sourceFile.resolvedTypeReferenceDirectiveNames) { - sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => { - if (!resolvedTypeReferenceDirective) { - return; - } - - const fileName = resolvedTypeReferenceDirective.resolvedFileName!; // TODO: GH#18217 - const typeFilePath = getReferencedFileFromFileName(program, fileName, sourceFileDirectory, getCanonicalFileName); - addReferencedFile(typeFilePath); - }); - } - - // Add module augmentation as references - if (sourceFile.moduleAugmentations.length) { - const checker = program.getTypeChecker(); - for (const moduleName of sourceFile.moduleAugmentations) { - if (!isStringLiteral(moduleName)) { continue; } - const symbol = checker.getSymbolAtLocation(moduleName); - if (!symbol) { continue; } - - // Add any file other than our own as reference - addReferenceFromAmbientModule(symbol); + const fileName = resolvedTypeReferenceDirective.resolvedFileName!; // TODO: GH#18217 + const typeFilePath = getReferencedFileFromFileName(program, fileName, sourceFileDirectory, getCanonicalFileName); + addReferencedFile(typeFilePath); + }); + } + // Add module augmentation as references + if (sourceFile.moduleAugmentations.length) { + const checker = program.getTypeChecker(); + for (const moduleName of sourceFile.moduleAugmentations) { + if (!isStringLiteral(moduleName)) { + continue; } - } - - // From ambient modules - for (const ambientModule of program.getTypeChecker().getAmbientModules()) { - if (ambientModule.declarations.length > 1) { - addReferenceFromAmbientModule(ambientModule); + const symbol = checker.getSymbolAtLocation(moduleName); + if (!symbol) { + continue; } - } - - return referencedFiles; - - function addReferenceFromAmbientModule(symbol: Symbol) { // Add any file other than our own as reference - for (const declaration of symbol.declarations) { - const declarationSourceFile = getSourceFileOfNode(declaration); - if (declarationSourceFile && - declarationSourceFile !== sourceFile) { - addReferencedFile(declarationSourceFile.resolvedPath); - } - } + addReferenceFromAmbientModule(symbol); + } + } + // From ambient modules + for (const ambientModule of program.getTypeChecker().getAmbientModules()) { + if (ambientModule.declarations.length > 1) { + addReferenceFromAmbientModule(ambientModule); } - - function addReferencedFile(referencedPath: Path) { - if (!referencedFiles) { - referencedFiles = createMap(); + } + return referencedFiles; + function addReferenceFromAmbientModule(symbol: Symbol) { + // Add any file other than our own as reference + for (const declaration of symbol.declarations) { + const declarationSourceFile = getSourceFileOfNode(declaration); + if (declarationSourceFile && + declarationSourceFile !== sourceFile) { + addReferencedFile(declarationSourceFile.resolvedPath); } - referencedFiles.set(referencedPath, true); } } - - /** - * Returns true if oldState is reusable, that is the emitKind = module/non module has not changed - */ - export function canReuseOldState(newReferencedMap: ReadonlyMap | undefined, oldState: Readonly | undefined) { - return oldState && !oldState.referencedMap === !newReferencedMap; + function addReferencedFile(referencedPath: Path) { + if (!referencedFiles) { + referencedFiles = createMap(); + } + referencedFiles.set(referencedPath, true); } - - /** - * Creates the state of file references and signature for the new program from oldState if it is safe - */ - export function create(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly): BuilderState { - const fileInfos = createMap(); - const referencedMap = newProgram.getCompilerOptions().module !== ModuleKind.None ? createMap() : undefined; - const exportedModulesMap = referencedMap ? createMap() : undefined; - const hasCalledUpdateShapeSignature = createMap(); - const useOldState = canReuseOldState(referencedMap, oldState); - - // Create the reference map, and set the file infos - for (const sourceFile of newProgram.getSourceFiles()) { - const version = Debug.assertDefined(sourceFile.version, "Program intended to be used with Builder should have source files with versions set"); - const oldInfo = useOldState ? oldState!.fileInfos.get(sourceFile.path) : undefined; - if (referencedMap) { - const newReferences = getReferencedFiles(newProgram, sourceFile, getCanonicalFileName); - if (newReferences) { - referencedMap.set(sourceFile.path, newReferences); - } - // Copy old visible to outside files map - if (useOldState) { - const exportedModules = oldState!.exportedModulesMap!.get(sourceFile.path); - if (exportedModules) { - exportedModulesMap!.set(sourceFile.path, exportedModules); - } + } + /** + * Returns true if oldState is reusable, that is the emitKind = module/non module has not changed + */ + export function canReuseOldState(newReferencedMap: ts.ReadonlyMap | undefined, oldState: Readonly | undefined) { + return oldState && !oldState.referencedMap === !newReferencedMap; + } + /** + * Creates the state of file references and signature for the new program from oldState if it is safe + */ + export function create(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly): BuilderState { + const fileInfos = createMap(); + const referencedMap = newProgram.getCompilerOptions().module !== ModuleKind.None ? createMap() : undefined; + const exportedModulesMap = referencedMap ? createMap() : undefined; + const hasCalledUpdateShapeSignature = createMap(); + const useOldState = canReuseOldState(referencedMap, oldState); + // Create the reference map, and set the file infos + for (const sourceFile of newProgram.getSourceFiles()) { + const version = Debug.assertDefined(sourceFile.version, "Program intended to be used with Builder should have source files with versions set"); + const oldInfo = useOldState ? oldState!.fileInfos.get(sourceFile.path) : undefined; + if (referencedMap) { + const newReferences = getReferencedFiles(newProgram, sourceFile, getCanonicalFileName); + if (newReferences) { + referencedMap.set(sourceFile.path, newReferences); + } + // Copy old visible to outside files map + if (useOldState) { + const exportedModules = oldState!.exportedModulesMap!.get(sourceFile.path); + if (exportedModules) { + exportedModulesMap!.set(sourceFile.path, exportedModules); } } - fileInfos.set(sourceFile.path, { version, signature: oldInfo && oldInfo.signature }); } - - return { - fileInfos, - referencedMap, - exportedModulesMap, - hasCalledUpdateShapeSignature - }; + fileInfos.set(sourceFile.path, { version, signature: oldInfo && oldInfo.signature }); } - - /** - * Releases needed properties - */ - export function releaseCache(state: BuilderState) { - state.allFilesExcludingDefaultLibraryFile = undefined; - state.allFileNames = undefined; + return { + fileInfos, + referencedMap, + exportedModulesMap, + hasCalledUpdateShapeSignature + }; + } + /** + * Releases needed properties + */ + export function releaseCache(state: BuilderState) { + state.allFilesExcludingDefaultLibraryFile = undefined; + state.allFileNames = undefined; + } + /** + * Creates a clone of the state + */ + export function clone(state: Readonly): BuilderState { + const fileInfos = createMap(); + state.fileInfos.forEach((value, key) => { + fileInfos.set(key, { ...value }); + }); + // Dont need to backup allFiles info since its cache anyway + return { + fileInfos, + referencedMap: cloneMapOrUndefined(state.referencedMap), + exportedModulesMap: cloneMapOrUndefined(state.exportedModulesMap), + hasCalledUpdateShapeSignature: cloneMap(state.hasCalledUpdateShapeSignature), + }; + } + /** + * Gets the files affected by the path from the program + */ + export function getFilesAffectedBy(state: BuilderState, programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, cacheToUpdateSignature?: ts.Map, exportedModulesMapCache?: ComputingExportedModulesMap): readonly SourceFile[] { + // Since the operation could be cancelled, the signatures are always stored in the cache + // They will be committed once it is safe to use them + // eg when calling this api from tsserver, if there is no cancellation of the operation + // In the other cases the affected files signatures are committed only after the iteration through the result is complete + const signatureCache = cacheToUpdateSignature || createMap(); + const sourceFile = programOfThisState.getSourceFileByPath(path); + if (!sourceFile) { + return emptyArray; } - - /** - * Creates a clone of the state - */ - export function clone(state: Readonly): BuilderState { - const fileInfos = createMap(); - state.fileInfos.forEach((value, key) => { - fileInfos.set(key, { ...value }); - }); - // Dont need to backup allFiles info since its cache anyway - return { - fileInfos, - referencedMap: cloneMapOrUndefined(state.referencedMap), - exportedModulesMap: cloneMapOrUndefined(state.exportedModulesMap), - hasCalledUpdateShapeSignature: cloneMap(state.hasCalledUpdateShapeSignature), - }; + if (!updateShapeSignature(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash, exportedModulesMapCache)) { + return [sourceFile]; } - - /** - * Gets the files affected by the path from the program - */ - export function getFilesAffectedBy(state: BuilderState, programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, cacheToUpdateSignature?: Map, exportedModulesMapCache?: ComputingExportedModulesMap): readonly SourceFile[] { - // Since the operation could be cancelled, the signatures are always stored in the cache - // They will be committed once it is safe to use them - // eg when calling this api from tsserver, if there is no cancellation of the operation - // In the other cases the affected files signatures are committed only after the iteration through the result is complete - const signatureCache = cacheToUpdateSignature || createMap(); - const sourceFile = programOfThisState.getSourceFileByPath(path); - if (!sourceFile) { - return emptyArray; - } - - if (!updateShapeSignature(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash, exportedModulesMapCache)) { - return [sourceFile]; - } - - const result = (state.referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit)(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash, exportedModulesMapCache); - if (!cacheToUpdateSignature) { - // Commit all the signatures in the signature cache - updateSignaturesFromCache(state, signatureCache); - } - return result; + const result = (state.referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit)(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash, exportedModulesMapCache); + if (!cacheToUpdateSignature) { + // Commit all the signatures in the signature cache + updateSignaturesFromCache(state, signatureCache); } - - /** - * Updates the signatures from the cache into state's fileinfo signatures - * This should be called whenever it is safe to commit the state of the builder - */ - export function updateSignaturesFromCache(state: BuilderState, signatureCache: Map) { - signatureCache.forEach((signature, path) => { - state.fileInfos.get(path)!.signature = signature; - state.hasCalledUpdateShapeSignature.set(path, true); - }); + return result; + } + /** + * Updates the signatures from the cache into state's fileinfo signatures + * This should be called whenever it is safe to commit the state of the builder + */ + export function updateSignaturesFromCache(state: BuilderState, signatureCache: ts.Map) { + signatureCache.forEach((signature, path) => { + state.fileInfos.get(path)!.signature = signature; + state.hasCalledUpdateShapeSignature.set(path, true); + }); + } + /** + * Returns if the shape of the signature has changed since last emit + */ + export function updateShapeSignature(state: Readonly, programOfThisState: Program, sourceFile: SourceFile, cacheToUpdateSignature: ts.Map, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, exportedModulesMapCache?: ComputingExportedModulesMap) { + Debug.assert(!!sourceFile); + Debug.assert(!exportedModulesMapCache || !!state.exportedModulesMap, "Compute visible to outside map only if visibleToOutsideReferencedMap present in the state"); + // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate + if (state.hasCalledUpdateShapeSignature.has(sourceFile.path) || cacheToUpdateSignature.has(sourceFile.path)) { + return false; } - - /** - * Returns if the shape of the signature has changed since last emit - */ - export function updateShapeSignature(state: Readonly, programOfThisState: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, exportedModulesMapCache?: ComputingExportedModulesMap) { - Debug.assert(!!sourceFile); - Debug.assert(!exportedModulesMapCache || !!state.exportedModulesMap, "Compute visible to outside map only if visibleToOutsideReferencedMap present in the state"); - - // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate - if (state.hasCalledUpdateShapeSignature.has(sourceFile.path) || cacheToUpdateSignature.has(sourceFile.path)) { - return false; + const info = state.fileInfos.get(sourceFile.path); + if (!info) + return Debug.fail(); + const prevSignature = info.signature; + let latestSignature: string; + if (sourceFile.isDeclarationFile) { + latestSignature = sourceFile.version; + if (exportedModulesMapCache && latestSignature !== prevSignature) { + // All the references in this file are exported + const references = state.referencedMap ? state.referencedMap.get(sourceFile.path) : undefined; + exportedModulesMapCache.set(sourceFile.path, references || false); } - - const info = state.fileInfos.get(sourceFile.path); - if (!info) return Debug.fail(); - - const prevSignature = info.signature; - let latestSignature: string; - if (sourceFile.isDeclarationFile) { - latestSignature = sourceFile.version; + } + else { + const emitOutput = getFileEmitOutput(programOfThisState, sourceFile, + /*emitOnlyDtsFiles*/ true, cancellationToken, + /*customTransformers*/ undefined, + /*forceDtsEmit*/ true); + const firstDts = emitOutput.outputFiles && + programOfThisState.getCompilerOptions().declarationMap ? + emitOutput.outputFiles.length > 1 ? emitOutput.outputFiles[1] : undefined : + emitOutput.outputFiles.length > 0 ? emitOutput.outputFiles[0] : undefined; + if (firstDts) { + Debug.assert(fileExtensionIs(firstDts.name, Extension.Dts), "File extension for signature expected to be dts", () => `Found: ${getAnyExtensionFromPath(firstDts.name)} for ${firstDts.name}:: All output files: ${JSON.stringify(emitOutput.outputFiles.map(f => f.name))}`); + latestSignature = computeHash(firstDts.text); if (exportedModulesMapCache && latestSignature !== prevSignature) { - // All the references in this file are exported - const references = state.referencedMap ? state.referencedMap.get(sourceFile.path) : undefined; - exportedModulesMapCache.set(sourceFile.path, references || false); + updateExportedModules(sourceFile, emitOutput.exportedModulesFromDeclarationEmit, exportedModulesMapCache); } } else { - const emitOutput = getFileEmitOutput( - programOfThisState, - sourceFile, - /*emitOnlyDtsFiles*/ true, - cancellationToken, - /*customTransformers*/ undefined, - /*forceDtsEmit*/ true - ); - const firstDts = emitOutput.outputFiles && - programOfThisState.getCompilerOptions().declarationMap ? - emitOutput.outputFiles.length > 1 ? emitOutput.outputFiles[1] : undefined : - emitOutput.outputFiles.length > 0 ? emitOutput.outputFiles[0] : undefined; - if (firstDts) { - Debug.assert(fileExtensionIs(firstDts.name, Extension.Dts), "File extension for signature expected to be dts", () => `Found: ${getAnyExtensionFromPath(firstDts.name)} for ${firstDts.name}:: All output files: ${JSON.stringify(emitOutput.outputFiles.map(f => f.name))}`); - latestSignature = computeHash(firstDts.text); - if (exportedModulesMapCache && latestSignature !== prevSignature) { - updateExportedModules(sourceFile, emitOutput.exportedModulesFromDeclarationEmit, exportedModulesMapCache); - } - } - else { - latestSignature = prevSignature!; // TODO: GH#18217 - } - + latestSignature = prevSignature!; // TODO: GH#18217 } - cacheToUpdateSignature.set(sourceFile.path, latestSignature); - - return !prevSignature || latestSignature !== prevSignature; } - - /** - * Coverts the declaration emit result into exported modules map - */ - function updateExportedModules(sourceFile: SourceFile, exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined, exportedModulesMapCache: ComputingExportedModulesMap) { - if (!exportedModulesFromDeclarationEmit) { - exportedModulesMapCache.set(sourceFile.path, false); - return; - } - - let exportedModules: Map | undefined; - exportedModulesFromDeclarationEmit.forEach(symbol => addExportedModule(getReferencedFileFromImportedModuleSymbol(symbol))); - exportedModulesMapCache.set(sourceFile.path, exportedModules || false); - - function addExportedModule(exportedModulePath: Path | undefined) { - if (exportedModulePath) { - if (!exportedModules) { - exportedModules = createMap(); - } - exportedModules.set(exportedModulePath, true); - } - } + cacheToUpdateSignature.set(sourceFile.path, latestSignature); + return !prevSignature || latestSignature !== prevSignature; + } + /** + * Coverts the declaration emit result into exported modules map + */ + function updateExportedModules(sourceFile: SourceFile, exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined, exportedModulesMapCache: ComputingExportedModulesMap) { + if (!exportedModulesFromDeclarationEmit) { + exportedModulesMapCache.set(sourceFile.path, false); + return; } - - /** - * Updates the exported modules from cache into state's exported modules map - * This should be called whenever it is safe to commit the state of the builder - */ - export function updateExportedFilesMapFromCache(state: BuilderState, exportedModulesMapCache: ComputingExportedModulesMap | undefined) { - if (exportedModulesMapCache) { - Debug.assert(!!state.exportedModulesMap); - exportedModulesMapCache.forEach((exportedModules, path) => { - if (exportedModules) { - state.exportedModulesMap!.set(path, exportedModules); - } - else { - state.exportedModulesMap!.delete(path); - } - }); + let exportedModules: ts.Map | undefined; + exportedModulesFromDeclarationEmit.forEach(symbol => addExportedModule(getReferencedFileFromImportedModuleSymbol(symbol))); + exportedModulesMapCache.set(sourceFile.path, exportedModules || false); + function addExportedModule(exportedModulePath: Path | undefined) { + if (exportedModulePath) { + if (!exportedModules) { + exportedModules = createMap(); + } + exportedModules.set(exportedModulePath, true); } } - - /** - * Get all the dependencies of the sourceFile - */ - export function getAllDependencies(state: BuilderState, programOfThisState: Program, sourceFile: SourceFile): readonly string[] { - const compilerOptions = programOfThisState.getCompilerOptions(); - // With --out or --outFile all outputs go into single file, all files depend on each other - if (compilerOptions.outFile || compilerOptions.out) { - return getAllFileNames(state, programOfThisState); - } - - // If this is non module emit, or its a global file, it depends on all the source files - if (!state.referencedMap || isFileAffectingGlobalScope(sourceFile)) { - return getAllFileNames(state, programOfThisState); - } - - // Get the references, traversing deep from the referenceMap - const seenMap = createMap(); - const queue = [sourceFile.path]; - while (queue.length) { - const path = queue.pop()!; - if (!seenMap.has(path)) { - seenMap.set(path, true); - const references = state.referencedMap.get(path); - if (references) { - const iterator = references.keys(); - for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) { - queue.push(iterResult.value as Path); - } - } + } + /** + * Updates the exported modules from cache into state's exported modules map + * This should be called whenever it is safe to commit the state of the builder + */ + export function updateExportedFilesMapFromCache(state: BuilderState, exportedModulesMapCache: ComputingExportedModulesMap | undefined) { + if (exportedModulesMapCache) { + Debug.assert(!!state.exportedModulesMap); + exportedModulesMapCache.forEach((exportedModules, path) => { + if (exportedModules) { + state.exportedModulesMap!.set(path, exportedModules); } - } - - return arrayFrom(mapDefinedIterator(seenMap.keys(), path => { - const file = programOfThisState.getSourceFileByPath(path as Path); - return file ? file.fileName : path; - })); + else { + state.exportedModulesMap!.delete(path); + } + }); } - - /** - * Gets the names of all files from the program - */ - function getAllFileNames(state: BuilderState, programOfThisState: Program): readonly string[] { - if (!state.allFileNames) { - const sourceFiles = programOfThisState.getSourceFiles(); - state.allFileNames = sourceFiles === emptyArray ? emptyArray : sourceFiles.map(file => file.fileName); - } - return state.allFileNames; + } + /** + * Get all the dependencies of the sourceFile + */ + export function getAllDependencies(state: BuilderState, programOfThisState: Program, sourceFile: SourceFile): readonly string[] { + const compilerOptions = programOfThisState.getCompilerOptions(); + // With --out or --outFile all outputs go into single file, all files depend on each other + if (compilerOptions.outFile || compilerOptions.out) { + return getAllFileNames(state, programOfThisState); } - - /** - * Gets the files referenced by the the file path - */ - export function getReferencedByPaths(state: Readonly, referencedFilePath: Path) { - return arrayFrom(mapDefinedIterator(state.referencedMap!.entries(), ([filePath, referencesInFile]) => - referencesInFile.has(referencedFilePath) ? filePath as Path : undefined - )); + // If this is non module emit, or its a global file, it depends on all the source files + if (!state.referencedMap || isFileAffectingGlobalScope(sourceFile)) { + return getAllFileNames(state, programOfThisState); } - - /** - * For script files that contains only ambient external modules, although they are not actually external module files, - * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, - * there are no point to rebuild all script files if these special files have changed. However, if any statement - * in the file is not ambient external module, we treat it as a regular script file. - */ - function containsOnlyAmbientModules(sourceFile: SourceFile) { - for (const statement of sourceFile.statements) { - if (!isModuleWithStringLiteralName(statement)) { - return false; + // Get the references, traversing deep from the referenceMap + const seenMap = createMap(); + const queue = [sourceFile.path]; + while (queue.length) { + const path = queue.pop()!; + if (!seenMap.has(path)) { + seenMap.set(path, true); + const references = state.referencedMap.get(path); + if (references) { + const iterator = references.keys(); + for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) { + queue.push((iterResult.value as Path)); + } } } - return true; - } - - /** - * Return true if file contains anything that augments to global scope we need to build them as if - * they are global files as well as module - */ - function containsGlobalScopeAugmentation(sourceFile: SourceFile) { - return some(sourceFile.moduleAugmentations, augmentation => isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)); } - - /** - * Return true if the file will invalidate all files because it affectes global scope - */ - function isFileAffectingGlobalScope(sourceFile: SourceFile) { - return containsGlobalScopeAugmentation(sourceFile) || - !isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile); + return arrayFrom(mapDefinedIterator(seenMap.keys(), path => { + const file = programOfThisState.getSourceFileByPath((path as Path)); + return file ? file.fileName : path; + })); + } + /** + * Gets the names of all files from the program + */ + function getAllFileNames(state: BuilderState, programOfThisState: Program): readonly string[] { + if (!state.allFileNames) { + const sourceFiles = programOfThisState.getSourceFiles(); + state.allFileNames = sourceFiles === emptyArray ? emptyArray : sourceFiles.map(file => file.fileName); } - - /** - * Gets all files of the program excluding the default library file - */ - function getAllFilesExcludingDefaultLibraryFile(state: BuilderState, programOfThisState: Program, firstSourceFile: SourceFile): readonly SourceFile[] { - // Use cached result - if (state.allFilesExcludingDefaultLibraryFile) { - return state.allFilesExcludingDefaultLibraryFile; - } - - let result: SourceFile[] | undefined; - addSourceFile(firstSourceFile); - for (const sourceFile of programOfThisState.getSourceFiles()) { - if (sourceFile !== firstSourceFile) { - addSourceFile(sourceFile); - } + return state.allFileNames; + } + /** + * Gets the files referenced by the the file path + */ + export function getReferencedByPaths(state: Readonly, referencedFilePath: Path) { + return arrayFrom(mapDefinedIterator(state.referencedMap!.entries(), ([filePath, referencesInFile]) => referencesInFile.has(referencedFilePath) ? filePath as Path : undefined)); + } + /** + * For script files that contains only ambient external modules, although they are not actually external module files, + * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, + * there are no point to rebuild all script files if these special files have changed. However, if any statement + * in the file is not ambient external module, we treat it as a regular script file. + */ + function containsOnlyAmbientModules(sourceFile: SourceFile) { + for (const statement of sourceFile.statements) { + if (!isModuleWithStringLiteralName(statement)) { + return false; } - state.allFilesExcludingDefaultLibraryFile = result || emptyArray; + } + return true; + } + /** + * Return true if file contains anything that augments to global scope we need to build them as if + * they are global files as well as module + */ + function containsGlobalScopeAugmentation(sourceFile: SourceFile) { + return some(sourceFile.moduleAugmentations, augmentation => isGlobalScopeAugmentation((augmentation.parent as ModuleDeclaration))); + } + /** + * Return true if the file will invalidate all files because it affectes global scope + */ + function isFileAffectingGlobalScope(sourceFile: SourceFile) { + return containsGlobalScopeAugmentation(sourceFile) || + !isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile); + } + /** + * Gets all files of the program excluding the default library file + */ + function getAllFilesExcludingDefaultLibraryFile(state: BuilderState, programOfThisState: Program, firstSourceFile: SourceFile): readonly SourceFile[] { + // Use cached result + if (state.allFilesExcludingDefaultLibraryFile) { return state.allFilesExcludingDefaultLibraryFile; - - function addSourceFile(sourceFile: SourceFile) { - if (!programOfThisState.isSourceFileDefaultLibrary(sourceFile)) { - (result || (result = [])).push(sourceFile); - } + } + let result: SourceFile[] | undefined; + addSourceFile(firstSourceFile); + for (const sourceFile of programOfThisState.getSourceFiles()) { + if (sourceFile !== firstSourceFile) { + addSourceFile(sourceFile); } } - - /** - * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed - */ - function getFilesAffectedByUpdatedShapeWhenNonModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { - const compilerOptions = programOfThisState.getCompilerOptions(); - // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, - // so returning the file itself is good enough. - if (compilerOptions && (compilerOptions.out || compilerOptions.outFile)) { - return [sourceFileWithUpdatedShape]; + state.allFilesExcludingDefaultLibraryFile = result || emptyArray; + return state.allFilesExcludingDefaultLibraryFile; + function addSourceFile(sourceFile: SourceFile) { + if (!programOfThisState.isSourceFileDefaultLibrary(sourceFile)) { + (result || (result = [])).push(sourceFile); } + } + } + /** + * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed + */ + function getFilesAffectedByUpdatedShapeWhenNonModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { + const compilerOptions = programOfThisState.getCompilerOptions(); + // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, + // so returning the file itself is good enough. + if (compilerOptions && (compilerOptions.out || compilerOptions.outFile)) { + return [sourceFileWithUpdatedShape]; + } + return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); + } + /** + * When program emits modular code, gets the files affected by the sourceFile whose shape has changed + */ + function getFilesAffectedByUpdatedShapeWhenModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: ts.Map, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash | undefined, exportedModulesMapCache: ComputingExportedModulesMap | undefined) { + if (isFileAffectingGlobalScope(sourceFileWithUpdatedShape)) { return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); } - - /** - * When program emits modular code, gets the files affected by the sourceFile whose shape has changed - */ - function getFilesAffectedByUpdatedShapeWhenModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash | undefined, exportedModulesMapCache: ComputingExportedModulesMap | undefined) { - if (isFileAffectingGlobalScope(sourceFileWithUpdatedShape)) { - return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); - } - - const compilerOptions = programOfThisState.getCompilerOptions(); - if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) { - return [sourceFileWithUpdatedShape]; - } - - // Now we need to if each file in the referencedBy list has a shape change as well. - // Because if so, its own referencedBy files need to be saved as well to make the - // emitting result consistent with files on disk. - const seenFileNamesMap = createMap(); - - // Start with the paths this file was referenced by - seenFileNamesMap.set(sourceFileWithUpdatedShape.path, sourceFileWithUpdatedShape); - const queue = getReferencedByPaths(state, sourceFileWithUpdatedShape.resolvedPath); - while (queue.length > 0) { - const currentPath = queue.pop()!; - if (!seenFileNamesMap.has(currentPath)) { - const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath)!; - seenFileNamesMap.set(currentPath, currentSourceFile); - if (currentSourceFile && updateShapeSignature(state, programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken, computeHash!, exportedModulesMapCache)) { // TODO: GH#18217 - queue.push(...getReferencedByPaths(state, currentSourceFile.resolvedPath)); - } + const compilerOptions = programOfThisState.getCompilerOptions(); + if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) { + return [sourceFileWithUpdatedShape]; + } + // Now we need to if each file in the referencedBy list has a shape change as well. + // Because if so, its own referencedBy files need to be saved as well to make the + // emitting result consistent with files on disk. + const seenFileNamesMap = createMap(); + // Start with the paths this file was referenced by + seenFileNamesMap.set(sourceFileWithUpdatedShape.path, sourceFileWithUpdatedShape); + const queue = getReferencedByPaths(state, sourceFileWithUpdatedShape.resolvedPath); + while (queue.length > 0) { + const currentPath = queue.pop()!; + if (!seenFileNamesMap.has(currentPath)) { + const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath)!; + seenFileNamesMap.set(currentPath, currentSourceFile); + if (currentSourceFile && updateShapeSignature(state, programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken, computeHash!, exportedModulesMapCache)) { // TODO: GH#18217 + queue.push(...getReferencedByPaths(state, currentSourceFile.resolvedPath)); } } - - // Return array of values that needs emit - // Return array of values that needs emit - return arrayFrom(mapDefinedIterator(seenFileNamesMap.values(), value => value)); } + // Return array of values that needs emit + // Return array of values that needs emit + return arrayFrom(mapDefinedIterator(seenFileNamesMap.values(), value => value)); } - - export function cloneMapOrUndefined(map: ReadonlyMap | undefined) { - return map ? cloneMap(map) : undefined; - } +} +/* @internal */ +export function cloneMapOrUndefined(map: ts.ReadonlyMap | undefined) { + return map ? cloneMap(map) : undefined; } diff --git a/src/compiler/builderStatePublic.ts b/src/compiler/builderStatePublic.ts index 2e70063f90a7f..357ac6952590d 100644 --- a/src/compiler/builderStatePublic.ts +++ b/src/compiler/builderStatePublic.ts @@ -1,13 +1,11 @@ -namespace ts { - export interface EmitOutput { - outputFiles: OutputFile[]; - emitSkipped: boolean; - /* @internal */ exportedModulesFromDeclarationEmit?: ExportedModulesFromDeclarationEmit; - } - - export interface OutputFile { - name: string; - writeByteOrderMark: boolean; - text: string; - } -} \ No newline at end of file +import { ExportedModulesFromDeclarationEmit } from "./ts"; +export interface EmitOutput { + outputFiles: OutputFile[]; + emitSkipped: boolean; + /* @internal */ exportedModulesFromDeclarationEmit?: ExportedModulesFromDeclarationEmit; +} +export interface OutputFile { + name: string; + writeByteOrderMark: boolean; + text: string; +} diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7e7235914f8ae..46f92e08b75d7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1,36344 +1,32919 @@ +import { GenericType, Type, Node, DiagnosticMessage, createMapFromTemplate, Symbol, Signature, DiagnosticWithLocation, and, ModuleDeclaration, getModuleInstanceState, ModuleInstanceState, TypeCheckerHost, TypeChecker, memoize, createMap, forEachEntry, CancellationToken, ExternalEmitHelpers, objectAllocator, createSymbolTable, identity, getEmitScriptTarget, getEmitModuleKind, getAllowSyntheticDefaultImports, getStrictOptionValue, ObjectFlags, SymbolFlags, __String, CheckFlags, sum, getParseTreeNode, isParameter, Debug, escapeLeadingUnderscores, isTypeNode, isExportSpecifier, isAssignmentPattern, isIdentifier, Expression, ContextFlags, isExpression, isObjectLiteralElementLike, isCallLikeExpression, isJsxAttributeLike, isPropertyAccessOrQualifiedNameOrImportTypeNode, isPropertyAccessExpression, isFunctionLike, createGetSymbolWalker, getFirstIdentifier, TypeFlags, TypeParameter, unescapeLeadingUnderscores, skipTypeChecking, emptyArray, NodeCheckFlags, addRange, containsParseError, NodeFlags, DiagnosticCategory, CallLikeExpression, UnionType, LiteralType, IndexedAccessType, SubstitutionType, EvolvingArrayType, UnderscoreEscapedMap, InternalSymbolName, FreshableIntrinsicType, ObjectType, TypeReference, TypePredicateKind, SignatureFlags, IterationTypes, Diagnostics, SourceFile, PatternAmbientModule, FlowNode, SymbolLinks, NodeLinks, FlowType, createDiagnosticCollection, EntityName, RelationComparisonResult, getSourceFileOfNode, isArray, parseIsolatedEntityName, Diagnostic, createDiagnosticForNode, createCompilerDiagnostic, DiagnosticMessageChain, createDiagnosticForNodeFromMessageChain, addRelatedInfo, TransientSymbol, cloneMap, isAssignmentDeclaration, isEffectiveModuleDeclaration, getNameOfDeclaration, comparePaths, Comparison, getOrUpdate, pushIfUnique, getExpandoInitializer, getNameOfExpando, forEach, length, SymbolTable, hasEntries, StringLiteral, Identifier, isGlobalScopeAugmentation, some, Declaration, SyntaxKind, isExternalOrCommonJsModule, getCheckFlags, ParameterDeclaration, getAncestor, BindingElement, findAncestor, isBindingElement, VariableDeclaration, isClassDeclaration, isComputedPropertyName, isPropertyDeclaration, ExportAssignment, getEnclosingBlockScopeContainer, isForInOrOfStatement, PropertyDeclaration, hasModifier, ModifierFlags, getContainingClass, FunctionLikeDeclaration, ScriptTarget, ConditionalTypeNode, isModuleDeclaration, getLocalSymbolForExportDefault, getDeclarationOfKind, isSourceFile, isJSDocTypeAlias, ClassLikeDeclaration, InterfaceDeclaration, ClassExpression, ExpressionWithTypeArguments, HeritageClause, isClassLike, FunctionExpression, isClassElement, getJSDocHost, getRootDeclaration, isInJSFile, isRequireCall, declarationNameToString, every, isNamespaceExportDeclaration, isTypeQueryNode, isFunctionLikeDeclaration, FunctionLike, ArrowFunction, getImmediatelyInvokedFunctionExpression, isString, isJSDocTemplateTag, find, JSDoc, getThisContainer, InterfaceType, getTextOfNode, EntityNameExpression, isEntityNameExpression, isQualifiedName, isBlockOrCatchScoped, AnyImportSyntax, ImportEqualsDeclaration, ImportClause, NamespaceImport, ImportSpecifier, isAliasSymbolDeclaration, getExternalModuleImportEqualsDeclarationExpression, isExportAssignment, isSourceFileJS, isShorthandAmbientModuleSymbol, ModuleKind, deduplicate, concatenate, equateValues, ImportDeclaration, ExportDeclaration, ImportOrExportSpecifier, NamespaceExportDeclaration, ExportSpecifier, BinaryExpression, isClassExpression, isEntityName, PropertyAssignment, PropertyAccessExpression, isBinaryExpression, ShorthandPropertyAssignment, isInternalModuleImportEqualsDeclaration, isRightSideOfQualifiedNameOrPropertyAccess, QualifiedName, SymbolFormatFlags, EntityNameOrEntityNameExpression, nodeIsMissing, isVariableDeclaration, CallExpression, TypeReferenceNode, isJSDocNode, isExpressionStatement, getAssignmentDeclarationKind, AssignmentDeclarationKind, isObjectLiteralMethod, isPropertyAssignment, getHostSignatureFromJSDocHost, getAssignedExpandoInitializer, hasOnlyExpressionInitializer, getDeclaredExpandoInitializer, isStringLiteralLike, startsWith, removePrefix, getResolvedModule, getResolutionDiagnostic, resolutionExtensionIsTSOrJson, findBestPatternMatch, tryExtractTSExtension, removeExtension, fileExtensionIs, Extension, getEmitModuleResolutionKind, ModuleResolutionKind, hasJsonModuleEmitEnabled, ResolvedModuleFull, isExternalModuleNameRelative, chainDiagnosticMessages, mangleScopedPackageName, getTypesPackageName, isImportDeclaration, getNamespaceDeclarationNode, isImportCall, SignatureKind, StructuredType, getObjectFlags, nodeIsSynthesized, append, isExternalModule, mapDefined, isAmbientModule, isAccessExpression, isModuleExportsAccessExpression, isExportsIdentifier, ConstructorDeclaration, nodeIsPresent, IntrinsicType, arrayFrom, CharacterCodes, IndexInfo, ResolvedType, isUMDExportSymbol, isExternalModuleImportEqualsDeclaration, SymbolAccessibility, SymbolAccessibilityResult, first, isObjectLiteralExpression, isModuleWithStringLiteralName, SymbolVisibilityResult, LateVisibilityPaintedStatement, filter, isVariableStatement, isLateVisibilityPaintedStatement, appendIfUnique, isExpressionWithTypeArgumentsInClassExtendsClause, EmitTextWriter, NodeBuilderFlags, usingSingleLineStringWriter, createPrinter, EmitHint, TypeFormatFlags, getTrailingSemicolonDeferringWriter, createTextWriter, defaultMaximumTruncationLength, SymbolTracker, IndexKind, noop, Program, maybeBind, TypeNode, createKeywordTypeNode, ImportTypeNode, createTypeReferenceNode, symbolName, StringLiteralType, createLiteralTypeNode, setEmitFlags, createLiteral, EmitFlags, NumberLiteralType, createPrefix, pseudoBigIntToString, BigIntLiteralType, createTrue, createFalse, createTypeOperatorNode, createThis, createIdentifier, contains, createInferTypeNode, idText, IntersectionType, createUnionOrIntersectionTypeNode, IndexType, createIndexedAccessTypeNode, ConditionalType, createConditionalTypeNode, MappedType, ReadonlyToken, PlusToken, MinusToken, createToken, QuestionToken, createMappedTypeNode, createTypeLiteralNode, FunctionTypeNode, ConstructorTypeNode, createArrayTypeNode, TupleType, createRestTypeNode, createOptionalTypeNode, createTupleTypeNode, rangeEquals, isImportTypeNode, createQualifiedName, TypeElement, createPropertySignature, CallSignatureDeclaration, ConstructSignatureDeclaration, IndexSignatureDeclaration, getDeclarationModifierFlagsFromSymbol, isElementAccessExpression, isPropertyAccessEntityNameExpression, MethodSignature, JSDocPropertyTag, setSyntheticLeadingComments, setCommentRange, getNameFromIndexInfo, createParameter, createIndexSignature, SignatureDeclaration, TypeParameterDeclaration, createThisTypeNode, createTypePredicateNodeWithModifier, createSignatureDeclaration, createTypeParameterDeclaration, JSDocParameterTag, getSynthesizedClone, isRestParameter, BindingName, visitEachChild, nullTransformationContext, pathIsRelative, NodeArray, createNodeArray, map, IndexedAccessTypeNode, isIndexedAccessTypeNode, getNonAugmentationDeclaration, getOriginalNode, createImportTypeNode, createTypeQueryNode, isSingleOrDoubleQuote, isIdentifierStart, createPropertyAccess, createElementAccess, isStringLiteral, isKnownSymbol, createComputedPropertyName, isIdentifierText, UniqueESSymbolType, Statement, ClassElement, createProperty, DeclarationStatement, isModuleBlock, getModifierFlags, createExportDeclaration, createNamedExports, flatMap, createExportSpecifier, nodeHasName, isExportDeclaration, group, isExternalModuleIndicator, hasScopeMarker, needsScopeMarker, createEmptyExports, isEnumDeclaration, isFunctionDeclaration, isExternalModuleAugmentation, isInterfaceDeclaration, createModifiersFromModifierFlags, isStringANonContextualKeyword, createExportAssignment, isParameterDeclaration, isJsonSourceFile, createTypeAliasDeclaration, createHeritageClause, createInterfaceDeclaration, arrayToMultiMap, createModuleBlock, createModuleDeclaration, createEnumDeclaration, isEnumMember, EnumMember, createEnumMember, isVariableDeclarationList, setTextRange, createVariableStatement, createVariableDeclarationList, createVariableDeclaration, FunctionDeclaration, NamespaceDeclaration, createClassDeclaration, createImportEqualsDeclaration, createExternalModuleReference, createNamespaceExportDeclaration, createImportDeclaration, createImportClause, createNamespaceImport, createNamedImports, createImportSpecifier, getExportAssignmentExpression, getPropertyAssignmentAliasLikeExpression, isStringAKeyword, Decorator, Modifier, PropertyName, AccessorDeclaration, or, isAccessor, isPropertySignature, createSetAccessor, isSetAccessor, createGetAccessor, isGetAccessor, MethodDeclaration, getEffectiveTypeAnnotationNode, visitNode, getMutableClone, isJSDocAllType, isJSDocUnknownType, isJSDocNullableType, createUnionTypeNode, isJSDocOptionalType, isJSDocNonNullableType, isExpressionWithTypeArguments, isTypeReferenceNode, isJSDocIndexSignature, isJSDocFunctionType, isJSDocConstructSignature, createConstructorTypeNode, visitNodes, createFunctionTypeNode, isLiteralImportTypeNode, updateImportTypeNode, updateLiteralTypeNode, createGetCanonicalFileName, getResolvedExternalModuleName, createExpressionWithTypeArguments, stripQuotes, TypePredicate, escapeString, firstDefined, isCallExpression, isBindableObjectDefinePropertyCall, isBindingPattern, BindingPattern, getCombinedModifierFlags, JSDocEnumTag, BindingElementGrandparent, isStringOrNumericLiteralLike, ElementAccessExpression, createNode, LeftHandSideExpression, ArrayLiteralExpression, TupleTypeReference, walkUpBindingElementsAndPatterns, UnionReduction, getJSDocType, skipParentheses, PropertySignature, getCombinedNodeFlags, isFunctionTypeNode, isJsxAttribute, getJSDocTypeTag, getAssignmentDeclarationPropertyAccessKind, BindableObjectDefinePropertyCall, copyEntries, isPrototypePropertyAssignment, ObjectBindingPattern, lastOrUndefined, isOmittedExpression, findLastIndex, VariableLikeDeclaration, isCatchClauseVariableDeclarationOrBindingElement, isBindableStaticElementAccessExpression, isJSDocPropertyLikeTag, isNumericLiteral, isMethodDeclaration, isMethodSignature, isShorthandPropertyAssignment, getEffectiveReturnTypeNode, getEffectiveSetAccessorTypeAnnotationNode, getDeclarationOfExpando, HasInitializer, ReverseMappedSymbol, MappedTypeNode, getEffectiveTypeParameterDeclarations, DeclarationWithTypeParameters, isTypeAlias, TypeAliasDeclaration, JSDocTypedefTag, JSDocCallbackTag, getEffectiveBaseTypeNode, sameMap, BaseType, resolvingEmptyArray, getInterfaceBaseTypeNodes, isJSDocEnumTag, PrefixUnaryExpression, EnumKind, EnumDeclaration, ArrayTypeNode, getEffectiveConstraintOfTypeParameter, hasInitializer, TypeMapper, InterfaceTypeWithDeclaredMembers, DeclarationName, LateBoundName, LateBoundDeclaration, LateBoundBinaryExpressionDeclaration, hasDynamicName, isDynamicName, getMembersOfDeclaration, hasStaticModifier, JSDocSignature, countWhere, AnonymousType, ReverseMappedType, TypeOperatorNode, UnionOrIntersectionType, ObjectLiteralExpression, JsxAttributes, ObjectLiteralElementLike, JsxAttributeLike, InstantiableType, isNodeDescendantOf, isTypeParameterDeclaration, getJSDocParameterTags, hasQuestionToken, isJSDocParameterTag, isValueSignatureDeclaration, hasJSDocParameterTags, ClassDeclaration, hasRestParameter, isJSDocSignature, getJSDocTags, isJSDocVariadicType, isExpressionNode, NamedDeclaration, nodeStartsNewLexicalEnvironment, isPartOfTypeNode, forEachChild, isTypePredicateNode, TypePredicateNode, findIndex, cast, isIndexSignatureDeclaration, getHostSignatureFromJSDoc, TupleTypeNode, DeferredTypeReference, NodeWithTypeArguments, isJSDocAugmentsTag, TypeReferenceType, TypeVariable, isStatement, JSDocNullableType, TypeQueryNode, isTypeOperatorNode, OptionalTypeNode, binarySearch, compareValues, orderedRemoveItemAt, UnionTypeNode, reduceLeft, replaceElement, IntersectionTypeNode, BigIntLiteral, parsePseudoBigInt, walkUpParenthesizedTypes, ArrayBindingPattern, ComputedPropertyName, NumericLiteral, SyntheticExpression, getPropertyNameForKnownSymbolName, isPropertyName, getPropertyNameForPropertyNameNode, isAssignmentTarget, getAssignmentTargetKind, AssignmentKind, isDeleteTarget, ConditionalRoot, InferenceFlags, InferencePriority, InferTypeNode, isJSDocTypeLiteral, isParenthesizedTypeNode, createUnderscoreEscapedMap, PseudoBigInt, LiteralTypeNode, isValidESSymbolDeclaration, isConstructorDeclaration, isFunctionExpression, ThisExpression, ThisTypeNode, JSDocOptionalType, ParenthesizedTypeNode, JSDocTypeReferencingNode, JSDocTypeExpression, RestTypeNode, JSDocVariadicType, InferenceContext, getParameterSymbolFromJSDoc, JsxChild, ConditionalExpression, ParenthesizedExpression, isJsxOpeningElement, JsxAttribute, JsxExpression, firstOrUndefined, parameterIsThisKeyword, Ternary, isBlock, isJsxSpreadAttribute, JsxElement, isJsxText, isJsxElement, formatMessage, isSpreadAssignment, TypeComparer, isIdentifierTypePredicate, IdentifierTypePredicate, FreshableType, DiagnosticRelatedInformation, concatenateDiagnosticMessageChains, FreshObjectLiteralType, isJsxAttributes, isJsxOpeningLikeElement, VarianceFlags, cartesianProduct, isAbstractConstructorType, getSelectedModifierFlags, OptionalChain, isOutermostOptionalChain, isExpressionOfOptionalChainRoot, isOptionalChain, WideningContext, isCheckJsEnabledForFile, isCallSignatureDeclaration, isTypeNodeKind, InferenceInfo, isWriteOnlyAccess, NonNullExpression, AccessExpression, ForOfStatement, SpreadElement, CaseClause, DefaultClause, SwitchStatement, IncompleteType, isPushOrUnshiftIdentifier, Block, ModuleBlock, isFunctionOrModuleBlock, getSpanOfTokenAtPosition, createFileDiagnostic, FlowFlags, PreFinallyFlow, FlowAssignment, FlowCondition, FlowArrayMutation, FlowCall, FlowLabel, FlowSwitchClause, AfterFinallyFlow, FlowStart, isVarConst, TypeOfExpression, LiteralExpression, isCallChain, getContainingFunction, nodeIsDecorated, isObjectLiteralOrClassExpressionMethod, ForStatement, isIterationStatement, isForStatement, PostfixUnaryExpression, SuperCall, isSuperCall, getClassExtendsHeritageElement, getThisParameter, getJSDocClassTag, JSDocFunctionType, getJSDocThisTag, getSuperContainer, isSuperProperty, getFunctionFlags, FunctionFlags, AwaitExpression, YieldExpression, hasTypeArguments, TemplateExpression, TaggedTemplateExpression, isDefaultedExpandoInitializer, getElementOrPropertyAccessName, JsxSpreadAttribute, NewExpression, isConstTypeReference, AssertionExpression, indexOfNode, JsxOpeningLikeElement, JsxReferenceKind, isInJsonFile, getJSDocEnumTag, isWellKnownSymbolSyntactically, JsxSelfClosingElement, JsxFragment, JsxEmit, stringContains, JsxTagNameExpression, isIntrinsicJsxName, JsxClosingElement, JsxFlags, JsxOpeningFragment, isThisProperty, getClassLikeDeclarationOfSymbol, getTextOfIdentifierOrLiteral, PropertyAccessChain, isCallOrNewExpression, tryGetPropertyAccessOrIdentifierToString, getSpellingSuggestion, ForInStatement, VariableDeclarationList, ElementAccessChain, JsxOpeningElement, isTaggedTemplateExpression, last, isOptionalChainRoot, skipOuterExpressions, getErrorSpanForNode, elementAt, createDiagnosticForNodeArray, flatten, minAndMax, isLineBreak, skipTrivia, isPrototypeAccess, getInitializerOfBinaryExpression, walkUpParenthesizedExpressions, isDottedName, ImportCall, SyntheticDefaultModuleType, UnaryExpression, MetaProperty, getNewTargetContainer, forEachYieldExpression, forEachReturnStatement, OuterExpressionKinds, DeleteExpression, VoidExpression, tokenToString, isAssignmentOperator, isJSDocTypedefTag, isJsxSelfClosingElement, HasExpressionInitializer, getEffectiveInitializer, isDeclarationReadonly, isAssertionExpression, isParenthesizedExpression, isArrayLiteralExpression, isSpreadElement, CallChain, TypeAssertion, StringLiteralLike, isParameterPropertyDeclaration, TypeLiteralNode, ExpressionStatement, isPrologueDirective, isInJSDoc, tryCast, isTypeReferenceType, UnionOrIntersectionTypeNode, getEscapedTextOfIdentifierOrLiteral, PromiseOrAwaitableType, getEntityNameFromTypeNode, entityNameToString, getRestParameterElementType, nodeCanBeDecorated, getFirstConstructorWithBody, JSDocTemplateTag, JSDocTypeTag, findLast, JSDocAugmentsTag, CaseBlock, NodeSet, DeclarationWithTypeParameterChildren, rangeOfNode, rangeOfTypeParameters, isObjectBindingPattern, isArrayBindingPattern, isVariableLike, VariableStatement, IfStatement, DoStatement, WhileStatement, ForInOrOfStatement, AwaitKeywordToken, IterableOrIteratorType, BreakOrContinueStatement, ReturnStatement, WithStatement, CaseOrDefaultClause, LabeledStatement, ThrowStatement, TryStatement, forEachKey, getClassImplementsHeritageClauseElements, getTextOfPropertyName, isEnumConst, isLiteralExpression, getExternalModuleName, hasModifiers, getEmitDeclarations, JSDocContainer, isJSDocTypeExpression, clear, relativeComplement, compareDiagnostics, introducesArgumentsExoticObject, getCombinedLocalAndExportSymbolFlags, isDeclarationName, PropertyAccessEntityNameExpression, getTypeParameterFromJsDoc, isJSXTagName, isImportOrExportSpecifier, isLiteralComputedPropertyDeclarationName, isInExpressionContext, isLiteralTypeNode, tryGetClassImplementingOrExtendingExpressionWithTypeArguments, isDeclaration, AssignmentPattern, singleElementArray, isGeneratedIdentifier, isModuleOrEnumDeclaration, isStatementWithLocals, isBlockScopedContainerTopLevel, isImportEqualsDeclaration, TypeReferenceSerializationKind, isVariableLikeOrAccessor, KeywordTypeNode, EmitResolver, AllAccessorDeclarations, isGetOrSetAccessorDeclaration, SetAccessorDeclaration, GetAccessorDeclaration, resolveTripleslashReference, AnyImportOrReExport, bindSourceFile, isEffectiveExternalModule, externalHelpersModuleNameText, getAllAccessorDeclarations, modifierToFlag, findUseStrictPrologue, isArrowFunction, getLineAndCharacterOfPosition, ExclamationToken, isCommaSequence, getSetAccessorValueParameter, isVariableDeclarationInVariableStatement, isLet, getJSDocTypeParameterDeclarations, TokenFlags, isChildOfNodeWithKind, isPrefixUnaryExpression, textSpanEnd } from "./ts"; +import { countPathComponents, getModuleSpecifiers } from "./ts.moduleSpecifiers"; +import { mark, measure } from "./ts.performance"; +import * as ts from "./ts"; /* @internal */ -namespace ts { - const ambientModuleSymbolRegex = /^".+"$/; - - let nextSymbolId = 1; - let nextNodeId = 1; - let nextMergeId = 1; - let nextFlowId = 1; - - const enum IterationUse { - AllowsSyncIterablesFlag = 1 << 0, - AllowsAsyncIterablesFlag = 1 << 1, - AllowsStringInputFlag = 1 << 2, - ForOfFlag = 1 << 3, - YieldStarFlag = 1 << 4, - SpreadFlag = 1 << 5, - DestructuringFlag = 1 << 6, - - // Spread, Destructuring, Array element assignment - Element = AllowsSyncIterablesFlag, - Spread = AllowsSyncIterablesFlag | SpreadFlag, - Destructuring = AllowsSyncIterablesFlag | DestructuringFlag, - - ForOf = AllowsSyncIterablesFlag | AllowsStringInputFlag | ForOfFlag, - ForAwaitOf = AllowsSyncIterablesFlag | AllowsAsyncIterablesFlag | AllowsStringInputFlag | ForOfFlag, - - YieldStar = AllowsSyncIterablesFlag | YieldStarFlag, - AsyncYieldStar = AllowsSyncIterablesFlag | AllowsAsyncIterablesFlag | YieldStarFlag, - - GeneratorReturnType = AllowsSyncIterablesFlag, - AsyncGeneratorReturnType = AllowsAsyncIterablesFlag, - +const ambientModuleSymbolRegex = /^".+"$/; +/* @internal */ +let nextSymbolId = 1; +/* @internal */ +let nextNodeId = 1; +/* @internal */ +let nextMergeId = 1; +/* @internal */ +let nextFlowId = 1; +/* @internal */ +const enum IterationUse { + AllowsSyncIterablesFlag = 1 << 0, + AllowsAsyncIterablesFlag = 1 << 1, + AllowsStringInputFlag = 1 << 2, + ForOfFlag = 1 << 3, + YieldStarFlag = 1 << 4, + SpreadFlag = 1 << 5, + DestructuringFlag = 1 << 6, + // Spread, Destructuring, Array element assignment + Element = AllowsSyncIterablesFlag, + Spread = AllowsSyncIterablesFlag | SpreadFlag, + Destructuring = AllowsSyncIterablesFlag | DestructuringFlag, + ForOf = AllowsSyncIterablesFlag | AllowsStringInputFlag | ForOfFlag, + ForAwaitOf = AllowsSyncIterablesFlag | AllowsAsyncIterablesFlag | AllowsStringInputFlag | ForOfFlag, + YieldStar = AllowsSyncIterablesFlag | YieldStarFlag, + AsyncYieldStar = AllowsSyncIterablesFlag | AllowsAsyncIterablesFlag | YieldStarFlag, + GeneratorReturnType = AllowsSyncIterablesFlag, + AsyncGeneratorReturnType = AllowsAsyncIterablesFlag +} +/* @internal */ +const enum IterationTypeKind { + Yield, + Return, + Next +} +/* @internal */ +interface IterationTypesResolver { + iterableCacheKey: "iterationTypesOfAsyncIterable" | "iterationTypesOfIterable"; + iteratorCacheKey: "iterationTypesOfAsyncIterator" | "iterationTypesOfIterator"; + iteratorSymbolName: "asyncIterator" | "iterator"; + getGlobalIteratorType: (reportErrors: boolean) => GenericType; + getGlobalIterableType: (reportErrors: boolean) => GenericType; + getGlobalIterableIteratorType: (reportErrors: boolean) => GenericType; + getGlobalGeneratorType: (reportErrors: boolean) => GenericType; + resolveIterationType: (type: Type, errorNode: Node | undefined) => Type | undefined; + mustHaveANextMethodDiagnostic: DiagnosticMessage; + mustBeAMethodDiagnostic: DiagnosticMessage; + mustHaveAValueDiagnostic: DiagnosticMessage; +} +/* @internal */ +const enum WideningKind { + Normal, + GeneratorYield +} +/* @internal */ +const enum TypeFacts { + None = 0, + TypeofEQString = 1 << 0, + TypeofEQNumber = 1 << 1, + TypeofEQBigInt = 1 << 2, + TypeofEQBoolean = 1 << 3, + TypeofEQSymbol = 1 << 4, + TypeofEQObject = 1 << 5, + TypeofEQFunction = 1 << 6, + TypeofEQHostObject = 1 << 7, + TypeofNEString = 1 << 8, + TypeofNENumber = 1 << 9, + TypeofNEBigInt = 1 << 10, + TypeofNEBoolean = 1 << 11, + TypeofNESymbol = 1 << 12, + TypeofNEObject = 1 << 13, + TypeofNEFunction = 1 << 14, + TypeofNEHostObject = 1 << 15, + EQUndefined = 1 << 16, + EQNull = 1 << 17, + EQUndefinedOrNull = 1 << 18, + NEUndefined = 1 << 19, + NENull = 1 << 20, + NEUndefinedOrNull = 1 << 21, + Truthy = 1 << 22, + Falsy = 1 << 23, + All = (1 << 24) - 1, + // The following members encode facts about particular kinds of types for use in the getTypeFacts function. + // The presence of a particular fact means that the given test is true for some (and possibly all) values + // of that kind of type. + BaseStringStrictFacts = TypeofEQString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, + BaseStringFacts = BaseStringStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + StringStrictFacts = BaseStringStrictFacts | Truthy | Falsy, + StringFacts = BaseStringFacts | Truthy, + EmptyStringStrictFacts = BaseStringStrictFacts | Falsy, + EmptyStringFacts = BaseStringFacts, + NonEmptyStringStrictFacts = BaseStringStrictFacts | Truthy, + NonEmptyStringFacts = BaseStringFacts | Truthy, + BaseNumberStrictFacts = TypeofEQNumber | TypeofNEString | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, + BaseNumberFacts = BaseNumberStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + NumberStrictFacts = BaseNumberStrictFacts | Truthy | Falsy, + NumberFacts = BaseNumberFacts | Truthy, + ZeroNumberStrictFacts = BaseNumberStrictFacts | Falsy, + ZeroNumberFacts = BaseNumberFacts, + NonZeroNumberStrictFacts = BaseNumberStrictFacts | Truthy, + NonZeroNumberFacts = BaseNumberFacts | Truthy, + BaseBigIntStrictFacts = TypeofEQBigInt | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, + BaseBigIntFacts = BaseBigIntStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + BigIntStrictFacts = BaseBigIntStrictFacts | Truthy | Falsy, + BigIntFacts = BaseBigIntFacts | Truthy, + ZeroBigIntStrictFacts = BaseBigIntStrictFacts | Falsy, + ZeroBigIntFacts = BaseBigIntFacts, + NonZeroBigIntStrictFacts = BaseBigIntStrictFacts | Truthy, + NonZeroBigIntFacts = BaseBigIntFacts | Truthy, + BaseBooleanStrictFacts = TypeofEQBoolean | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, + BaseBooleanFacts = BaseBooleanStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + BooleanStrictFacts = BaseBooleanStrictFacts | Truthy | Falsy, + BooleanFacts = BaseBooleanFacts | Truthy, + FalseStrictFacts = BaseBooleanStrictFacts | Falsy, + FalseFacts = BaseBooleanFacts, + TrueStrictFacts = BaseBooleanStrictFacts | Truthy, + TrueFacts = BaseBooleanFacts | Truthy, + SymbolStrictFacts = TypeofEQSymbol | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy, + SymbolFacts = SymbolStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + ObjectStrictFacts = TypeofEQObject | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | NEUndefined | NENull | NEUndefinedOrNull | Truthy, + ObjectFacts = ObjectStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + FunctionStrictFacts = TypeofEQFunction | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy, + FunctionFacts = FunctionStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + UndefinedFacts = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQUndefinedOrNull | NENull | Falsy, + NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy, + EmptyObjectStrictFacts = All & ~(EQUndefined | EQNull | EQUndefinedOrNull), + EmptyObjectFacts = All +} +/* @internal */ +const typeofEQFacts: ts.ReadonlyMap = createMapFromTemplate({ + string: TypeFacts.TypeofEQString, + number: TypeFacts.TypeofEQNumber, + bigint: TypeFacts.TypeofEQBigInt, + boolean: TypeFacts.TypeofEQBoolean, + symbol: TypeFacts.TypeofEQSymbol, + undefined: TypeFacts.EQUndefined, + object: TypeFacts.TypeofEQObject, + function: TypeFacts.TypeofEQFunction +}); +/* @internal */ +const typeofNEFacts: ts.ReadonlyMap = createMapFromTemplate({ + string: TypeFacts.TypeofNEString, + number: TypeFacts.TypeofNENumber, + bigint: TypeFacts.TypeofNEBigInt, + boolean: TypeFacts.TypeofNEBoolean, + symbol: TypeFacts.TypeofNESymbol, + undefined: TypeFacts.NEUndefined, + object: TypeFacts.TypeofNEObject, + function: TypeFacts.TypeofNEFunction +}); +/* @internal */ +type TypeSystemEntity = Node | Symbol | Type | Signature; +/* @internal */ +const enum TypeSystemPropertyName { + Type, + ResolvedBaseConstructorType, + DeclaredType, + ResolvedReturnType, + ImmediateBaseConstraint, + EnumTagType, + JSDocTypeReference, + ResolvedTypeArguments +} +/* @internal */ +const enum CheckMode { + Normal = 0, + Contextual = 1 << 0, + Inferential = 1 << 1, + SkipContextSensitive = 1 << 2, + SkipGenericFunctions = 1 << 3, + IsForSignatureHelp = 1 << 4 +} +/* @internal */ +const enum AccessFlags { + None = 0, + NoIndexSignatures = 1 << 0, + Writing = 1 << 1, + CacheSymbol = 1 << 2, + NoTupleBoundsCheck = 1 << 3 +} +/* @internal */ +const enum CallbackCheck { + None, + Bivariant, + Strict +} +/* @internal */ +const enum MappedTypeModifiers { + IncludeReadonly = 1 << 0, + ExcludeReadonly = 1 << 1, + IncludeOptional = 1 << 2, + ExcludeOptional = 1 << 3 +} +/* @internal */ +const enum ExpandingFlags { + None = 0, + Source = 1, + Target = 1 << 1, + Both = Source | Target +} +/* @internal */ +const enum MembersOrExportsResolutionKind { + resolvedExports = "resolvedExports", + resolvedMembers = "resolvedMembers" +} +/* @internal */ +const enum UnusedKind { + Local, + Parameter +} +/** @param containingNode Node to check for parse error */ +/* @internal */ +type AddUnusedDiagnostic = (containingNode: Node, type: UnusedKind, diagnostic: DiagnosticWithLocation) => void; +/* @internal */ +const isNotOverloadAndNotAccessor = and(isNotOverload, isNotAccessor); +/* @internal */ +const enum DeclarationMeaning { + GetAccessor = 1, + SetAccessor = 2, + PropertyAssignment = 4, + Method = 8, + GetOrSetAccessor = GetAccessor | SetAccessor, + PropertyAssignmentOrMethod = PropertyAssignment | Method +} +/* @internal */ +const enum DeclarationSpaces { + None = 0, + ExportValue = 1 << 0, + ExportType = 1 << 1, + ExportNamespace = 1 << 2 +} +/* @internal */ +export function getNodeId(node: Node): number { + if (!node.id) { + node.id = nextNodeId; + nextNodeId++; } - - const enum IterationTypeKind { - Yield, - Return, - Next, + return node.id; +} +/* @internal */ +export function getSymbolId(symbol: Symbol): number { + if (!symbol.id) { + symbol.id = nextSymbolId; + nextSymbolId++; } - - interface IterationTypesResolver { - iterableCacheKey: "iterationTypesOfAsyncIterable" | "iterationTypesOfIterable"; - iteratorCacheKey: "iterationTypesOfAsyncIterator" | "iterationTypesOfIterator"; - iteratorSymbolName: "asyncIterator" | "iterator"; - getGlobalIteratorType: (reportErrors: boolean) => GenericType; - getGlobalIterableType: (reportErrors: boolean) => GenericType; - getGlobalIterableIteratorType: (reportErrors: boolean) => GenericType; - getGlobalGeneratorType: (reportErrors: boolean) => GenericType; - resolveIterationType: (type: Type, errorNode: Node | undefined) => Type | undefined; - mustHaveANextMethodDiagnostic: DiagnosticMessage; - mustBeAMethodDiagnostic: DiagnosticMessage; - mustHaveAValueDiagnostic: DiagnosticMessage; + return symbol.id; +} +/* @internal */ +export function isInstantiatedModule(node: ModuleDeclaration, preserveConstEnums: boolean) { + const moduleState = getModuleInstanceState(node); + return moduleState === ModuleInstanceState.Instantiated || + (preserveConstEnums && moduleState === ModuleInstanceState.ConstEnumOnly); +} +/* @internal */ +export function createTypeChecker(host: TypeCheckerHost, produceDiagnostics: boolean): TypeChecker { + const getPackagesSet: () => ts.Map = memoize(() => { + const set = createMap(); + host.getSourceFiles().forEach(sf => { + if (!sf.resolvedModules) + return; + forEachEntry(sf.resolvedModules, r => { + if (r && r.packageId) + set.set(r.packageId.name, true); + }); + }); + return set; + }); + // Cancellation that controls whether or not we can cancel in the middle of type checking. + // In general cancelling is *not* safe for the type checker. We might be in the middle of + // computing something, and we will leave our internals in an inconsistent state. Callers + // who set the cancellation token should catch if a cancellation exception occurs, and + // should throw away and create a new TypeChecker. + // + // Currently we only support setting the cancellation token when getting diagnostics. This + // is because diagnostics can be quite expensive, and we want to allow hosts to bail out if + // they no longer need the information (for example, if the user started editing again). + let cancellationToken: CancellationToken | undefined; + let requestedExternalEmitHelpers: ExternalEmitHelpers; + let externalHelpersModule: ts.Symbol; + const Symbol = objectAllocator.getSymbolConstructor(); + const Type = objectAllocator.getTypeConstructor(); + const Signature = objectAllocator.getSignatureConstructor(); + let typeCount = 0; + let symbolCount = 0; + let enumCount = 0; + let instantiationCount = 0; + let instantiationDepth = 0; + let constraintDepth = 0; + let currentNode: Node | undefined; + const emptySymbols = createSymbolTable(); + const identityMapper: (type: ts.Type) => ts.Type = identity; + const compilerOptions = host.getCompilerOptions(); + const languageVersion = getEmitScriptTarget(compilerOptions); + const moduleKind = getEmitModuleKind(compilerOptions); + const allowSyntheticDefaultImports = getAllowSyntheticDefaultImports(compilerOptions); + const strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks"); + const strictFunctionTypes = getStrictOptionValue(compilerOptions, "strictFunctionTypes"); + const strictBindCallApply = getStrictOptionValue(compilerOptions, "strictBindCallApply"); + const strictPropertyInitialization = getStrictOptionValue(compilerOptions, "strictPropertyInitialization"); + const noImplicitAny = getStrictOptionValue(compilerOptions, "noImplicitAny"); + const noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis"); + const keyofStringsOnly = !!compilerOptions.keyofStringsOnly; + const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : ObjectFlags.FreshLiteral; + const emitResolver = createResolver(); + const nodeBuilder = createNodeBuilder(); + const globals = createSymbolTable(); + const undefinedSymbol = createSymbol(SymbolFlags.Property, ("undefined" as __String)); + undefinedSymbol.declarations = []; + const globalThisSymbol = createSymbol(SymbolFlags.Module, ("globalThis" as __String), CheckFlags.Readonly); + globalThisSymbol.exports = globals; + globalThisSymbol.declarations = []; + globals.set(globalThisSymbol.escapedName, globalThisSymbol); + const argumentsSymbol = createSymbol(SymbolFlags.Property, ("arguments" as __String)); + const requireSymbol = createSymbol(SymbolFlags.Property, ("require" as __String)); + /** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */ + let apparentArgumentCount: number | undefined; + // for public members that accept a Node or one of its subtypes, we must guard against + // synthetic nodes created during transformations by calling `getParseTreeNode`. + // for most of these, we perform the guard only on `checker` to avoid any possible + // extra cost of calling `getParseTreeNode` when calling these functions from inside the + // checker. + const checker: TypeChecker = { + getNodeCount: () => sum(host.getSourceFiles(), "nodeCount"), + getIdentifierCount: () => sum(host.getSourceFiles(), "identifierCount"), + getSymbolCount: () => sum(host.getSourceFiles(), "symbolCount") + symbolCount, + getTypeCount: () => typeCount, + getRelationCacheSizes: () => ({ + assignable: assignableRelation.size, + identity: identityRelation.size, + subtype: subtypeRelation.size, + }), + isUndefinedSymbol: symbol => symbol === undefinedSymbol, + isArgumentsSymbol: symbol => symbol === argumentsSymbol, + isUnknownSymbol: symbol => symbol === unknownSymbol, + getMergedSymbol, + getDiagnostics, + getGlobalDiagnostics, + getTypeOfSymbolAtLocation: (symbol, location) => { + location = getParseTreeNode(location); + return location ? getTypeOfSymbolAtLocation(symbol, location) : errorType; + }, + getSymbolsOfParameterPropertyDeclaration: (parameterIn, parameterName) => { + const parameter = getParseTreeNode(parameterIn, isParameter); + if (parameter === undefined) + return Debug.fail("Cannot get symbols of a synthetic parameter that cannot be resolved to a parse-tree node."); + return getSymbolsOfParameterPropertyDeclaration(parameter, escapeLeadingUnderscores(parameterName)); + }, + getDeclaredTypeOfSymbol, + getPropertiesOfType, + getPropertyOfType: (type, name) => getPropertyOfType(type, escapeLeadingUnderscores(name)), + getTypeOfPropertyOfType: (type, name) => getTypeOfPropertyOfType(type, escapeLeadingUnderscores(name)), + getIndexInfoOfType, + getSignaturesOfType, + getIndexTypeOfType, + getBaseTypes, + getBaseTypeOfLiteralType, + getWidenedType, + getTypeFromTypeNode: nodeIn => { + const node = getParseTreeNode(nodeIn, isTypeNode); + return node ? getTypeFromTypeNode(node) : errorType; + }, + getParameterType: getTypeAtPosition, + getPromisedTypeOfPromise, + getReturnTypeOfSignature, + isNullableType, + getNullableType, + getNonNullableType, + getNonOptionalType: removeOptionalTypeMarker, + getTypeArguments, + typeToTypeNode: nodeBuilder.typeToTypeNode, + indexInfoToIndexSignatureDeclaration: nodeBuilder.indexInfoToIndexSignatureDeclaration, + signatureToSignatureDeclaration: nodeBuilder.signatureToSignatureDeclaration, + symbolToEntityName: nodeBuilder.symbolToEntityName, + symbolToExpression: nodeBuilder.symbolToExpression, + symbolToTypeParameterDeclarations: nodeBuilder.symbolToTypeParameterDeclarations, + symbolToParameterDeclaration: nodeBuilder.symbolToParameterDeclaration, + typeParameterToDeclaration: nodeBuilder.typeParameterToDeclaration, + getSymbolsInScope: (location, meaning) => { + location = getParseTreeNode(location); + return location ? getSymbolsInScope(location, meaning) : []; + }, + getSymbolAtLocation: node => { + node = getParseTreeNode(node); + return node ? getSymbolAtLocation(node) : undefined; + }, + getShorthandAssignmentValueSymbol: node => { + node = getParseTreeNode(node); + return node ? getShorthandAssignmentValueSymbol(node) : undefined; + }, + getExportSpecifierLocalTargetSymbol: nodeIn => { + const node = getParseTreeNode(nodeIn, isExportSpecifier); + return node ? getExportSpecifierLocalTargetSymbol(node) : undefined; + }, + getExportSymbolOfSymbol(symbol) { + return getMergedSymbol(symbol.exportSymbol || symbol); + }, + getTypeAtLocation: node => { + node = getParseTreeNode(node); + return node ? getTypeOfNode(node) : errorType; + }, + getTypeOfAssignmentPattern: nodeIn => { + const node = getParseTreeNode(nodeIn, isAssignmentPattern); + return node && getTypeOfAssignmentPattern(node) || errorType; + }, + getPropertySymbolOfDestructuringAssignment: locationIn => { + const location = getParseTreeNode(locationIn, isIdentifier); + return location ? getPropertySymbolOfDestructuringAssignment(location) : undefined; + }, + signatureToString: (signature, enclosingDeclaration, flags, kind) => { + return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind); + }, + typeToString: (type, enclosingDeclaration, flags) => { + return typeToString(type, getParseTreeNode(enclosingDeclaration), flags); + }, + symbolToString: (symbol, enclosingDeclaration, meaning, flags) => { + return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags); + }, + typePredicateToString: (predicate, enclosingDeclaration, flags) => { + return typePredicateToString(predicate, getParseTreeNode(enclosingDeclaration), flags); + }, + writeSignature: (signature, enclosingDeclaration, flags, kind, writer) => { + return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind, writer); + }, + writeType: (type, enclosingDeclaration, flags, writer) => { + return typeToString(type, getParseTreeNode(enclosingDeclaration), flags, writer); + }, + writeSymbol: (symbol, enclosingDeclaration, meaning, flags, writer) => { + return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags, writer); + }, + writeTypePredicate: (predicate, enclosingDeclaration, flags, writer) => { + return typePredicateToString(predicate, getParseTreeNode(enclosingDeclaration), flags, writer); + }, + getAugmentedPropertiesOfType, + getRootSymbols, + getContextualType: (nodeIn: Expression, contextFlags?: ContextFlags) => { + const node = getParseTreeNode(nodeIn, isExpression); + return node ? getContextualType(node, contextFlags) : undefined; + }, + getContextualTypeForObjectLiteralElement: nodeIn => { + const node = getParseTreeNode(nodeIn, isObjectLiteralElementLike); + return node ? getContextualTypeForObjectLiteralElement(node) : undefined; + }, + getContextualTypeForArgumentAtIndex: (nodeIn, argIndex) => { + const node = getParseTreeNode(nodeIn, isCallLikeExpression); + return node && getContextualTypeForArgumentAtIndex(node, argIndex); + }, + getContextualTypeForJsxAttribute: (nodeIn) => { + const node = getParseTreeNode(nodeIn, isJsxAttributeLike); + return node && getContextualTypeForJsxAttribute(node); + }, + isContextSensitive, + getFullyQualifiedName, + getResolvedSignature: (node, candidatesOutArray, argumentCount) => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal), + getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.IsForSignatureHelp), + getExpandedParameters, + hasEffectiveRestParameter, + getConstantValue: nodeIn => { + const node = getParseTreeNode(nodeIn, canHaveConstantValue); + return node ? getConstantValue(node) : undefined; + }, + isValidPropertyAccess: (nodeIn, propertyName) => { + const node = getParseTreeNode(nodeIn, isPropertyAccessOrQualifiedNameOrImportTypeNode); + return !!node && isValidPropertyAccess(node, escapeLeadingUnderscores(propertyName)); + }, + isValidPropertyAccessForCompletions: (nodeIn, type, property) => { + const node = getParseTreeNode(nodeIn, isPropertyAccessExpression); + return !!node && isValidPropertyAccessForCompletions(node, type, property); + }, + getSignatureFromDeclaration: declarationIn => { + const declaration = getParseTreeNode(declarationIn, isFunctionLike); + return declaration ? getSignatureFromDeclaration(declaration) : undefined; + }, + isImplementationOfOverload: node => { + const parsed = getParseTreeNode(node, isFunctionLike); + return parsed ? isImplementationOfOverload(parsed) : undefined; + }, + getImmediateAliasedSymbol, + getAliasedSymbol: resolveAlias, + getEmitResolver, + getExportsOfModule: getExportsOfModuleAsArray, + getExportsAndPropertiesOfModule, + getSymbolWalker: createGetSymbolWalker(getRestTypeOfSignature, getTypePredicateOfSignature, getReturnTypeOfSignature, getBaseTypes, resolveStructuredTypeMembers, getTypeOfSymbol, getResolvedSymbol, getIndexTypeOfStructuredType, getConstraintOfTypeParameter, getFirstIdentifier, getTypeArguments), + getAmbientModules, + getJsxIntrinsicTagNamesAt, + isOptionalParameter: nodeIn => { + const node = getParseTreeNode(nodeIn, isParameter); + return node ? isOptionalParameter(node) : false; + }, + tryGetMemberInModuleExports: (name, symbol) => tryGetMemberInModuleExports(escapeLeadingUnderscores(name), symbol), + tryGetMemberInModuleExportsAndProperties: (name, symbol) => tryGetMemberInModuleExportsAndProperties(escapeLeadingUnderscores(name), symbol), + tryFindAmbientModuleWithoutAugmentations: moduleName => { + // we deliberately exclude augmentations + // since we are only interested in declarations of the module itself + return tryFindAmbientModule(moduleName, /*withAugmentations*/ false); + }, + getApparentType, + getUnionType, + isTypeAssignableTo: (source, target) => { + return isTypeAssignableTo(source, target); + }, + createAnonymousType, + createSignature, + createSymbol, + createIndexInfo, + getAnyType: () => anyType, + getStringType: () => stringType, + getNumberType: () => numberType, + createPromiseType, + createArrayType, + getElementTypeOfArrayType, + getBooleanType: () => booleanType, + getFalseType: (fresh?) => fresh ? falseType : regularFalseType, + getTrueType: (fresh?) => fresh ? trueType : regularTrueType, + getVoidType: () => voidType, + getUndefinedType: () => undefinedType, + getNullType: () => nullType, + getESSymbolType: () => esSymbolType, + getNeverType: () => neverType, + getOptionalType: () => optionalType, + isSymbolAccessible, + isArrayType, + isTupleType, + isArrayLikeType, + isTypeInvalidDueToUnionDiscriminant, + getAllPossiblePropertiesOfTypes, + getSuggestionForNonexistentProperty: (node, type) => getSuggestionForNonexistentProperty(node, type), + getSuggestionForNonexistentSymbol: (location, name, meaning) => getSuggestionForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning), + getSuggestionForNonexistentExport: (node, target) => getSuggestionForNonexistentExport(node, target), + getBaseConstraintOfType, + getDefaultFromTypeParameter: type => type && type.flags & TypeFlags.TypeParameter ? getDefaultFromTypeParameter((type as TypeParameter)) : undefined, + resolveName(name, location, meaning, excludeGlobals) { + return resolveName(location, escapeLeadingUnderscores(name), meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false, excludeGlobals); + }, + getJsxNamespace: n => unescapeLeadingUnderscores(getJsxNamespace(n)), + getAccessibleSymbolChain, + getTypePredicateOfSignature, + resolveExternalModuleSymbol, + tryGetThisTypeAt: (node, includeGlobalThis) => { + node = getParseTreeNode(node); + return node && tryGetThisTypeAt(node, includeGlobalThis); + }, + getTypeArgumentConstraint: nodeIn => { + const node = getParseTreeNode(nodeIn, isTypeNode); + return node && getTypeArgumentConstraint(node); + }, + getSuggestionDiagnostics: (file, ct) => { + if (skipTypeChecking(file, compilerOptions, host)) { + return emptyArray; + } + let diagnostics: DiagnosticWithLocation[] | undefined; + try { + // Record the cancellation token so it can be checked later on during checkSourceElement. + // Do this in a finally block so we can ensure that it gets reset back to nothing after + // this call is done. + cancellationToken = ct; + // Ensure file is type checked + checkSourceFile(file); + Debug.assert(!!(getNodeLinks(file).flags & NodeCheckFlags.TypeChecked)); + diagnostics = addRange(diagnostics, suggestionDiagnostics.getDiagnostics(file.fileName)); + checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(file), (containingNode, kind, diag) => { + if (!containsParseError(containingNode) && !unusedIsError(kind, !!(containingNode.flags & NodeFlags.Ambient))) { + (diagnostics || (diagnostics = [])).push({ ...diag, category: DiagnosticCategory.Suggestion }); + } + }); + return diagnostics || emptyArray; + } + finally { + cancellationToken = undefined; + } + }, + runWithCancellationToken: (token, callback) => { + try { + cancellationToken = token; + return callback(checker); + } + finally { + cancellationToken = undefined; + } + }, + getLocalTypeParametersOfClassOrInterfaceOrTypeAlias, + isDeclarationVisible, + }; + function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: ts.Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): ts.Signature | undefined { + const node = getParseTreeNode(nodeIn, isCallLikeExpression); + apparentArgumentCount = argumentCount; + const res = node ? getResolvedSignature(node, candidatesOutArray, checkMode) : undefined; + apparentArgumentCount = undefined; + return res; } - - const enum WideningKind { - Normal, - GeneratorYield + const tupleTypes = createMap(); + const unionTypes = createMap(); + const intersectionTypes = createMap(); + const literalTypes = createMap(); + const indexedAccessTypes = createMap(); + const substitutionTypes = createMap(); + const evolvingArrayTypes: EvolvingArrayType[] = []; + const undefinedProperties = (createMap() as UnderscoreEscapedMap); + const unknownSymbol = createSymbol(SymbolFlags.Property, ("unknown" as __String)); + const resolvingSymbol = createSymbol(0, InternalSymbolName.Resolving); + const anyType = createIntrinsicType(TypeFlags.Any, "any"); + const autoType = createIntrinsicType(TypeFlags.Any, "any"); + const wildcardType = createIntrinsicType(TypeFlags.Any, "any"); + const errorType = createIntrinsicType(TypeFlags.Any, "error"); + const nonInferrableAnyType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.ContainsWideningType); + const unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); + const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined"); + const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType); + const optionalType = createIntrinsicType(TypeFlags.Undefined, "undefined"); + const nullType = createIntrinsicType(TypeFlags.Null, "null"); + const nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null, "null", ObjectFlags.ContainsWideningType); + const stringType = createIntrinsicType(TypeFlags.String, "string"); + const numberType = createIntrinsicType(TypeFlags.Number, "number"); + const bigintType = createIntrinsicType(TypeFlags.BigInt, "bigint"); + const falseType = (createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType); + const regularFalseType = (createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType); + const trueType = (createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType); + const regularTrueType = (createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType); + trueType.regularType = regularTrueType; + trueType.freshType = trueType; + regularTrueType.regularType = regularTrueType; + regularTrueType.freshType = trueType; + falseType.regularType = regularFalseType; + falseType.freshType = falseType; + regularFalseType.regularType = regularFalseType; + regularFalseType.freshType = falseType; + const booleanType = createBooleanType([regularFalseType, regularTrueType]); + // Also mark all combinations of fresh/regular booleans as "Boolean" so they print as `boolean` instead of `true | false` + // (The union is cached, so simply doing the marking here is sufficient) + createBooleanType([regularFalseType, trueType]); + createBooleanType([falseType, regularTrueType]); + createBooleanType([falseType, trueType]); + const esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol"); + const voidType = createIntrinsicType(TypeFlags.Void, "void"); + const neverType = createIntrinsicType(TypeFlags.Never, "never"); + const silentNeverType = createIntrinsicType(TypeFlags.Never, "never"); + const nonInferrableType = createIntrinsicType(TypeFlags.Never, "never", ObjectFlags.NonInferrableType); + const implicitNeverType = createIntrinsicType(TypeFlags.Never, "never"); + const unreachableNeverType = createIntrinsicType(TypeFlags.Never, "never"); + const nonPrimitiveType = createIntrinsicType(TypeFlags.NonPrimitive, "object"); + const stringNumberSymbolType = getUnionType([stringType, numberType, esSymbolType]); + const keyofConstraintType = keyofStringsOnly ? stringType : stringNumberSymbolType; + const numberOrBigIntType = getUnionType([numberType, bigintType]); + const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); + const emptyJsxObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); + emptyJsxObjectType.objectFlags |= ObjectFlags.JsxAttributes; + const emptyTypeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); + emptyTypeLiteralSymbol.members = createSymbolTable(); + const emptyTypeLiteralType = createAnonymousType(emptyTypeLiteralSymbol, emptySymbols, emptyArray, emptyArray, undefined, undefined); + const emptyGenericType = (createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined)); + emptyGenericType.instantiations = createMap(); + const anyFunctionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); + // The anyFunctionType contains the anyFunctionType by definition. The flag is further propagated + // in getPropagatingFlagsOfTypes, and it is checked in inferFromTypes. + anyFunctionType.objectFlags |= ObjectFlags.NonInferrableType; + const noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); + const circularConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); + const resolvingDefaultType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); + const markerSuperType = createTypeParameter(); + const markerSubType = createTypeParameter(); + markerSubType.constraint = markerSuperType; + const markerOtherType = createTypeParameter(); + const noTypePredicate = createTypePredicate(TypePredicateKind.Identifier, "<>", 0, anyType); + const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, errorType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + const silentNeverSignature = createSignature(undefined, undefined, undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true); + const iterationTypesCache = createMap(); // cache for common IterationTypes instances + const noIterationTypes: IterationTypes = { + get yieldType(): ts.Type { throw new Error("Not supported"); }, + get returnType(): ts.Type { throw new Error("Not supported"); }, + get nextType(): ts.Type { throw new Error("Not supported"); }, + }; + const anyIterationTypes = createIterationTypes(anyType, anyType, anyType); + const anyIterationTypesExceptNext = createIterationTypes(anyType, anyType, unknownType); + const defaultIterationTypes = createIterationTypes(neverType, anyType, undefinedType); // default iteration types for `Iterator`. + const asyncIterationTypesResolver: IterationTypesResolver = { + iterableCacheKey: "iterationTypesOfAsyncIterable", + iteratorCacheKey: "iterationTypesOfAsyncIterator", + iteratorSymbolName: "asyncIterator", + getGlobalIteratorType: getGlobalAsyncIteratorType, + getGlobalIterableType: getGlobalAsyncIterableType, + getGlobalIterableIteratorType: getGlobalAsyncIterableIteratorType, + getGlobalGeneratorType: getGlobalAsyncGeneratorType, + resolveIterationType: getAwaitedType, + mustHaveANextMethodDiagnostic: Diagnostics.An_async_iterator_must_have_a_next_method, + mustBeAMethodDiagnostic: Diagnostics.The_0_property_of_an_async_iterator_must_be_a_method, + mustHaveAValueDiagnostic: Diagnostics.The_type_returned_by_the_0_method_of_an_async_iterator_must_be_a_promise_for_a_type_with_a_value_property, + }; + const syncIterationTypesResolver: IterationTypesResolver = { + iterableCacheKey: "iterationTypesOfIterable", + iteratorCacheKey: "iterationTypesOfIterator", + iteratorSymbolName: "iterator", + getGlobalIteratorType, + getGlobalIterableType, + getGlobalIterableIteratorType, + getGlobalGeneratorType, + resolveIterationType: (type, _errorNode) => type, + mustHaveANextMethodDiagnostic: Diagnostics.An_iterator_must_have_a_next_method, + mustBeAMethodDiagnostic: Diagnostics.The_0_property_of_an_iterator_must_be_a_method, + mustHaveAValueDiagnostic: Diagnostics.The_type_returned_by_the_0_method_of_an_iterator_must_have_a_value_property, + }; + interface DuplicateInfoForSymbol { + readonly firstFileLocations: Node[]; + readonly secondFileLocations: Node[]; + readonly isBlockScoped: boolean; } - - const enum TypeFacts { - None = 0, - TypeofEQString = 1 << 0, // typeof x === "string" - TypeofEQNumber = 1 << 1, // typeof x === "number" - TypeofEQBigInt = 1 << 2, // typeof x === "bigint" - TypeofEQBoolean = 1 << 3, // typeof x === "boolean" - TypeofEQSymbol = 1 << 4, // typeof x === "symbol" - TypeofEQObject = 1 << 5, // typeof x === "object" - TypeofEQFunction = 1 << 6, // typeof x === "function" - TypeofEQHostObject = 1 << 7, // typeof x === "xxx" - TypeofNEString = 1 << 8, // typeof x !== "string" - TypeofNENumber = 1 << 9, // typeof x !== "number" - TypeofNEBigInt = 1 << 10, // typeof x !== "bigint" - TypeofNEBoolean = 1 << 11, // typeof x !== "boolean" - TypeofNESymbol = 1 << 12, // typeof x !== "symbol" - TypeofNEObject = 1 << 13, // typeof x !== "object" - TypeofNEFunction = 1 << 14, // typeof x !== "function" - TypeofNEHostObject = 1 << 15, // typeof x !== "xxx" - EQUndefined = 1 << 16, // x === undefined - EQNull = 1 << 17, // x === null - EQUndefinedOrNull = 1 << 18, // x === undefined / x === null - NEUndefined = 1 << 19, // x !== undefined - NENull = 1 << 20, // x !== null - NEUndefinedOrNull = 1 << 21, // x != undefined / x != null - Truthy = 1 << 22, // x - Falsy = 1 << 23, // !x - All = (1 << 24) - 1, - // The following members encode facts about particular kinds of types for use in the getTypeFacts function. - // The presence of a particular fact means that the given test is true for some (and possibly all) values - // of that kind of type. - BaseStringStrictFacts = TypeofEQString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, - BaseStringFacts = BaseStringStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - StringStrictFacts = BaseStringStrictFacts | Truthy | Falsy, - StringFacts = BaseStringFacts | Truthy, - EmptyStringStrictFacts = BaseStringStrictFacts | Falsy, - EmptyStringFacts = BaseStringFacts, - NonEmptyStringStrictFacts = BaseStringStrictFacts | Truthy, - NonEmptyStringFacts = BaseStringFacts | Truthy, - BaseNumberStrictFacts = TypeofEQNumber | TypeofNEString | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, - BaseNumberFacts = BaseNumberStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - NumberStrictFacts = BaseNumberStrictFacts | Truthy | Falsy, - NumberFacts = BaseNumberFacts | Truthy, - ZeroNumberStrictFacts = BaseNumberStrictFacts | Falsy, - ZeroNumberFacts = BaseNumberFacts, - NonZeroNumberStrictFacts = BaseNumberStrictFacts | Truthy, - NonZeroNumberFacts = BaseNumberFacts | Truthy, - BaseBigIntStrictFacts = TypeofEQBigInt | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, - BaseBigIntFacts = BaseBigIntStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - BigIntStrictFacts = BaseBigIntStrictFacts | Truthy | Falsy, - BigIntFacts = BaseBigIntFacts | Truthy, - ZeroBigIntStrictFacts = BaseBigIntStrictFacts | Falsy, - ZeroBigIntFacts = BaseBigIntFacts, - NonZeroBigIntStrictFacts = BaseBigIntStrictFacts | Truthy, - NonZeroBigIntFacts = BaseBigIntFacts | Truthy, - BaseBooleanStrictFacts = TypeofEQBoolean | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, - BaseBooleanFacts = BaseBooleanStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - BooleanStrictFacts = BaseBooleanStrictFacts | Truthy | Falsy, - BooleanFacts = BaseBooleanFacts | Truthy, - FalseStrictFacts = BaseBooleanStrictFacts | Falsy, - FalseFacts = BaseBooleanFacts, - TrueStrictFacts = BaseBooleanStrictFacts | Truthy, - TrueFacts = BaseBooleanFacts | Truthy, - SymbolStrictFacts = TypeofEQSymbol | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy, - SymbolFacts = SymbolStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - ObjectStrictFacts = TypeofEQObject | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | NEUndefined | NENull | NEUndefinedOrNull | Truthy, - ObjectFacts = ObjectStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - FunctionStrictFacts = TypeofEQFunction | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy, - FunctionFacts = FunctionStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - UndefinedFacts = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQUndefinedOrNull | NENull | Falsy, - NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy, - EmptyObjectStrictFacts = All & ~(EQUndefined | EQNull | EQUndefinedOrNull), - EmptyObjectFacts = All, + interface DuplicateInfoForFiles { + readonly firstFile: SourceFile; + readonly secondFile: SourceFile; + /** Key is symbol name. */ + readonly conflictingSymbols: ts.Map; } - - const typeofEQFacts: ReadonlyMap = createMapFromTemplate({ - string: TypeFacts.TypeofEQString, - number: TypeFacts.TypeofEQNumber, - bigint: TypeFacts.TypeofEQBigInt, - boolean: TypeFacts.TypeofEQBoolean, - symbol: TypeFacts.TypeofEQSymbol, - undefined: TypeFacts.EQUndefined, - object: TypeFacts.TypeofEQObject, - function: TypeFacts.TypeofEQFunction - }); - - const typeofNEFacts: ReadonlyMap = createMapFromTemplate({ - string: TypeFacts.TypeofNEString, - number: TypeFacts.TypeofNENumber, - bigint: TypeFacts.TypeofNEBigInt, - boolean: TypeFacts.TypeofNEBoolean, - symbol: TypeFacts.TypeofNESymbol, - undefined: TypeFacts.NEUndefined, - object: TypeFacts.TypeofNEObject, - function: TypeFacts.TypeofNEFunction + /** Key is "/path/to/a.ts|/path/to/b.ts". */ + let amalgamatedDuplicates: ts.Map | undefined; + const reverseMappedCache = createMap(); + let ambientModulesCache: ts.Symbol[] | undefined; + /** + * List of every ambient module with a "*" wildcard. + * Unlike other ambient modules, these can't be stored in `globals` because symbol tables only deal with exact matches. + * This is only used if there is no exact match. + */ + let patternAmbientModules: PatternAmbientModule[]; + let patternAmbientModuleAugmentations: ts.Map | undefined; + let globalObjectType: ObjectType; + let globalFunctionType: ObjectType; + let globalCallableFunctionType: ObjectType; + let globalNewableFunctionType: ObjectType; + let globalArrayType: GenericType; + let globalReadonlyArrayType: GenericType; + let globalStringType: ObjectType; + let globalNumberType: ObjectType; + let globalBooleanType: ObjectType; + let globalRegExpType: ObjectType; + let globalThisType: GenericType; + let anyArrayType: ts.Type; + let autoArrayType: ts.Type; + let anyReadonlyArrayType: ts.Type; + let deferredGlobalNonNullableTypeAlias: ts.Symbol; + // The library files are only loaded when the feature is used. + // This allows users to just specify library files they want to used through --lib + // and they will not get an error from not having unrelated library files + let deferredGlobalESSymbolConstructorSymbol: ts.Symbol | undefined; + let deferredGlobalESSymbolType: ObjectType; + let deferredGlobalTypedPropertyDescriptorType: GenericType; + let deferredGlobalPromiseType: GenericType; + let deferredGlobalPromiseLikeType: GenericType; + let deferredGlobalPromiseConstructorSymbol: ts.Symbol | undefined; + let deferredGlobalPromiseConstructorLikeType: ObjectType; + let deferredGlobalIterableType: GenericType; + let deferredGlobalIteratorType: GenericType; + let deferredGlobalIterableIteratorType: GenericType; + let deferredGlobalGeneratorType: GenericType; + let deferredGlobalIteratorYieldResultType: GenericType; + let deferredGlobalIteratorReturnResultType: GenericType; + let deferredGlobalAsyncIterableType: GenericType; + let deferredGlobalAsyncIteratorType: GenericType; + let deferredGlobalAsyncIterableIteratorType: GenericType; + let deferredGlobalAsyncGeneratorType: GenericType; + let deferredGlobalTemplateStringsArrayType: ObjectType; + let deferredGlobalImportMetaType: ObjectType; + let deferredGlobalExtractSymbol: ts.Symbol; + let deferredGlobalOmitSymbol: ts.Symbol; + let deferredGlobalBigIntType: ObjectType; + const allPotentiallyUnusedIdentifiers = createMap(); // key is file name + let flowLoopStart = 0; + let flowLoopCount = 0; + let sharedFlowCount = 0; + let flowAnalysisDisabled = false; + let flowInvocationCount = 0; + let lastFlowNode: FlowNode | undefined; + let lastFlowNodeReachable: boolean; + let flowTypeCache: ts.Type[] | undefined; + const emptyStringType = getLiteralType(""); + const zeroType = getLiteralType(0); + const zeroBigIntType = getLiteralType({ negative: false, base10Value: "0" }); + const resolutionTargets: TypeSystemEntity[] = []; + const resolutionResults: boolean[] = []; + const resolutionPropertyNames: TypeSystemPropertyName[] = []; + let suggestionCount = 0; + const maximumSuggestionCount = 10; + const mergedSymbols: ts.Symbol[] = []; + const symbolLinks: SymbolLinks[] = []; + const nodeLinks: NodeLinks[] = []; + const flowLoopCaches: ts.Map[] = []; + const flowLoopNodes: FlowNode[] = []; + const flowLoopKeys: string[] = []; + const flowLoopTypes: ts.Type[][] = []; + const sharedFlowNodes: FlowNode[] = []; + const sharedFlowTypes: FlowType[] = []; + const flowNodeReachable: (boolean | undefined)[] = []; + const potentialThisCollisions: Node[] = []; + const potentialNewTargetCollisions: Node[] = []; + const awaitedTypeStack: number[] = []; + const diagnostics = createDiagnosticCollection(); + const suggestionDiagnostics = createDiagnosticCollection(); + const typeofTypesByName: ts.ReadonlyMap = createMapFromTemplate({ + string: stringType, + number: numberType, + bigint: bigintType, + boolean: booleanType, + symbol: esSymbolType, + undefined: undefinedType }); - - type TypeSystemEntity = Node | Symbol | Type | Signature; - - const enum TypeSystemPropertyName { - Type, - ResolvedBaseConstructorType, - DeclaredType, - ResolvedReturnType, - ImmediateBaseConstraint, - EnumTagType, - JSDocTypeReference, - ResolvedTypeArguments, + const typeofType = createTypeofType(); + let _jsxNamespace: __String; + let _jsxFactoryEntity: EntityName | undefined; + let outofbandVarianceMarkerHandler: ((onlyUnreliable: boolean) => void) | undefined; + const subtypeRelation = createMap(); + const assignableRelation = createMap(); + const comparableRelation = createMap(); + const identityRelation = createMap(); + const enumRelation = createMap(); + const builtinGlobals = createSymbolTable(); + builtinGlobals.set(undefinedSymbol.escapedName, undefinedSymbol); + initializeTypeChecker(); + return checker; + function getJsxNamespace(location: Node | undefined): __String { + if (location) { + const file = getSourceFileOfNode(location); + if (file) { + if (file.localJsxNamespace) { + return file.localJsxNamespace; + } + const jsxPragma = file.pragmas.get("jsx"); + if (jsxPragma) { + const chosenpragma = isArray(jsxPragma) ? jsxPragma[0] : jsxPragma; + file.localJsxFactory = parseIsolatedEntityName(chosenpragma.arguments.factory, languageVersion); + if (file.localJsxFactory) { + return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText; + } + } + } + } + if (!_jsxNamespace) { + _jsxNamespace = ("React" as __String); + if (compilerOptions.jsxFactory) { + _jsxFactoryEntity = parseIsolatedEntityName(compilerOptions.jsxFactory, languageVersion); + if (_jsxFactoryEntity) { + _jsxNamespace = getFirstIdentifier(_jsxFactoryEntity).escapedText; + } + } + else if (compilerOptions.reactNamespace) { + _jsxNamespace = escapeLeadingUnderscores(compilerOptions.reactNamespace); + } + } + return _jsxNamespace; } - - const enum CheckMode { - Normal = 0, // Normal type checking - Contextual = 1 << 0, // Explicitly assigned contextual type, therefore not cacheable - Inferential = 1 << 1, // Inferential typing - SkipContextSensitive = 1 << 2, // Skip context sensitive function expressions - SkipGenericFunctions = 1 << 3, // Skip single signature generic functions - IsForSignatureHelp = 1 << 4, // Call resolution for purposes of signature help + function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken) { + // Ensure we have all the type information in place for this file so that all the + // emitter questions of this resolver will return the right information. + getDiagnostics(sourceFile, cancellationToken); + return emitResolver; } - - const enum AccessFlags { - None = 0, - NoIndexSignatures = 1 << 0, - Writing = 1 << 1, - CacheSymbol = 1 << 2, - NoTupleBoundsCheck = 1 << 3, + function lookupOrIssueError(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { + const diagnostic = location + ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) + : createCompilerDiagnostic(message, arg0, arg1, arg2, arg3); + const existing = diagnostics.lookup(diagnostic); + if (existing) { + return existing; + } + else { + diagnostics.add(diagnostic); + return diagnostic; + } } - - const enum CallbackCheck { - None, - Bivariant, - Strict, + function error(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { + const diagnostic = location + ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) + : createCompilerDiagnostic(message, arg0, arg1, arg2, arg3); + diagnostics.add(diagnostic); + return diagnostic; } - - const enum MappedTypeModifiers { - IncludeReadonly = 1 << 0, - ExcludeReadonly = 1 << 1, - IncludeOptional = 1 << 2, - ExcludeOptional = 1 << 3, + function addErrorOrSuggestion(isError: boolean, diagnostic: DiagnosticWithLocation) { + if (isError) { + diagnostics.add(diagnostic); + } + else { + suggestionDiagnostics.add({ ...diagnostic, category: DiagnosticCategory.Suggestion }); + } } - - const enum ExpandingFlags { - None = 0, - Source = 1, - Target = 1 << 1, - Both = Source | Target, + function errorOrSuggestion(isError: boolean, location: Node, message: DiagnosticMessage | DiagnosticMessageChain, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void { + addErrorOrSuggestion(isError, "message" in message ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) : createDiagnosticForNodeFromMessageChain(location, message)); // eslint-disable-line no-in-operator } - - const enum MembersOrExportsResolutionKind { - resolvedExports = "resolvedExports", - resolvedMembers = "resolvedMembers" + function errorAndMaybeSuggestAwait(location: Node, maybeMissingAwait: boolean, message: DiagnosticMessage, arg0?: string | number | undefined, arg1?: string | number | undefined, arg2?: string | number | undefined, arg3?: string | number | undefined): Diagnostic { + const diagnostic = error(location, message, arg0, arg1, arg2, arg3); + if (maybeMissingAwait) { + const related = createDiagnosticForNode(location, Diagnostics.Did_you_forget_to_use_await); + addRelatedInfo(diagnostic, related); + } + return diagnostic; } - - const enum UnusedKind { - Local, - Parameter, + function createSymbol(flags: SymbolFlags, name: __String, checkFlags?: CheckFlags) { + symbolCount++; + const symbol = ((new Symbol(flags | SymbolFlags.Transient, name))); + symbol.checkFlags = checkFlags || 0; + return symbol; } - - /** @param containingNode Node to check for parse error */ - type AddUnusedDiagnostic = (containingNode: Node, type: UnusedKind, diagnostic: DiagnosticWithLocation) => void; - - const isNotOverloadAndNotAccessor = and(isNotOverload, isNotAccessor); - - const enum DeclarationMeaning { - GetAccessor = 1, - SetAccessor = 2, - PropertyAssignment = 4, - Method = 8, - GetOrSetAccessor = GetAccessor | SetAccessor, - PropertyAssignmentOrMethod = PropertyAssignment | Method, + function isTransientSymbol(symbol: ts.Symbol): symbol is TransientSymbol { + return (symbol.flags & SymbolFlags.Transient) !== 0; } - - const enum DeclarationSpaces { - None = 0, - ExportValue = 1 << 0, - ExportType = 1 << 1, - ExportNamespace = 1 << 2, + function getExcludedSymbolFlags(flags: SymbolFlags): SymbolFlags { + let result: SymbolFlags = 0; + if (flags & SymbolFlags.BlockScopedVariable) + result |= SymbolFlags.BlockScopedVariableExcludes; + if (flags & SymbolFlags.FunctionScopedVariable) + result |= SymbolFlags.FunctionScopedVariableExcludes; + if (flags & SymbolFlags.Property) + result |= SymbolFlags.PropertyExcludes; + if (flags & SymbolFlags.EnumMember) + result |= SymbolFlags.EnumMemberExcludes; + if (flags & SymbolFlags.Function) + result |= SymbolFlags.FunctionExcludes; + if (flags & SymbolFlags.Class) + result |= SymbolFlags.ClassExcludes; + if (flags & SymbolFlags.Interface) + result |= SymbolFlags.InterfaceExcludes; + if (flags & SymbolFlags.RegularEnum) + result |= SymbolFlags.RegularEnumExcludes; + if (flags & SymbolFlags.ConstEnum) + result |= SymbolFlags.ConstEnumExcludes; + if (flags & SymbolFlags.ValueModule) + result |= SymbolFlags.ValueModuleExcludes; + if (flags & SymbolFlags.Method) + result |= SymbolFlags.MethodExcludes; + if (flags & SymbolFlags.GetAccessor) + result |= SymbolFlags.GetAccessorExcludes; + if (flags & SymbolFlags.SetAccessor) + result |= SymbolFlags.SetAccessorExcludes; + if (flags & SymbolFlags.TypeParameter) + result |= SymbolFlags.TypeParameterExcludes; + if (flags & SymbolFlags.TypeAlias) + result |= SymbolFlags.TypeAliasExcludes; + if (flags & SymbolFlags.Alias) + result |= SymbolFlags.AliasExcludes; + return result; } - - export function getNodeId(node: Node): number { - if (!node.id) { - node.id = nextNodeId; - nextNodeId++; + function recordMergedSymbol(target: ts.Symbol, source: ts.Symbol) { + if (!source.mergeId) { + source.mergeId = nextMergeId; + nextMergeId++; } - return node.id; + mergedSymbols[source.mergeId] = target; } - - export function getSymbolId(symbol: Symbol): number { - if (!symbol.id) { - symbol.id = nextSymbolId; - nextSymbolId++; - } - - return symbol.id; + function cloneSymbol(symbol: ts.Symbol): ts.Symbol { + const result = createSymbol(symbol.flags, symbol.escapedName); + result.declarations = symbol.declarations ? symbol.declarations.slice() : []; + result.parent = symbol.parent; + if (symbol.valueDeclaration) + result.valueDeclaration = symbol.valueDeclaration; + if (symbol.constEnumOnlyModule) + result.constEnumOnlyModule = true; + if (symbol.members) + result.members = cloneMap(symbol.members); + if (symbol.exports) + result.exports = cloneMap(symbol.exports); + recordMergedSymbol(result, symbol); + return result; } - - export function isInstantiatedModule(node: ModuleDeclaration, preserveConstEnums: boolean) { - const moduleState = getModuleInstanceState(node); - return moduleState === ModuleInstanceState.Instantiated || - (preserveConstEnums && moduleState === ModuleInstanceState.ConstEnumOnly); + /** + * Note: if target is transient, then it is mutable, and mergeSymbol with both mutate and return it. + * If target is not transient, mergeSymbol will produce a transient clone, mutate that and return it. + */ + function mergeSymbol(target: ts.Symbol, source: ts.Symbol, unidirectional = false): ts.Symbol { + if (!(target.flags & getExcludedSymbolFlags(source.flags)) || + (source.flags | target.flags) & SymbolFlags.Assignment) { + if (source === target) { + // This can happen when an export assigned namespace exports something also erroneously exported at the top level + // See `declarationFileNoCrashOnExtraExportModifier` for an example + return target; + } + if (!(target.flags & SymbolFlags.Transient)) { + const resolvedTarget = resolveSymbol(target); + if (resolvedTarget === unknownSymbol) { + return source; + } + target = cloneSymbol(resolvedTarget); + } + // Javascript static-property-assignment declarations always merge, even though they are also values + if (source.flags & SymbolFlags.ValueModule && target.flags & SymbolFlags.ValueModule && target.constEnumOnlyModule && !source.constEnumOnlyModule) { + // reset flag when merging instantiated module into value module that has only const enums + target.constEnumOnlyModule = false; + } + target.flags |= source.flags; + if (source.valueDeclaration && + (!target.valueDeclaration || + isAssignmentDeclaration(target.valueDeclaration) && !isAssignmentDeclaration(source.valueDeclaration) || + isEffectiveModuleDeclaration(target.valueDeclaration) && !isEffectiveModuleDeclaration(source.valueDeclaration))) { + // other kinds of value declarations take precedence over modules and assignment declarations + target.valueDeclaration = source.valueDeclaration; + } + addRange(target.declarations, source.declarations); + if (source.members) { + if (!target.members) + target.members = createSymbolTable(); + mergeSymbolTable(target.members, source.members, unidirectional); + } + if (source.exports) { + if (!target.exports) + target.exports = createSymbolTable(); + mergeSymbolTable(target.exports, source.exports, unidirectional); + } + if (!unidirectional) { + recordMergedSymbol(target, source); + } + } + else if (target.flags & SymbolFlags.NamespaceModule) { + // Do not report an error when merging `var globalThis` with the built-in `globalThis`, + // as we will already report a "Declaration name conflicts..." error, and this error + // won't make much sense. + if (target !== globalThisSymbol) { + error(getNameOfDeclaration(source.declarations[0]), Diagnostics.Cannot_augment_module_0_with_value_exports_because_it_resolves_to_a_non_module_entity, symbolToString(target)); + } + } + else { // error + const isEitherEnum = !!(target.flags & SymbolFlags.Enum || source.flags & SymbolFlags.Enum); + const isEitherBlockScoped = !!(target.flags & SymbolFlags.BlockScopedVariable || source.flags & SymbolFlags.BlockScopedVariable); + const message = isEitherEnum + ? Diagnostics.Enum_declarations_can_only_merge_with_namespace_or_other_enum_declarations + : isEitherBlockScoped + ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 + : Diagnostics.Duplicate_identifier_0; + const sourceSymbolFile = source.declarations && getSourceFileOfNode(source.declarations[0]); + const targetSymbolFile = target.declarations && getSourceFileOfNode(target.declarations[0]); + const symbolName = symbolToString(source); + // Collect top-level duplicate identifier errors into one mapping, so we can then merge their diagnostics if there are a bunch + if (sourceSymbolFile && targetSymbolFile && amalgamatedDuplicates && !isEitherEnum && sourceSymbolFile !== targetSymbolFile) { + const firstFile = comparePaths(sourceSymbolFile.path, targetSymbolFile.path) === Comparison.LessThan ? sourceSymbolFile : targetSymbolFile; + const secondFile = firstFile === sourceSymbolFile ? targetSymbolFile : sourceSymbolFile; + const filesDuplicates = getOrUpdate(amalgamatedDuplicates, `${firstFile.path}|${secondFile.path}`, () => ({ firstFile, secondFile, conflictingSymbols: createMap() })); + const conflictingSymbolInfo = getOrUpdate(filesDuplicates.conflictingSymbols, symbolName, () => ({ isBlockScoped: isEitherBlockScoped, firstFileLocations: [], secondFileLocations: [] })); + addDuplicateLocations(conflictingSymbolInfo.firstFileLocations, source); + addDuplicateLocations(conflictingSymbolInfo.secondFileLocations, target); + } + else { + addDuplicateDeclarationErrorsForSymbols(source, message, symbolName, target); + addDuplicateDeclarationErrorsForSymbols(target, message, symbolName, source); + } + } + return target; + function addDuplicateLocations(locs: Node[], symbol: ts.Symbol): void { + for (const decl of symbol.declarations) { + pushIfUnique(locs, (getExpandoInitializer(decl, /*isPrototypeAssignment*/ false) ? getNameOfExpando(decl) : getNameOfDeclaration(decl)) || decl); + } + } } - - export function createTypeChecker(host: TypeCheckerHost, produceDiagnostics: boolean): TypeChecker { - const getPackagesSet: () => Map = memoize(() => { - const set = createMap(); - host.getSourceFiles().forEach(sf => { - if (!sf.resolvedModules) return; - - forEachEntry(sf.resolvedModules, r => { - if (r && r.packageId) set.set(r.packageId.name, true); - }); - }); - return set; + function addDuplicateDeclarationErrorsForSymbols(target: ts.Symbol, message: DiagnosticMessage, symbolName: string, source: ts.Symbol) { + forEach(target.declarations, node => { + const errorNode = (getExpandoInitializer(node, /*isPrototypeAssignment*/ false) ? getNameOfExpando(node) : getNameOfDeclaration(node)) || node; + addDuplicateDeclarationError(errorNode, message, symbolName, source.declarations); }); - - // Cancellation that controls whether or not we can cancel in the middle of type checking. - // In general cancelling is *not* safe for the type checker. We might be in the middle of - // computing something, and we will leave our internals in an inconsistent state. Callers - // who set the cancellation token should catch if a cancellation exception occurs, and - // should throw away and create a new TypeChecker. - // - // Currently we only support setting the cancellation token when getting diagnostics. This - // is because diagnostics can be quite expensive, and we want to allow hosts to bail out if - // they no longer need the information (for example, if the user started editing again). - let cancellationToken: CancellationToken | undefined; - let requestedExternalEmitHelpers: ExternalEmitHelpers; - let externalHelpersModule: Symbol; - - const Symbol = objectAllocator.getSymbolConstructor(); - const Type = objectAllocator.getTypeConstructor(); - const Signature = objectAllocator.getSignatureConstructor(); - - let typeCount = 0; - let symbolCount = 0; - let enumCount = 0; - let instantiationCount = 0; - let instantiationDepth = 0; - let constraintDepth = 0; - let currentNode: Node | undefined; - - const emptySymbols = createSymbolTable(); - const identityMapper: (type: Type) => Type = identity; - - const compilerOptions = host.getCompilerOptions(); - const languageVersion = getEmitScriptTarget(compilerOptions); - const moduleKind = getEmitModuleKind(compilerOptions); - const allowSyntheticDefaultImports = getAllowSyntheticDefaultImports(compilerOptions); - const strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks"); - const strictFunctionTypes = getStrictOptionValue(compilerOptions, "strictFunctionTypes"); - const strictBindCallApply = getStrictOptionValue(compilerOptions, "strictBindCallApply"); - const strictPropertyInitialization = getStrictOptionValue(compilerOptions, "strictPropertyInitialization"); - const noImplicitAny = getStrictOptionValue(compilerOptions, "noImplicitAny"); - const noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis"); - const keyofStringsOnly = !!compilerOptions.keyofStringsOnly; - const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : ObjectFlags.FreshLiteral; - - const emitResolver = createResolver(); - const nodeBuilder = createNodeBuilder(); - - const globals = createSymbolTable(); - const undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined" as __String); - undefinedSymbol.declarations = []; - - const globalThisSymbol = createSymbol(SymbolFlags.Module, "globalThis" as __String, CheckFlags.Readonly); - globalThisSymbol.exports = globals; - globalThisSymbol.declarations = []; - globals.set(globalThisSymbol.escapedName, globalThisSymbol); - - const argumentsSymbol = createSymbol(SymbolFlags.Property, "arguments" as __String); - const requireSymbol = createSymbol(SymbolFlags.Property, "require" as __String); - - /** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */ - let apparentArgumentCount: number | undefined; - - // for public members that accept a Node or one of its subtypes, we must guard against - // synthetic nodes created during transformations by calling `getParseTreeNode`. - // for most of these, we perform the guard only on `checker` to avoid any possible - // extra cost of calling `getParseTreeNode` when calling these functions from inside the - // checker. - const checker: TypeChecker = { - getNodeCount: () => sum(host.getSourceFiles(), "nodeCount"), - getIdentifierCount: () => sum(host.getSourceFiles(), "identifierCount"), - getSymbolCount: () => sum(host.getSourceFiles(), "symbolCount") + symbolCount, - getTypeCount: () => typeCount, - getRelationCacheSizes: () => ({ - assignable: assignableRelation.size, - identity: identityRelation.size, - subtype: subtypeRelation.size, - }), - isUndefinedSymbol: symbol => symbol === undefinedSymbol, - isArgumentsSymbol: symbol => symbol === argumentsSymbol, - isUnknownSymbol: symbol => symbol === unknownSymbol, - getMergedSymbol, - getDiagnostics, - getGlobalDiagnostics, - getTypeOfSymbolAtLocation: (symbol, location) => { - location = getParseTreeNode(location); - return location ? getTypeOfSymbolAtLocation(symbol, location) : errorType; - }, - getSymbolsOfParameterPropertyDeclaration: (parameterIn, parameterName) => { - const parameter = getParseTreeNode(parameterIn, isParameter); - if (parameter === undefined) return Debug.fail("Cannot get symbols of a synthetic parameter that cannot be resolved to a parse-tree node."); - return getSymbolsOfParameterPropertyDeclaration(parameter, escapeLeadingUnderscores(parameterName)); - }, - getDeclaredTypeOfSymbol, - getPropertiesOfType, - getPropertyOfType: (type, name) => getPropertyOfType(type, escapeLeadingUnderscores(name)), - getTypeOfPropertyOfType: (type, name) => getTypeOfPropertyOfType(type, escapeLeadingUnderscores(name)), - getIndexInfoOfType, - getSignaturesOfType, - getIndexTypeOfType, - getBaseTypes, - getBaseTypeOfLiteralType, - getWidenedType, - getTypeFromTypeNode: nodeIn => { - const node = getParseTreeNode(nodeIn, isTypeNode); - return node ? getTypeFromTypeNode(node) : errorType; - }, - getParameterType: getTypeAtPosition, - getPromisedTypeOfPromise, - getReturnTypeOfSignature, - isNullableType, - getNullableType, - getNonNullableType, - getNonOptionalType: removeOptionalTypeMarker, - getTypeArguments, - typeToTypeNode: nodeBuilder.typeToTypeNode, - indexInfoToIndexSignatureDeclaration: nodeBuilder.indexInfoToIndexSignatureDeclaration, - signatureToSignatureDeclaration: nodeBuilder.signatureToSignatureDeclaration, - symbolToEntityName: nodeBuilder.symbolToEntityName, - symbolToExpression: nodeBuilder.symbolToExpression, - symbolToTypeParameterDeclarations: nodeBuilder.symbolToTypeParameterDeclarations, - symbolToParameterDeclaration: nodeBuilder.symbolToParameterDeclaration, - typeParameterToDeclaration: nodeBuilder.typeParameterToDeclaration, - getSymbolsInScope: (location, meaning) => { - location = getParseTreeNode(location); - return location ? getSymbolsInScope(location, meaning) : []; - }, - getSymbolAtLocation: node => { - node = getParseTreeNode(node); - return node ? getSymbolAtLocation(node) : undefined; - }, - getShorthandAssignmentValueSymbol: node => { - node = getParseTreeNode(node); - return node ? getShorthandAssignmentValueSymbol(node) : undefined; - }, - getExportSpecifierLocalTargetSymbol: nodeIn => { - const node = getParseTreeNode(nodeIn, isExportSpecifier); - return node ? getExportSpecifierLocalTargetSymbol(node) : undefined; - }, - getExportSymbolOfSymbol(symbol) { - return getMergedSymbol(symbol.exportSymbol || symbol); - }, - getTypeAtLocation: node => { - node = getParseTreeNode(node); - return node ? getTypeOfNode(node) : errorType; - }, - getTypeOfAssignmentPattern: nodeIn => { - const node = getParseTreeNode(nodeIn, isAssignmentPattern); - return node && getTypeOfAssignmentPattern(node) || errorType; - }, - getPropertySymbolOfDestructuringAssignment: locationIn => { - const location = getParseTreeNode(locationIn, isIdentifier); - return location ? getPropertySymbolOfDestructuringAssignment(location) : undefined; - }, - signatureToString: (signature, enclosingDeclaration, flags, kind) => { - return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind); - }, - typeToString: (type, enclosingDeclaration, flags) => { - return typeToString(type, getParseTreeNode(enclosingDeclaration), flags); - }, - symbolToString: (symbol, enclosingDeclaration, meaning, flags) => { - return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags); - }, - typePredicateToString: (predicate, enclosingDeclaration, flags) => { - return typePredicateToString(predicate, getParseTreeNode(enclosingDeclaration), flags); - }, - writeSignature: (signature, enclosingDeclaration, flags, kind, writer) => { - return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind, writer); - }, - writeType: (type, enclosingDeclaration, flags, writer) => { - return typeToString(type, getParseTreeNode(enclosingDeclaration), flags, writer); - }, - writeSymbol: (symbol, enclosingDeclaration, meaning, flags, writer) => { - return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags, writer); - }, - writeTypePredicate: (predicate, enclosingDeclaration, flags, writer) => { - return typePredicateToString(predicate, getParseTreeNode(enclosingDeclaration), flags, writer); - }, - getAugmentedPropertiesOfType, - getRootSymbols, - getContextualType: (nodeIn: Expression, contextFlags?: ContextFlags) => { - const node = getParseTreeNode(nodeIn, isExpression); - return node ? getContextualType(node, contextFlags) : undefined; - }, - getContextualTypeForObjectLiteralElement: nodeIn => { - const node = getParseTreeNode(nodeIn, isObjectLiteralElementLike); - return node ? getContextualTypeForObjectLiteralElement(node) : undefined; - }, - getContextualTypeForArgumentAtIndex: (nodeIn, argIndex) => { - const node = getParseTreeNode(nodeIn, isCallLikeExpression); - return node && getContextualTypeForArgumentAtIndex(node, argIndex); - }, - getContextualTypeForJsxAttribute: (nodeIn) => { - const node = getParseTreeNode(nodeIn, isJsxAttributeLike); - return node && getContextualTypeForJsxAttribute(node); - }, - isContextSensitive, - getFullyQualifiedName, - getResolvedSignature: (node, candidatesOutArray, argumentCount) => - getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal), - getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) => - getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.IsForSignatureHelp), - getExpandedParameters, - hasEffectiveRestParameter, - getConstantValue: nodeIn => { - const node = getParseTreeNode(nodeIn, canHaveConstantValue); - return node ? getConstantValue(node) : undefined; - }, - isValidPropertyAccess: (nodeIn, propertyName) => { - const node = getParseTreeNode(nodeIn, isPropertyAccessOrQualifiedNameOrImportTypeNode); - return !!node && isValidPropertyAccess(node, escapeLeadingUnderscores(propertyName)); - }, - isValidPropertyAccessForCompletions: (nodeIn, type, property) => { - const node = getParseTreeNode(nodeIn, isPropertyAccessExpression); - return !!node && isValidPropertyAccessForCompletions(node, type, property); - }, - getSignatureFromDeclaration: declarationIn => { - const declaration = getParseTreeNode(declarationIn, isFunctionLike); - return declaration ? getSignatureFromDeclaration(declaration) : undefined; - }, - isImplementationOfOverload: node => { - const parsed = getParseTreeNode(node, isFunctionLike); - return parsed ? isImplementationOfOverload(parsed) : undefined; - }, - getImmediateAliasedSymbol, - getAliasedSymbol: resolveAlias, - getEmitResolver, - getExportsOfModule: getExportsOfModuleAsArray, - getExportsAndPropertiesOfModule, - getSymbolWalker: createGetSymbolWalker( - getRestTypeOfSignature, - getTypePredicateOfSignature, - getReturnTypeOfSignature, - getBaseTypes, - resolveStructuredTypeMembers, - getTypeOfSymbol, - getResolvedSymbol, - getIndexTypeOfStructuredType, - getConstraintOfTypeParameter, - getFirstIdentifier, - getTypeArguments, - ), - getAmbientModules, - getJsxIntrinsicTagNamesAt, - isOptionalParameter: nodeIn => { - const node = getParseTreeNode(nodeIn, isParameter); - return node ? isOptionalParameter(node) : false; - }, - tryGetMemberInModuleExports: (name, symbol) => tryGetMemberInModuleExports(escapeLeadingUnderscores(name), symbol), - tryGetMemberInModuleExportsAndProperties: (name, symbol) => tryGetMemberInModuleExportsAndProperties(escapeLeadingUnderscores(name), symbol), - tryFindAmbientModuleWithoutAugmentations: moduleName => { - // we deliberately exclude augmentations - // since we are only interested in declarations of the module itself - return tryFindAmbientModule(moduleName, /*withAugmentations*/ false); - }, - getApparentType, - getUnionType, - isTypeAssignableTo: (source, target) => { - return isTypeAssignableTo(source, target); - }, - createAnonymousType, - createSignature, - createSymbol, - createIndexInfo, - getAnyType: () => anyType, - getStringType: () => stringType, - getNumberType: () => numberType, - createPromiseType, - createArrayType, - getElementTypeOfArrayType, - getBooleanType: () => booleanType, - getFalseType: (fresh?) => fresh ? falseType : regularFalseType, - getTrueType: (fresh?) => fresh ? trueType : regularTrueType, - getVoidType: () => voidType, - getUndefinedType: () => undefinedType, - getNullType: () => nullType, - getESSymbolType: () => esSymbolType, - getNeverType: () => neverType, - getOptionalType: () => optionalType, - isSymbolAccessible, - isArrayType, - isTupleType, - isArrayLikeType, - isTypeInvalidDueToUnionDiscriminant, - getAllPossiblePropertiesOfTypes, - getSuggestionForNonexistentProperty: (node, type) => getSuggestionForNonexistentProperty(node, type), - getSuggestionForNonexistentSymbol: (location, name, meaning) => getSuggestionForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning), - getSuggestionForNonexistentExport: (node, target) => getSuggestionForNonexistentExport(node, target), - getBaseConstraintOfType, - getDefaultFromTypeParameter: type => type && type.flags & TypeFlags.TypeParameter ? getDefaultFromTypeParameter(type as TypeParameter) : undefined, - resolveName(name, location, meaning, excludeGlobals) { - return resolveName(location, escapeLeadingUnderscores(name), meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false, excludeGlobals); - }, - getJsxNamespace: n => unescapeLeadingUnderscores(getJsxNamespace(n)), - getAccessibleSymbolChain, - getTypePredicateOfSignature, - resolveExternalModuleSymbol, - tryGetThisTypeAt: (node, includeGlobalThis) => { - node = getParseTreeNode(node); - return node && tryGetThisTypeAt(node, includeGlobalThis); - }, - getTypeArgumentConstraint: nodeIn => { - const node = getParseTreeNode(nodeIn, isTypeNode); - return node && getTypeArgumentConstraint(node); - }, - getSuggestionDiagnostics: (file, ct) => { - if (skipTypeChecking(file, compilerOptions, host)) { - return emptyArray; - } - - let diagnostics: DiagnosticWithLocation[] | undefined; - try { - // Record the cancellation token so it can be checked later on during checkSourceElement. - // Do this in a finally block so we can ensure that it gets reset back to nothing after - // this call is done. - cancellationToken = ct; - - // Ensure file is type checked - checkSourceFile(file); - Debug.assert(!!(getNodeLinks(file).flags & NodeCheckFlags.TypeChecked)); - - diagnostics = addRange(diagnostics, suggestionDiagnostics.getDiagnostics(file.fileName)); - checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(file), (containingNode, kind, diag) => { - if (!containsParseError(containingNode) && !unusedIsError(kind, !!(containingNode.flags & NodeFlags.Ambient))) { - (diagnostics || (diagnostics = [])).push({ ...diag, category: DiagnosticCategory.Suggestion }); - } - }); - - return diagnostics || emptyArray; - } - finally { - cancellationToken = undefined; - } - }, - - runWithCancellationToken: (token, callback) => { - try { - cancellationToken = token; - return callback(checker); - } - finally { - cancellationToken = undefined; - } - }, - - getLocalTypeParametersOfClassOrInterfaceOrTypeAlias, - isDeclarationVisible, - }; - - function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined { - const node = getParseTreeNode(nodeIn, isCallLikeExpression); - apparentArgumentCount = argumentCount; - const res = node ? getResolvedSignature(node, candidatesOutArray, checkMode) : undefined; - apparentArgumentCount = undefined; - return res; + } + function addDuplicateDeclarationError(errorNode: Node, message: DiagnosticMessage, symbolName: string, relatedNodes: readonly Node[] | undefined) { + const err = lookupOrIssueError(errorNode, message, symbolName); + for (const relatedNode of relatedNodes || emptyArray) { + err.relatedInformation = err.relatedInformation || []; + if (length(err.relatedInformation) >= 5) + continue; + addRelatedInfo(err, !length(err.relatedInformation) ? createDiagnosticForNode(relatedNode, Diagnostics._0_was_also_declared_here, symbolName) : createDiagnosticForNode(relatedNode, Diagnostics.and_here)); } - - const tupleTypes = createMap(); - const unionTypes = createMap(); - const intersectionTypes = createMap(); - const literalTypes = createMap(); - const indexedAccessTypes = createMap(); - const substitutionTypes = createMap(); - const evolvingArrayTypes: EvolvingArrayType[] = []; - const undefinedProperties = createMap() as UnderscoreEscapedMap; - - const unknownSymbol = createSymbol(SymbolFlags.Property, "unknown" as __String); - const resolvingSymbol = createSymbol(0, InternalSymbolName.Resolving); - - const anyType = createIntrinsicType(TypeFlags.Any, "any"); - const autoType = createIntrinsicType(TypeFlags.Any, "any"); - const wildcardType = createIntrinsicType(TypeFlags.Any, "any"); - const errorType = createIntrinsicType(TypeFlags.Any, "error"); - const nonInferrableAnyType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.ContainsWideningType); - const unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); - const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined"); - const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType); - const optionalType = createIntrinsicType(TypeFlags.Undefined, "undefined"); - const nullType = createIntrinsicType(TypeFlags.Null, "null"); - const nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null, "null", ObjectFlags.ContainsWideningType); - const stringType = createIntrinsicType(TypeFlags.String, "string"); - const numberType = createIntrinsicType(TypeFlags.Number, "number"); - const bigintType = createIntrinsicType(TypeFlags.BigInt, "bigint"); - const falseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType; - const regularFalseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType; - const trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType; - const regularTrueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType; - trueType.regularType = regularTrueType; - trueType.freshType = trueType; - regularTrueType.regularType = regularTrueType; - regularTrueType.freshType = trueType; - falseType.regularType = regularFalseType; - falseType.freshType = falseType; - regularFalseType.regularType = regularFalseType; - regularFalseType.freshType = falseType; - const booleanType = createBooleanType([regularFalseType, regularTrueType]); - // Also mark all combinations of fresh/regular booleans as "Boolean" so they print as `boolean` instead of `true | false` - // (The union is cached, so simply doing the marking here is sufficient) - createBooleanType([regularFalseType, trueType]); - createBooleanType([falseType, regularTrueType]); - createBooleanType([falseType, trueType]); - const esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol"); - const voidType = createIntrinsicType(TypeFlags.Void, "void"); - const neverType = createIntrinsicType(TypeFlags.Never, "never"); - const silentNeverType = createIntrinsicType(TypeFlags.Never, "never"); - const nonInferrableType = createIntrinsicType(TypeFlags.Never, "never", ObjectFlags.NonInferrableType); - const implicitNeverType = createIntrinsicType(TypeFlags.Never, "never"); - const unreachableNeverType = createIntrinsicType(TypeFlags.Never, "never"); - const nonPrimitiveType = createIntrinsicType(TypeFlags.NonPrimitive, "object"); - const stringNumberSymbolType = getUnionType([stringType, numberType, esSymbolType]); - const keyofConstraintType = keyofStringsOnly ? stringType : stringNumberSymbolType; - const numberOrBigIntType = getUnionType([numberType, bigintType]); - - const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); - const emptyJsxObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); - emptyJsxObjectType.objectFlags |= ObjectFlags.JsxAttributes; - - const emptyTypeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); - emptyTypeLiteralSymbol.members = createSymbolTable(); - const emptyTypeLiteralType = createAnonymousType(emptyTypeLiteralSymbol, emptySymbols, emptyArray, emptyArray, undefined, undefined); - - const emptyGenericType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); - emptyGenericType.instantiations = createMap(); - - const anyFunctionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); - // The anyFunctionType contains the anyFunctionType by definition. The flag is further propagated - // in getPropagatingFlagsOfTypes, and it is checked in inferFromTypes. - anyFunctionType.objectFlags |= ObjectFlags.NonInferrableType; - - const noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); - const circularConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); - const resolvingDefaultType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); - - const markerSuperType = createTypeParameter(); - const markerSubType = createTypeParameter(); - markerSubType.constraint = markerSuperType; - const markerOtherType = createTypeParameter(); - - const noTypePredicate = createTypePredicate(TypePredicateKind.Identifier, "<>", 0, anyType); - - const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); - const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, errorType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); - const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); - const silentNeverSignature = createSignature(undefined, undefined, undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); - - const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true); - - const iterationTypesCache = createMap(); // cache for common IterationTypes instances - const noIterationTypes: IterationTypes = { - get yieldType(): Type { throw new Error("Not supported"); }, - get returnType(): Type { throw new Error("Not supported"); }, - get nextType(): Type { throw new Error("Not supported"); }, - }; - const anyIterationTypes = createIterationTypes(anyType, anyType, anyType); - const anyIterationTypesExceptNext = createIterationTypes(anyType, anyType, unknownType); - const defaultIterationTypes = createIterationTypes(neverType, anyType, undefinedType); // default iteration types for `Iterator`. - - const asyncIterationTypesResolver: IterationTypesResolver = { - iterableCacheKey: "iterationTypesOfAsyncIterable", - iteratorCacheKey: "iterationTypesOfAsyncIterator", - iteratorSymbolName: "asyncIterator", - getGlobalIteratorType: getGlobalAsyncIteratorType, - getGlobalIterableType: getGlobalAsyncIterableType, - getGlobalIterableIteratorType: getGlobalAsyncIterableIteratorType, - getGlobalGeneratorType: getGlobalAsyncGeneratorType, - resolveIterationType: getAwaitedType, - mustHaveANextMethodDiagnostic: Diagnostics.An_async_iterator_must_have_a_next_method, - mustBeAMethodDiagnostic: Diagnostics.The_0_property_of_an_async_iterator_must_be_a_method, - mustHaveAValueDiagnostic: Diagnostics.The_type_returned_by_the_0_method_of_an_async_iterator_must_be_a_promise_for_a_type_with_a_value_property, - }; - - const syncIterationTypesResolver: IterationTypesResolver = { - iterableCacheKey: "iterationTypesOfIterable", - iteratorCacheKey: "iterationTypesOfIterator", - iteratorSymbolName: "iterator", - getGlobalIteratorType, - getGlobalIterableType, - getGlobalIterableIteratorType, - getGlobalGeneratorType, - resolveIterationType: (type, _errorNode) => type, - mustHaveANextMethodDiagnostic: Diagnostics.An_iterator_must_have_a_next_method, - mustBeAMethodDiagnostic: Diagnostics.The_0_property_of_an_iterator_must_be_a_method, - mustHaveAValueDiagnostic: Diagnostics.The_type_returned_by_the_0_method_of_an_iterator_must_have_a_value_property, - }; - - interface DuplicateInfoForSymbol { - readonly firstFileLocations: Node[]; - readonly secondFileLocations: Node[]; - readonly isBlockScoped: boolean; - } - interface DuplicateInfoForFiles { - readonly firstFile: SourceFile; - readonly secondFile: SourceFile; - /** Key is symbol name. */ - readonly conflictingSymbols: Map; - } - /** Key is "/path/to/a.ts|/path/to/b.ts". */ - let amalgamatedDuplicates: Map | undefined; - const reverseMappedCache = createMap(); - let ambientModulesCache: Symbol[] | undefined; - /** - * List of every ambient module with a "*" wildcard. - * Unlike other ambient modules, these can't be stored in `globals` because symbol tables only deal with exact matches. - * This is only used if there is no exact match. - */ - let patternAmbientModules: PatternAmbientModule[]; - let patternAmbientModuleAugmentations: Map | undefined; - - let globalObjectType: ObjectType; - let globalFunctionType: ObjectType; - let globalCallableFunctionType: ObjectType; - let globalNewableFunctionType: ObjectType; - let globalArrayType: GenericType; - let globalReadonlyArrayType: GenericType; - let globalStringType: ObjectType; - let globalNumberType: ObjectType; - let globalBooleanType: ObjectType; - let globalRegExpType: ObjectType; - let globalThisType: GenericType; - let anyArrayType: Type; - let autoArrayType: Type; - let anyReadonlyArrayType: Type; - let deferredGlobalNonNullableTypeAlias: Symbol; - - // The library files are only loaded when the feature is used. - // This allows users to just specify library files they want to used through --lib - // and they will not get an error from not having unrelated library files - let deferredGlobalESSymbolConstructorSymbol: Symbol | undefined; - let deferredGlobalESSymbolType: ObjectType; - let deferredGlobalTypedPropertyDescriptorType: GenericType; - let deferredGlobalPromiseType: GenericType; - let deferredGlobalPromiseLikeType: GenericType; - let deferredGlobalPromiseConstructorSymbol: Symbol | undefined; - let deferredGlobalPromiseConstructorLikeType: ObjectType; - let deferredGlobalIterableType: GenericType; - let deferredGlobalIteratorType: GenericType; - let deferredGlobalIterableIteratorType: GenericType; - let deferredGlobalGeneratorType: GenericType; - let deferredGlobalIteratorYieldResultType: GenericType; - let deferredGlobalIteratorReturnResultType: GenericType; - let deferredGlobalAsyncIterableType: GenericType; - let deferredGlobalAsyncIteratorType: GenericType; - let deferredGlobalAsyncIterableIteratorType: GenericType; - let deferredGlobalAsyncGeneratorType: GenericType; - let deferredGlobalTemplateStringsArrayType: ObjectType; - let deferredGlobalImportMetaType: ObjectType; - let deferredGlobalExtractSymbol: Symbol; - let deferredGlobalOmitSymbol: Symbol; - let deferredGlobalBigIntType: ObjectType; - - const allPotentiallyUnusedIdentifiers = createMap(); // key is file name - - let flowLoopStart = 0; - let flowLoopCount = 0; - let sharedFlowCount = 0; - let flowAnalysisDisabled = false; - let flowInvocationCount = 0; - let lastFlowNode: FlowNode | undefined; - let lastFlowNodeReachable: boolean; - let flowTypeCache: Type[] | undefined; - - const emptyStringType = getLiteralType(""); - const zeroType = getLiteralType(0); - const zeroBigIntType = getLiteralType({ negative: false, base10Value: "0" }); - - const resolutionTargets: TypeSystemEntity[] = []; - const resolutionResults: boolean[] = []; - const resolutionPropertyNames: TypeSystemPropertyName[] = []; - - let suggestionCount = 0; - const maximumSuggestionCount = 10; - const mergedSymbols: Symbol[] = []; - const symbolLinks: SymbolLinks[] = []; - const nodeLinks: NodeLinks[] = []; - const flowLoopCaches: Map[] = []; - const flowLoopNodes: FlowNode[] = []; - const flowLoopKeys: string[] = []; - const flowLoopTypes: Type[][] = []; - const sharedFlowNodes: FlowNode[] = []; - const sharedFlowTypes: FlowType[] = []; - const flowNodeReachable: (boolean | undefined)[] = []; - const potentialThisCollisions: Node[] = []; - const potentialNewTargetCollisions: Node[] = []; - const awaitedTypeStack: number[] = []; - - const diagnostics = createDiagnosticCollection(); - const suggestionDiagnostics = createDiagnosticCollection(); - - const typeofTypesByName: ReadonlyMap = createMapFromTemplate({ - string: stringType, - number: numberType, - bigint: bigintType, - boolean: booleanType, - symbol: esSymbolType, - undefined: undefinedType + } + function combineSymbolTables(first: SymbolTable | undefined, second: SymbolTable | undefined): SymbolTable | undefined { + if (!hasEntries(first)) + return second; + if (!hasEntries(second)) + return first; + const combined = createSymbolTable(); + mergeSymbolTable(combined, first); + mergeSymbolTable(combined, second); + return combined; + } + function mergeSymbolTable(target: SymbolTable, source: SymbolTable, unidirectional = false) { + source.forEach((sourceSymbol, id) => { + const targetSymbol = target.get(id); + target.set(id, targetSymbol ? mergeSymbol(targetSymbol, sourceSymbol, unidirectional) : sourceSymbol); }); - const typeofType = createTypeofType(); - - let _jsxNamespace: __String; - let _jsxFactoryEntity: EntityName | undefined; - let outofbandVarianceMarkerHandler: ((onlyUnreliable: boolean) => void) | undefined; - - const subtypeRelation = createMap(); - const assignableRelation = createMap(); - const comparableRelation = createMap(); - const identityRelation = createMap(); - const enumRelation = createMap(); - - const builtinGlobals = createSymbolTable(); - builtinGlobals.set(undefinedSymbol.escapedName, undefinedSymbol); - - initializeTypeChecker(); - - return checker; - - function getJsxNamespace(location: Node | undefined): __String { - if (location) { - const file = getSourceFileOfNode(location); - if (file) { - if (file.localJsxNamespace) { - return file.localJsxNamespace; - } - const jsxPragma = file.pragmas.get("jsx"); - if (jsxPragma) { - const chosenpragma = isArray(jsxPragma) ? jsxPragma[0] : jsxPragma; - file.localJsxFactory = parseIsolatedEntityName(chosenpragma.arguments.factory, languageVersion); - if (file.localJsxFactory) { - return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText; - } - } - } + } + function mergeModuleAugmentation(moduleName: StringLiteral | Identifier): void { + const moduleAugmentation = (moduleName.parent); + if (moduleAugmentation.symbol.declarations[0] !== moduleAugmentation) { + // this is a combined symbol for multiple augmentations within the same file. + // its symbol already has accumulated information for all declarations + // so we need to add it just once - do the work only for first declaration + Debug.assert(moduleAugmentation.symbol.declarations.length > 1); + return; + } + if (isGlobalScopeAugmentation(moduleAugmentation)) { + mergeSymbolTable(globals, moduleAugmentation.symbol.exports!); + } + else { + // find a module that about to be augmented + // do not validate names of augmentations that are defined in ambient context + const moduleNotFoundError = !(moduleName.parent.parent.flags & NodeFlags.Ambient) + ? Diagnostics.Invalid_module_name_in_augmentation_module_0_cannot_be_found + : undefined; + let mainModule = resolveExternalModuleNameWorker(moduleName, moduleName, moduleNotFoundError, /*isForAugmentation*/ true); + if (!mainModule) { + return; } - if (!_jsxNamespace) { - _jsxNamespace = "React" as __String; - if (compilerOptions.jsxFactory) { - _jsxFactoryEntity = parseIsolatedEntityName(compilerOptions.jsxFactory, languageVersion); - if (_jsxFactoryEntity) { - _jsxNamespace = getFirstIdentifier(_jsxFactoryEntity).escapedText; + // obtain item referenced by 'export=' + mainModule = resolveExternalModuleSymbol(mainModule); + if (mainModule.flags & SymbolFlags.Namespace) { + // If we're merging an augmentation to a pattern ambient module, we want to + // perform the merge unidirectionally from the augmentation ('a.foo') to + // the pattern ('*.foo'), so that 'getMergedSymbol()' on a.foo gives you + // all the exports both from the pattern and from the augmentation, but + // 'getMergedSymbol()' on *.foo only gives you exports from *.foo. + if (some(patternAmbientModules, module => mainModule === module.symbol)) { + const merged = mergeSymbol(moduleAugmentation.symbol, mainModule, /*unidirectional*/ true); + if (!patternAmbientModuleAugmentations) { + patternAmbientModuleAugmentations = createMap(); } + // moduleName will be a StringLiteral since this is not `declare global`. + patternAmbientModuleAugmentations.set((moduleName as StringLiteral).text, merged); } - else if (compilerOptions.reactNamespace) { - _jsxNamespace = escapeLeadingUnderscores(compilerOptions.reactNamespace); + else { + mergeSymbol(mainModule, moduleAugmentation.symbol); } } - return _jsxNamespace; - } - - function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken) { - // Ensure we have all the type information in place for this file so that all the - // emitter questions of this resolver will return the right information. - getDiagnostics(sourceFile, cancellationToken); - return emitResolver; - } - - function lookupOrIssueError(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { - const diagnostic = location - ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) - : createCompilerDiagnostic(message, arg0, arg1, arg2, arg3); - const existing = diagnostics.lookup(diagnostic); - if (existing) { - return existing; - } else { - diagnostics.add(diagnostic); - return diagnostic; + // moduleName will be a StringLiteral since this is not `declare global`. + error(moduleName, Diagnostics.Cannot_augment_module_0_because_it_resolves_to_a_non_module_entity, (moduleName as StringLiteral).text); } } - - function error(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { - const diagnostic = location - ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) - : createCompilerDiagnostic(message, arg0, arg1, arg2, arg3); - diagnostics.add(diagnostic); - return diagnostic; - } - - function addErrorOrSuggestion(isError: boolean, diagnostic: DiagnosticWithLocation) { - if (isError) { - diagnostics.add(diagnostic); + } + function addToSymbolTable(target: SymbolTable, source: SymbolTable, message: DiagnosticMessage) { + source.forEach((sourceSymbol, id) => { + const targetSymbol = target.get(id); + if (targetSymbol) { + // Error on redeclarations + forEach(targetSymbol.declarations, addDeclarationDiagnostic(unescapeLeadingUnderscores(id), message)); } else { - suggestionDiagnostics.add({ ...diagnostic, category: DiagnosticCategory.Suggestion }); + target.set(id, sourceSymbol); } + }); + function addDeclarationDiagnostic(id: string, message: DiagnosticMessage) { + return (declaration: Declaration) => diagnostics.add(createDiagnosticForNode(declaration, message, id)); } - function errorOrSuggestion(isError: boolean, location: Node, message: DiagnosticMessage | DiagnosticMessageChain, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void { - addErrorOrSuggestion(isError, "message" in message ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) : createDiagnosticForNodeFromMessageChain(location, message)); // eslint-disable-line no-in-operator - } - - function errorAndMaybeSuggestAwait( - location: Node, - maybeMissingAwait: boolean, - message: DiagnosticMessage, - arg0?: string | number | undefined, arg1?: string | number | undefined, arg2?: string | number | undefined, arg3?: string | number | undefined): Diagnostic { - const diagnostic = error(location, message, arg0, arg1, arg2, arg3); - if (maybeMissingAwait) { - const related = createDiagnosticForNode(location, Diagnostics.Did_you_forget_to_use_await); - addRelatedInfo(diagnostic, related); - } - return diagnostic; - } - - function createSymbol(flags: SymbolFlags, name: __String, checkFlags?: CheckFlags) { - symbolCount++; - const symbol = (new Symbol(flags | SymbolFlags.Transient, name)); - symbol.checkFlags = checkFlags || 0; - return symbol; - } - - function isTransientSymbol(symbol: Symbol): symbol is TransientSymbol { - return (symbol.flags & SymbolFlags.Transient) !== 0; - } - - function getExcludedSymbolFlags(flags: SymbolFlags): SymbolFlags { - let result: SymbolFlags = 0; - if (flags & SymbolFlags.BlockScopedVariable) result |= SymbolFlags.BlockScopedVariableExcludes; - if (flags & SymbolFlags.FunctionScopedVariable) result |= SymbolFlags.FunctionScopedVariableExcludes; - if (flags & SymbolFlags.Property) result |= SymbolFlags.PropertyExcludes; - if (flags & SymbolFlags.EnumMember) result |= SymbolFlags.EnumMemberExcludes; - if (flags & SymbolFlags.Function) result |= SymbolFlags.FunctionExcludes; - if (flags & SymbolFlags.Class) result |= SymbolFlags.ClassExcludes; - if (flags & SymbolFlags.Interface) result |= SymbolFlags.InterfaceExcludes; - if (flags & SymbolFlags.RegularEnum) result |= SymbolFlags.RegularEnumExcludes; - if (flags & SymbolFlags.ConstEnum) result |= SymbolFlags.ConstEnumExcludes; - if (flags & SymbolFlags.ValueModule) result |= SymbolFlags.ValueModuleExcludes; - if (flags & SymbolFlags.Method) result |= SymbolFlags.MethodExcludes; - if (flags & SymbolFlags.GetAccessor) result |= SymbolFlags.GetAccessorExcludes; - if (flags & SymbolFlags.SetAccessor) result |= SymbolFlags.SetAccessorExcludes; - if (flags & SymbolFlags.TypeParameter) result |= SymbolFlags.TypeParameterExcludes; - if (flags & SymbolFlags.TypeAlias) result |= SymbolFlags.TypeAliasExcludes; - if (flags & SymbolFlags.Alias) result |= SymbolFlags.AliasExcludes; - return result; - } - - function recordMergedSymbol(target: Symbol, source: Symbol) { - if (!source.mergeId) { - source.mergeId = nextMergeId; - nextMergeId++; - } - mergedSymbols[source.mergeId] = target; - } - - function cloneSymbol(symbol: Symbol): Symbol { - const result = createSymbol(symbol.flags, symbol.escapedName); - result.declarations = symbol.declarations ? symbol.declarations.slice() : []; - result.parent = symbol.parent; - if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; - if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; - if (symbol.members) result.members = cloneMap(symbol.members); - if (symbol.exports) result.exports = cloneMap(symbol.exports); - recordMergedSymbol(result, symbol); - return result; - } - - /** - * Note: if target is transient, then it is mutable, and mergeSymbol with both mutate and return it. - * If target is not transient, mergeSymbol will produce a transient clone, mutate that and return it. - */ - function mergeSymbol(target: Symbol, source: Symbol, unidirectional = false): Symbol { - if (!(target.flags & getExcludedSymbolFlags(source.flags)) || - (source.flags | target.flags) & SymbolFlags.Assignment) { - if (source === target) { - // This can happen when an export assigned namespace exports something also erroneously exported at the top level - // See `declarationFileNoCrashOnExtraExportModifier` for an example - return target; - } - if (!(target.flags & SymbolFlags.Transient)) { - const resolvedTarget = resolveSymbol(target); - if (resolvedTarget === unknownSymbol) { - return source; - } - target = cloneSymbol(resolvedTarget); - } - // Javascript static-property-assignment declarations always merge, even though they are also values - if (source.flags & SymbolFlags.ValueModule && target.flags & SymbolFlags.ValueModule && target.constEnumOnlyModule && !source.constEnumOnlyModule) { - // reset flag when merging instantiated module into value module that has only const enums - target.constEnumOnlyModule = false; - } - target.flags |= source.flags; - if (source.valueDeclaration && - (!target.valueDeclaration || - isAssignmentDeclaration(target.valueDeclaration) && !isAssignmentDeclaration(source.valueDeclaration) || - isEffectiveModuleDeclaration(target.valueDeclaration) && !isEffectiveModuleDeclaration(source.valueDeclaration))) { - // other kinds of value declarations take precedence over modules and assignment declarations - target.valueDeclaration = source.valueDeclaration; - } - addRange(target.declarations, source.declarations); - if (source.members) { - if (!target.members) target.members = createSymbolTable(); - mergeSymbolTable(target.members, source.members, unidirectional); - } - if (source.exports) { - if (!target.exports) target.exports = createSymbolTable(); - mergeSymbolTable(target.exports, source.exports, unidirectional); - } - if (!unidirectional) { - recordMergedSymbol(target, source); - } - } - else if (target.flags & SymbolFlags.NamespaceModule) { - // Do not report an error when merging `var globalThis` with the built-in `globalThis`, - // as we will already report a "Declaration name conflicts..." error, and this error - // won't make much sense. - if (target !== globalThisSymbol) { - error(getNameOfDeclaration(source.declarations[0]), Diagnostics.Cannot_augment_module_0_with_value_exports_because_it_resolves_to_a_non_module_entity, symbolToString(target)); - } - } - else { // error - const isEitherEnum = !!(target.flags & SymbolFlags.Enum || source.flags & SymbolFlags.Enum); - const isEitherBlockScoped = !!(target.flags & SymbolFlags.BlockScopedVariable || source.flags & SymbolFlags.BlockScopedVariable); - const message = isEitherEnum - ? Diagnostics.Enum_declarations_can_only_merge_with_namespace_or_other_enum_declarations - : isEitherBlockScoped - ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 - : Diagnostics.Duplicate_identifier_0; - const sourceSymbolFile = source.declarations && getSourceFileOfNode(source.declarations[0]); - const targetSymbolFile = target.declarations && getSourceFileOfNode(target.declarations[0]); - const symbolName = symbolToString(source); - - // Collect top-level duplicate identifier errors into one mapping, so we can then merge their diagnostics if there are a bunch - if (sourceSymbolFile && targetSymbolFile && amalgamatedDuplicates && !isEitherEnum && sourceSymbolFile !== targetSymbolFile) { - const firstFile = comparePaths(sourceSymbolFile.path, targetSymbolFile.path) === Comparison.LessThan ? sourceSymbolFile : targetSymbolFile; - const secondFile = firstFile === sourceSymbolFile ? targetSymbolFile : sourceSymbolFile; - const filesDuplicates = getOrUpdate(amalgamatedDuplicates, `${firstFile.path}|${secondFile.path}`, () => - ({ firstFile, secondFile, conflictingSymbols: createMap() })); - const conflictingSymbolInfo = getOrUpdate(filesDuplicates.conflictingSymbols, symbolName, () => - ({ isBlockScoped: isEitherBlockScoped, firstFileLocations: [], secondFileLocations: [] })); - addDuplicateLocations(conflictingSymbolInfo.firstFileLocations, source); - addDuplicateLocations(conflictingSymbolInfo.secondFileLocations, target); - } - else { - addDuplicateDeclarationErrorsForSymbols(source, message, symbolName, target); - addDuplicateDeclarationErrorsForSymbols(target, message, symbolName, source); + } + function getSymbolLinks(symbol: ts.Symbol): SymbolLinks { + if (symbol.flags & SymbolFlags.Transient) + return symbol; + const id = getSymbolId(symbol); + return symbolLinks[id] || (symbolLinks[id] = {}); + } + function getNodeLinks(node: Node): NodeLinks { + const nodeId = getNodeId(node); + return nodeLinks[nodeId] || (nodeLinks[nodeId] = ({ flags: 0 } as NodeLinks)); + } + function isGlobalSourceFile(node: Node) { + return node.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule((node)); + } + function getSymbol(symbols: SymbolTable, name: __String, meaning: SymbolFlags): ts.Symbol | undefined { + if (meaning) { + const symbol = symbols.get(name); + if (symbol) { + Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); + if (symbol.flags & meaning) { + return symbol; } - } - return target; - - function addDuplicateLocations(locs: Node[], symbol: Symbol): void { - for (const decl of symbol.declarations) { - pushIfUnique(locs, (getExpandoInitializer(decl, /*isPrototypeAssignment*/ false) ? getNameOfExpando(decl) : getNameOfDeclaration(decl)) || decl); + if (symbol.flags & SymbolFlags.Alias) { + const target = resolveAlias(symbol); + // Unknown symbol means an error occurred in alias resolution, treat it as positive answer to avoid cascading errors + if (target === unknownSymbol || target.flags & meaning) { + return symbol; + } } } } - - function addDuplicateDeclarationErrorsForSymbols(target: Symbol, message: DiagnosticMessage, symbolName: string, source: Symbol) { - forEach(target.declarations, node => { - const errorNode = (getExpandoInitializer(node, /*isPrototypeAssignment*/ false) ? getNameOfExpando(node) : getNameOfDeclaration(node)) || node; - addDuplicateDeclarationError(errorNode, message, symbolName, source.declarations); - }); - } - - function addDuplicateDeclarationError(errorNode: Node, message: DiagnosticMessage, symbolName: string, relatedNodes: readonly Node[] | undefined) { - const err = lookupOrIssueError(errorNode, message, symbolName); - for (const relatedNode of relatedNodes || emptyArray) { - err.relatedInformation = err.relatedInformation || []; - if (length(err.relatedInformation) >= 5) continue; - addRelatedInfo(err, !length(err.relatedInformation) ? createDiagnosticForNode(relatedNode, Diagnostics._0_was_also_declared_here, symbolName) : createDiagnosticForNode(relatedNode, Diagnostics.and_here)); + // return undefined if we can't find a symbol. + } + /** + * Get symbols that represent parameter-property-declaration as parameter and as property declaration + * @param parameter a parameterDeclaration node + * @param parameterName a name of the parameter to get the symbols for. + * @return a tuple of two symbols + */ + function getSymbolsOfParameterPropertyDeclaration(parameter: ParameterDeclaration, parameterName: __String): [ts.Symbol, ts.Symbol] { + const constructorDeclaration = parameter.parent; + const classDeclaration = parameter.parent.parent; + const parameterSymbol = getSymbol((constructorDeclaration.locals!), parameterName, SymbolFlags.Value); + const propertySymbol = getSymbol(getMembersOfSymbol(classDeclaration.symbol), parameterName, SymbolFlags.Value); + if (parameterSymbol && propertySymbol) { + return [parameterSymbol, propertySymbol]; + } + return Debug.fail("There should exist two symbols, one as property declaration and one as parameter declaration"); + } + function isBlockScopedNameDeclaredBeforeUse(declaration: Declaration, usage: Node): boolean { + const declarationFile = getSourceFileOfNode(declaration); + const useFile = getSourceFileOfNode(usage); + if (declarationFile !== useFile) { + if ((moduleKind && (declarationFile.externalModuleIndicator || useFile.externalModuleIndicator)) || + (!compilerOptions.outFile && !compilerOptions.out) || + isInTypeQuery(usage) || + declaration.flags & NodeFlags.Ambient) { + // nodes are in different files and order cannot be determined + return true; } + // declaration is after usage + // can be legal if usage is deferred (i.e. inside function or in initializer of instance property) + if (isUsedInFunctionOrInstanceProperty(usage, declaration)) { + return true; + } + const sourceFiles = host.getSourceFiles(); + return sourceFiles.indexOf(declarationFile) <= sourceFiles.indexOf(useFile); } - - function combineSymbolTables(first: SymbolTable | undefined, second: SymbolTable | undefined): SymbolTable | undefined { - if (!hasEntries(first)) return second; - if (!hasEntries(second)) return first; - const combined = createSymbolTable(); - mergeSymbolTable(combined, first); - mergeSymbolTable(combined, second); - return combined; - } - - function mergeSymbolTable(target: SymbolTable, source: SymbolTable, unidirectional = false) { - source.forEach((sourceSymbol, id) => { - const targetSymbol = target.get(id); - target.set(id, targetSymbol ? mergeSymbol(targetSymbol, sourceSymbol, unidirectional) : sourceSymbol); - }); - } - - function mergeModuleAugmentation(moduleName: StringLiteral | Identifier): void { - const moduleAugmentation = moduleName.parent; - if (moduleAugmentation.symbol.declarations[0] !== moduleAugmentation) { - // this is a combined symbol for multiple augmentations within the same file. - // its symbol already has accumulated information for all declarations - // so we need to add it just once - do the work only for first declaration - Debug.assert(moduleAugmentation.symbol.declarations.length > 1); - return; + if (declaration.pos <= usage.pos) { + // declaration is before usage + if (declaration.kind === SyntaxKind.BindingElement) { + // still might be illegal if declaration and usage are both binding elements (eg var [a = b, b = b] = [1, 2]) + const errorBindingElement = (getAncestor(usage, SyntaxKind.BindingElement) as BindingElement); + if (errorBindingElement) { + return findAncestor(errorBindingElement, isBindingElement) !== findAncestor(declaration, isBindingElement) || + declaration.pos < errorBindingElement.pos; + } + // or it might be illegal if usage happens before parent variable is declared (eg var [a] = a) + return isBlockScopedNameDeclaredBeforeUse((getAncestor(declaration, SyntaxKind.VariableDeclaration) as Declaration), usage); } - - if (isGlobalScopeAugmentation(moduleAugmentation)) { - mergeSymbolTable(globals, moduleAugmentation.symbol.exports!); + else if (declaration.kind === SyntaxKind.VariableDeclaration) { + // still might be illegal if usage is in the initializer of the variable declaration (eg var a = a) + return !isImmediatelyUsedInInitializerOfBlockScopedVariable((declaration as VariableDeclaration), usage); } - else { - // find a module that about to be augmented - // do not validate names of augmentations that are defined in ambient context - const moduleNotFoundError = !(moduleName.parent.parent.flags & NodeFlags.Ambient) - ? Diagnostics.Invalid_module_name_in_augmentation_module_0_cannot_be_found - : undefined; - let mainModule = resolveExternalModuleNameWorker(moduleName, moduleName, moduleNotFoundError, /*isForAugmentation*/ true); - if (!mainModule) { - return; - } - // obtain item referenced by 'export=' - mainModule = resolveExternalModuleSymbol(mainModule); - if (mainModule.flags & SymbolFlags.Namespace) { - // If we're merging an augmentation to a pattern ambient module, we want to - // perform the merge unidirectionally from the augmentation ('a.foo') to - // the pattern ('*.foo'), so that 'getMergedSymbol()' on a.foo gives you - // all the exports both from the pattern and from the augmentation, but - // 'getMergedSymbol()' on *.foo only gives you exports from *.foo. - if (some(patternAmbientModules, module => mainModule === module.symbol)) { - const merged = mergeSymbol(moduleAugmentation.symbol, mainModule, /*unidirectional*/ true); - if (!patternAmbientModuleAugmentations) { - patternAmbientModuleAugmentations = createMap(); - } - // moduleName will be a StringLiteral since this is not `declare global`. - patternAmbientModuleAugmentations.set((moduleName as StringLiteral).text, merged); - } - else { - mergeSymbol(mainModule, moduleAugmentation.symbol); - } - } - else { - // moduleName will be a StringLiteral since this is not `declare global`. - error(moduleName, Diagnostics.Cannot_augment_module_0_because_it_resolves_to_a_non_module_entity, (moduleName as StringLiteral).text); - } + else if (isClassDeclaration(declaration)) { + // still might be illegal if the usage is within a computed property name in the class (eg class A { static p = "a"; [A.p]() {} }) + return !findAncestor(usage, n => isComputedPropertyName(n) && n.parent.parent === declaration); } - } - - function addToSymbolTable(target: SymbolTable, source: SymbolTable, message: DiagnosticMessage) { - source.forEach((sourceSymbol, id) => { - const targetSymbol = target.get(id); - if (targetSymbol) { - // Error on redeclarations - forEach(targetSymbol.declarations, addDeclarationDiagnostic(unescapeLeadingUnderscores(id), message)); - } - else { - target.set(id, sourceSymbol); - } - }); - - function addDeclarationDiagnostic(id: string, message: DiagnosticMessage) { - return (declaration: Declaration) => diagnostics.add(createDiagnosticForNode(declaration, message, id)); + else if (isPropertyDeclaration(declaration)) { + // still might be illegal if a self-referencing property initializer (eg private x = this.x) + return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage); } + return true; } - - function getSymbolLinks(symbol: Symbol): SymbolLinks { - if (symbol.flags & SymbolFlags.Transient) return symbol; - const id = getSymbolId(symbol); - return symbolLinks[id] || (symbolLinks[id] = {}); - } - - function getNodeLinks(node: Node): NodeLinks { - const nodeId = getNodeId(node); - return nodeLinks[nodeId] || (nodeLinks[nodeId] = { flags: 0 } as NodeLinks); + // declaration is after usage, but it can still be legal if usage is deferred: + // 1. inside an export specifier + // 2. inside a function + // 3. inside an instance property initializer, a reference to a non-instance property + // 4. inside a static property initializer, a reference to a static method in the same class + // 5. inside a TS export= declaration (since we will move the export statement during emit to avoid TDZ) + // or if usage is in a type context: + // 1. inside a type query (typeof in type position) + // 2. inside a jsdoc comment + if (usage.parent.kind === SyntaxKind.ExportSpecifier || (usage.parent.kind === SyntaxKind.ExportAssignment && (usage.parent as ExportAssignment).isExportEquals)) { + // export specifiers do not use the variable, they only make it available for use + return true; } - - function isGlobalSourceFile(node: Node) { - return node.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(node); + // When resolving symbols for exports, the `usage` location passed in can be the export site directly + if (usage.kind === SyntaxKind.ExportAssignment && (usage as ExportAssignment).isExportEquals) { + return true; } - - function getSymbol(symbols: SymbolTable, name: __String, meaning: SymbolFlags): Symbol | undefined { - if (meaning) { - const symbol = symbols.get(name); - if (symbol) { - Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); - if (symbol.flags & meaning) { - return symbol; - } - if (symbol.flags & SymbolFlags.Alias) { - const target = resolveAlias(symbol); - // Unknown symbol means an error occurred in alias resolution, treat it as positive answer to avoid cascading errors - if (target === unknownSymbol || target.flags & meaning) { - return symbol; - } + const container = getEnclosingBlockScopeContainer(declaration); + return !!(usage.flags & NodeFlags.JSDoc) || isInTypeQuery(usage) || isUsedInFunctionOrInstanceProperty(usage, declaration, container); + function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean { + const container = getEnclosingBlockScopeContainer(declaration); + switch (declaration.parent.parent.kind) { + case SyntaxKind.VariableStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForOfStatement: + // variable statement/for/for-of statement case, + // use site should not be inside variable declaration (initializer of declaration or binding element) + if (isSameScopeDescendentOf(usage, declaration, container)) { + return true; } - } - } - // return undefined if we can't find a symbol. - } - - /** - * Get symbols that represent parameter-property-declaration as parameter and as property declaration - * @param parameter a parameterDeclaration node - * @param parameterName a name of the parameter to get the symbols for. - * @return a tuple of two symbols - */ - function getSymbolsOfParameterPropertyDeclaration(parameter: ParameterDeclaration, parameterName: __String): [Symbol, Symbol] { - const constructorDeclaration = parameter.parent; - const classDeclaration = parameter.parent.parent; - - const parameterSymbol = getSymbol(constructorDeclaration.locals!, parameterName, SymbolFlags.Value); - const propertySymbol = getSymbol(getMembersOfSymbol(classDeclaration.symbol), parameterName, SymbolFlags.Value); - - if (parameterSymbol && propertySymbol) { - return [parameterSymbol, propertySymbol]; + break; } - - return Debug.fail("There should exist two symbols, one as property declaration and one as parameter declaration"); + // ForIn/ForOf case - use site should not be used in expression part + const grandparent = declaration.parent.parent; + return isForInOrOfStatement(grandparent) && isSameScopeDescendentOf(usage, grandparent.expression, container); } - - function isBlockScopedNameDeclaredBeforeUse(declaration: Declaration, usage: Node): boolean { - const declarationFile = getSourceFileOfNode(declaration); - const useFile = getSourceFileOfNode(usage); - if (declarationFile !== useFile) { - if ((moduleKind && (declarationFile.externalModuleIndicator || useFile.externalModuleIndicator)) || - (!compilerOptions.outFile && !compilerOptions.out) || - isInTypeQuery(usage) || - declaration.flags & NodeFlags.Ambient) { - // nodes are in different files and order cannot be determined - return true; + function isUsedInFunctionOrInstanceProperty(usage: Node, declaration: Node, container?: Node): boolean { + return !!findAncestor(usage, current => { + if (current === container) { + return "quit"; } - // declaration is after usage - // can be legal if usage is deferred (i.e. inside function or in initializer of instance property) - if (isUsedInFunctionOrInstanceProperty(usage, declaration)) { + if (isFunctionLike(current)) { return true; } - const sourceFiles = host.getSourceFiles(); - return sourceFiles.indexOf(declarationFile) <= sourceFiles.indexOf(useFile); - } - - if (declaration.pos <= usage.pos) { - // declaration is before usage - if (declaration.kind === SyntaxKind.BindingElement) { - // still might be illegal if declaration and usage are both binding elements (eg var [a = b, b = b] = [1, 2]) - const errorBindingElement = getAncestor(usage, SyntaxKind.BindingElement) as BindingElement; - if (errorBindingElement) { - return findAncestor(errorBindingElement, isBindingElement) !== findAncestor(declaration, isBindingElement) || - declaration.pos < errorBindingElement.pos; - } - // or it might be illegal if usage happens before parent variable is declared (eg var [a] = a) - return isBlockScopedNameDeclaredBeforeUse(getAncestor(declaration, SyntaxKind.VariableDeclaration) as Declaration, usage); - } - else if (declaration.kind === SyntaxKind.VariableDeclaration) { - // still might be illegal if usage is in the initializer of the variable declaration (eg var a = a) - return !isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration as VariableDeclaration, usage); - } - else if (isClassDeclaration(declaration)) { - // still might be illegal if the usage is within a computed property name in the class (eg class A { static p = "a"; [A.p]() {} }) - return !findAncestor(usage, n => isComputedPropertyName(n) && n.parent.parent === declaration); - } - else if (isPropertyDeclaration(declaration)) { - // still might be illegal if a self-referencing property initializer (eg private x = this.x) - return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage); - } - return true; - } - - - // declaration is after usage, but it can still be legal if usage is deferred: - // 1. inside an export specifier - // 2. inside a function - // 3. inside an instance property initializer, a reference to a non-instance property - // 4. inside a static property initializer, a reference to a static method in the same class - // 5. inside a TS export= declaration (since we will move the export statement during emit to avoid TDZ) - // or if usage is in a type context: - // 1. inside a type query (typeof in type position) - // 2. inside a jsdoc comment - if (usage.parent.kind === SyntaxKind.ExportSpecifier || (usage.parent.kind === SyntaxKind.ExportAssignment && (usage.parent as ExportAssignment).isExportEquals)) { - // export specifiers do not use the variable, they only make it available for use - return true; - } - // When resolving symbols for exports, the `usage` location passed in can be the export site directly - if (usage.kind === SyntaxKind.ExportAssignment && (usage as ExportAssignment).isExportEquals) { - return true; - } - - const container = getEnclosingBlockScopeContainer(declaration); - return !!(usage.flags & NodeFlags.JSDoc) || isInTypeQuery(usage) || isUsedInFunctionOrInstanceProperty(usage, declaration, container); - - function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean { - const container = getEnclosingBlockScopeContainer(declaration); - - switch (declaration.parent.parent.kind) { - case SyntaxKind.VariableStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.ForOfStatement: - // variable statement/for/for-of statement case, - // use site should not be inside variable declaration (initializer of declaration or binding element) - if (isSameScopeDescendentOf(usage, declaration, container)) { + const initializerOfProperty = current.parent && + current.parent.kind === SyntaxKind.PropertyDeclaration && + (current.parent).initializer === current; + if (initializerOfProperty) { + if (hasModifier(current.parent, ModifierFlags.Static)) { + if (declaration.kind === SyntaxKind.MethodDeclaration) { return true; } - break; + } + else { + const isDeclarationInstanceProperty = declaration.kind === SyntaxKind.PropertyDeclaration && !hasModifier(declaration, ModifierFlags.Static); + if (!isDeclarationInstanceProperty || getContainingClass(usage) !== getContainingClass(declaration)) { + return true; + } + } } - - // ForIn/ForOf case - use site should not be used in expression part - const grandparent = declaration.parent.parent; - return isForInOrOfStatement(grandparent) && isSameScopeDescendentOf(usage, grandparent.expression, container); + return false; + }); + } + function isPropertyImmediatelyReferencedWithinDeclaration(declaration: PropertyDeclaration, usage: Node) { + // always legal if usage is after declaration + if (usage.end > declaration.end) { + return false; } - - function isUsedInFunctionOrInstanceProperty(usage: Node, declaration: Node, container?: Node): boolean { - return !!findAncestor(usage, current => { - if (current === container) { - return "quit"; - } - if (isFunctionLike(current)) { + // still might be legal if usage is deferred (e.g. x: any = () => this.x) + // otherwise illegal if immediately referenced within the declaration (e.g. x: any = this.x) + const ancestorChangingReferenceScope = findAncestor(usage, (node: Node) => { + if (node === declaration) { + return "quit"; + } + switch (node.kind) { + case SyntaxKind.ArrowFunction: + case SyntaxKind.PropertyDeclaration: return true; - } - - const initializerOfProperty = current.parent && - current.parent.kind === SyntaxKind.PropertyDeclaration && - (current.parent).initializer === current; - - if (initializerOfProperty) { - if (hasModifier(current.parent, ModifierFlags.Static)) { - if (declaration.kind === SyntaxKind.MethodDeclaration) { + case SyntaxKind.Block: + switch (node.parent.kind) { + case SyntaxKind.GetAccessor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.SetAccessor: return true; - } + default: + return false; } - else { - const isDeclarationInstanceProperty = declaration.kind === SyntaxKind.PropertyDeclaration && !hasModifier(declaration, ModifierFlags.Static); - if (!isDeclarationInstanceProperty || getContainingClass(usage) !== getContainingClass(declaration)) { - return true; + default: + return false; + } + }); + return ancestorChangingReferenceScope === undefined; + } + } + /** + * Resolve a given name for a given meaning at a given location. An error is reported if the name was not found and + * the nameNotFoundMessage argument is not undefined. Returns the resolved symbol, or undefined if no symbol with + * the given name can be found. + * + * @param isUse If true, this will count towards --noUnusedLocals / --noUnusedParameters. + */ + function resolveName(location: Node | undefined, name: __String, meaning: SymbolFlags, nameNotFoundMessage: DiagnosticMessage | undefined, nameArg: __String | Identifier | undefined, isUse: boolean, excludeGlobals = false, suggestedNameNotFoundMessage?: DiagnosticMessage): ts.Symbol | undefined { + return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSymbol, suggestedNameNotFoundMessage); + } + function resolveNameHelper(location: Node | undefined, name: __String, meaning: SymbolFlags, nameNotFoundMessage: DiagnosticMessage | undefined, nameArg: __String | Identifier | undefined, isUse: boolean, excludeGlobals: boolean, lookup: typeof getSymbol, suggestedNameNotFoundMessage?: DiagnosticMessage): ts.Symbol | undefined { + const originalLocation = location; // needed for did-you-mean error reporting, which gathers candidates starting from the original location + let result: ts.Symbol | undefined; + let lastLocation: Node | undefined; + let lastSelfReferenceLocation: Node | undefined; + let propertyWithInvalidInitializer: Node | undefined; + let associatedDeclarationForContainingInitializer: ParameterDeclaration | BindingElement | undefined; + let withinDeferredContext = false; + const errorLocation = location; + let grandparent: Node; + let isInExternalModule = false; + loop: while (location) { + // Locals of a source file are not in scope (because they get merged into the global symbol table) + if (location.locals && !isGlobalSourceFile(location)) { + if (result = lookup(location.locals, name, meaning)) { + let useResult = true; + if (isFunctionLike(location) && lastLocation && lastLocation !== (location).body) { + // symbol lookup restrictions for function-like declarations + // - Type parameters of a function are in scope in the entire function declaration, including the parameter + // list and return type. However, local types are only in scope in the function body. + // - parameters are only in the scope of function body + // This restriction does not apply to JSDoc comment types because they are parented + // at a higher level than type parameters would normally be + if (meaning & result.flags & SymbolFlags.Type && lastLocation.kind !== SyntaxKind.JSDocComment) { + useResult = result.flags & SymbolFlags.TypeParameter + // type parameters are visible in parameter list, return type and type parameter list + ? lastLocation === (location).type || + lastLocation.kind === SyntaxKind.Parameter || + lastLocation.kind === SyntaxKind.TypeParameter + // local types not visible outside the function body + : false; + } + if (meaning & result.flags & SymbolFlags.Variable) { + // expression inside parameter will lookup as normal variable scope when targeting es2015+ + const functionLocation = (location); + if (compilerOptions.target && compilerOptions.target >= ScriptTarget.ES2015 && isParameter(lastLocation) && + functionLocation.body && result.valueDeclaration.pos >= functionLocation.body.pos && result.valueDeclaration.end <= functionLocation.body.end) { + useResult = false; + } + else if (result.flags & SymbolFlags.FunctionScopedVariable) { + // parameters are visible only inside function body, parameter list and return type + // technically for parameter list case here we might mix parameters and variables declared in function, + // however it is detected separately when checking initializers of parameters + // to make sure that they reference no variables declared after them. + useResult = + lastLocation.kind === SyntaxKind.Parameter || + (lastLocation === (location).type && + !!findAncestor(result.valueDeclaration, isParameter)); } } } - return false; - }); - } - - function isPropertyImmediatelyReferencedWithinDeclaration(declaration: PropertyDeclaration, usage: Node) { - // always legal if usage is after declaration - if (usage.end > declaration.end) { - return false; - } - - // still might be legal if usage is deferred (e.g. x: any = () => this.x) - // otherwise illegal if immediately referenced within the declaration (e.g. x: any = this.x) - const ancestorChangingReferenceScope = findAncestor(usage, (node: Node) => { - if (node === declaration) { - return "quit"; + else if (location.kind === SyntaxKind.ConditionalType) { + // A type parameter declared using 'infer T' in a conditional type is visible only in + // the true branch of the conditional type. + useResult = lastLocation === (location).trueType; } - - switch (node.kind) { - case SyntaxKind.ArrowFunction: - case SyntaxKind.PropertyDeclaration: - return true; - case SyntaxKind.Block: - switch (node.parent.kind) { - case SyntaxKind.GetAccessor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.SetAccessor: - return true; - default: - return false; - } - default: - return false; + if (useResult) { + break loop; } - }); - - return ancestorChangingReferenceScope === undefined; + else { + result = undefined; + } + } } - } - - /** - * Resolve a given name for a given meaning at a given location. An error is reported if the name was not found and - * the nameNotFoundMessage argument is not undefined. Returns the resolved symbol, or undefined if no symbol with - * the given name can be found. - * - * @param isUse If true, this will count towards --noUnusedLocals / --noUnusedParameters. - */ - function resolveName( - location: Node | undefined, - name: __String, - meaning: SymbolFlags, - nameNotFoundMessage: DiagnosticMessage | undefined, - nameArg: __String | Identifier | undefined, - isUse: boolean, - excludeGlobals = false, - suggestedNameNotFoundMessage?: DiagnosticMessage): Symbol | undefined { - return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSymbol, suggestedNameNotFoundMessage); - } - - function resolveNameHelper( - location: Node | undefined, - name: __String, - meaning: SymbolFlags, - nameNotFoundMessage: DiagnosticMessage | undefined, - nameArg: __String | Identifier | undefined, - isUse: boolean, - excludeGlobals: boolean, - lookup: typeof getSymbol, - suggestedNameNotFoundMessage?: DiagnosticMessage): Symbol | undefined { - const originalLocation = location; // needed for did-you-mean error reporting, which gathers candidates starting from the original location - let result: Symbol | undefined; - let lastLocation: Node | undefined; - let lastSelfReferenceLocation: Node | undefined; - let propertyWithInvalidInitializer: Node | undefined; - let associatedDeclarationForContainingInitializer: ParameterDeclaration | BindingElement | undefined; - let withinDeferredContext = false; - const errorLocation = location; - let grandparent: Node; - let isInExternalModule = false; - - loop: while (location) { - // Locals of a source file are not in scope (because they get merged into the global symbol table) - if (location.locals && !isGlobalSourceFile(location)) { - if (result = lookup(location.locals, name, meaning)) { - let useResult = true; - if (isFunctionLike(location) && lastLocation && lastLocation !== (location).body) { - // symbol lookup restrictions for function-like declarations - // - Type parameters of a function are in scope in the entire function declaration, including the parameter - // list and return type. However, local types are only in scope in the function body. - // - parameters are only in the scope of function body - // This restriction does not apply to JSDoc comment types because they are parented - // at a higher level than type parameters would normally be - if (meaning & result.flags & SymbolFlags.Type && lastLocation.kind !== SyntaxKind.JSDocComment) { - useResult = result.flags & SymbolFlags.TypeParameter - // type parameters are visible in parameter list, return type and type parameter list - ? lastLocation === (location).type || - lastLocation.kind === SyntaxKind.Parameter || - lastLocation.kind === SyntaxKind.TypeParameter - // local types not visible outside the function body - : false; - } - if (meaning & result.flags & SymbolFlags.Variable) { - // expression inside parameter will lookup as normal variable scope when targeting es2015+ - const functionLocation = location; - if (compilerOptions.target && compilerOptions.target >= ScriptTarget.ES2015 && isParameter(lastLocation) && - functionLocation.body && result.valueDeclaration.pos >= functionLocation.body.pos && result.valueDeclaration.end <= functionLocation.body.end) { - useResult = false; - } - else if (result.flags & SymbolFlags.FunctionScopedVariable) { - // parameters are visible only inside function body, parameter list and return type - // technically for parameter list case here we might mix parameters and variables declared in function, - // however it is detected separately when checking initializers of parameters - // to make sure that they reference no variables declared after them. - useResult = - lastLocation.kind === SyntaxKind.Parameter || - ( - lastLocation === (location).type && - !!findAncestor(result.valueDeclaration, isParameter) - ); - } + withinDeferredContext = withinDeferredContext || getIsDeferredContext(location, lastLocation); + switch (location.kind) { + case SyntaxKind.SourceFile: + if (!isExternalOrCommonJsModule((location))) + break; + isInExternalModule = true; + // falls through + case SyntaxKind.ModuleDeclaration: + const moduleExports = getSymbolOfNode((location as SourceFile | ModuleDeclaration)).exports || emptySymbols; + if (location.kind === SyntaxKind.SourceFile || (isModuleDeclaration(location) && location.flags & NodeFlags.Ambient && !isGlobalScopeAugmentation(location))) { + // It's an external module. First see if the module has an export default and if the local + // name of that export default matches. + if (result = moduleExports.get(InternalSymbolName.Default)) { + const localSymbol = getLocalSymbolForExportDefault(result); + if (localSymbol && (result.flags & meaning) && localSymbol.escapedName === name) { + break loop; } + result = undefined; } - else if (location.kind === SyntaxKind.ConditionalType) { - // A type parameter declared using 'infer T' in a conditional type is visible only in - // the true branch of the conditional type. - useResult = lastLocation === (location).trueType; + // Because of module/namespace merging, a module's exports are in scope, + // yet we never want to treat an export specifier as putting a member in scope. + // Therefore, if the name we find is purely an export specifier, it is not actually considered in scope. + // Two things to note about this: + // 1. We have to check this without calling getSymbol. The problem with calling getSymbol + // on an export specifier is that it might find the export specifier itself, and try to + // resolve it as an alias. This will cause the checker to consider the export specifier + // a circular alias reference when it might not be. + // 2. We check === SymbolFlags.Alias in order to check that the symbol is *purely* + // an alias. If we used &, we'd be throwing out symbols that have non alias aspects, + // which is not the desired behavior. + const moduleExport = moduleExports.get(name); + if (moduleExport && + moduleExport.flags === SymbolFlags.Alias && + getDeclarationOfKind(moduleExport, SyntaxKind.ExportSpecifier)) { + break; } - - if (useResult) { - break loop; + } + // ES6 exports are also visible locally (except for 'default'), but commonjs exports are not (except typedefs) + if (name !== InternalSymbolName.Default && (result = lookup(moduleExports, name, meaning & SymbolFlags.ModuleMember))) { + if (isSourceFile(location) && location.commonJsModuleIndicator && !result.declarations.some(isJSDocTypeAlias)) { + result = undefined; } else { - result = undefined; + break loop; } } - } - withinDeferredContext = withinDeferredContext || getIsDeferredContext(location, lastLocation); - switch (location.kind) { - case SyntaxKind.SourceFile: - if (!isExternalOrCommonJsModule(location)) break; - isInExternalModule = true; - // falls through - case SyntaxKind.ModuleDeclaration: - const moduleExports = getSymbolOfNode(location as SourceFile | ModuleDeclaration).exports || emptySymbols; - if (location.kind === SyntaxKind.SourceFile || (isModuleDeclaration(location) && location.flags & NodeFlags.Ambient && !isGlobalScopeAugmentation(location))) { - - // It's an external module. First see if the module has an export default and if the local - // name of that export default matches. - if (result = moduleExports.get(InternalSymbolName.Default)) { - const localSymbol = getLocalSymbolForExportDefault(result); - if (localSymbol && (result.flags & meaning) && localSymbol.escapedName === name) { - break loop; - } - result = undefined; - } - - // Because of module/namespace merging, a module's exports are in scope, - // yet we never want to treat an export specifier as putting a member in scope. - // Therefore, if the name we find is purely an export specifier, it is not actually considered in scope. - // Two things to note about this: - // 1. We have to check this without calling getSymbol. The problem with calling getSymbol - // on an export specifier is that it might find the export specifier itself, and try to - // resolve it as an alias. This will cause the checker to consider the export specifier - // a circular alias reference when it might not be. - // 2. We check === SymbolFlags.Alias in order to check that the symbol is *purely* - // an alias. If we used &, we'd be throwing out symbols that have non alias aspects, - // which is not the desired behavior. - const moduleExport = moduleExports.get(name); - if (moduleExport && - moduleExport.flags === SymbolFlags.Alias && - getDeclarationOfKind(moduleExport, SyntaxKind.ExportSpecifier)) { - break; - } - } - - // ES6 exports are also visible locally (except for 'default'), but commonjs exports are not (except typedefs) - if (name !== InternalSymbolName.Default && (result = lookup(moduleExports, name, meaning & SymbolFlags.ModuleMember))) { - if (isSourceFile(location) && location.commonJsModuleIndicator && !result.declarations.some(isJSDocTypeAlias)) { - result = undefined; - } - else { - break loop; + break; + case SyntaxKind.EnumDeclaration: + if (result = lookup((getSymbolOfNode(location)!.exports!), name, meaning & SymbolFlags.EnumMember)) { + break loop; + } + break; + case SyntaxKind.PropertyDeclaration: + // TypeScript 1.0 spec (April 2014): 8.4.1 + // Initializer expressions for instance member variables are evaluated in the scope + // of the class constructor body but are not permitted to reference parameters or + // local variables of the constructor. This effectively means that entities from outer scopes + // by the same name as a constructor parameter or local variable are inaccessible + // in initializer expressions for instance member variables. + if (!hasModifier(location, ModifierFlags.Static)) { + const ctor = findConstructorDeclaration((location.parent as ClassLikeDeclaration)); + if (ctor && ctor.locals) { + if (lookup(ctor.locals, name, meaning & SymbolFlags.Value)) { + // Remember the property node, it will be used later to report appropriate error + propertyWithInvalidInitializer = location; } } - break; - case SyntaxKind.EnumDeclaration: - if (result = lookup(getSymbolOfNode(location)!.exports!, name, meaning & SymbolFlags.EnumMember)) { - break loop; + } + break; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + // The below is used to lookup type parameters within a class or interface, as they are added to the class/interface locals + // These can never be latebound, so the symbol's raw members are sufficient. `getMembersOfNode` cannot be used, as it would + // trigger resolving late-bound names, which we may already be in the process of doing while we're here! + if (result = lookup(getSymbolOfNode((location as ClassLikeDeclaration | InterfaceDeclaration)).members || emptySymbols, name, meaning & SymbolFlags.Type)) { + if (!isTypeParameterSymbolDeclaredInContainer(result, location)) { + // ignore type parameters not declared in this container + result = undefined; + break; } - break; - case SyntaxKind.PropertyDeclaration: - // TypeScript 1.0 spec (April 2014): 8.4.1 - // Initializer expressions for instance member variables are evaluated in the scope - // of the class constructor body but are not permitted to reference parameters or - // local variables of the constructor. This effectively means that entities from outer scopes - // by the same name as a constructor parameter or local variable are inaccessible - // in initializer expressions for instance member variables. - if (!hasModifier(location, ModifierFlags.Static)) { - const ctor = findConstructorDeclaration(location.parent as ClassLikeDeclaration); - if (ctor && ctor.locals) { - if (lookup(ctor.locals, name, meaning & SymbolFlags.Value)) { - // Remember the property node, it will be used later to report appropriate error - propertyWithInvalidInitializer = location; - } - } + if (lastLocation && hasModifier(lastLocation, ModifierFlags.Static)) { + // TypeScript 1.0 spec (April 2014): 3.4.1 + // The scope of a type parameter extends over the entire declaration with which the type + // parameter list is associated, with the exception of static member declarations in classes. + error(errorLocation, Diagnostics.Static_members_cannot_reference_class_type_parameters); + return undefined; } - break; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - // The below is used to lookup type parameters within a class or interface, as they are added to the class/interface locals - // These can never be latebound, so the symbol's raw members are sufficient. `getMembersOfNode` cannot be used, as it would - // trigger resolving late-bound names, which we may already be in the process of doing while we're here! - if (result = lookup(getSymbolOfNode(location as ClassLikeDeclaration | InterfaceDeclaration).members || emptySymbols, name, meaning & SymbolFlags.Type)) { - if (!isTypeParameterSymbolDeclaredInContainer(result, location)) { - // ignore type parameters not declared in this container - result = undefined; - break; - } - if (lastLocation && hasModifier(lastLocation, ModifierFlags.Static)) { - // TypeScript 1.0 spec (April 2014): 3.4.1 - // The scope of a type parameter extends over the entire declaration with which the type - // parameter list is associated, with the exception of static member declarations in classes. - error(errorLocation, Diagnostics.Static_members_cannot_reference_class_type_parameters); - return undefined; - } + break loop; + } + if (location.kind === SyntaxKind.ClassExpression && meaning & SymbolFlags.Class) { + const className = (location).name; + if (className && name === className.escapedText) { + result = location.symbol; break loop; } - if (location.kind === SyntaxKind.ClassExpression && meaning & SymbolFlags.Class) { - const className = (location).name; - if (className && name === className.escapedText) { - result = location.symbol; - break loop; - } - } - break; - case SyntaxKind.ExpressionWithTypeArguments: - // The type parameters of a class are not in scope in the base class expression. - if (lastLocation === (location).expression && (location.parent).token === SyntaxKind.ExtendsKeyword) { - const container = location.parent.parent; - if (isClassLike(container) && (result = lookup(getSymbolOfNode(container).members!, name, meaning & SymbolFlags.Type))) { - if (nameNotFoundMessage) { - error(errorLocation, Diagnostics.Base_class_expressions_cannot_reference_class_type_parameters); - } - return undefined; + } + break; + case SyntaxKind.ExpressionWithTypeArguments: + // The type parameters of a class are not in scope in the base class expression. + if (lastLocation === (location).expression && (location.parent).token === SyntaxKind.ExtendsKeyword) { + const container = location.parent.parent; + if (isClassLike(container) && (result = lookup((getSymbolOfNode(container).members!), name, meaning & SymbolFlags.Type))) { + if (nameNotFoundMessage) { + error(errorLocation, Diagnostics.Base_class_expressions_cannot_reference_class_type_parameters); } + return undefined; } - break; - // It is not legal to reference a class's own type parameters from a computed property name that - // belongs to the class. For example: - // - // function foo() { return '' } - // class C { // <-- Class's own type parameter T - // [foo()]() { } // <-- Reference to T from class's own computed property - // } - // - case SyntaxKind.ComputedPropertyName: - grandparent = location.parent.parent; - if (isClassLike(grandparent) || grandparent.kind === SyntaxKind.InterfaceDeclaration) { - // A reference to this grandparent's type parameters would be an error - if (result = lookup(getSymbolOfNode(grandparent as ClassLikeDeclaration | InterfaceDeclaration).members!, name, meaning & SymbolFlags.Type)) { - error(errorLocation, Diagnostics.A_computed_property_name_cannot_reference_a_type_parameter_from_its_containing_type); - return undefined; - } - } - break; - case SyntaxKind.ArrowFunction: - // when targeting ES6 or higher there is no 'arguments' in an arrow function - // for lower compile targets the resolved symbol is used to emit an error - if (compilerOptions.target! >= ScriptTarget.ES2015) { - break; - } - // falls through - case SyntaxKind.MethodDeclaration: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionDeclaration: - if (meaning & SymbolFlags.Variable && name === "arguments") { - result = argumentsSymbol; - break loop; + } + break; + // It is not legal to reference a class's own type parameters from a computed property name that + // belongs to the class. For example: + // + // function foo() { return '' } + // class C { // <-- Class's own type parameter T + // [foo()]() { } // <-- Reference to T from class's own computed property + // } + // + case SyntaxKind.ComputedPropertyName: + grandparent = location.parent.parent; + if (isClassLike(grandparent) || grandparent.kind === SyntaxKind.InterfaceDeclaration) { + // A reference to this grandparent's type parameters would be an error + if (result = lookup((getSymbolOfNode((grandparent as ClassLikeDeclaration | InterfaceDeclaration)).members!), name, meaning & SymbolFlags.Type)) { + error(errorLocation, Diagnostics.A_computed_property_name_cannot_reference_a_type_parameter_from_its_containing_type); + return undefined; } + } + break; + case SyntaxKind.ArrowFunction: + // when targeting ES6 or higher there is no 'arguments' in an arrow function + // for lower compile targets the resolved symbol is used to emit an error + if ((compilerOptions.target!) >= ScriptTarget.ES2015) { break; - case SyntaxKind.FunctionExpression: - if (meaning & SymbolFlags.Variable && name === "arguments") { - result = argumentsSymbol; + } + // falls through + case SyntaxKind.MethodDeclaration: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionDeclaration: + if (meaning & SymbolFlags.Variable && name === "arguments") { + result = argumentsSymbol; + break loop; + } + break; + case SyntaxKind.FunctionExpression: + if (meaning & SymbolFlags.Variable && name === "arguments") { + result = argumentsSymbol; + break loop; + } + if (meaning & SymbolFlags.Function) { + const functionName = (location).name; + if (functionName && name === functionName.escapedText) { + result = location.symbol; break loop; } - - if (meaning & SymbolFlags.Function) { - const functionName = (location).name; - if (functionName && name === functionName.escapedText) { - result = location.symbol; - break loop; - } - } - break; - case SyntaxKind.Decorator: - // Decorators are resolved at the class declaration. Resolving at the parameter - // or member would result in looking up locals in the method. - // - // function y() {} - // class C { - // method(@y x, y) {} // <-- decorator y should be resolved at the class declaration, not the parameter. - // } - // - if (location.parent && location.parent.kind === SyntaxKind.Parameter) { - location = location.parent; - } - // - // function y() {} - // class C { - // @y method(x, y) {} // <-- decorator y should be resolved at the class declaration, not the method. - // } - // - - // class Decorators are resolved outside of the class to avoid referencing type parameters of that class. - // - // type T = number; - // declare function y(x: T): any; - // @param(1 as T) // <-- T should resolve to the type alias outside of class C - // class C {} - if (location.parent && (isClassElement(location.parent) || location.parent.kind === SyntaxKind.ClassDeclaration)) { - location = location.parent; - } - break; - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocEnumTag: - // js type aliases do not resolve names from their host, so skip past it - location = getJSDocHost(location); - break; - case SyntaxKind.Parameter: - if (lastLocation && lastLocation === (location as ParameterDeclaration).initializer) { - associatedDeclarationForContainingInitializer = location as ParameterDeclaration; - } - break; - case SyntaxKind.BindingElement: - if (lastLocation && lastLocation === (location as BindingElement).initializer) { - const root = getRootDeclaration(location); - if (root.kind === SyntaxKind.Parameter) { - associatedDeclarationForContainingInitializer = location as BindingElement; - } + } + break; + case SyntaxKind.Decorator: + // Decorators are resolved at the class declaration. Resolving at the parameter + // or member would result in looking up locals in the method. + // + // function y() {} + // class C { + // method(@y x, y) {} // <-- decorator y should be resolved at the class declaration, not the parameter. + // } + // + if (location.parent && location.parent.kind === SyntaxKind.Parameter) { + location = location.parent; + } + // + // function y() {} + // class C { + // @y method(x, y) {} // <-- decorator y should be resolved at the class declaration, not the method. + // } + // + // class Decorators are resolved outside of the class to avoid referencing type parameters of that class. + // + // type T = number; + // declare function y(x: T): any; + // @param(1 as T) // <-- T should resolve to the type alias outside of class C + // class C {} + if (location.parent && (isClassElement(location.parent) || location.parent.kind === SyntaxKind.ClassDeclaration)) { + location = location.parent; + } + break; + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocEnumTag: + // js type aliases do not resolve names from their host, so skip past it + location = getJSDocHost(location); + break; + case SyntaxKind.Parameter: + if (lastLocation && lastLocation === (location as ParameterDeclaration).initializer) { + associatedDeclarationForContainingInitializer = (location as ParameterDeclaration); + } + break; + case SyntaxKind.BindingElement: + if (lastLocation && lastLocation === (location as BindingElement).initializer) { + const root = getRootDeclaration(location); + if (root.kind === SyntaxKind.Parameter) { + associatedDeclarationForContainingInitializer = (location as BindingElement); } - break; - } - if (isSelfReferenceLocation(location)) { - lastSelfReferenceLocation = location; - } - lastLocation = location; - location = location.parent; + } + break; } - - // We just climbed up parents looking for the name, meaning that we started in a descendant node of `lastLocation`. - // If `result === lastSelfReferenceLocation.symbol`, that means that we are somewhere inside `lastSelfReferenceLocation` looking up a name, and resolving to `lastLocation` itself. - // That means that this is a self-reference of `lastLocation`, and shouldn't count this when considering whether `lastLocation` is used. - if (isUse && result && (!lastSelfReferenceLocation || result !== lastSelfReferenceLocation.symbol)) { - result.isReferenced! |= meaning; + if (isSelfReferenceLocation(location)) { + lastSelfReferenceLocation = location; } - - if (!result) { - if (lastLocation) { - Debug.assert(lastLocation.kind === SyntaxKind.SourceFile); - if ((lastLocation as SourceFile).commonJsModuleIndicator && name === "exports" && meaning & lastLocation.symbol.flags) { - return lastLocation.symbol; - } - } - - if (!excludeGlobals) { - result = lookup(globals, name, meaning); + lastLocation = location; + location = location.parent; + } + // We just climbed up parents looking for the name, meaning that we started in a descendant node of `lastLocation`. + // If `result === lastSelfReferenceLocation.symbol`, that means that we are somewhere inside `lastSelfReferenceLocation` looking up a name, and resolving to `lastLocation` itself. + // That means that this is a self-reference of `lastLocation`, and shouldn't count this when considering whether `lastLocation` is used. + if (isUse && result && (!lastSelfReferenceLocation || result !== lastSelfReferenceLocation.symbol)) { + result.isReferenced! |= meaning; + } + if (!result) { + if (lastLocation) { + Debug.assert(lastLocation.kind === SyntaxKind.SourceFile); + if ((lastLocation as SourceFile).commonJsModuleIndicator && name === "exports" && meaning & lastLocation.symbol.flags) { + return lastLocation.symbol; } } - if (!result) { - if (originalLocation && isInJSFile(originalLocation) && originalLocation.parent) { - if (isRequireCall(originalLocation.parent, /*checkArgumentIsStringLiteralLike*/ false)) { - return requireSymbol; - } + if (!excludeGlobals) { + result = lookup(globals, name, meaning); + } + } + if (!result) { + if (originalLocation && isInJSFile(originalLocation) && originalLocation.parent) { + if (isRequireCall(originalLocation.parent, /*checkArgumentIsStringLiteralLike*/ false)) { + return requireSymbol; } } - if (!result) { - if (nameNotFoundMessage) { - if (!errorLocation || - !checkAndReportErrorForMissingPrefix(errorLocation, name, nameArg!) && // TODO: GH#18217 + } + if (!result) { + if (nameNotFoundMessage) { + if (!errorLocation || + !checkAndReportErrorForMissingPrefix(errorLocation, name, nameArg!) && // TODO: GH#18217 !checkAndReportErrorForExtendingInterface(errorLocation) && !checkAndReportErrorForUsingTypeAsNamespace(errorLocation, name, meaning) && !checkAndReportErrorForUsingTypeAsValue(errorLocation, name, meaning) && !checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation, name, meaning) && !checkAndReportErrorForUsingValueAsType(errorLocation, name, meaning)) { - let suggestion: Symbol | undefined; - if (suggestedNameNotFoundMessage && suggestionCount < maximumSuggestionCount) { - suggestion = getSuggestedSymbolForNonexistentSymbol(originalLocation, name, meaning); - if (suggestion) { - const suggestionName = symbolToString(suggestion); - const diagnostic = error(errorLocation, suggestedNameNotFoundMessage, diagnosticName(nameArg!), suggestionName); - if (suggestion.valueDeclaration) { - addRelatedInfo( - diagnostic, - createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName) - ); - } + let suggestion: ts.Symbol | undefined; + if (suggestedNameNotFoundMessage && suggestionCount < maximumSuggestionCount) { + suggestion = getSuggestedSymbolForNonexistentSymbol(originalLocation, name, meaning); + if (suggestion) { + const suggestionName = symbolToString(suggestion); + const diagnostic = error(errorLocation, suggestedNameNotFoundMessage, diagnosticName(nameArg!), suggestionName); + if (suggestion.valueDeclaration) { + addRelatedInfo(diagnostic, createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName)); } } - if (!suggestion) { - error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg!)); - } - suggestionCount++; - } - } - return undefined; - } - - // Perform extra checks only if error reporting was requested - if (nameNotFoundMessage) { - if (propertyWithInvalidInitializer) { - // We have a match, but the reference occurred within a property initializer and the identifier also binds - // to a local variable in the constructor where the code will be emitted. - const propertyName = (propertyWithInvalidInitializer).name; - error(errorLocation, Diagnostics.Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor, - declarationNameToString(propertyName), diagnosticName(nameArg!)); - return undefined; - } - - // Only check for block-scoped variable if we have an error location and are looking for the - // name with variable meaning - // For example, - // declare module foo { - // interface bar {} - // } - // const foo/*1*/: foo/*2*/.bar; - // The foo at /*1*/ and /*2*/ will share same symbol with two meanings: - // block-scoped variable and namespace module. However, only when we - // try to resolve name in /*1*/ which is used in variable position, - // we want to check for block-scoped - if (errorLocation && - (meaning & SymbolFlags.BlockScopedVariable || - ((meaning & SymbolFlags.Class || meaning & SymbolFlags.Enum) && (meaning & SymbolFlags.Value) === SymbolFlags.Value))) { - const exportOrLocalSymbol = getExportSymbolOfValueSymbolIfExported(result); - if (exportOrLocalSymbol.flags & SymbolFlags.BlockScopedVariable || exportOrLocalSymbol.flags & SymbolFlags.Class || exportOrLocalSymbol.flags & SymbolFlags.Enum) { - checkResolvedBlockScopedVariable(exportOrLocalSymbol, errorLocation); - } - } - - // If we're in an external module, we can't reference value symbols created from UMD export declarations - if (result && isInExternalModule && (meaning & SymbolFlags.Value) === SymbolFlags.Value && !(originalLocation!.flags & NodeFlags.JSDoc)) { - const merged = getMergedSymbol(result); - if (length(merged.declarations) && every(merged.declarations, d => isNamespaceExportDeclaration(d) || isSourceFile(d) && !!d.symbol.globalExports)) { - errorOrSuggestion(!compilerOptions.allowUmdGlobalAccess, errorLocation!, Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead, unescapeLeadingUnderscores(name)); - } - } - - // If we're in a parameter initializer, we can't reference the values of the parameter whose initializer we're within or parameters to the right - if (result && associatedDeclarationForContainingInitializer && !withinDeferredContext && (meaning & SymbolFlags.Value) === SymbolFlags.Value) { - const candidate = getMergedSymbol(getLateBoundSymbol(result)); - const root = (getRootDeclaration(associatedDeclarationForContainingInitializer) as ParameterDeclaration); - // A parameter initializer or binding pattern initializer within a parameter cannot refer to itself - if (candidate === getSymbolOfNode(associatedDeclarationForContainingInitializer)) { - error(errorLocation, Diagnostics.Parameter_0_cannot_be_referenced_in_its_initializer, declarationNameToString(associatedDeclarationForContainingInitializer.name)); } - // And it cannot refer to any declarations which come after it - else if (candidate.valueDeclaration && candidate.valueDeclaration.pos > associatedDeclarationForContainingInitializer.pos && root.parent.locals && lookup(root.parent.locals, candidate.escapedName, meaning) === candidate) { - error(errorLocation, Diagnostics.Initializer_of_parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(associatedDeclarationForContainingInitializer.name), declarationNameToString(errorLocation)); + if (!suggestion) { + error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg!)); } + suggestionCount++; } } - return result; + return undefined; } - - function getIsDeferredContext(location: Node, lastLocation: Node | undefined): boolean { - if (location.kind !== SyntaxKind.ArrowFunction && location.kind !== SyntaxKind.FunctionExpression) { - // initializers in instance property declaration of class like entities are executed in constructor and thus deferred - return isTypeQueryNode(location) || (( - isFunctionLikeDeclaration(location) || - (location.kind === SyntaxKind.PropertyDeclaration && !hasModifier(location, ModifierFlags.Static)) - ) && (!lastLocation || lastLocation !== (location as FunctionLike | PropertyDeclaration).name)); // A name is evaluated within the enclosing scope - so it shouldn't count as deferred - } - if (lastLocation && lastLocation === (location as FunctionExpression | ArrowFunction).name) { - return false; + // Perform extra checks only if error reporting was requested + if (nameNotFoundMessage) { + if (propertyWithInvalidInitializer) { + // We have a match, but the reference occurred within a property initializer and the identifier also binds + // to a local variable in the constructor where the code will be emitted. + const propertyName = (propertyWithInvalidInitializer).name; + error(errorLocation, Diagnostics.Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor, declarationNameToString(propertyName), diagnosticName(nameArg!)); + return undefined; } - // generator functions and async functions are not inlined in control flow when immediately invoked - if ((location as FunctionExpression | ArrowFunction).asteriskToken || hasModifier(location, ModifierFlags.Async)) { + // Only check for block-scoped variable if we have an error location and are looking for the + // name with variable meaning + // For example, + // declare module foo { + // interface bar {} + // } + // const foo/*1*/: foo/*2*/.bar; + // The foo at /*1*/ and /*2*/ will share same symbol with two meanings: + // block-scoped variable and namespace module. However, only when we + // try to resolve name in /*1*/ which is used in variable position, + // we want to check for block-scoped + if (errorLocation && + (meaning & SymbolFlags.BlockScopedVariable || + ((meaning & SymbolFlags.Class || meaning & SymbolFlags.Enum) && (meaning & SymbolFlags.Value) === SymbolFlags.Value))) { + const exportOrLocalSymbol = getExportSymbolOfValueSymbolIfExported(result); + if (exportOrLocalSymbol.flags & SymbolFlags.BlockScopedVariable || exportOrLocalSymbol.flags & SymbolFlags.Class || exportOrLocalSymbol.flags & SymbolFlags.Enum) { + checkResolvedBlockScopedVariable(exportOrLocalSymbol, errorLocation); + } + } + // If we're in an external module, we can't reference value symbols created from UMD export declarations + if (result && isInExternalModule && (meaning & SymbolFlags.Value) === SymbolFlags.Value && !(originalLocation!.flags & NodeFlags.JSDoc)) { + const merged = getMergedSymbol(result); + if (length(merged.declarations) && every(merged.declarations, d => isNamespaceExportDeclaration(d) || isSourceFile(d) && !!d.symbol.globalExports)) { + errorOrSuggestion(!compilerOptions.allowUmdGlobalAccess, (errorLocation!), Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead, unescapeLeadingUnderscores(name)); + } + } + // If we're in a parameter initializer, we can't reference the values of the parameter whose initializer we're within or parameters to the right + if (result && associatedDeclarationForContainingInitializer && !withinDeferredContext && (meaning & SymbolFlags.Value) === SymbolFlags.Value) { + const candidate = getMergedSymbol(getLateBoundSymbol(result)); + const root = (getRootDeclaration(associatedDeclarationForContainingInitializer) as ParameterDeclaration); + // A parameter initializer or binding pattern initializer within a parameter cannot refer to itself + if (candidate === getSymbolOfNode(associatedDeclarationForContainingInitializer)) { + error(errorLocation, Diagnostics.Parameter_0_cannot_be_referenced_in_its_initializer, declarationNameToString(associatedDeclarationForContainingInitializer.name)); + } + // And it cannot refer to any declarations which come after it + else if (candidate.valueDeclaration && candidate.valueDeclaration.pos > associatedDeclarationForContainingInitializer.pos && root.parent.locals && lookup(root.parent.locals, candidate.escapedName, meaning) === candidate) { + error(errorLocation, Diagnostics.Initializer_of_parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(associatedDeclarationForContainingInitializer.name), declarationNameToString((errorLocation))); + } + } + } + return result; + } + function getIsDeferredContext(location: Node, lastLocation: Node | undefined): boolean { + if (location.kind !== SyntaxKind.ArrowFunction && location.kind !== SyntaxKind.FunctionExpression) { + // initializers in instance property declaration of class like entities are executed in constructor and thus deferred + return isTypeQueryNode(location) || ((isFunctionLikeDeclaration(location) || + (location.kind === SyntaxKind.PropertyDeclaration && !hasModifier(location, ModifierFlags.Static))) && (!lastLocation || lastLocation !== (location as FunctionLike | PropertyDeclaration).name)); // A name is evaluated within the enclosing scope - so it shouldn't count as deferred + } + if (lastLocation && lastLocation === (location as FunctionExpression | ArrowFunction).name) { + return false; + } + // generator functions and async functions are not inlined in control flow when immediately invoked + if ((location as FunctionExpression | ArrowFunction).asteriskToken || hasModifier(location, ModifierFlags.Async)) { + return true; + } + return !getImmediatelyInvokedFunctionExpression(location); + } + function isSelfReferenceLocation(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.ModuleDeclaration: // For `namespace N { N; }` return true; - } - return !getImmediatelyInvokedFunctionExpression(location); + default: + return false; } - - function isSelfReferenceLocation(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.ModuleDeclaration: // For `namespace N { N; }` - return true; - default: - return false; + } + function diagnosticName(nameArg: __String | Identifier) { + return isString(nameArg) ? unescapeLeadingUnderscores((nameArg as __String)) : declarationNameToString((nameArg as Identifier)); + } + function isTypeParameterSymbolDeclaredInContainer(symbol: ts.Symbol, container: Node) { + for (const decl of symbol.declarations) { + if (decl.kind === SyntaxKind.TypeParameter) { + const parent = isJSDocTemplateTag(decl.parent) ? getJSDocHost(decl.parent) : decl.parent; + if (parent === container) { + return !(isJSDocTemplateTag(decl.parent) && find(((decl.parent.parent as JSDoc).tags!), isJSDocTypeAlias)); // TODO: GH#18217 + } } } - - function diagnosticName(nameArg: __String | Identifier) { - return isString(nameArg) ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier); + return false; + } + function checkAndReportErrorForMissingPrefix(errorLocation: Node, name: __String, nameArg: __String | Identifier): boolean { + if (!isIdentifier(errorLocation) || errorLocation.escapedText !== name || isTypeReferenceIdentifier(errorLocation) || isInTypeQuery(errorLocation)) { + return false; } - - function isTypeParameterSymbolDeclaredInContainer(symbol: Symbol, container: Node) { - for (const decl of symbol.declarations) { - if (decl.kind === SyntaxKind.TypeParameter) { - const parent = isJSDocTemplateTag(decl.parent) ? getJSDocHost(decl.parent) : decl.parent; - if (parent === container) { - return !(isJSDocTemplateTag(decl.parent) && find((decl.parent.parent as JSDoc).tags!, isJSDocTypeAlias)); // TODO: GH#18217 + const container = getThisContainer(errorLocation, /*includeArrowFunctions*/ false); + let location = container; + while (location) { + if (isClassLike(location.parent)) { + const classSymbol = getSymbolOfNode(location.parent); + if (!classSymbol) { + break; + } + // Check to see if a static member exists. + const constructorType = getTypeOfSymbol(classSymbol); + if (getPropertyOfType(constructorType, name)) { + error(errorLocation, Diagnostics.Cannot_find_name_0_Did_you_mean_the_static_member_1_0, diagnosticName(nameArg), symbolToString(classSymbol)); + return true; + } + // No static member is present. + // Check if we're in an instance method and look for a relevant instance member. + if (location === container && !hasModifier(location, ModifierFlags.Static)) { + const instanceType = ((getDeclaredTypeOfSymbol(classSymbol)).thisType!); // TODO: GH#18217 + if (getPropertyOfType(instanceType, name)) { + error(errorLocation, Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0, diagnosticName(nameArg)); + return true; } } } - - return false; + location = location.parent; } - - function checkAndReportErrorForMissingPrefix(errorLocation: Node, name: __String, nameArg: __String | Identifier): boolean { - if (!isIdentifier(errorLocation) || errorLocation.escapedText !== name || isTypeReferenceIdentifier(errorLocation) || isInTypeQuery(errorLocation)) { - return false; - } - - const container = getThisContainer(errorLocation, /*includeArrowFunctions*/ false); - let location = container; - while (location) { - if (isClassLike(location.parent)) { - const classSymbol = getSymbolOfNode(location.parent); - if (!classSymbol) { - break; - } - - // Check to see if a static member exists. - const constructorType = getTypeOfSymbol(classSymbol); - if (getPropertyOfType(constructorType, name)) { - error(errorLocation, Diagnostics.Cannot_find_name_0_Did_you_mean_the_static_member_1_0, diagnosticName(nameArg), symbolToString(classSymbol)); + return false; + } + function checkAndReportErrorForExtendingInterface(errorLocation: Node): boolean { + const expression = getEntityNameForExtendingInterface(errorLocation); + if (expression && resolveEntityName(expression, SymbolFlags.Interface, /*ignoreErrors*/ true)) { + error(errorLocation, Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements, getTextOfNode(expression)); + return true; + } + return false; + } + /** + * Climbs up parents to an ExpressionWithTypeArguments, and returns its expression, + * but returns undefined if that expression is not an EntityNameExpression. + */ + function getEntityNameForExtendingInterface(node: Node): EntityNameExpression | undefined { + switch (node.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.PropertyAccessExpression: + return node.parent ? getEntityNameForExtendingInterface(node.parent) : undefined; + case SyntaxKind.ExpressionWithTypeArguments: + if (isEntityNameExpression((node).expression)) { + return (node).expression; + } + // falls through + default: + return undefined; + } + } + function checkAndReportErrorForUsingTypeAsNamespace(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { + const namespaceMeaning = SymbolFlags.Namespace | (isInJSFile(errorLocation) ? SymbolFlags.Value : 0); + if (meaning === namespaceMeaning) { + const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Type & ~namespaceMeaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false)); + const parent = errorLocation.parent; + if (symbol) { + if (isQualifiedName(parent)) { + Debug.assert(parent.left === errorLocation, "Should only be resolving left side of qualified name as a namespace"); + const propName = parent.right.escapedText; + const propType = getPropertyOfType(getDeclaredTypeOfSymbol(symbol), propName); + if (propType) { + error(parent, Diagnostics.Cannot_access_0_1_because_0_is_a_type_but_not_a_namespace_Did_you_mean_to_retrieve_the_type_of_the_property_1_in_0_with_0_1, unescapeLeadingUnderscores(name), unescapeLeadingUnderscores(propName)); return true; } - - // No static member is present. - // Check if we're in an instance method and look for a relevant instance member. - if (location === container && !hasModifier(location, ModifierFlags.Static)) { - const instanceType = (getDeclaredTypeOfSymbol(classSymbol)).thisType!; // TODO: GH#18217 - if (getPropertyOfType(instanceType, name)) { - error(errorLocation, Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0, diagnosticName(nameArg)); - return true; - } - } } - - location = location.parent; + error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_namespace_here, unescapeLeadingUnderscores(name)); + return true; } - return false; } - - - function checkAndReportErrorForExtendingInterface(errorLocation: Node): boolean { - const expression = getEntityNameForExtendingInterface(errorLocation); - if (expression && resolveEntityName(expression, SymbolFlags.Interface, /*ignoreErrors*/ true)) { - error(errorLocation, Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements, getTextOfNode(expression)); + return false; + } + function checkAndReportErrorForUsingValueAsType(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { + if (meaning & (SymbolFlags.Type & ~SymbolFlags.Namespace)) { + const symbol = resolveSymbol(resolveName(errorLocation, name, ~SymbolFlags.Type & SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false)); + if (symbol && !(symbol.flags & SymbolFlags.Namespace)) { + error(errorLocation, Diagnostics._0_refers_to_a_value_but_is_being_used_as_a_type_here, unescapeLeadingUnderscores(name)); return true; } - return false; } - /** - * Climbs up parents to an ExpressionWithTypeArguments, and returns its expression, - * but returns undefined if that expression is not an EntityNameExpression. - */ - function getEntityNameForExtendingInterface(node: Node): EntityNameExpression | undefined { - switch (node.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.PropertyAccessExpression: - return node.parent ? getEntityNameForExtendingInterface(node.parent) : undefined; - case SyntaxKind.ExpressionWithTypeArguments: - if (isEntityNameExpression((node).expression)) { - return (node).expression; - } - // falls through - default: - return undefined; + return false; + } + function checkAndReportErrorForUsingTypeAsValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { + if (meaning & (SymbolFlags.Value & ~SymbolFlags.NamespaceModule)) { + if (name === "any" || name === "string" || name === "number" || name === "boolean" || name === "never") { + error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here, unescapeLeadingUnderscores(name)); + return true; } - } - - function checkAndReportErrorForUsingTypeAsNamespace(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { - const namespaceMeaning = SymbolFlags.Namespace | (isInJSFile(errorLocation) ? SymbolFlags.Value : 0); - if (meaning === namespaceMeaning) { - const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Type & ~namespaceMeaning, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); - const parent = errorLocation.parent; - if (symbol) { - if (isQualifiedName(parent)) { - Debug.assert(parent.left === errorLocation, "Should only be resolving left side of qualified name as a namespace"); - const propName = parent.right.escapedText; - const propType = getPropertyOfType(getDeclaredTypeOfSymbol(symbol), propName); - if (propType) { - error( - parent, - Diagnostics.Cannot_access_0_1_because_0_is_a_type_but_not_a_namespace_Did_you_mean_to_retrieve_the_type_of_the_property_1_in_0_with_0_1, - unescapeLeadingUnderscores(name), - unescapeLeadingUnderscores(propName), - ); - return true; - } - } - error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_namespace_here, unescapeLeadingUnderscores(name)); - return true; - } + const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Type & ~SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false)); + if (symbol && !(symbol.flags & SymbolFlags.NamespaceModule)) { + const message = isES2015OrLaterConstructorName(name) + ? Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_es2015_or_later + : Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here; + error(errorLocation, message, unescapeLeadingUnderscores(name)); + return true; } - - return false; } - - function checkAndReportErrorForUsingValueAsType(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { - if (meaning & (SymbolFlags.Type & ~SymbolFlags.Namespace)) { - const symbol = resolveSymbol(resolveName(errorLocation, name, ~SymbolFlags.Type & SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); - if (symbol && !(symbol.flags & SymbolFlags.Namespace)) { - error(errorLocation, Diagnostics._0_refers_to_a_value_but_is_being_used_as_a_type_here, unescapeLeadingUnderscores(name)); - return true; - } - } - return false; + return false; + } + function isES2015OrLaterConstructorName(n: __String) { + switch (n) { + case "Promise": + case "Symbol": + case "Map": + case "WeakMap": + case "Set": + case "WeakSet": + return true; } - - function checkAndReportErrorForUsingTypeAsValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { - if (meaning & (SymbolFlags.Value & ~SymbolFlags.NamespaceModule)) { - if (name === "any" || name === "string" || name === "number" || name === "boolean" || name === "never") { - error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here, unescapeLeadingUnderscores(name)); - return true; - } - const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Type & ~SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); - if (symbol && !(symbol.flags & SymbolFlags.NamespaceModule)) { - const message = isES2015OrLaterConstructorName(name) - ? Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_es2015_or_later - : Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here; - error(errorLocation, message, unescapeLeadingUnderscores(name)); - return true; - } + return false; + } + function checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { + if (meaning & (SymbolFlags.Value & ~SymbolFlags.NamespaceModule & ~SymbolFlags.Type)) { + const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.NamespaceModule & ~SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false)); + if (symbol) { + error(errorLocation, Diagnostics.Cannot_use_namespace_0_as_a_value, unescapeLeadingUnderscores(name)); + return true; } - return false; } - - function isES2015OrLaterConstructorName(n: __String) { - switch (n) { - case "Promise": - case "Symbol": - case "Map": - case "WeakMap": - case "Set": - case "WeakSet": - return true; + else if (meaning & (SymbolFlags.Type & ~SymbolFlags.NamespaceModule & ~SymbolFlags.Value)) { + const symbol = resolveSymbol(resolveName(errorLocation, name, (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) & ~SymbolFlags.Type, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false)); + if (symbol) { + error(errorLocation, Diagnostics.Cannot_use_namespace_0_as_a_type, unescapeLeadingUnderscores(name)); + return true; } - return false; } - - function checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { - if (meaning & (SymbolFlags.Value & ~SymbolFlags.NamespaceModule & ~SymbolFlags.Type)) { - const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.NamespaceModule & ~SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); - if (symbol) { - error(errorLocation, Diagnostics.Cannot_use_namespace_0_as_a_value, unescapeLeadingUnderscores(name)); - return true; - } + return false; + } + function checkResolvedBlockScopedVariable(result: ts.Symbol, errorLocation: Node): void { + Debug.assert(!!(result.flags & SymbolFlags.BlockScopedVariable || result.flags & SymbolFlags.Class || result.flags & SymbolFlags.Enum)); + if (result.flags & (SymbolFlags.Function | SymbolFlags.FunctionScopedVariable | SymbolFlags.Assignment) && result.flags & SymbolFlags.Class) { + // constructor functions aren't block scoped + return; + } + // Block-scoped variables cannot be used before their definition + const declaration = find(result.declarations, d => isBlockOrCatchScoped(d) || isClassLike(d) || (d.kind === SyntaxKind.EnumDeclaration)); + if (declaration === undefined) + return Debug.fail("checkResolvedBlockScopedVariable could not find block-scoped declaration"); + if (!(declaration.flags & NodeFlags.Ambient) && !isBlockScopedNameDeclaredBeforeUse(declaration, errorLocation)) { + let diagnosticMessage; + const declarationName = declarationNameToString(getNameOfDeclaration(declaration)); + if (result.flags & SymbolFlags.BlockScopedVariable) { + diagnosticMessage = error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationName); } - else if (meaning & (SymbolFlags.Type & ~SymbolFlags.NamespaceModule & ~SymbolFlags.Value)) { - const symbol = resolveSymbol(resolveName(errorLocation, name, (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) & ~SymbolFlags.Type, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); - if (symbol) { - error(errorLocation, Diagnostics.Cannot_use_namespace_0_as_a_type, unescapeLeadingUnderscores(name)); - return true; - } + else if (result.flags & SymbolFlags.Class) { + diagnosticMessage = error(errorLocation, Diagnostics.Class_0_used_before_its_declaration, declarationName); } - return false; - } - - function checkResolvedBlockScopedVariable(result: Symbol, errorLocation: Node): void { - Debug.assert(!!(result.flags & SymbolFlags.BlockScopedVariable || result.flags & SymbolFlags.Class || result.flags & SymbolFlags.Enum)); - if (result.flags & (SymbolFlags.Function | SymbolFlags.FunctionScopedVariable | SymbolFlags.Assignment) && result.flags & SymbolFlags.Class) { - // constructor functions aren't block scoped - return; + else if (result.flags & SymbolFlags.RegularEnum) { + diagnosticMessage = error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, declarationName); } - // Block-scoped variables cannot be used before their definition - const declaration = find( - result.declarations, - d => isBlockOrCatchScoped(d) || isClassLike(d) || (d.kind === SyntaxKind.EnumDeclaration)); - - if (declaration === undefined) return Debug.fail("checkResolvedBlockScopedVariable could not find block-scoped declaration"); - - if (!(declaration.flags & NodeFlags.Ambient) && !isBlockScopedNameDeclaredBeforeUse(declaration, errorLocation)) { - let diagnosticMessage; - const declarationName = declarationNameToString(getNameOfDeclaration(declaration)); - if (result.flags & SymbolFlags.BlockScopedVariable) { - diagnosticMessage = error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationName); - } - else if (result.flags & SymbolFlags.Class) { + else { + Debug.assert(!!(result.flags & SymbolFlags.ConstEnum)); + if (compilerOptions.preserveConstEnums) { diagnosticMessage = error(errorLocation, Diagnostics.Class_0_used_before_its_declaration, declarationName); } - else if (result.flags & SymbolFlags.RegularEnum) { - diagnosticMessage = error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, declarationName); - } - else { - Debug.assert(!!(result.flags & SymbolFlags.ConstEnum)); - if (compilerOptions.preserveConstEnums) { - diagnosticMessage = error(errorLocation, Diagnostics.Class_0_used_before_its_declaration, declarationName); - } - } - - if (diagnosticMessage) { - addRelatedInfo(diagnosticMessage, - createDiagnosticForNode(declaration, Diagnostics._0_is_declared_here, declarationName) - ); - } + } + if (diagnosticMessage) { + addRelatedInfo(diagnosticMessage, createDiagnosticForNode(declaration, Diagnostics._0_is_declared_here, declarationName)); } } - - /* Starting from 'initial' node walk up the parent chain until 'stopAt' node is reached. - * If at any point current node is equal to 'parent' node - return true. - * Return false if 'stopAt' node is reached or isFunctionLike(current) === true. - */ - function isSameScopeDescendentOf(initial: Node, parent: Node | undefined, stopAt: Node): boolean { - return !!parent && !!findAncestor(initial, n => n === stopAt || isFunctionLike(n) ? "quit" : n === parent); - } - - function getAnyImportSyntax(node: Node): AnyImportSyntax | undefined { - switch (node.kind) { - case SyntaxKind.ImportEqualsDeclaration: - return node as ImportEqualsDeclaration; - case SyntaxKind.ImportClause: - return (node as ImportClause).parent; - case SyntaxKind.NamespaceImport: - return (node as NamespaceImport).parent.parent; - case SyntaxKind.ImportSpecifier: - return (node as ImportSpecifier).parent.parent.parent; - default: - return undefined; - } - } - - function getDeclarationOfAliasSymbol(symbol: Symbol): Declaration | undefined { - return find(symbol.declarations, isAliasSymbolDeclaration); - } - - function getTargetOfImportEqualsDeclaration(node: ImportEqualsDeclaration, dontResolveAlias: boolean): Symbol | undefined { - if (node.moduleReference.kind === SyntaxKind.ExternalModuleReference) { - return resolveExternalModuleSymbol(resolveExternalModuleName(node, getExternalModuleImportEqualsDeclarationExpression(node))); - } - return getSymbolOfPartOfRightHandSideOfImportEquals(node.moduleReference, dontResolveAlias); + } + /* Starting from 'initial' node walk up the parent chain until 'stopAt' node is reached. + * If at any point current node is equal to 'parent' node - return true. + * Return false if 'stopAt' node is reached or isFunctionLike(current) === true. + */ + function isSameScopeDescendentOf(initial: Node, parent: Node | undefined, stopAt: Node): boolean { + return !!parent && !!findAncestor(initial, n => n === stopAt || isFunctionLike(n) ? "quit" : n === parent); + } + function getAnyImportSyntax(node: Node): AnyImportSyntax | undefined { + switch (node.kind) { + case SyntaxKind.ImportEqualsDeclaration: + return node as ImportEqualsDeclaration; + case SyntaxKind.ImportClause: + return (node as ImportClause).parent; + case SyntaxKind.NamespaceImport: + return (node as NamespaceImport).parent.parent; + case SyntaxKind.ImportSpecifier: + return (node as ImportSpecifier).parent.parent.parent; + default: + return undefined; } - - function resolveExportByName(moduleSymbol: Symbol, name: __String, dontResolveAlias: boolean) { - const exportValue = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals); - return exportValue - ? getPropertyOfType(getTypeOfSymbol(exportValue), name) - : resolveSymbol(moduleSymbol.exports!.get(name), dontResolveAlias); + } + function getDeclarationOfAliasSymbol(symbol: ts.Symbol): Declaration | undefined { + return find(symbol.declarations, isAliasSymbolDeclaration); + } + function getTargetOfImportEqualsDeclaration(node: ImportEqualsDeclaration, dontResolveAlias: boolean): ts.Symbol | undefined { + if (node.moduleReference.kind === SyntaxKind.ExternalModuleReference) { + return resolveExternalModuleSymbol(resolveExternalModuleName(node, getExternalModuleImportEqualsDeclarationExpression(node))); } - - function isSyntacticDefault(node: Node) { - return ((isExportAssignment(node) && !node.isExportEquals) || hasModifier(node, ModifierFlags.Default) || isExportSpecifier(node)); + return getSymbolOfPartOfRightHandSideOfImportEquals(node.moduleReference, dontResolveAlias); + } + function resolveExportByName(moduleSymbol: ts.Symbol, name: __String, dontResolveAlias: boolean) { + const exportValue = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals); + return exportValue + ? getPropertyOfType(getTypeOfSymbol(exportValue), name) + : resolveSymbol(moduleSymbol.exports!.get(name), dontResolveAlias); + } + function isSyntacticDefault(node: Node) { + return ((isExportAssignment(node) && !node.isExportEquals) || hasModifier(node, ModifierFlags.Default) || isExportSpecifier(node)); + } + function canHaveSyntheticDefault(file: SourceFile | undefined, moduleSymbol: ts.Symbol, dontResolveAlias: boolean) { + if (!allowSyntheticDefaultImports) { + return false; } - - function canHaveSyntheticDefault(file: SourceFile | undefined, moduleSymbol: Symbol, dontResolveAlias: boolean) { - if (!allowSyntheticDefaultImports) { + // Declaration files (and ambient modules) + if (!file || file.isDeclarationFile) { + // Definitely cannot have a synthetic default if they have a syntactic default member specified + const defaultExportSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, /*dontResolveAlias*/ true); // Dont resolve alias because we want the immediately exported symbol's declaration + if (defaultExportSymbol && some(defaultExportSymbol.declarations, isSyntacticDefault)) { return false; } - // Declaration files (and ambient modules) - if (!file || file.isDeclarationFile) { - // Definitely cannot have a synthetic default if they have a syntactic default member specified - const defaultExportSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, /*dontResolveAlias*/ true); // Dont resolve alias because we want the immediately exported symbol's declaration - if (defaultExportSymbol && some(defaultExportSymbol.declarations, isSyntacticDefault)) { - return false; - } - // It _might_ still be incorrect to assume there is no __esModule marker on the import at runtime, even if there is no `default` member - // So we check a bit more, - if (resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), dontResolveAlias)) { - // If there is an `__esModule` specified in the declaration (meaning someone explicitly added it or wrote it in their code), - // it definitely is a module and does not have a synthetic default - return false; - } - // There are _many_ declaration files not written with esmodules in mind that still get compiled into a format with __esModule set - // Meaning there may be no default at runtime - however to be on the permissive side, we allow access to a synthetic default member - // as there is no marker to indicate if the accompanying JS has `__esModule` or not, or is even native esm - return true; - } - // TypeScript files never have a synthetic default (as they are always emitted with an __esModule marker) _unless_ they contain an export= statement - if (!isSourceFileJS(file)) { - return hasExportAssignmentSymbol(moduleSymbol); + // It _might_ still be incorrect to assume there is no __esModule marker on the import at runtime, even if there is no `default` member + // So we check a bit more, + if (resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), dontResolveAlias)) { + // If there is an `__esModule` specified in the declaration (meaning someone explicitly added it or wrote it in their code), + // it definitely is a module and does not have a synthetic default + return false; } - // JS files have a synthetic default if they do not contain ES2015+ module syntax (export = is not valid in js) _and_ do not have an __esModule marker - return !file.externalModuleIndicator && !resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), dontResolveAlias); + // There are _many_ declaration files not written with esmodules in mind that still get compiled into a format with __esModule set + // Meaning there may be no default at runtime - however to be on the permissive side, we allow access to a synthetic default member + // as there is no marker to indicate if the accompanying JS has `__esModule` or not, or is even native esm + return true; } - - function getTargetOfImportClause(node: ImportClause, dontResolveAlias: boolean): Symbol | undefined { - const moduleSymbol = resolveExternalModuleName(node, node.parent.moduleSpecifier); - - if (moduleSymbol) { - let exportDefaultSymbol: Symbol | undefined; - if (isShorthandAmbientModuleSymbol(moduleSymbol)) { - exportDefaultSymbol = moduleSymbol; + // TypeScript files never have a synthetic default (as they are always emitted with an __esModule marker) _unless_ they contain an export= statement + if (!isSourceFileJS(file)) { + return hasExportAssignmentSymbol(moduleSymbol); + } + // JS files have a synthetic default if they do not contain ES2015+ module syntax (export = is not valid in js) _and_ do not have an __esModule marker + return !file.externalModuleIndicator && !resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), dontResolveAlias); + } + function getTargetOfImportClause(node: ImportClause, dontResolveAlias: boolean): ts.Symbol | undefined { + const moduleSymbol = resolveExternalModuleName(node, node.parent.moduleSpecifier); + if (moduleSymbol) { + let exportDefaultSymbol: ts.Symbol | undefined; + if (isShorthandAmbientModuleSymbol(moduleSymbol)) { + exportDefaultSymbol = moduleSymbol; + } + else { + exportDefaultSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, dontResolveAlias); + } + const file = find(moduleSymbol.declarations, isSourceFile); + const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias); + if (!exportDefaultSymbol && !hasSyntheticDefault) { + if (hasExportAssignmentSymbol(moduleSymbol)) { + const compilerOptionName = moduleKind >= ModuleKind.ES2015 ? "allowSyntheticDefaultImports" : "esModuleInterop"; + const exportEqualsSymbol = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals); + const exportAssignment = exportEqualsSymbol!.valueDeclaration; + const err = error(node.name, Diagnostics.Module_0_can_only_be_default_imported_using_the_1_flag, symbolToString(moduleSymbol), compilerOptionName); + addRelatedInfo(err, createDiagnosticForNode(exportAssignment, Diagnostics.This_module_is_declared_with_using_export_and_can_only_be_used_with_a_default_import_when_using_the_0_flag, compilerOptionName)); } else { - exportDefaultSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, dontResolveAlias); - } - - const file = find(moduleSymbol.declarations, isSourceFile); - const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias); - if (!exportDefaultSymbol && !hasSyntheticDefault) { - if (hasExportAssignmentSymbol(moduleSymbol)) { - const compilerOptionName = moduleKind >= ModuleKind.ES2015 ? "allowSyntheticDefaultImports" : "esModuleInterop"; - const exportEqualsSymbol = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals); - const exportAssignment = exportEqualsSymbol!.valueDeclaration; - const err = error(node.name, Diagnostics.Module_0_can_only_be_default_imported_using_the_1_flag, symbolToString(moduleSymbol), compilerOptionName); - - addRelatedInfo(err, createDiagnosticForNode( - exportAssignment, - Diagnostics.This_module_is_declared_with_using_export_and_can_only_be_used_with_a_default_import_when_using_the_0_flag, - compilerOptionName - )); + if (moduleSymbol.exports && moduleSymbol.exports.has(node.symbol.escapedName)) { + error(node.name, Diagnostics.Module_0_has_no_default_export_Did_you_mean_to_use_import_1_from_0_instead, symbolToString(moduleSymbol), symbolToString(node.symbol)); } else { - if (moduleSymbol.exports && moduleSymbol.exports.has(node.symbol.escapedName)) { - error( - node.name, - Diagnostics.Module_0_has_no_default_export_Did_you_mean_to_use_import_1_from_0_instead, - symbolToString(moduleSymbol), - symbolToString(node.symbol), - ); - } - else { - error(node.name, Diagnostics.Module_0_has_no_default_export, symbolToString(moduleSymbol)); - } - + error(node.name, Diagnostics.Module_0_has_no_default_export, symbolToString(moduleSymbol)); } } - else if (hasSyntheticDefault) { - // per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present - return resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); - } - return exportDefaultSymbol; } + else if (hasSyntheticDefault) { + // per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present + return resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); + } + return exportDefaultSymbol; } - - function getTargetOfNamespaceImport(node: NamespaceImport, dontResolveAlias: boolean): Symbol | undefined { - const moduleSpecifier = node.parent.parent.moduleSpecifier; - return resolveESModuleSymbol(resolveExternalModuleName(node, moduleSpecifier), moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false); - } - - // This function creates a synthetic symbol that combines the value side of one symbol with the - // type/namespace side of another symbol. Consider this example: - // - // declare module graphics { - // interface Point { - // x: number; - // y: number; - // } - // } - // declare var graphics: { - // Point: new (x: number, y: number) => graphics.Point; - // } - // declare module "graphics" { - // export = graphics; - // } - // - // An 'import { Point } from "graphics"' needs to create a symbol that combines the value side 'Point' - // property with the type/namespace side interface 'Point'. - function combineValueAndTypeSymbols(valueSymbol: Symbol, typeSymbol: Symbol): Symbol { - if (valueSymbol === unknownSymbol && typeSymbol === unknownSymbol) { - return unknownSymbol; - } - if (valueSymbol.flags & (SymbolFlags.Type | SymbolFlags.Namespace)) { - return valueSymbol; - } - const result = createSymbol(valueSymbol.flags | typeSymbol.flags, valueSymbol.escapedName); - result.declarations = deduplicate(concatenate(valueSymbol.declarations, typeSymbol.declarations), equateValues); - result.parent = valueSymbol.parent || typeSymbol.parent; - if (valueSymbol.valueDeclaration) result.valueDeclaration = valueSymbol.valueDeclaration; - if (typeSymbol.members) result.members = typeSymbol.members; - if (valueSymbol.exports) result.exports = valueSymbol.exports; - return result; + } + function getTargetOfNamespaceImport(node: NamespaceImport, dontResolveAlias: boolean): ts.Symbol | undefined { + const moduleSpecifier = node.parent.parent.moduleSpecifier; + return resolveESModuleSymbol(resolveExternalModuleName(node, moduleSpecifier), moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false); + } + // This function creates a synthetic symbol that combines the value side of one symbol with the + // type/namespace side of another symbol. Consider this example: + // + // declare module graphics { + // interface Point { + // x: number; + // y: number; + // } + // } + // declare var graphics: { + // Point: new (x: number, y: number) => graphics.Point; + // } + // declare module "graphics" { + // export = graphics; + // } + // + // An 'import { Point } from "graphics"' needs to create a symbol that combines the value side 'Point' + // property with the type/namespace side interface 'Point'. + function combineValueAndTypeSymbols(valueSymbol: ts.Symbol, typeSymbol: ts.Symbol): ts.Symbol { + if (valueSymbol === unknownSymbol && typeSymbol === unknownSymbol) { + return unknownSymbol; + } + if (valueSymbol.flags & (SymbolFlags.Type | SymbolFlags.Namespace)) { + return valueSymbol; + } + const result = createSymbol(valueSymbol.flags | typeSymbol.flags, valueSymbol.escapedName); + result.declarations = deduplicate(concatenate(valueSymbol.declarations, typeSymbol.declarations), equateValues); + result.parent = valueSymbol.parent || typeSymbol.parent; + if (valueSymbol.valueDeclaration) + result.valueDeclaration = valueSymbol.valueDeclaration; + if (typeSymbol.members) + result.members = typeSymbol.members; + if (valueSymbol.exports) + result.exports = valueSymbol.exports; + return result; + } + function getExportOfModule(symbol: ts.Symbol, name: __String, dontResolveAlias: boolean): ts.Symbol | undefined { + if (symbol.flags & SymbolFlags.Module) { + return resolveSymbol(getExportsOfSymbol(symbol).get(name)!, dontResolveAlias); } - - function getExportOfModule(symbol: Symbol, name: __String, dontResolveAlias: boolean): Symbol | undefined { - if (symbol.flags & SymbolFlags.Module) { - return resolveSymbol(getExportsOfSymbol(symbol).get(name)!, dontResolveAlias); + } + function getPropertyOfVariable(symbol: ts.Symbol, name: __String): ts.Symbol | undefined { + if (symbol.flags & SymbolFlags.Variable) { + const typeAnnotation = (symbol.valueDeclaration).type; + if (typeAnnotation) { + return resolveSymbol(getPropertyOfType(getTypeFromTypeNode(typeAnnotation), name)); } } - - function getPropertyOfVariable(symbol: Symbol, name: __String): Symbol | undefined { - if (symbol.flags & SymbolFlags.Variable) { - const typeAnnotation = (symbol.valueDeclaration).type; - if (typeAnnotation) { - return resolveSymbol(getPropertyOfType(getTypeFromTypeNode(typeAnnotation), name)); + } + function getExternalModuleMember(node: ImportDeclaration | ExportDeclaration, specifier: ImportOrExportSpecifier, dontResolveAlias = false): ts.Symbol | undefined { + const moduleSymbol = resolveExternalModuleName(node, node.moduleSpecifier!)!; // TODO: GH#18217 + const name = specifier.propertyName || specifier.name; + const suppressInteropError = name.escapedText === InternalSymbolName.Default && !!(compilerOptions.allowSyntheticDefaultImports || compilerOptions.esModuleInterop); + const targetSymbol = resolveESModuleSymbol(moduleSymbol, node.moduleSpecifier!, dontResolveAlias, suppressInteropError); + if (targetSymbol) { + if (name.escapedText) { + if (isShorthandAmbientModuleSymbol(moduleSymbol)) { + return moduleSymbol; } - } - } - - function getExternalModuleMember(node: ImportDeclaration | ExportDeclaration, specifier: ImportOrExportSpecifier, dontResolveAlias = false): Symbol | undefined { - const moduleSymbol = resolveExternalModuleName(node, node.moduleSpecifier!)!; // TODO: GH#18217 - const name = specifier.propertyName || specifier.name; - const suppressInteropError = name.escapedText === InternalSymbolName.Default && !!(compilerOptions.allowSyntheticDefaultImports || compilerOptions.esModuleInterop); - const targetSymbol = resolveESModuleSymbol(moduleSymbol, node.moduleSpecifier!, dontResolveAlias, suppressInteropError); - if (targetSymbol) { - if (name.escapedText) { - if (isShorthandAmbientModuleSymbol(moduleSymbol)) { - return moduleSymbol; - } - - let symbolFromVariable: Symbol | undefined; - // First check if module was specified with "export=". If so, get the member from the resolved type - if (moduleSymbol && moduleSymbol.exports && moduleSymbol.exports.get(InternalSymbolName.ExportEquals)) { - symbolFromVariable = getPropertyOfType(getTypeOfSymbol(targetSymbol), name.escapedText); + let symbolFromVariable: ts.Symbol | undefined; + // First check if module was specified with "export=". If so, get the member from the resolved type + if (moduleSymbol && moduleSymbol.exports && moduleSymbol.exports.get(InternalSymbolName.ExportEquals)) { + symbolFromVariable = getPropertyOfType(getTypeOfSymbol(targetSymbol), name.escapedText); + } + else { + symbolFromVariable = getPropertyOfVariable(targetSymbol, name.escapedText); + } + // if symbolFromVariable is export - get its final target + symbolFromVariable = resolveSymbol(symbolFromVariable, dontResolveAlias); + let symbolFromModule = getExportOfModule(targetSymbol, name.escapedText, dontResolveAlias); + // If the export member we're looking for is default, and there is no real default but allowSyntheticDefaultImports is on, return the entire module as the default + if (!symbolFromModule && allowSyntheticDefaultImports && name.escapedText === InternalSymbolName.Default) { + symbolFromModule = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); + } + const symbol = symbolFromModule && symbolFromVariable && symbolFromModule !== symbolFromVariable ? + combineValueAndTypeSymbols(symbolFromVariable, symbolFromModule) : + symbolFromModule || symbolFromVariable; + if (!symbol) { + const moduleName = getFullyQualifiedName(moduleSymbol, node); + const declarationName = declarationNameToString(name); + const suggestion = getSuggestedSymbolForNonexistentModule(name, targetSymbol); + if (suggestion !== undefined) { + const suggestionName = symbolToString(suggestion); + const diagnostic = error(name, Diagnostics.Module_0_has_no_exported_member_1_Did_you_mean_2, moduleName, declarationName, suggestionName); + if (suggestion.valueDeclaration) { + addRelatedInfo(diagnostic, createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName)); + } } else { - symbolFromVariable = getPropertyOfVariable(targetSymbol, name.escapedText); - } - // if symbolFromVariable is export - get its final target - symbolFromVariable = resolveSymbol(symbolFromVariable, dontResolveAlias); - let symbolFromModule = getExportOfModule(targetSymbol, name.escapedText, dontResolveAlias); - // If the export member we're looking for is default, and there is no real default but allowSyntheticDefaultImports is on, return the entire module as the default - if (!symbolFromModule && allowSyntheticDefaultImports && name.escapedText === InternalSymbolName.Default) { - symbolFromModule = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); - } - const symbol = symbolFromModule && symbolFromVariable && symbolFromModule !== symbolFromVariable ? - combineValueAndTypeSymbols(symbolFromVariable, symbolFromModule) : - symbolFromModule || symbolFromVariable; - if (!symbol) { - const moduleName = getFullyQualifiedName(moduleSymbol, node); - const declarationName = declarationNameToString(name); - const suggestion = getSuggestedSymbolForNonexistentModule(name, targetSymbol); - if (suggestion !== undefined) { - const suggestionName = symbolToString(suggestion); - const diagnostic = error(name, Diagnostics.Module_0_has_no_exported_member_1_Did_you_mean_2, moduleName, declarationName, suggestionName); - if (suggestion.valueDeclaration) { - addRelatedInfo(diagnostic, - createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName) - ); - } + if (moduleSymbol.exports && moduleSymbol.exports.has(InternalSymbolName.Default)) { + error(name, Diagnostics.Module_0_has_no_exported_member_1_Did_you_mean_to_use_import_1_from_0_instead, moduleName, declarationName); } else { - if (moduleSymbol.exports && moduleSymbol.exports.has(InternalSymbolName.Default)) { - error( - name, - Diagnostics.Module_0_has_no_exported_member_1_Did_you_mean_to_use_import_1_from_0_instead, - moduleName, - declarationName - ); - } - else { - error(name, Diagnostics.Module_0_has_no_exported_member_1, moduleName, declarationName); - } + error(name, Diagnostics.Module_0_has_no_exported_member_1, moduleName, declarationName); } } - return symbol; } + return symbol; } } - - function getTargetOfImportSpecifier(node: ImportSpecifier, dontResolveAlias: boolean): Symbol | undefined { - return getExternalModuleMember(node.parent.parent.parent, node, dontResolveAlias); - } - - function getTargetOfNamespaceExportDeclaration(node: NamespaceExportDeclaration, dontResolveAlias: boolean): Symbol { - return resolveExternalModuleSymbol(node.parent.symbol, dontResolveAlias); + } + function getTargetOfImportSpecifier(node: ImportSpecifier, dontResolveAlias: boolean): ts.Symbol | undefined { + return getExternalModuleMember(node.parent.parent.parent, node, dontResolveAlias); + } + function getTargetOfNamespaceExportDeclaration(node: NamespaceExportDeclaration, dontResolveAlias: boolean): ts.Symbol { + return resolveExternalModuleSymbol(node.parent.symbol, dontResolveAlias); + } + function getTargetOfExportSpecifier(node: ExportSpecifier, meaning: SymbolFlags, dontResolveAlias?: boolean) { + return node.parent.parent.moduleSpecifier ? + getExternalModuleMember(node.parent.parent, node, dontResolveAlias) : + resolveEntityName(node.propertyName || node.name, meaning, /*ignoreErrors*/ false, dontResolveAlias); + } + function getTargetOfExportAssignment(node: ExportAssignment | BinaryExpression, dontResolveAlias: boolean): ts.Symbol | undefined { + const expression = ((isExportAssignment(node) ? node.expression : node.right) as EntityNameExpression | ClassExpression); + return getTargetOfAliasLikeExpression(expression, dontResolveAlias); + } + function getTargetOfAliasLikeExpression(expression: Expression, dontResolveAlias: boolean) { + if (isClassExpression(expression)) { + return checkExpressionCached(expression).symbol; } - - function getTargetOfExportSpecifier(node: ExportSpecifier, meaning: SymbolFlags, dontResolveAlias?: boolean) { - return node.parent.parent.moduleSpecifier ? - getExternalModuleMember(node.parent.parent, node, dontResolveAlias) : - resolveEntityName(node.propertyName || node.name, meaning, /*ignoreErrors*/ false, dontResolveAlias); + if (!isEntityName(expression) && !isEntityNameExpression(expression)) { + return undefined; } - - function getTargetOfExportAssignment(node: ExportAssignment | BinaryExpression, dontResolveAlias: boolean): Symbol | undefined { - const expression = (isExportAssignment(node) ? node.expression : node.right) as EntityNameExpression | ClassExpression; - return getTargetOfAliasLikeExpression(expression, dontResolveAlias); + const aliasLike = resolveEntityName(expression, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontResolveAlias); + if (aliasLike) { + return aliasLike; } - - function getTargetOfAliasLikeExpression(expression: Expression, dontResolveAlias: boolean) { - if (isClassExpression(expression)) { - return checkExpressionCached(expression).symbol; - } - if (!isEntityName(expression) && !isEntityNameExpression(expression)) { - return undefined; - } - const aliasLike = resolveEntityName(expression, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontResolveAlias); - if (aliasLike) { - return aliasLike; - } - checkExpressionCached(expression); - return getNodeLinks(expression).resolvedSymbol; + checkExpressionCached(expression); + return getNodeLinks(expression).resolvedSymbol; + } + function getTargetOfPropertyAssignment(node: PropertyAssignment, dontRecursivelyResolve: boolean): ts.Symbol | undefined { + const expression = node.initializer; + return getTargetOfAliasLikeExpression(expression, dontRecursivelyResolve); + } + function getTargetOfPropertyAccessExpression(node: PropertyAccessExpression, dontRecursivelyResolve: boolean): ts.Symbol | undefined { + if (!(isBinaryExpression(node.parent) && node.parent.left === node && node.parent.operatorToken.kind === SyntaxKind.EqualsToken)) { + return undefined; } - - function getTargetOfPropertyAssignment(node: PropertyAssignment, dontRecursivelyResolve: boolean): Symbol | undefined { - const expression = node.initializer; - return getTargetOfAliasLikeExpression(expression, dontRecursivelyResolve); + return getTargetOfAliasLikeExpression(node.parent.right, dontRecursivelyResolve); + } + function getTargetOfAliasDeclaration(node: Declaration, dontRecursivelyResolve = false): ts.Symbol | undefined { + switch (node.kind) { + case SyntaxKind.ImportEqualsDeclaration: + return getTargetOfImportEqualsDeclaration((node), dontRecursivelyResolve); + case SyntaxKind.ImportClause: + return getTargetOfImportClause((node), dontRecursivelyResolve); + case SyntaxKind.NamespaceImport: + return getTargetOfNamespaceImport((node), dontRecursivelyResolve); + case SyntaxKind.ImportSpecifier: + return getTargetOfImportSpecifier((node), dontRecursivelyResolve); + case SyntaxKind.ExportSpecifier: + return getTargetOfExportSpecifier((node), SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, dontRecursivelyResolve); + case SyntaxKind.ExportAssignment: + case SyntaxKind.BinaryExpression: + return getTargetOfExportAssignment((node), dontRecursivelyResolve); + case SyntaxKind.NamespaceExportDeclaration: + return getTargetOfNamespaceExportDeclaration((node), dontRecursivelyResolve); + case SyntaxKind.ShorthandPropertyAssignment: + return resolveEntityName((node as ShorthandPropertyAssignment).name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontRecursivelyResolve); + case SyntaxKind.PropertyAssignment: + return getTargetOfPropertyAssignment((node as PropertyAssignment), dontRecursivelyResolve); + case SyntaxKind.PropertyAccessExpression: + return getTargetOfPropertyAccessExpression((node as PropertyAccessExpression), dontRecursivelyResolve); + default: + return Debug.fail(); } - - function getTargetOfPropertyAccessExpression(node: PropertyAccessExpression, dontRecursivelyResolve: boolean): Symbol | undefined { - if (!(isBinaryExpression(node.parent) && node.parent.left === node && node.parent.operatorToken.kind === SyntaxKind.EqualsToken)) { - return undefined; + } + /** + * Indicates that a symbol is an alias that does not merge with a local declaration. + * OR Is a JSContainer which may merge an alias with a local declaration + */ + function isNonLocalAlias(symbol: ts.Symbol | undefined, excludes = SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace): symbol is ts.Symbol { + if (!symbol) + return false; + return (symbol.flags & (SymbolFlags.Alias | excludes)) === SymbolFlags.Alias || !!(symbol.flags & SymbolFlags.Alias && symbol.flags & SymbolFlags.Assignment); + } + function resolveSymbol(symbol: ts.Symbol, dontResolveAlias?: boolean): ts.Symbol; + function resolveSymbol(symbol: ts.Symbol | undefined, dontResolveAlias?: boolean): ts.Symbol | undefined; + function resolveSymbol(symbol: ts.Symbol | undefined, dontResolveAlias?: boolean): ts.Symbol | undefined { + return !dontResolveAlias && isNonLocalAlias(symbol) ? resolveAlias(symbol) : symbol; + } + function resolveAlias(symbol: ts.Symbol): ts.Symbol { + Debug.assert((symbol.flags & SymbolFlags.Alias) !== 0, "Should only get Alias here."); + const links = getSymbolLinks(symbol); + if (!links.target) { + links.target = resolvingSymbol; + const node = getDeclarationOfAliasSymbol(symbol); + if (!node) + return Debug.fail(); + const target = getTargetOfAliasDeclaration(node); + if (links.target === resolvingSymbol) { + links.target = target || unknownSymbol; } - - return getTargetOfAliasLikeExpression(node.parent.right, dontRecursivelyResolve); - } - - function getTargetOfAliasDeclaration(node: Declaration, dontRecursivelyResolve = false): Symbol | undefined { - switch (node.kind) { - case SyntaxKind.ImportEqualsDeclaration: - return getTargetOfImportEqualsDeclaration(node, dontRecursivelyResolve); - case SyntaxKind.ImportClause: - return getTargetOfImportClause(node, dontRecursivelyResolve); - case SyntaxKind.NamespaceImport: - return getTargetOfNamespaceImport(node, dontRecursivelyResolve); - case SyntaxKind.ImportSpecifier: - return getTargetOfImportSpecifier(node, dontRecursivelyResolve); - case SyntaxKind.ExportSpecifier: - return getTargetOfExportSpecifier(node, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, dontRecursivelyResolve); - case SyntaxKind.ExportAssignment: - case SyntaxKind.BinaryExpression: - return getTargetOfExportAssignment((node), dontRecursivelyResolve); - case SyntaxKind.NamespaceExportDeclaration: - return getTargetOfNamespaceExportDeclaration(node, dontRecursivelyResolve); - case SyntaxKind.ShorthandPropertyAssignment: - return resolveEntityName((node as ShorthandPropertyAssignment).name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontRecursivelyResolve); - case SyntaxKind.PropertyAssignment: - return getTargetOfPropertyAssignment(node as PropertyAssignment, dontRecursivelyResolve); - case SyntaxKind.PropertyAccessExpression: - return getTargetOfPropertyAccessExpression(node as PropertyAccessExpression, dontRecursivelyResolve); - default: - return Debug.fail(); + else { + error(node, Diagnostics.Circular_definition_of_import_alias_0, symbolToString(symbol)); } } - - /** - * Indicates that a symbol is an alias that does not merge with a local declaration. - * OR Is a JSContainer which may merge an alias with a local declaration - */ - function isNonLocalAlias(symbol: Symbol | undefined, excludes = SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace): symbol is Symbol { - if (!symbol) return false; - return (symbol.flags & (SymbolFlags.Alias | excludes)) === SymbolFlags.Alias || !!(symbol.flags & SymbolFlags.Alias && symbol.flags & SymbolFlags.Assignment); - } - - function resolveSymbol(symbol: Symbol, dontResolveAlias?: boolean): Symbol; - function resolveSymbol(symbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined; - function resolveSymbol(symbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined { - return !dontResolveAlias && isNonLocalAlias(symbol) ? resolveAlias(symbol) : symbol; + else if (links.target === resolvingSymbol) { + links.target = unknownSymbol; } - - function resolveAlias(symbol: Symbol): Symbol { - Debug.assert((symbol.flags & SymbolFlags.Alias) !== 0, "Should only get Alias here."); - const links = getSymbolLinks(symbol); - if (!links.target) { - links.target = resolvingSymbol; - const node = getDeclarationOfAliasSymbol(symbol); - if (!node) return Debug.fail(); - const target = getTargetOfAliasDeclaration(node); - if (links.target === resolvingSymbol) { - links.target = target || unknownSymbol; - } - else { - error(node, Diagnostics.Circular_definition_of_import_alias_0, symbolToString(symbol)); - } - } - else if (links.target === resolvingSymbol) { - links.target = unknownSymbol; + return links.target; + } + function markExportAsReferenced(node: ImportEqualsDeclaration | ExportSpecifier) { + const symbol = getSymbolOfNode(node); + const target = resolveAlias(symbol); + if (target) { + const markAlias = target === unknownSymbol || + ((target.flags & SymbolFlags.Value) && !isConstEnumOrConstEnumOnlyModule(target)); + if (markAlias) { + markAliasSymbolAsReferenced(symbol); } - return links.target; } - - function markExportAsReferenced(node: ImportEqualsDeclaration | ExportSpecifier) { - const symbol = getSymbolOfNode(node); - const target = resolveAlias(symbol); - if (target) { - const markAlias = target === unknownSymbol || - ((target.flags & SymbolFlags.Value) && !isConstEnumOrConstEnumOnlyModule(target)); - - if (markAlias) { - markAliasSymbolAsReferenced(symbol); + } + // When an alias symbol is referenced, we need to mark the entity it references as referenced and in turn repeat that until + // we reach a non-alias or an exported entity (which is always considered referenced). We do this by checking the target of + // the alias as an expression (which recursively takes us back here if the target references another alias). + function markAliasSymbolAsReferenced(symbol: ts.Symbol) { + const links = getSymbolLinks(symbol); + if (!links.referenced) { + links.referenced = true; + const node = getDeclarationOfAliasSymbol(symbol); + if (!node) + return Debug.fail(); + // We defer checking of the reference of an `import =` until the import itself is referenced, + // This way a chain of imports can be elided if ultimately the final input is only used in a type + // position. + if (isInternalModuleImportEqualsDeclaration(node)) { + const target = resolveSymbol(symbol); + if (target === unknownSymbol || target.flags & SymbolFlags.Value) { + // import foo = + checkExpressionCached((node.moduleReference)); } } } - - // When an alias symbol is referenced, we need to mark the entity it references as referenced and in turn repeat that until - // we reach a non-alias or an exported entity (which is always considered referenced). We do this by checking the target of - // the alias as an expression (which recursively takes us back here if the target references another alias). - function markAliasSymbolAsReferenced(symbol: Symbol) { - const links = getSymbolLinks(symbol); - if (!links.referenced) { - links.referenced = true; - const node = getDeclarationOfAliasSymbol(symbol); - if (!node) return Debug.fail(); - // We defer checking of the reference of an `import =` until the import itself is referenced, - // This way a chain of imports can be elided if ultimately the final input is only used in a type - // position. - if (isInternalModuleImportEqualsDeclaration(node)) { - const target = resolveSymbol(symbol); - if (target === unknownSymbol || target.flags & SymbolFlags.Value) { - // import foo = - checkExpressionCached(node.moduleReference); - } - } - } + } + // This function is only for imports with entity names + function getSymbolOfPartOfRightHandSideOfImportEquals(entityName: EntityName, dontResolveAlias?: boolean): ts.Symbol | undefined { + // There are three things we might try to look for. In the following examples, + // the search term is enclosed in |...|: + // + // import a = |b|; // Namespace + // import a = |b.c|; // Value, type, namespace + // import a = |b.c|.d; // Namespace + if (entityName.kind === SyntaxKind.Identifier && isRightSideOfQualifiedNameOrPropertyAccess(entityName)) { + entityName = (entityName.parent); + } + // Check for case 1 and 3 in the above example + if (entityName.kind === SyntaxKind.Identifier || entityName.parent.kind === SyntaxKind.QualifiedName) { + return resolveEntityName(entityName, SymbolFlags.Namespace, /*ignoreErrors*/ false, dontResolveAlias); + } + else { + // Case 2 in above example + // entityName.kind could be a QualifiedName or a Missing identifier + Debug.assert(entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration); + return resolveEntityName(entityName, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ false, dontResolveAlias); } - - // This function is only for imports with entity names - function getSymbolOfPartOfRightHandSideOfImportEquals(entityName: EntityName, dontResolveAlias?: boolean): Symbol | undefined { - // There are three things we might try to look for. In the following examples, - // the search term is enclosed in |...|: - // - // import a = |b|; // Namespace - // import a = |b.c|; // Value, type, namespace - // import a = |b.c|.d; // Namespace - if (entityName.kind === SyntaxKind.Identifier && isRightSideOfQualifiedNameOrPropertyAccess(entityName)) { - entityName = entityName.parent; - } - // Check for case 1 and 3 in the above example - if (entityName.kind === SyntaxKind.Identifier || entityName.parent.kind === SyntaxKind.QualifiedName) { - return resolveEntityName(entityName, SymbolFlags.Namespace, /*ignoreErrors*/ false, dontResolveAlias); - } - else { - // Case 2 in above example - // entityName.kind could be a QualifiedName or a Missing identifier - Debug.assert(entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration); - return resolveEntityName(entityName, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ false, dontResolveAlias); - } + } + function getFullyQualifiedName(symbol: ts.Symbol, containingLocation?: Node): string { + return symbol.parent ? getFullyQualifiedName(symbol.parent, containingLocation) + "." + symbolToString(symbol) : symbolToString(symbol, containingLocation, /*meaning*/ undefined, SymbolFormatFlags.DoNotIncludeSymbolChain | SymbolFormatFlags.AllowAnyNodeKind); + } + /** + * Resolves a qualified name and any involved aliases. + */ + function resolveEntityName(name: EntityNameOrEntityNameExpression, meaning: SymbolFlags, ignoreErrors?: boolean, dontResolveAlias?: boolean, location?: Node): ts.Symbol | undefined { + if (nodeIsMissing(name)) { + return undefined; } - - function getFullyQualifiedName(symbol: Symbol, containingLocation?: Node): string { - return symbol.parent ? getFullyQualifiedName(symbol.parent, containingLocation) + "." + symbolToString(symbol) : symbolToString(symbol, containingLocation, /*meaning*/ undefined, SymbolFormatFlags.DoNotIncludeSymbolChain | SymbolFormatFlags.AllowAnyNodeKind); + const namespaceMeaning = SymbolFlags.Namespace | (isInJSFile(name) ? meaning & SymbolFlags.Value : 0); + let symbol: ts.Symbol | undefined; + if (name.kind === SyntaxKind.Identifier) { + const message = meaning === namespaceMeaning ? Diagnostics.Cannot_find_namespace_0 : getCannotFindNameDiagnosticForName(getFirstIdentifier(name)); + const symbolFromJSPrototype = isInJSFile(name) ? resolveEntityNameFromAssignmentDeclaration(name, meaning) : undefined; + symbol = resolveName(location || name, name.escapedText, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, name, /*isUse*/ true); + if (!symbol) { + return symbolFromJSPrototype; + } } - - /** - * Resolves a qualified name and any involved aliases. - */ - function resolveEntityName(name: EntityNameOrEntityNameExpression, meaning: SymbolFlags, ignoreErrors?: boolean, dontResolveAlias?: boolean, location?: Node): Symbol | undefined { - if (nodeIsMissing(name)) { + else if (name.kind === SyntaxKind.QualifiedName || name.kind === SyntaxKind.PropertyAccessExpression) { + const left = name.kind === SyntaxKind.QualifiedName ? name.left : name.expression; + const right = name.kind === SyntaxKind.QualifiedName ? name.right : name.name; + let namespace = resolveEntityName(left, namespaceMeaning, ignoreErrors, /*dontResolveAlias*/ false, location); + if (!namespace || nodeIsMissing(right)) { return undefined; } - - const namespaceMeaning = SymbolFlags.Namespace | (isInJSFile(name) ? meaning & SymbolFlags.Value : 0); - let symbol: Symbol | undefined; - if (name.kind === SyntaxKind.Identifier) { - const message = meaning === namespaceMeaning ? Diagnostics.Cannot_find_namespace_0 : getCannotFindNameDiagnosticForName(getFirstIdentifier(name)); - const symbolFromJSPrototype = isInJSFile(name) ? resolveEntityNameFromAssignmentDeclaration(name, meaning) : undefined; - symbol = resolveName(location || name, name.escapedText, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, name, /*isUse*/ true); - if (!symbol) { - return symbolFromJSPrototype; - } + else if (namespace === unknownSymbol) { + return namespace; } - else if (name.kind === SyntaxKind.QualifiedName || name.kind === SyntaxKind.PropertyAccessExpression) { - const left = name.kind === SyntaxKind.QualifiedName ? name.left : name.expression; - const right = name.kind === SyntaxKind.QualifiedName ? name.right : name.name; - let namespace = resolveEntityName(left, namespaceMeaning, ignoreErrors, /*dontResolveAlias*/ false, location); - if (!namespace || nodeIsMissing(right)) { - return undefined; - } - else if (namespace === unknownSymbol) { - return namespace; - } - if (isInJSFile(name)) { - if (namespace.valueDeclaration && - isVariableDeclaration(namespace.valueDeclaration) && - namespace.valueDeclaration.initializer && - isCommonJsRequire(namespace.valueDeclaration.initializer)) { - const moduleName = (namespace.valueDeclaration.initializer as CallExpression).arguments[0] as StringLiteral; - const moduleSym = resolveExternalModuleName(moduleName, moduleName); - if (moduleSym) { - const resolvedModuleSymbol = resolveExternalModuleSymbol(moduleSym); - if (resolvedModuleSymbol) { - namespace = resolvedModuleSymbol; - } + if (isInJSFile(name)) { + if (namespace.valueDeclaration && + isVariableDeclaration(namespace.valueDeclaration) && + namespace.valueDeclaration.initializer && + isCommonJsRequire(namespace.valueDeclaration.initializer)) { + const moduleName = ((namespace.valueDeclaration.initializer as CallExpression).arguments[0] as StringLiteral); + const moduleSym = resolveExternalModuleName(moduleName, moduleName); + if (moduleSym) { + const resolvedModuleSymbol = resolveExternalModuleSymbol(moduleSym); + if (resolvedModuleSymbol) { + namespace = resolvedModuleSymbol; } } } - symbol = getSymbol(getExportsOfSymbol(namespace), right.escapedText, meaning); - if (!symbol) { - if (!ignoreErrors) { - error(right, Diagnostics.Namespace_0_has_no_exported_member_1, getFullyQualifiedName(namespace), declarationNameToString(right)); - } - return undefined; + } + symbol = getSymbol(getExportsOfSymbol(namespace), right.escapedText, meaning); + if (!symbol) { + if (!ignoreErrors) { + error(right, Diagnostics.Namespace_0_has_no_exported_member_1, getFullyQualifiedName(namespace), declarationNameToString(right)); } + return undefined; } - else { - throw Debug.assertNever(name, "Unknown entity name kind."); + } + else { + throw Debug.assertNever(name, "Unknown entity name kind."); + } + Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); + return (symbol.flags & meaning) || dontResolveAlias ? symbol : resolveAlias(symbol); + } + /** + * 1. For prototype-property methods like `A.prototype.m = function () ...`, try to resolve names in the scope of `A` too. + * Note that prototype-property assignment to locations outside the current file (eg globals) doesn't work, so + * name resolution won't work either. + * 2. For property assignments like `{ x: function f () { } }`, try to resolve names in the scope of `f` too. + */ + function resolveEntityNameFromAssignmentDeclaration(name: Identifier, meaning: SymbolFlags) { + if (isJSDocTypeReference(name.parent)) { + const secondaryLocation = getAssignmentDeclarationLocation(name.parent); + if (secondaryLocation) { + return resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true); } - Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); - return (symbol.flags & meaning) || dontResolveAlias ? symbol : resolveAlias(symbol); } - - /** - * 1. For prototype-property methods like `A.prototype.m = function () ...`, try to resolve names in the scope of `A` too. - * Note that prototype-property assignment to locations outside the current file (eg globals) doesn't work, so - * name resolution won't work either. - * 2. For property assignments like `{ x: function f () { } }`, try to resolve names in the scope of `f` too. - */ - function resolveEntityNameFromAssignmentDeclaration(name: Identifier, meaning: SymbolFlags) { - if (isJSDocTypeReference(name.parent)) { - const secondaryLocation = getAssignmentDeclarationLocation(name.parent); - if (secondaryLocation) { - return resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true); - } + } + function getAssignmentDeclarationLocation(node: TypeReferenceNode): Node | undefined { + const typeAlias = findAncestor(node, node => !(isJSDocNode(node) || node.flags & NodeFlags.JSDoc) ? "quit" : isJSDocTypeAlias(node)); + if (typeAlias) { + return; + } + const host = getJSDocHost(node); + if (isExpressionStatement(host) && + isBinaryExpression(host.expression) && + getAssignmentDeclarationKind(host.expression) === AssignmentDeclarationKind.PrototypeProperty) { + // X.prototype.m = /** @param {K} p */ function () { } <-- look for K on X's declaration + const symbol = getSymbolOfNode(host.expression.left); + if (symbol) { + return getDeclarationOfJSPrototypeContainer(symbol); } } - - function getAssignmentDeclarationLocation(node: TypeReferenceNode): Node | undefined { - const typeAlias = findAncestor(node, node => !(isJSDocNode(node) || node.flags & NodeFlags.JSDoc) ? "quit" : isJSDocTypeAlias(node)); - if (typeAlias) { - return; + if ((isObjectLiteralMethod(host) || isPropertyAssignment(host)) && + isBinaryExpression(host.parent.parent) && + getAssignmentDeclarationKind(host.parent.parent) === AssignmentDeclarationKind.Prototype) { + // X.prototype = { /** @param {K} p */m() { } } <-- look for K on X's declaration + const symbol = getSymbolOfNode(host.parent.parent.left); + if (symbol) { + return getDeclarationOfJSPrototypeContainer(symbol); } - const host = getJSDocHost(node); - if (isExpressionStatement(host) && - isBinaryExpression(host.expression) && - getAssignmentDeclarationKind(host.expression) === AssignmentDeclarationKind.PrototypeProperty) { - // X.prototype.m = /** @param {K} p */ function () { } <-- look for K on X's declaration - const symbol = getSymbolOfNode(host.expression.left); - if (symbol) { - return getDeclarationOfJSPrototypeContainer(symbol); - } + } + const sig = getHostSignatureFromJSDocHost(host); + if (sig) { + const symbol = getSymbolOfNode(sig); + return symbol && symbol.valueDeclaration; + } + } + function getDeclarationOfJSPrototypeContainer(symbol: ts.Symbol) { + const decl = symbol.parent!.valueDeclaration; + if (!decl) { + return undefined; + } + const initializer = isAssignmentDeclaration(decl) ? getAssignedExpandoInitializer(decl) : + hasOnlyExpressionInitializer(decl) ? getDeclaredExpandoInitializer(decl) : + undefined; + return initializer || decl; + } + /** + * Get the real symbol of a declaration with an expando initializer. + * + * Normally, declarations have an associated symbol, but when a declaration has an expando + * initializer, the expando's symbol is the one that has all the members merged into it. + */ + function getExpandoSymbol(symbol: ts.Symbol): ts.Symbol | undefined { + const decl = symbol.valueDeclaration; + if (!decl || !isInJSFile(decl) || symbol.flags & SymbolFlags.TypeAlias) { + return undefined; + } + const init = isVariableDeclaration(decl) ? getDeclaredExpandoInitializer(decl) : getAssignedExpandoInitializer(decl); + if (init) { + const initSymbol = getSymbolOfNode(init); + if (initSymbol) { + return mergeJSSymbols(initSymbol, symbol); } - if ((isObjectLiteralMethod(host) || isPropertyAssignment(host)) && - isBinaryExpression(host.parent.parent) && - getAssignmentDeclarationKind(host.parent.parent) === AssignmentDeclarationKind.Prototype) { - // X.prototype = { /** @param {K} p */m() { } } <-- look for K on X's declaration - const symbol = getSymbolOfNode(host.parent.parent.left); - if (symbol) { - return getDeclarationOfJSPrototypeContainer(symbol); - } + } + } + function resolveExternalModuleName(location: Node, moduleReferenceExpression: Expression, ignoreErrors?: boolean): ts.Symbol | undefined { + return resolveExternalModuleNameWorker(location, moduleReferenceExpression, ignoreErrors ? undefined : Diagnostics.Cannot_find_module_0); + } + function resolveExternalModuleNameWorker(location: Node, moduleReferenceExpression: Expression, moduleNotFoundError: DiagnosticMessage | undefined, isForAugmentation = false): ts.Symbol | undefined { + return isStringLiteralLike(moduleReferenceExpression) + ? resolveExternalModule(location, moduleReferenceExpression.text, moduleNotFoundError, moduleReferenceExpression, isForAugmentation) + : undefined; + } + function resolveExternalModule(location: Node, moduleReference: string, moduleNotFoundError: DiagnosticMessage | undefined, errorNode: Node, isForAugmentation = false): ts.Symbol | undefined { + if (startsWith(moduleReference, "@types/")) { + const diag = Diagnostics.Cannot_import_type_declaration_files_Consider_importing_0_instead_of_1; + const withoutAtTypePrefix = removePrefix(moduleReference, "@types/"); + error(errorNode, diag, withoutAtTypePrefix, moduleReference); + } + const ambientModule = tryFindAmbientModule(moduleReference, /*withAugmentations*/ true); + if (ambientModule) { + return ambientModule; + } + const currentSourceFile = getSourceFileOfNode(location); + const resolvedModule = (getResolvedModule(currentSourceFile, moduleReference)!); // TODO: GH#18217 + const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule); + const sourceFile = resolvedModule && !resolutionDiagnostic && host.getSourceFile(resolvedModule.resolvedFileName); + if (sourceFile) { + if (sourceFile.symbol) { + if (resolvedModule.isExternalLibraryImport && !resolutionExtensionIsTSOrJson(resolvedModule.extension)) { + errorOnImplicitAnyModule(/*isError*/ false, errorNode, resolvedModule, moduleReference); + } + // merged symbol is module declaration symbol combined with all augmentations + return getMergedSymbol(sourceFile.symbol); } - const sig = getHostSignatureFromJSDocHost(host); - if (sig) { - const symbol = getSymbolOfNode(sig); - return symbol && symbol.valueDeclaration; + if (moduleNotFoundError) { + // report errors only if it was requested + error(errorNode, Diagnostics.File_0_is_not_a_module, sourceFile.fileName); } + return undefined; } - - function getDeclarationOfJSPrototypeContainer(symbol: Symbol) { - const decl = symbol.parent!.valueDeclaration; - if (!decl) { - return undefined; + if (patternAmbientModules) { + const pattern = findBestPatternMatch(patternAmbientModules, _ => _.pattern, moduleReference); + if (pattern) { + // If the module reference matched a pattern ambient module ('*.foo') but there's also a + // module augmentation by the specific name requested ('a.foo'), we store the merged symbol + // by the augmentation name ('a.foo'), because asking for *.foo should not give you exports + // from a.foo. + const augmentation = patternAmbientModuleAugmentations && patternAmbientModuleAugmentations.get(moduleReference); + if (augmentation) { + return getMergedSymbol(augmentation); + } + return getMergedSymbol(pattern.symbol); } - const initializer = isAssignmentDeclaration(decl) ? getAssignedExpandoInitializer(decl) : - hasOnlyExpressionInitializer(decl) ? getDeclaredExpandoInitializer(decl) : - undefined; - return initializer || decl; } - - /** - * Get the real symbol of a declaration with an expando initializer. - * - * Normally, declarations have an associated symbol, but when a declaration has an expando - * initializer, the expando's symbol is the one that has all the members merged into it. - */ - function getExpandoSymbol(symbol: Symbol): Symbol | undefined { - const decl = symbol.valueDeclaration; - if (!decl || !isInJSFile(decl) || symbol.flags & SymbolFlags.TypeAlias) { - return undefined; + // May be an untyped module. If so, ignore resolutionDiagnostic. + if (resolvedModule && !resolutionExtensionIsTSOrJson(resolvedModule.extension) && resolutionDiagnostic === undefined || resolutionDiagnostic === Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type) { + if (isForAugmentation) { + const diag = Diagnostics.Invalid_module_name_in_augmentation_Module_0_resolves_to_an_untyped_module_at_1_which_cannot_be_augmented; + error(errorNode, diag, moduleReference, resolvedModule.resolvedFileName); } - const init = isVariableDeclaration(decl) ? getDeclaredExpandoInitializer(decl) : getAssignedExpandoInitializer(decl); - if (init) { - const initSymbol = getSymbolOfNode(init); - if (initSymbol) { - return mergeJSSymbols(initSymbol, symbol); - } + else { + errorOnImplicitAnyModule(/*isError*/ noImplicitAny && !!moduleNotFoundError, errorNode, resolvedModule, moduleReference); } + // Failed imports and untyped modules are both treated in an untyped manner; only difference is whether we give a diagnostic first. + return undefined; } - - - function resolveExternalModuleName(location: Node, moduleReferenceExpression: Expression, ignoreErrors?: boolean): Symbol | undefined { - return resolveExternalModuleNameWorker(location, moduleReferenceExpression, ignoreErrors ? undefined : Diagnostics.Cannot_find_module_0); - } - - function resolveExternalModuleNameWorker(location: Node, moduleReferenceExpression: Expression, moduleNotFoundError: DiagnosticMessage | undefined, isForAugmentation = false): Symbol | undefined { - return isStringLiteralLike(moduleReferenceExpression) - ? resolveExternalModule(location, moduleReferenceExpression.text, moduleNotFoundError, moduleReferenceExpression, isForAugmentation) - : undefined; - } - - function resolveExternalModule(location: Node, moduleReference: string, moduleNotFoundError: DiagnosticMessage | undefined, errorNode: Node, isForAugmentation = false): Symbol | undefined { - if (startsWith(moduleReference, "@types/")) { - const diag = Diagnostics.Cannot_import_type_declaration_files_Consider_importing_0_instead_of_1; - const withoutAtTypePrefix = removePrefix(moduleReference, "@types/"); - error(errorNode, diag, withoutAtTypePrefix, moduleReference); - } - - const ambientModule = tryFindAmbientModule(moduleReference, /*withAugmentations*/ true); - if (ambientModule) { - return ambientModule; - } - const currentSourceFile = getSourceFileOfNode(location); - const resolvedModule = getResolvedModule(currentSourceFile, moduleReference)!; // TODO: GH#18217 - const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule); - const sourceFile = resolvedModule && !resolutionDiagnostic && host.getSourceFile(resolvedModule.resolvedFileName); - if (sourceFile) { - if (sourceFile.symbol) { - if (resolvedModule.isExternalLibraryImport && !resolutionExtensionIsTSOrJson(resolvedModule.extension)) { - errorOnImplicitAnyModule(/*isError*/ false, errorNode, resolvedModule, moduleReference); - } - // merged symbol is module declaration symbol combined with all augmentations - return getMergedSymbol(sourceFile.symbol); - } - if (moduleNotFoundError) { - // report errors only if it was requested - error(errorNode, Diagnostics.File_0_is_not_a_module, sourceFile.fileName); + if (moduleNotFoundError) { + // See if this was possibly a projectReference redirect + if (resolvedModule) { + const redirect = host.getProjectReferenceRedirect(resolvedModule.resolvedFileName); + if (redirect) { + error(errorNode, Diagnostics.Output_file_0_has_not_been_built_from_source_file_1, redirect, resolvedModule.resolvedFileName); + return undefined; } - return undefined; } - - if (patternAmbientModules) { - const pattern = findBestPatternMatch(patternAmbientModules, _ => _.pattern, moduleReference); - if (pattern) { - // If the module reference matched a pattern ambient module ('*.foo') but there's also a - // module augmentation by the specific name requested ('a.foo'), we store the merged symbol - // by the augmentation name ('a.foo'), because asking for *.foo should not give you exports - // from a.foo. - const augmentation = patternAmbientModuleAugmentations && patternAmbientModuleAugmentations.get(moduleReference); - if (augmentation) { - return getMergedSymbol(augmentation); - } - return getMergedSymbol(pattern.symbol); - } + if (resolutionDiagnostic) { + error(errorNode, resolutionDiagnostic, moduleReference, resolvedModule.resolvedFileName); } - - // May be an untyped module. If so, ignore resolutionDiagnostic. - if (resolvedModule && !resolutionExtensionIsTSOrJson(resolvedModule.extension) && resolutionDiagnostic === undefined || resolutionDiagnostic === Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type) { - if (isForAugmentation) { - const diag = Diagnostics.Invalid_module_name_in_augmentation_Module_0_resolves_to_an_untyped_module_at_1_which_cannot_be_augmented; - error(errorNode, diag, moduleReference, resolvedModule.resolvedFileName); + else { + const tsExtension = tryExtractTSExtension(moduleReference); + if (tsExtension) { + const diag = Diagnostics.An_import_path_cannot_end_with_a_0_extension_Consider_importing_1_instead; + error(errorNode, diag, tsExtension, removeExtension(moduleReference, tsExtension)); + } + else if (!compilerOptions.resolveJsonModule && + fileExtensionIs(moduleReference, Extension.Json) && + getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs && + hasJsonModuleEmitEnabled(compilerOptions)) { + error(errorNode, Diagnostics.Cannot_find_module_0_Consider_using_resolveJsonModule_to_import_module_with_json_extension, moduleReference); } else { - errorOnImplicitAnyModule(/*isError*/ noImplicitAny && !!moduleNotFoundError, errorNode, resolvedModule, moduleReference); + error(errorNode, moduleNotFoundError, moduleReference); } - // Failed imports and untyped modules are both treated in an untyped manner; only difference is whether we give a diagnostic first. - return undefined; } - - if (moduleNotFoundError) { - // See if this was possibly a projectReference redirect - if (resolvedModule) { - const redirect = host.getProjectReferenceRedirect(resolvedModule.resolvedFileName); - if (redirect) { - error(errorNode, Diagnostics.Output_file_0_has_not_been_built_from_source_file_1, redirect, resolvedModule.resolvedFileName); - return undefined; - } - } - - if (resolutionDiagnostic) { - error(errorNode, resolutionDiagnostic, moduleReference, resolvedModule.resolvedFileName); - } - else { - const tsExtension = tryExtractTSExtension(moduleReference); - if (tsExtension) { - const diag = Diagnostics.An_import_path_cannot_end_with_a_0_extension_Consider_importing_1_instead; - error(errorNode, diag, tsExtension, removeExtension(moduleReference, tsExtension)); - } - else if (!compilerOptions.resolveJsonModule && - fileExtensionIs(moduleReference, Extension.Json) && - getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs && - hasJsonModuleEmitEnabled(compilerOptions)) { - error(errorNode, Diagnostics.Cannot_find_module_0_Consider_using_resolveJsonModule_to_import_module_with_json_extension, moduleReference); - } - else { - error(errorNode, moduleNotFoundError, moduleReference); + } + return undefined; + } + function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void { + const errorInfo = !isExternalModuleNameRelative(moduleReference) && packageId + ? typesPackageExists(packageId.name) + ? chainDiagnosticMessages( + /*details*/ undefined, Diagnostics.If_the_0_package_actually_exposes_this_module_consider_sending_a_pull_request_to_amend_https_Colon_Slash_Slashgithub_com_SlashDefinitelyTyped_SlashDefinitelyTyped_Slashtree_Slashmaster_Slashtypes_Slash_1, packageId.name, mangleScopedPackageName(packageId.name)) + : chainDiagnosticMessages( + /*details*/ undefined, Diagnostics.Try_npm_install_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0, moduleReference, mangleScopedPackageName(packageId.name)) + : undefined; + errorOrSuggestion(isError, errorNode, chainDiagnosticMessages(errorInfo, Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type, moduleReference, resolvedFileName)); + } + function typesPackageExists(packageName: string): boolean { + return getPackagesSet().has(getTypesPackageName(packageName)); + } + function resolveExternalModuleSymbol(moduleSymbol: ts.Symbol, dontResolveAlias?: boolean): ts.Symbol; + function resolveExternalModuleSymbol(moduleSymbol: ts.Symbol | undefined, dontResolveAlias?: boolean): ts.Symbol | undefined; + function resolveExternalModuleSymbol(moduleSymbol: ts.Symbol, dontResolveAlias?: boolean): ts.Symbol { + if (moduleSymbol) { + const exportEquals = resolveSymbol(moduleSymbol.exports!.get(InternalSymbolName.ExportEquals), dontResolveAlias); + const exported = getCommonJsExportEquals(getMergedSymbol(exportEquals), getMergedSymbol(moduleSymbol)); + return getMergedSymbol(exported) || moduleSymbol; + } + return undefined!; + } + function getCommonJsExportEquals(exported: ts.Symbol | undefined, moduleSymbol: ts.Symbol): ts.Symbol | undefined { + if (!exported || exported === unknownSymbol || exported === moduleSymbol || moduleSymbol.exports!.size === 1 || exported.flags & SymbolFlags.Alias) { + return exported; + } + const links = getSymbolLinks(exported); + if (links.cjsExportMerged) { + return links.cjsExportMerged; + } + const merged = exported.flags & SymbolFlags.Transient ? exported : cloneSymbol(exported); + merged.flags = merged.flags | SymbolFlags.ValueModule; + if (merged.exports === undefined) { + merged.exports = createSymbolTable(); + } + moduleSymbol.exports!.forEach((s, name) => { + if (name === InternalSymbolName.ExportEquals) + return; + merged.exports!.set(name, merged.exports!.has(name) ? mergeSymbol(merged.exports!.get(name)!, s) : s); + }); + getSymbolLinks(merged).cjsExportMerged = merged; + return links.cjsExportMerged = merged; + } + // An external module with an 'export =' declaration may be referenced as an ES6 module provided the 'export =' + // references a symbol that is at least declared as a module or a variable. The target of the 'export =' may + // combine other declarations with the module or variable (e.g. a class/module, function/module, interface/variable). + function resolveESModuleSymbol(moduleSymbol: ts.Symbol | undefined, referencingLocation: Node, dontResolveAlias: boolean, suppressInteropError: boolean): ts.Symbol | undefined { + const symbol = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias); + if (!dontResolveAlias && symbol) { + if (!suppressInteropError && !(symbol.flags & (SymbolFlags.Module | SymbolFlags.Variable)) && !getDeclarationOfKind(symbol, SyntaxKind.SourceFile)) { + const compilerOptionName = moduleKind >= ModuleKind.ES2015 + ? "allowSyntheticDefaultImports" + : "esModuleInterop"; + error(referencingLocation, Diagnostics.This_module_can_only_be_referenced_with_ECMAScript_imports_Slashexports_by_turning_on_the_0_flag_and_referencing_its_default_export, compilerOptionName); + return symbol; + } + if (compilerOptions.esModuleInterop) { + const referenceParent = referencingLocation.parent; + if ((isImportDeclaration(referenceParent) && getNamespaceDeclarationNode(referenceParent)) || + isImportCall(referenceParent)) { + const type = getTypeOfSymbol(symbol); + let sigs = getSignaturesOfStructuredType(type, SignatureKind.Call); + if (!sigs || !sigs.length) { + sigs = getSignaturesOfStructuredType(type, SignatureKind.Construct); + } + if (sigs && sigs.length) { + const moduleType = getTypeWithSyntheticDefaultImportType(type, symbol, moduleSymbol!); + // Create a new symbol which has the module's type less the call and construct signatures + const result = createSymbol(symbol.flags, symbol.escapedName); + result.declarations = symbol.declarations ? symbol.declarations.slice() : []; + result.parent = symbol.parent; + result.target = symbol; + result.originatingImport = referenceParent; + if (symbol.valueDeclaration) + result.valueDeclaration = symbol.valueDeclaration; + if (symbol.constEnumOnlyModule) + result.constEnumOnlyModule = true; + if (symbol.members) + result.members = cloneMap(symbol.members); + if (symbol.exports) + result.exports = cloneMap(symbol.exports); + const resolvedModuleType = resolveStructuredTypeMembers((moduleType as StructuredType)); // Should already be resolved from the signature checks above + result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.stringIndexInfo, resolvedModuleType.numberIndexInfo); + return result; } } } - return undefined; } - - function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void { - const errorInfo = !isExternalModuleNameRelative(moduleReference) && packageId - ? typesPackageExists(packageId.name) - ? chainDiagnosticMessages( - /*details*/ undefined, - Diagnostics.If_the_0_package_actually_exposes_this_module_consider_sending_a_pull_request_to_amend_https_Colon_Slash_Slashgithub_com_SlashDefinitelyTyped_SlashDefinitelyTyped_Slashtree_Slashmaster_Slashtypes_Slash_1, - packageId.name, mangleScopedPackageName(packageId.name)) - : chainDiagnosticMessages( - /*details*/ undefined, - Diagnostics.Try_npm_install_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0, - moduleReference, - mangleScopedPackageName(packageId.name)) - : undefined; - errorOrSuggestion(isError, errorNode, chainDiagnosticMessages( - errorInfo, - Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type, - moduleReference, - resolvedFileName)); + return symbol; + } + function hasExportAssignmentSymbol(moduleSymbol: ts.Symbol): boolean { + return moduleSymbol.exports!.get(InternalSymbolName.ExportEquals) !== undefined; + } + function getExportsOfModuleAsArray(moduleSymbol: ts.Symbol): ts.Symbol[] { + return symbolsToArray(getExportsOfModule(moduleSymbol)); + } + function getExportsAndPropertiesOfModule(moduleSymbol: ts.Symbol): ts.Symbol[] { + const exports = getExportsOfModuleAsArray(moduleSymbol); + const exportEquals = resolveExternalModuleSymbol(moduleSymbol); + if (exportEquals !== moduleSymbol) { + addRange(exports, getPropertiesOfType(getTypeOfSymbol(exportEquals))); } - function typesPackageExists(packageName: string): boolean { - return getPackagesSet().has(getTypesPackageName(packageName)); + return exports; + } + function tryGetMemberInModuleExports(memberName: __String, moduleSymbol: ts.Symbol): ts.Symbol | undefined { + const symbolTable = getExportsOfModule(moduleSymbol); + if (symbolTable) { + return symbolTable.get(memberName); } - - function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol; - function resolveExternalModuleSymbol(moduleSymbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined; - function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol { - if (moduleSymbol) { - const exportEquals = resolveSymbol(moduleSymbol.exports!.get(InternalSymbolName.ExportEquals), dontResolveAlias); - const exported = getCommonJsExportEquals(getMergedSymbol(exportEquals), getMergedSymbol(moduleSymbol)); - return getMergedSymbol(exported) || moduleSymbol; - } - return undefined!; + } + function tryGetMemberInModuleExportsAndProperties(memberName: __String, moduleSymbol: ts.Symbol): ts.Symbol | undefined { + const symbol = tryGetMemberInModuleExports(memberName, moduleSymbol); + if (symbol) { + return symbol; } - - function getCommonJsExportEquals(exported: Symbol | undefined, moduleSymbol: Symbol): Symbol | undefined { - if (!exported || exported === unknownSymbol || exported === moduleSymbol || moduleSymbol.exports!.size === 1 || exported.flags & SymbolFlags.Alias) { - return exported; + const exportEquals = resolveExternalModuleSymbol(moduleSymbol); + if (exportEquals === moduleSymbol) { + return undefined; + } + const type = getTypeOfSymbol(exportEquals); + return type.flags & TypeFlags.Primitive || + getObjectFlags(type) & ObjectFlags.Class || + isArrayOrTupleLikeType(type) + ? undefined + : getPropertyOfType(type, memberName); + } + function getExportsOfSymbol(symbol: ts.Symbol): SymbolTable { + return symbol.flags & SymbolFlags.LateBindingContainer ? getResolvedMembersOrExportsOfSymbol(symbol, MembersOrExportsResolutionKind.resolvedExports) : + symbol.flags & SymbolFlags.Module ? getExportsOfModule(symbol) : + symbol.exports || emptySymbols; + } + function getExportsOfModule(moduleSymbol: ts.Symbol): SymbolTable { + const links = getSymbolLinks(moduleSymbol); + return links.resolvedExports || (links.resolvedExports = getExportsOfModuleWorker(moduleSymbol)); + } + interface ExportCollisionTracker { + specifierText: string; + exportsWithDuplicate: ExportDeclaration[]; + } + type ExportCollisionTrackerTable = UnderscoreEscapedMap; + /** + * Extends one symbol table with another while collecting information on name collisions for error message generation into the `lookupTable` argument + * Not passing `lookupTable` and `exportNode` disables this collection, and just extends the tables + */ + function extendExportSymbols(target: SymbolTable, source: SymbolTable | undefined, lookupTable?: ExportCollisionTrackerTable, exportNode?: ExportDeclaration) { + if (!source) + return; + source.forEach((sourceSymbol, id) => { + if (id === InternalSymbolName.Default) + return; + const targetSymbol = target.get(id); + if (!targetSymbol) { + target.set(id, sourceSymbol); + if (lookupTable && exportNode) { + lookupTable.set(id, ({ + specifierText: getTextOfNode((exportNode.moduleSpecifier!)) + } as ExportCollisionTracker)); + } } - const links = getSymbolLinks(exported); - if (links.cjsExportMerged) { - return links.cjsExportMerged; + else if (lookupTable && exportNode && targetSymbol && resolveSymbol(targetSymbol) !== resolveSymbol(sourceSymbol)) { + const collisionTracker = lookupTable.get(id)!; + if (!collisionTracker.exportsWithDuplicate) { + collisionTracker.exportsWithDuplicate = [exportNode]; + } + else { + collisionTracker.exportsWithDuplicate.push(exportNode); + } } - const merged = exported.flags & SymbolFlags.Transient ? exported : cloneSymbol(exported); - merged.flags = merged.flags | SymbolFlags.ValueModule; - if (merged.exports === undefined) { - merged.exports = createSymbolTable(); + }); + } + function getExportsOfModuleWorker(moduleSymbol: ts.Symbol): SymbolTable { + const visitedSymbols: ts.Symbol[] = []; + // A module defined by an 'export=' consists of one export that needs to be resolved + moduleSymbol = resolveExternalModuleSymbol(moduleSymbol); + return visit(moduleSymbol) || emptySymbols; + // The ES6 spec permits export * declarations in a module to circularly reference the module itself. For example, + // module 'a' can 'export * from "b"' and 'b' can 'export * from "a"' without error. + function visit(symbol: ts.Symbol | undefined): SymbolTable | undefined { + if (!(symbol && symbol.exports && pushIfUnique(visitedSymbols, symbol))) { + return; } - moduleSymbol.exports!.forEach((s, name) => { - if (name === InternalSymbolName.ExportEquals) return; - merged.exports!.set(name, merged.exports!.has(name) ? mergeSymbol(merged.exports!.get(name)!, s) : s); - }); - getSymbolLinks(merged).cjsExportMerged = merged; - return links.cjsExportMerged = merged; - } - - // An external module with an 'export =' declaration may be referenced as an ES6 module provided the 'export =' - // references a symbol that is at least declared as a module or a variable. The target of the 'export =' may - // combine other declarations with the module or variable (e.g. a class/module, function/module, interface/variable). - function resolveESModuleSymbol(moduleSymbol: Symbol | undefined, referencingLocation: Node, dontResolveAlias: boolean, suppressInteropError: boolean): Symbol | undefined { - const symbol = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias); - - if (!dontResolveAlias && symbol) { - if (!suppressInteropError && !(symbol.flags & (SymbolFlags.Module | SymbolFlags.Variable)) && !getDeclarationOfKind(symbol, SyntaxKind.SourceFile)) { - const compilerOptionName = moduleKind >= ModuleKind.ES2015 - ? "allowSyntheticDefaultImports" - : "esModuleInterop"; - - error(referencingLocation, Diagnostics.This_module_can_only_be_referenced_with_ECMAScript_imports_Slashexports_by_turning_on_the_0_flag_and_referencing_its_default_export, compilerOptionName); - - return symbol; - } - - if (compilerOptions.esModuleInterop) { - const referenceParent = referencingLocation.parent; - if ( - (isImportDeclaration(referenceParent) && getNamespaceDeclarationNode(referenceParent)) || - isImportCall(referenceParent) - ) { - const type = getTypeOfSymbol(symbol); - let sigs = getSignaturesOfStructuredType(type, SignatureKind.Call); - if (!sigs || !sigs.length) { - sigs = getSignaturesOfStructuredType(type, SignatureKind.Construct); - } - if (sigs && sigs.length) { - const moduleType = getTypeWithSyntheticDefaultImportType(type, symbol, moduleSymbol!); - // Create a new symbol which has the module's type less the call and construct signatures - const result = createSymbol(symbol.flags, symbol.escapedName); - result.declarations = symbol.declarations ? symbol.declarations.slice() : []; - result.parent = symbol.parent; - result.target = symbol; - result.originatingImport = referenceParent; - if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; - if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; - if (symbol.members) result.members = cloneMap(symbol.members); - if (symbol.exports) result.exports = cloneMap(symbol.exports); - const resolvedModuleType = resolveStructuredTypeMembers(moduleType as StructuredType); // Should already be resolved from the signature checks above - result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.stringIndexInfo, resolvedModuleType.numberIndexInfo); - return result; - } + const symbols = cloneMap(symbol.exports); + // All export * declarations are collected in an __export symbol by the binder + const exportStars = symbol.exports.get(InternalSymbolName.ExportStar); + if (exportStars) { + const nestedSymbols = createSymbolTable(); + const lookupTable = (createMap() as ExportCollisionTrackerTable); + for (const node of exportStars.declarations) { + const resolvedModule = resolveExternalModuleName(node, ((node as ExportDeclaration).moduleSpecifier!)); + const exportedSymbols = visit(resolvedModule); + extendExportSymbols(nestedSymbols, exportedSymbols, lookupTable, (node as ExportDeclaration)); + } + lookupTable.forEach(({ exportsWithDuplicate }, id) => { + // It's not an error if the file with multiple `export *`s with duplicate names exports a member with that name itself + if (id === "export=" || !(exportsWithDuplicate && exportsWithDuplicate.length) || symbols.has(id)) { + return; } - } + for (const node of exportsWithDuplicate) { + diagnostics.add(createDiagnosticForNode(node, Diagnostics.Module_0_has_already_exported_a_member_named_1_Consider_explicitly_re_exporting_to_resolve_the_ambiguity, lookupTable.get(id)!.specifierText, unescapeLeadingUnderscores(id))); + } + }); + extendExportSymbols(symbols, nestedSymbols); + } + return symbols; + } + } + function getMergedSymbol(symbol: ts.Symbol): ts.Symbol; + function getMergedSymbol(symbol: ts.Symbol | undefined): ts.Symbol | undefined; + function getMergedSymbol(symbol: ts.Symbol | undefined): ts.Symbol | undefined { + let merged: ts.Symbol; + return symbol && symbol.mergeId && (merged = mergedSymbols[symbol.mergeId]) ? merged : symbol; + } + function getSymbolOfNode(node: Declaration): ts.Symbol; + function getSymbolOfNode(node: Node): ts.Symbol | undefined; + function getSymbolOfNode(node: Node): ts.Symbol | undefined { + return getMergedSymbol(node.symbol && getLateBoundSymbol(node.symbol)); + } + function getParentOfSymbol(symbol: ts.Symbol): ts.Symbol | undefined { + return getMergedSymbol(symbol.parent && getLateBoundSymbol(symbol.parent)); + } + function getAlternativeContainingModules(symbol: ts.Symbol, enclosingDeclaration: Node): ts.Symbol[] { + const containingFile = getSourceFileOfNode(enclosingDeclaration); + const id = "" + getNodeId(containingFile); + const links = getSymbolLinks(symbol); + let results: ts.Symbol[] | undefined; + if (links.extendedContainersByFile && (results = links.extendedContainersByFile.get(id))) { + return results; + } + if (containingFile && containingFile.imports) { + // Try to make an import using an import already in the enclosing file, if possible + for (const importRef of containingFile.imports) { + if (nodeIsSynthesized(importRef)) + continue; // Synthetic names can't be resolved by `resolveExternalModuleName` - they'll cause a debug assert if they error + const resolvedModule = resolveExternalModuleName(enclosingDeclaration, importRef, /*ignoreErrors*/ true); + if (!resolvedModule) + continue; + const ref = getAliasForSymbolInContainer(resolvedModule, symbol); + if (!ref) + continue; + results = append(results, resolvedModule); + } + if (length(results)) { + (links.extendedContainersByFile || (links.extendedContainersByFile = createMap())).set(id, (results!)); + return results!; + } + } + if (links.extendedContainers) { + return links.extendedContainers; + } + // No results from files already being imported by this file - expand search (expensive, but not location-specific, so cached) + const otherFiles = host.getSourceFiles(); + for (const file of otherFiles) { + if (!isExternalModule(file)) + continue; + const sym = getSymbolOfNode(file); + const ref = getAliasForSymbolInContainer(sym, symbol); + if (!ref) + continue; + results = append(results, sym); + } + return links.extendedContainers = results || emptyArray; + } + /** + * Attempts to find the symbol corresponding to the container a symbol is in - usually this + * is just its' `.parent`, but for locals, this value is `undefined` + */ + function getContainersOfSymbol(symbol: ts.Symbol, enclosingDeclaration: Node | undefined): ts.Symbol[] | undefined { + const container = getParentOfSymbol(symbol); + // Type parameters end up in the `members` lists but are not externally visible + if (container && !(symbol.flags & SymbolFlags.TypeParameter)) { + const additionalContainers = mapDefined(container.declarations, fileSymbolIfFileSymbolExportEqualsContainer); + const reexportContainers = enclosingDeclaration && getAlternativeContainingModules(symbol, enclosingDeclaration); + if (enclosingDeclaration && getAccessibleSymbolChain(container, enclosingDeclaration, SymbolFlags.Namespace, /*externalOnly*/ false)) { + return concatenate(concatenate([container], additionalContainers), reexportContainers); // This order expresses a preference for the real container if it is in scope + } + const res = append(additionalContainers, container); + return concatenate(res, reexportContainers); + } + const candidates = mapDefined(symbol.declarations, d => { + if (!isAmbientModule(d) && d.parent && hasNonGlobalAugmentationExternalModuleSymbol(d.parent)) { + return getSymbolOfNode(d.parent); + } + if (isClassExpression(d) && isBinaryExpression(d.parent) && d.parent.operatorToken.kind === SyntaxKind.EqualsToken && isAccessExpression(d.parent.left) && isEntityNameExpression(d.parent.left.expression)) { + if (isModuleExportsAccessExpression(d.parent.left) || isExportsIdentifier(d.parent.left.expression)) { + return getSymbolOfNode(getSourceFileOfNode(d)); + } + checkExpressionCached(d.parent.left.expression); + return getNodeLinks(d.parent.left.expression).resolvedSymbol; } + }); + if (!length(candidates)) { + return undefined; + } + return mapDefined(candidates, candidate => getAliasForSymbolInContainer(candidate, symbol) ? candidate : undefined); + function fileSymbolIfFileSymbolExportEqualsContainer(d: Declaration) { + const fileSymbol = getExternalModuleContainer(d); + const exported = fileSymbol && fileSymbol.exports && fileSymbol.exports.get(InternalSymbolName.ExportEquals); + return exported && container && getSymbolIfSameReference(exported, container) ? fileSymbol : undefined; + } + } + function getAliasForSymbolInContainer(container: ts.Symbol, symbol: ts.Symbol) { + if (container === getParentOfSymbol(symbol)) { + // fast path, `symbol` is either already the alias or isn't aliased return symbol; } - - function hasExportAssignmentSymbol(moduleSymbol: Symbol): boolean { - return moduleSymbol.exports!.get(InternalSymbolName.ExportEquals) !== undefined; + // Check if container is a thing with an `export=` which points directly at `symbol`, and if so, return + // the container itself as the alias for the symbol + const exportEquals = container.exports && container.exports.get(InternalSymbolName.ExportEquals); + if (exportEquals && getSymbolIfSameReference(exportEquals, symbol)) { + return container; } - - function getExportsOfModuleAsArray(moduleSymbol: Symbol): Symbol[] { - return symbolsToArray(getExportsOfModule(moduleSymbol)); + const exports = getExportsOfSymbol(container); + const quick = exports.get(symbol.escapedName); + if (quick && getSymbolIfSameReference(quick, symbol)) { + return quick; } - - function getExportsAndPropertiesOfModule(moduleSymbol: Symbol): Symbol[] { - const exports = getExportsOfModuleAsArray(moduleSymbol); - const exportEquals = resolveExternalModuleSymbol(moduleSymbol); - if (exportEquals !== moduleSymbol) { - addRange(exports, getPropertiesOfType(getTypeOfSymbol(exportEquals))); + return forEachEntry(exports, exported => { + if (getSymbolIfSameReference(exported, symbol)) { + return exported; } - return exports; + }); + } + /** + * Checks if two symbols, through aliasing and/or merging, refer to the same thing + */ + function getSymbolIfSameReference(s1: ts.Symbol, s2: ts.Symbol) { + if (getMergedSymbol(resolveSymbol(getMergedSymbol(s1))) === getMergedSymbol(resolveSymbol(getMergedSymbol(s2)))) { + return s1; } - - function tryGetMemberInModuleExports(memberName: __String, moduleSymbol: Symbol): Symbol | undefined { - const symbolTable = getExportsOfModule(moduleSymbol); - if (symbolTable) { - return symbolTable.get(memberName); + } + function getExportSymbolOfValueSymbolIfExported(symbol: ts.Symbol): ts.Symbol; + function getExportSymbolOfValueSymbolIfExported(symbol: ts.Symbol | undefined): ts.Symbol | undefined; + function getExportSymbolOfValueSymbolIfExported(symbol: ts.Symbol | undefined): ts.Symbol | undefined { + return getMergedSymbol(symbol && (symbol.flags & SymbolFlags.ExportValue) !== 0 ? symbol.exportSymbol : symbol); + } + function symbolIsValue(symbol: ts.Symbol): boolean { + return !!(symbol.flags & SymbolFlags.Value || symbol.flags & SymbolFlags.Alias && resolveAlias(symbol).flags & SymbolFlags.Value); + } + function findConstructorDeclaration(node: ClassLikeDeclaration): ConstructorDeclaration | undefined { + const members = node.members; + for (const member of members) { + if (member.kind === SyntaxKind.Constructor && nodeIsPresent((member).body)) { + return member; } } - - function tryGetMemberInModuleExportsAndProperties(memberName: __String, moduleSymbol: Symbol): Symbol | undefined { - const symbol = tryGetMemberInModuleExports(memberName, moduleSymbol); - if (symbol) { - return symbol; + } + function createType(flags: TypeFlags): ts.Type { + const result = new Type(checker, flags); + typeCount++; + result.id = typeCount; + return result; + } + function createIntrinsicType(kind: TypeFlags, intrinsicName: string, objectFlags: ObjectFlags = 0): IntrinsicType { + const type = (createType(kind)); + type.intrinsicName = intrinsicName; + type.objectFlags = objectFlags; + return type; + } + function createBooleanType(trueFalseTypes: readonly ts.Type[]): IntrinsicType & UnionType { + const type = (getUnionType(trueFalseTypes)); + type.flags |= TypeFlags.Boolean; + type.intrinsicName = "boolean"; + return type; + } + function createObjectType(objectFlags: ObjectFlags, symbol?: ts.Symbol): ObjectType { + const type = (createType(TypeFlags.Object)); + type.objectFlags = objectFlags; + type.symbol = symbol!; + type.members = undefined; + type.properties = undefined; + type.callSignatures = undefined; + type.constructSignatures = undefined; + type.stringIndexInfo = undefined; + type.numberIndexInfo = undefined; + return type; + } + function createTypeofType() { + return getUnionType(arrayFrom(typeofEQFacts.keys(), getLiteralType)); + } + function createTypeParameter(symbol?: ts.Symbol) { + const type = (createType(TypeFlags.TypeParameter)); + if (symbol) + type.symbol = symbol; + return type; + } + // A reserved member name starts with two underscores, but the third character cannot be an underscore + // or the @ symbol. A third underscore indicates an escaped form of an identifier that started + // with at least two underscores. The @ character indicates that the name is denoted by a well known ES + // Symbol instance. + function isReservedMemberName(name: __String) { + return (name as string).charCodeAt(0) === CharacterCodes._ && + (name as string).charCodeAt(1) === CharacterCodes._ && + (name as string).charCodeAt(2) !== CharacterCodes._ && + (name as string).charCodeAt(2) !== CharacterCodes.at; + } + function getNamedMembers(members: SymbolTable): ts.Symbol[] { + let result: ts.Symbol[] | undefined; + members.forEach((symbol, id) => { + if (!isReservedMemberName(id) && symbolIsValue(symbol)) { + (result || (result = [])).push(symbol); } - - const exportEquals = resolveExternalModuleSymbol(moduleSymbol); - if (exportEquals === moduleSymbol) { - return undefined; + }); + return result || emptyArray; + } + function setStructuredTypeMembers(type: StructuredType, members: SymbolTable, callSignatures: readonly ts.Signature[], constructSignatures: readonly ts.Signature[], stringIndexInfo: IndexInfo | undefined, numberIndexInfo: IndexInfo | undefined): ResolvedType { + (type).members = members; + (type).properties = members === emptySymbols ? emptyArray : getNamedMembers(members); + (type).callSignatures = callSignatures; + (type).constructSignatures = constructSignatures; + (type).stringIndexInfo = stringIndexInfo; + (type).numberIndexInfo = numberIndexInfo; + return type; + } + function createAnonymousType(symbol: ts.Symbol | undefined, members: SymbolTable, callSignatures: readonly ts.Signature[], constructSignatures: readonly ts.Signature[], stringIndexInfo: IndexInfo | undefined, numberIndexInfo: IndexInfo | undefined): ResolvedType { + return setStructuredTypeMembers(createObjectType(ObjectFlags.Anonymous, symbol), members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); + } + function forEachSymbolTableInScope(enclosingDeclaration: Node | undefined, callback: (symbolTable: SymbolTable) => T): T { + let result: T; + for (let location = enclosingDeclaration; location; location = location.parent) { + // Locals of a source file are not in scope (because they get merged into the global symbol table) + if (location.locals && !isGlobalSourceFile(location)) { + if (result = callback(location.locals)) { + return result; + } + } + switch (location.kind) { + case SyntaxKind.SourceFile: + if (!isExternalOrCommonJsModule((location))) { + break; + } + // falls through + case SyntaxKind.ModuleDeclaration: + const sym = getSymbolOfNode((location as ModuleDeclaration)); + // `sym` may not have exports if this module declaration is backed by the symbol for a `const` that's being rewritten + // into a namespace - in such cases, it's best to just let the namespace appear empty (the const members couldn't have referred + // to one another anyway) + if (result = callback(sym.exports || emptySymbols)) { + return result; + } + break; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + // Type parameters are bound into `members` lists so they can merge across declarations + // This is troublesome, since in all other respects, they behave like locals :cries: + // TODO: the below is shared with similar code in `resolveName` - in fact, rephrasing all this symbol + // lookup logic in terms of `resolveName` would be nice + // The below is used to lookup type parameters within a class or interface, as they are added to the class/interface locals + // These can never be latebound, so the symbol's raw members are sufficient. `getMembersOfNode` cannot be used, as it would + // trigger resolving late-bound names, which we may already be in the process of doing while we're here! + let table: UnderscoreEscapedMap | undefined; + // TODO: Should this filtered table be cached in some way? + (getSymbolOfNode((location as ClassLikeDeclaration | InterfaceDeclaration)).members || emptySymbols).forEach((memberSymbol, key) => { + if (memberSymbol.flags & (SymbolFlags.Type & ~SymbolFlags.Assignment)) { + (table || (table = createSymbolTable())).set(key, memberSymbol); + } + }); + if (table && (result = callback(table))) { + return result; + } + break; } - - const type = getTypeOfSymbol(exportEquals); - return type.flags & TypeFlags.Primitive || - getObjectFlags(type) & ObjectFlags.Class || - isArrayOrTupleLikeType(type) - ? undefined - : getPropertyOfType(type, memberName); - } - - function getExportsOfSymbol(symbol: Symbol): SymbolTable { - return symbol.flags & SymbolFlags.LateBindingContainer ? getResolvedMembersOrExportsOfSymbol(symbol, MembersOrExportsResolutionKind.resolvedExports) : - symbol.flags & SymbolFlags.Module ? getExportsOfModule(symbol) : - symbol.exports || emptySymbols; } - - function getExportsOfModule(moduleSymbol: Symbol): SymbolTable { - const links = getSymbolLinks(moduleSymbol); - return links.resolvedExports || (links.resolvedExports = getExportsOfModuleWorker(moduleSymbol)); + return callback(globals); + } + function getQualifiedLeftMeaning(rightMeaning: SymbolFlags) { + // If we are looking in value space, the parent meaning is value, other wise it is namespace + return rightMeaning === SymbolFlags.Value ? SymbolFlags.Value : SymbolFlags.Namespace; + } + function getAccessibleSymbolChain(symbol: ts.Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean, visitedSymbolTablesMap: ts.Map = createMap()): ts.Symbol[] | undefined { + if (!(symbol && !isPropertyOrMethodDeclarationSymbol(symbol))) { + return undefined; } - - interface ExportCollisionTracker { - specifierText: string; - exportsWithDuplicate: ExportDeclaration[]; + const id = "" + getSymbolId(symbol); + let visitedSymbolTables = visitedSymbolTablesMap.get(id); + if (!visitedSymbolTables) { + visitedSymbolTablesMap.set(id, visitedSymbolTables = []); } - - type ExportCollisionTrackerTable = UnderscoreEscapedMap; - + return forEachSymbolTableInScope(enclosingDeclaration, getAccessibleSymbolChainFromSymbolTable); /** - * Extends one symbol table with another while collecting information on name collisions for error message generation into the `lookupTable` argument - * Not passing `lookupTable` and `exportNode` disables this collection, and just extends the tables + * @param {ignoreQualification} boolean Set when a symbol is being looked for through the exports of another symbol (meaning we have a route to qualify it already) */ - function extendExportSymbols(target: SymbolTable, source: SymbolTable | undefined, lookupTable?: ExportCollisionTrackerTable, exportNode?: ExportDeclaration) { - if (!source) return; - source.forEach((sourceSymbol, id) => { - if (id === InternalSymbolName.Default) return; - - const targetSymbol = target.get(id); - if (!targetSymbol) { - target.set(id, sourceSymbol); - if (lookupTable && exportNode) { - lookupTable.set(id, { - specifierText: getTextOfNode(exportNode.moduleSpecifier!) - } as ExportCollisionTracker); + function getAccessibleSymbolChainFromSymbolTable(symbols: SymbolTable, ignoreQualification?: boolean): ts.Symbol[] | undefined { + if (!pushIfUnique((visitedSymbolTables!), symbols)) { + return undefined; + } + const result = trySymbolTable(symbols, ignoreQualification); + visitedSymbolTables!.pop(); + return result; + } + function canQualifySymbol(symbolFromSymbolTable: ts.Symbol, meaning: SymbolFlags) { + // If the symbol is equivalent and doesn't need further qualification, this symbol is accessible + return !needsQualification(symbolFromSymbolTable, enclosingDeclaration, meaning) || + // If symbol needs qualification, make sure that parent is accessible, if it is then this symbol is accessible too + !!getAccessibleSymbolChain(symbolFromSymbolTable.parent, enclosingDeclaration, getQualifiedLeftMeaning(meaning), useOnlyExternalAliasing, visitedSymbolTablesMap); + } + function isAccessible(symbolFromSymbolTable: ts.Symbol, resolvedAliasSymbol?: ts.Symbol, ignoreQualification?: boolean) { + return (symbol === (resolvedAliasSymbol || symbolFromSymbolTable) || getMergedSymbol(symbol) === getMergedSymbol(resolvedAliasSymbol || symbolFromSymbolTable)) && + // if the symbolFromSymbolTable is not external module (it could be if it was determined as ambient external module and would be in globals table) + // and if symbolFromSymbolTable or alias resolution matches the symbol, + // check the symbol can be qualified, it is only then this symbol is accessible + !some(symbolFromSymbolTable.declarations, hasNonGlobalAugmentationExternalModuleSymbol) && + (ignoreQualification || canQualifySymbol(getMergedSymbol(symbolFromSymbolTable), meaning)); + } + function trySymbolTable(symbols: SymbolTable, ignoreQualification: boolean | undefined): ts.Symbol[] | undefined { + // If symbol is directly available by its name in the symbol table + if (isAccessible(symbols.get(symbol!.escapedName)!, /*resolvedAliasSymbol*/ undefined, ignoreQualification)) { + return [symbol!]; + } + // Check if symbol is any of the aliases in scope + const result = forEachEntry(symbols, symbolFromSymbolTable => { + if (symbolFromSymbolTable.flags & SymbolFlags.Alias + && symbolFromSymbolTable.escapedName !== InternalSymbolName.ExportEquals + && symbolFromSymbolTable.escapedName !== InternalSymbolName.Default + && !(isUMDExportSymbol(symbolFromSymbolTable) && enclosingDeclaration && isExternalModule(getSourceFileOfNode(enclosingDeclaration))) + // If `!useOnlyExternalAliasing`, we can use any type of alias to get the name + && (!useOnlyExternalAliasing || some(symbolFromSymbolTable.declarations, isExternalModuleImportEqualsDeclaration)) + // While exports are generally considered to be in scope, export-specifier declared symbols are _not_ + // See similar comment in `resolveName` for details + && (ignoreQualification || !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier))) { + const resolvedImportedSymbol = resolveAlias(symbolFromSymbolTable); + const candidate = getCandidateListForSymbol(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification); + if (candidate) { + return candidate; } } - else if (lookupTable && exportNode && targetSymbol && resolveSymbol(targetSymbol) !== resolveSymbol(sourceSymbol)) { - const collisionTracker = lookupTable.get(id)!; - if (!collisionTracker.exportsWithDuplicate) { - collisionTracker.exportsWithDuplicate = [exportNode]; - } - else { - collisionTracker.exportsWithDuplicate.push(exportNode); + if (symbolFromSymbolTable.escapedName === symbol!.escapedName && symbolFromSymbolTable.exportSymbol) { + if (isAccessible(getMergedSymbol(symbolFromSymbolTable.exportSymbol), /*aliasSymbol*/ undefined, ignoreQualification)) { + return [symbol!]; } } }); + // If there's no result and we're looking at the global symbol table, treat `globalThis` like an alias and try to lookup thru that + return result || (symbols === globals ? getCandidateListForSymbol(globalThisSymbol, globalThisSymbol, ignoreQualification) : undefined); } - - function getExportsOfModuleWorker(moduleSymbol: Symbol): SymbolTable { - const visitedSymbols: Symbol[] = []; - - // A module defined by an 'export=' consists of one export that needs to be resolved - moduleSymbol = resolveExternalModuleSymbol(moduleSymbol); - - return visit(moduleSymbol) || emptySymbols; - - // The ES6 spec permits export * declarations in a module to circularly reference the module itself. For example, - // module 'a' can 'export * from "b"' and 'b' can 'export * from "a"' without error. - function visit(symbol: Symbol | undefined): SymbolTable | undefined { - if (!(symbol && symbol.exports && pushIfUnique(visitedSymbols, symbol))) { - return; - } - const symbols = cloneMap(symbol.exports); - // All export * declarations are collected in an __export symbol by the binder - const exportStars = symbol.exports.get(InternalSymbolName.ExportStar); - if (exportStars) { - const nestedSymbols = createSymbolTable(); - const lookupTable = createMap() as ExportCollisionTrackerTable; - for (const node of exportStars.declarations) { - const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!); - const exportedSymbols = visit(resolvedModule); - extendExportSymbols( - nestedSymbols, - exportedSymbols, - lookupTable, - node as ExportDeclaration - ); - } - lookupTable.forEach(({ exportsWithDuplicate }, id) => { - // It's not an error if the file with multiple `export *`s with duplicate names exports a member with that name itself - if (id === "export=" || !(exportsWithDuplicate && exportsWithDuplicate.length) || symbols.has(id)) { - return; - } - for (const node of exportsWithDuplicate) { - diagnostics.add(createDiagnosticForNode( - node, - Diagnostics.Module_0_has_already_exported_a_member_named_1_Consider_explicitly_re_exporting_to_resolve_the_ambiguity, - lookupTable.get(id)!.specifierText, - unescapeLeadingUnderscores(id) - )); - } - }); - extendExportSymbols(symbols, nestedSymbols); - } - return symbols; + function getCandidateListForSymbol(symbolFromSymbolTable: ts.Symbol, resolvedImportedSymbol: ts.Symbol, ignoreQualification: boolean | undefined) { + if (isAccessible(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification)) { + return [symbolFromSymbolTable]; + } + // Look in the exported members, if we can find accessibleSymbolChain, symbol is accessible using this chain + // but only if the symbolFromSymbolTable can be qualified + const candidateTable = getExportsOfSymbol(resolvedImportedSymbol); + const accessibleSymbolsFromExports = candidateTable && getAccessibleSymbolChainFromSymbolTable(candidateTable, /*ignoreQualification*/ true); + if (accessibleSymbolsFromExports && canQualifySymbol(symbolFromSymbolTable, getQualifiedLeftMeaning(meaning))) { + return [symbolFromSymbolTable].concat(accessibleSymbolsFromExports); } } - - function getMergedSymbol(symbol: Symbol): Symbol; - function getMergedSymbol(symbol: Symbol | undefined): Symbol | undefined; - function getMergedSymbol(symbol: Symbol | undefined): Symbol | undefined { - let merged: Symbol; - return symbol && symbol.mergeId && (merged = mergedSymbols[symbol.mergeId]) ? merged : symbol; - } - - function getSymbolOfNode(node: Declaration): Symbol; - function getSymbolOfNode(node: Node): Symbol | undefined; - function getSymbolOfNode(node: Node): Symbol | undefined { - return getMergedSymbol(node.symbol && getLateBoundSymbol(node.symbol)); - } - - function getParentOfSymbol(symbol: Symbol): Symbol | undefined { - return getMergedSymbol(symbol.parent && getLateBoundSymbol(symbol.parent)); - } - - function getAlternativeContainingModules(symbol: Symbol, enclosingDeclaration: Node): Symbol[] { - const containingFile = getSourceFileOfNode(enclosingDeclaration); - const id = "" + getNodeId(containingFile); - const links = getSymbolLinks(symbol); - let results: Symbol[] | undefined; - if (links.extendedContainersByFile && (results = links.extendedContainersByFile.get(id))) { - return results; + } + function needsQualification(symbol: ts.Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags) { + let qualify = false; + forEachSymbolTableInScope(enclosingDeclaration, symbolTable => { + // If symbol of this name is not available in the symbol table we are ok + let symbolFromSymbolTable = getMergedSymbol(symbolTable.get(symbol.escapedName)); + if (!symbolFromSymbolTable) { + // Continue to the next symbol table + return false; } - if (containingFile && containingFile.imports) { - // Try to make an import using an import already in the enclosing file, if possible - for (const importRef of containingFile.imports) { - if (nodeIsSynthesized(importRef)) continue; // Synthetic names can't be resolved by `resolveExternalModuleName` - they'll cause a debug assert if they error - const resolvedModule = resolveExternalModuleName(enclosingDeclaration, importRef, /*ignoreErrors*/ true); - if (!resolvedModule) continue; - const ref = getAliasForSymbolInContainer(resolvedModule, symbol); - if (!ref) continue; - results = append(results, resolvedModule); - } - if (length(results)) { - (links.extendedContainersByFile || (links.extendedContainersByFile = createMap())).set(id, results!); - return results!; - } + // If the symbol with this name is present it should refer to the symbol + if (symbolFromSymbolTable === symbol) { + // No need to qualify + return true; } - if (links.extendedContainers) { - return links.extendedContainers; + // Qualify if the symbol from symbol table has same meaning as expected + symbolFromSymbolTable = (symbolFromSymbolTable.flags & SymbolFlags.Alias && !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier)) ? resolveAlias(symbolFromSymbolTable) : symbolFromSymbolTable; + if (symbolFromSymbolTable.flags & meaning) { + qualify = true; + return true; } - // No results from files already being imported by this file - expand search (expensive, but not location-specific, so cached) - const otherFiles = host.getSourceFiles(); - for (const file of otherFiles) { - if (!isExternalModule(file)) continue; - const sym = getSymbolOfNode(file); - const ref = getAliasForSymbolInContainer(sym, symbol); - if (!ref) continue; - results = append(results, sym); + // Continue to the next symbol table + return false; + }); + return qualify; + } + function isPropertyOrMethodDeclarationSymbol(symbol: ts.Symbol) { + if (symbol.declarations && symbol.declarations.length) { + for (const declaration of symbol.declarations) { + switch (declaration.kind) { + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + continue; + default: + return false; + } } - return links.extendedContainers = results || emptyArray; + return true; } - - /** - * Attempts to find the symbol corresponding to the container a symbol is in - usually this - * is just its' `.parent`, but for locals, this value is `undefined` - */ - function getContainersOfSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined): Symbol[] | undefined { - const container = getParentOfSymbol(symbol); - // Type parameters end up in the `members` lists but are not externally visible - if (container && !(symbol.flags & SymbolFlags.TypeParameter)) { - const additionalContainers = mapDefined(container.declarations, fileSymbolIfFileSymbolExportEqualsContainer); - const reexportContainers = enclosingDeclaration && getAlternativeContainingModules(symbol, enclosingDeclaration); - if (enclosingDeclaration && getAccessibleSymbolChain(container, enclosingDeclaration, SymbolFlags.Namespace, /*externalOnly*/ false)) { - return concatenate(concatenate([container], additionalContainers), reexportContainers); // This order expresses a preference for the real container if it is in scope + return false; + } + function isTypeSymbolAccessible(typeSymbol: ts.Symbol, enclosingDeclaration: Node | undefined): boolean { + const access = isSymbolAccessible(typeSymbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false); + return access.accessibility === SymbolAccessibility.Accessible; + } + function isValueSymbolAccessible(typeSymbol: ts.Symbol, enclosingDeclaration: Node | undefined): boolean { + const access = isSymbolAccessible(typeSymbol, enclosingDeclaration, SymbolFlags.Value, /*shouldComputeAliasesToMakeVisible*/ false); + return access.accessibility === SymbolAccessibility.Accessible; + } + function isAnySymbolAccessible(symbols: ts.Symbol[] | undefined, enclosingDeclaration: Node | undefined, initialSymbol: ts.Symbol, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean): SymbolAccessibilityResult | undefined { + if (!length(symbols)) + return; + let hadAccessibleChain: ts.Symbol | undefined; + for (const symbol of symbols!) { + // Symbol is accessible if it by itself is accessible + const accessibleSymbolChain = getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning, /*useOnlyExternalAliasing*/ false); + if (accessibleSymbolChain) { + hadAccessibleChain = symbol; + const hasAccessibleDeclarations = hasVisibleDeclarations(accessibleSymbolChain[0], shouldComputeAliasesToMakeVisible); + if (hasAccessibleDeclarations) { + return hasAccessibleDeclarations; } - const res = append(additionalContainers, container); - return concatenate(res, reexportContainers); } - const candidates = mapDefined(symbol.declarations, d => { - if (!isAmbientModule(d) && d.parent && hasNonGlobalAugmentationExternalModuleSymbol(d.parent)) { - return getSymbolOfNode(d.parent); - } - if (isClassExpression(d) && isBinaryExpression(d.parent) && d.parent.operatorToken.kind === SyntaxKind.EqualsToken && isAccessExpression(d.parent.left) && isEntityNameExpression(d.parent.left.expression)) { - if (isModuleExportsAccessExpression(d.parent.left) || isExportsIdentifier(d.parent.left.expression)) { - return getSymbolOfNode(getSourceFileOfNode(d)); - } - checkExpressionCached(d.parent.left.expression); - return getNodeLinks(d.parent.left.expression).resolvedSymbol; + else { + if (some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { + // Any meaning of a module symbol is always accessible via an `import` type + return { + accessibility: SymbolAccessibility.Accessible + }; } - }); - if (!length(candidates)) { - return undefined; - } - return mapDefined(candidates, candidate => getAliasForSymbolInContainer(candidate, symbol) ? candidate : undefined); - - function fileSymbolIfFileSymbolExportEqualsContainer(d: Declaration) { - const fileSymbol = getExternalModuleContainer(d); - const exported = fileSymbol && fileSymbol.exports && fileSymbol.exports.get(InternalSymbolName.ExportEquals); - return exported && container && getSymbolIfSameReference(exported, container) ? fileSymbol : undefined; } + // If we haven't got the accessible symbol, it doesn't mean the symbol is actually inaccessible. + // It could be a qualified symbol and hence verify the path + // e.g.: + // module m { + // export class c { + // } + // } + // const x: typeof m.c + // In the above example when we start with checking if typeof m.c symbol is accessible, + // we are going to see if c can be accessed in scope directly. + // But it can't, hence the accessible is going to be undefined, but that doesn't mean m.c is inaccessible + // It is accessible if the parent m is accessible because then m.c can be accessed through qualification + let containers = getContainersOfSymbol(symbol, enclosingDeclaration); + // If we're trying to reference some object literal in, eg `var a = { x: 1 }`, the symbol for the literal, `__object`, is distinct + // from the symbol of the declaration it is being assigned to. Since we can use the declaration to refer to the literal, however, + // we'd like to make that connection here - potentially causing us to paint the declaration's visibility, and therefore the literal. + const firstDecl: Node = first(symbol.declarations); + if (!length(containers) && meaning & SymbolFlags.Value && firstDecl && isObjectLiteralExpression(firstDecl)) { + if (firstDecl.parent && isVariableDeclaration(firstDecl.parent) && firstDecl === firstDecl.parent.initializer) { + containers = [getSymbolOfNode(firstDecl.parent)]; + } + } + const parentResult = isAnySymbolAccessible(containers, enclosingDeclaration, initialSymbol, initialSymbol === symbol ? getQualifiedLeftMeaning(meaning) : meaning, shouldComputeAliasesToMakeVisible); + if (parentResult) { + return parentResult; + } + } + if (hadAccessibleChain) { + return { + accessibility: SymbolAccessibility.NotAccessible, + errorSymbolName: symbolToString(initialSymbol, enclosingDeclaration, meaning), + errorModuleName: hadAccessibleChain !== initialSymbol ? symbolToString(hadAccessibleChain, enclosingDeclaration, SymbolFlags.Namespace) : undefined, + }; } - - function getAliasForSymbolInContainer(container: Symbol, symbol: Symbol) { - if (container === getParentOfSymbol(symbol)) { - // fast path, `symbol` is either already the alias or isn't aliased - return symbol; - } - // Check if container is a thing with an `export=` which points directly at `symbol`, and if so, return - // the container itself as the alias for the symbol - const exportEquals = container.exports && container.exports.get(InternalSymbolName.ExportEquals); - if (exportEquals && getSymbolIfSameReference(exportEquals, symbol)) { - return container; - } - const exports = getExportsOfSymbol(container); - const quick = exports.get(symbol.escapedName); - if (quick && getSymbolIfSameReference(quick, symbol)) { - return quick; + } + /** + * Check if the given symbol in given enclosing declaration is accessible and mark all associated alias to be visible if requested + * + * @param symbol a Symbol to check if accessible + * @param enclosingDeclaration a Node containing reference to the symbol + * @param meaning a SymbolFlags to check if such meaning of the symbol is accessible + * @param shouldComputeAliasToMakeVisible a boolean value to indicate whether to return aliases to be mark visible in case the symbol is accessible + */ + function isSymbolAccessible(symbol: ts.Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean): SymbolAccessibilityResult { + if (symbol && enclosingDeclaration) { + const result = isAnySymbolAccessible([symbol], enclosingDeclaration, symbol, meaning, shouldComputeAliasesToMakeVisible); + if (result) { + return result; } - return forEachEntry(exports, exported => { - if (getSymbolIfSameReference(exported, symbol)) { - return exported; + // This could be a symbol that is not exported in the external module + // or it could be a symbol from different external module that is not aliased and hence cannot be named + const symbolExternalModule = forEach(symbol.declarations, getExternalModuleContainer); + if (symbolExternalModule) { + const enclosingExternalModule = getExternalModuleContainer(enclosingDeclaration); + if (symbolExternalModule !== enclosingExternalModule) { + // name from different external module that is not visible + return { + accessibility: SymbolAccessibility.CannotBeNamed, + errorSymbolName: symbolToString(symbol, enclosingDeclaration, meaning), + errorModuleName: symbolToString(symbolExternalModule) + }; } - }); - } - - /** - * Checks if two symbols, through aliasing and/or merging, refer to the same thing - */ - function getSymbolIfSameReference(s1: Symbol, s2: Symbol) { - if (getMergedSymbol(resolveSymbol(getMergedSymbol(s1))) === getMergedSymbol(resolveSymbol(getMergedSymbol(s2)))) { - return s1; } + // Just a local name that is not accessible + return { + accessibility: SymbolAccessibility.NotAccessible, + errorSymbolName: symbolToString(symbol, enclosingDeclaration, meaning), + }; } - - function getExportSymbolOfValueSymbolIfExported(symbol: Symbol): Symbol; - function getExportSymbolOfValueSymbolIfExported(symbol: Symbol | undefined): Symbol | undefined; - function getExportSymbolOfValueSymbolIfExported(symbol: Symbol | undefined): Symbol | undefined { - return getMergedSymbol(symbol && (symbol.flags & SymbolFlags.ExportValue) !== 0 ? symbol.exportSymbol : symbol); + return { accessibility: SymbolAccessibility.Accessible }; + } + function getExternalModuleContainer(declaration: Node) { + const node = findAncestor(declaration, hasExternalModuleSymbol); + return node && getSymbolOfNode(node); + } + function hasExternalModuleSymbol(declaration: Node) { + return isAmbientModule(declaration) || (declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule((declaration))); + } + function hasNonGlobalAugmentationExternalModuleSymbol(declaration: Node) { + return isModuleWithStringLiteralName(declaration) || (declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule((declaration))); + } + function hasVisibleDeclarations(symbol: ts.Symbol, shouldComputeAliasToMakeVisible: boolean): SymbolVisibilityResult | undefined { + let aliasesToMakeVisible: LateVisibilityPaintedStatement[] | undefined; + if (!every(filter(symbol.declarations, d => d.kind !== SyntaxKind.Identifier), getIsDeclarationVisible)) { + return undefined; } - - function symbolIsValue(symbol: Symbol): boolean { - return !!(symbol.flags & SymbolFlags.Value || symbol.flags & SymbolFlags.Alias && resolveAlias(symbol).flags & SymbolFlags.Value); + return { accessibility: SymbolAccessibility.Accessible, aliasesToMakeVisible }; + function getIsDeclarationVisible(declaration: Declaration) { + if (!isDeclarationVisible(declaration)) { + // Mark the unexported alias as visible if its parent is visible + // because these kind of aliases can be used to name types in declaration file + const anyImportSyntax = getAnyImportSyntax(declaration); + if (anyImportSyntax && + !hasModifier(anyImportSyntax, ModifierFlags.Export) && // import clause without export + isDeclarationVisible(anyImportSyntax.parent)) { + return addVisibleAlias(declaration, anyImportSyntax); + } + else if (isVariableDeclaration(declaration) && isVariableStatement(declaration.parent.parent) && + !hasModifier(declaration.parent.parent, ModifierFlags.Export) && // unexported variable statement + isDeclarationVisible(declaration.parent.parent.parent)) { + return addVisibleAlias(declaration, declaration.parent.parent); + } + else if (isLateVisibilityPaintedStatement(declaration) // unexported top-level statement + && !hasModifier(declaration, ModifierFlags.Export) + && isDeclarationVisible(declaration.parent)) { + return addVisibleAlias(declaration, declaration); + } + // Declaration is not visible + return false; + } + return true; } - - function findConstructorDeclaration(node: ClassLikeDeclaration): ConstructorDeclaration | undefined { - const members = node.members; - for (const member of members) { - if (member.kind === SyntaxKind.Constructor && nodeIsPresent((member).body)) { - return member; - } + function addVisibleAlias(declaration: Declaration, aliasingStatement: LateVisibilityPaintedStatement) { + // In function "buildTypeDisplay" where we decide whether to write type-alias or serialize types, + // we want to just check if type- alias is accessible or not but we don't care about emitting those alias at that time + // since we will do the emitting later in trackSymbol. + if (shouldComputeAliasToMakeVisible) { + getNodeLinks(declaration).isVisible = true; + aliasesToMakeVisible = appendIfUnique(aliasesToMakeVisible, aliasingStatement); } + return true; } - - function createType(flags: TypeFlags): Type { - const result = new Type(checker, flags); - typeCount++; - result.id = typeCount; - return result; + } + function isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult { + // get symbol of the first identifier of the entityName + let meaning: SymbolFlags; + if (entityName.parent.kind === SyntaxKind.TypeQuery || + isExpressionWithTypeArgumentsInClassExtendsClause(entityName.parent) || + entityName.parent.kind === SyntaxKind.ComputedPropertyName) { + // Typeof value + meaning = SymbolFlags.Value | SymbolFlags.ExportValue; + } + else if (entityName.kind === SyntaxKind.QualifiedName || entityName.kind === SyntaxKind.PropertyAccessExpression || + entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration) { + // Left identifier from type reference or TypeAlias + // Entity name of the import declaration + meaning = SymbolFlags.Namespace; + } + else { + // Type Reference or TypeAlias entity = Identifier + meaning = SymbolFlags.Type; + } + const firstIdentifier = getFirstIdentifier(entityName); + const symbol = resolveName(enclosingDeclaration, firstIdentifier.escapedText, meaning, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); + // Verify if the symbol is accessible + return (symbol && hasVisibleDeclarations(symbol, /*shouldComputeAliasToMakeVisible*/ true)) || { + accessibility: SymbolAccessibility.NotAccessible, + errorSymbolName: getTextOfNode(firstIdentifier), + errorNode: firstIdentifier + }; + } + function symbolToString(symbol: ts.Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags: SymbolFormatFlags = SymbolFormatFlags.AllowAnyNodeKind, writer?: EmitTextWriter): string { + let nodeFlags = NodeBuilderFlags.IgnoreErrors; + if (flags & SymbolFormatFlags.UseOnlyExternalAliasing) { + nodeFlags |= NodeBuilderFlags.UseOnlyExternalAliasing; } - - function createIntrinsicType(kind: TypeFlags, intrinsicName: string, objectFlags: ObjectFlags = 0): IntrinsicType { - const type = createType(kind); - type.intrinsicName = intrinsicName; - type.objectFlags = objectFlags; - return type; + if (flags & SymbolFormatFlags.WriteTypeParametersOrArguments) { + nodeFlags |= NodeBuilderFlags.WriteTypeParametersInQualifiedName; } - - function createBooleanType(trueFalseTypes: readonly Type[]): IntrinsicType & UnionType { - const type = getUnionType(trueFalseTypes); - type.flags |= TypeFlags.Boolean; - type.intrinsicName = "boolean"; - return type; + if (flags & SymbolFormatFlags.UseAliasDefinedOutsideCurrentScope) { + nodeFlags |= NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; } - - function createObjectType(objectFlags: ObjectFlags, symbol?: Symbol): ObjectType { - const type = createType(TypeFlags.Object); - type.objectFlags = objectFlags; - type.symbol = symbol!; - type.members = undefined; - type.properties = undefined; - type.callSignatures = undefined; - type.constructSignatures = undefined; - type.stringIndexInfo = undefined; - type.numberIndexInfo = undefined; - return type; + if (flags & SymbolFormatFlags.DoNotIncludeSymbolChain) { + nodeFlags |= NodeBuilderFlags.DoNotIncludeSymbolChain; } - - function createTypeofType() { - return getUnionType(arrayFrom(typeofEQFacts.keys(), getLiteralType)); + const builder = flags & SymbolFormatFlags.AllowAnyNodeKind ? nodeBuilder.symbolToExpression : nodeBuilder.symbolToEntityName; + return writer ? symbolToStringWorker(writer).getText() : usingSingleLineStringWriter(symbolToStringWorker); + function symbolToStringWorker(writer: EmitTextWriter) { + const entity = builder(symbol, meaning!, enclosingDeclaration, nodeFlags)!; // TODO: GH#18217 + const printer = createPrinter({ removeComments: true }); + const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); + printer.writeNode(EmitHint.Unspecified, entity, /*sourceFile*/ sourceFile, writer); + return writer; } - - function createTypeParameter(symbol?: Symbol) { - const type = createType(TypeFlags.TypeParameter); - if (symbol) type.symbol = symbol; - return type; - } - - // A reserved member name starts with two underscores, but the third character cannot be an underscore - // or the @ symbol. A third underscore indicates an escaped form of an identifier that started - // with at least two underscores. The @ character indicates that the name is denoted by a well known ES - // Symbol instance. - function isReservedMemberName(name: __String) { - return (name as string).charCodeAt(0) === CharacterCodes._ && - (name as string).charCodeAt(1) === CharacterCodes._ && - (name as string).charCodeAt(2) !== CharacterCodes._ && - (name as string).charCodeAt(2) !== CharacterCodes.at; - } - - function getNamedMembers(members: SymbolTable): Symbol[] { - let result: Symbol[] | undefined; - members.forEach((symbol, id) => { - if (!isReservedMemberName(id) && symbolIsValue(symbol)) { - (result || (result = [])).push(symbol); - } - }); - return result || emptyArray; + } + function signatureToString(signature: ts.Signature, enclosingDeclaration?: Node, flags = TypeFormatFlags.None, kind?: SignatureKind, writer?: EmitTextWriter): string { + return writer ? signatureToStringWorker(writer).getText() : usingSingleLineStringWriter(signatureToStringWorker); + function signatureToStringWorker(writer: EmitTextWriter) { + let sigOutput: SyntaxKind; + if (flags & TypeFormatFlags.WriteArrowStyleSignature) { + sigOutput = kind === SignatureKind.Construct ? SyntaxKind.ConstructorType : SyntaxKind.FunctionType; + } + else { + sigOutput = kind === SignatureKind.Construct ? SyntaxKind.ConstructSignature : SyntaxKind.CallSignature; + } + const sig = nodeBuilder.signatureToSignatureDeclaration(signature, sigOutput, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName); + const printer = createPrinter({ removeComments: true, omitTrailingSemicolon: true }); + const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); + printer.writeNode(EmitHint.Unspecified, (sig!), /*sourceFile*/ sourceFile, getTrailingSemicolonDeferringWriter(writer)); // TODO: GH#18217 + return writer; } - - function setStructuredTypeMembers(type: StructuredType, members: SymbolTable, callSignatures: readonly Signature[], constructSignatures: readonly Signature[], stringIndexInfo: IndexInfo | undefined, numberIndexInfo: IndexInfo | undefined): ResolvedType { - (type).members = members; - (type).properties = members === emptySymbols ? emptyArray : getNamedMembers(members); - (type).callSignatures = callSignatures; - (type).constructSignatures = constructSignatures; - (type).stringIndexInfo = stringIndexInfo; - (type).numberIndexInfo = numberIndexInfo; - return type; + } + function typeToString(type: ts.Type, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer: EmitTextWriter = createTextWriter("")): string { + const noTruncation = compilerOptions.noErrorTruncation || flags & TypeFormatFlags.NoTruncation; + const typeNode = nodeBuilder.typeToTypeNode(type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | (noTruncation ? NodeBuilderFlags.NoTruncation : 0), writer); + if (typeNode === undefined) + return Debug.fail("should always get typenode"); + const options = { removeComments: true }; + const printer = createPrinter(options); + const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); + printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ sourceFile, writer); + const result = writer.getText(); + const maxLength = noTruncation ? undefined : defaultMaximumTruncationLength * 2; + if (maxLength && result && result.length >= maxLength) { + return result.substr(0, maxLength - "...".length) + "..."; + } + return result; + } + function getTypeNamesForErrorDisplay(left: ts.Type, right: ts.Type): [string, string] { + let leftStr = symbolValueDeclarationIsContextSensitive(left.symbol) ? typeToString(left, left.symbol.valueDeclaration) : typeToString(left); + let rightStr = symbolValueDeclarationIsContextSensitive(right.symbol) ? typeToString(right, right.symbol.valueDeclaration) : typeToString(right); + if (leftStr === rightStr) { + leftStr = typeToString(left, /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType); + rightStr = typeToString(right, /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType); + } + return [leftStr, rightStr]; + } + function symbolValueDeclarationIsContextSensitive(symbol: ts.Symbol): boolean { + return symbol && symbol.valueDeclaration && isExpression(symbol.valueDeclaration) && !isContextSensitive(symbol.valueDeclaration); + } + function toNodeBuilderFlags(flags = TypeFormatFlags.None): NodeBuilderFlags { + return flags & TypeFormatFlags.NodeBuilderFlagsMask; + } + function createNodeBuilder() { + return { + typeToTypeNode: (type: ts.Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => typeToTypeNodeHelper(type, context)), + indexInfoToIndexSignatureDeclaration: (indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => indexInfoToIndexSignatureDeclarationHelper(indexInfo, kind, context)), + signatureToSignatureDeclaration: (signature: ts.Signature, kind: SyntaxKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => signatureToSignatureDeclarationHelper(signature, kind, context)), + symbolToEntityName: (symbol: ts.Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => symbolToName(symbol, context, meaning, /*expectsIdentifier*/ false)), + symbolToExpression: (symbol: ts.Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => symbolToExpression(symbol, context, meaning)), + symbolToTypeParameterDeclarations: (symbol: ts.Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => typeParametersToTypeParameterDeclarations(symbol, context)), + symbolToParameterDeclaration: (symbol: ts.Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => symbolToParameterDeclaration(symbol, context)), + typeParameterToDeclaration: (parameter: TypeParameter, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => typeParameterToDeclaration(parameter, context)), + symbolTableToDeclarationStatements: (symbolTable: SymbolTable, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker, bundled?: boolean) => withContext(enclosingDeclaration, flags, tracker, context => symbolTableToDeclarationStatements(symbolTable, context, bundled)), + }; + function withContext(enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined, tracker: SymbolTracker | undefined, cb: (context: NodeBuilderContext) => T): T | undefined { + Debug.assert(enclosingDeclaration === undefined || (enclosingDeclaration.flags & NodeFlags.Synthesized) === 0); + const context: NodeBuilderContext = { + enclosingDeclaration, + flags: flags || NodeBuilderFlags.None, + // If no full tracker is provided, fake up a dummy one with a basic limited-functionality moduleResolverHost + tracker: tracker && tracker.trackSymbol ? tracker : { trackSymbol: noop, moduleResolverHost: (flags!) & NodeBuilderFlags.DoNotIncludeSymbolChain ? { + getCommonSourceDirectory: (host as Program).getCommonSourceDirectory ? () => (host as Program).getCommonSourceDirectory() : () => "", + getSourceFiles: () => host.getSourceFiles(), + getCurrentDirectory: maybeBind(host, host.getCurrentDirectory), + getProbableSymlinks: maybeBind(host, host.getProbableSymlinks), + } : undefined }, + encounteredError: false, + visitedTypes: undefined, + symbolDepth: undefined, + inferTypeParameters: undefined, + approximateLength: 0 + }; + const resultingNode = cb(context); + return context.encounteredError ? undefined : resultingNode; } - - function createAnonymousType(symbol: Symbol | undefined, members: SymbolTable, callSignatures: readonly Signature[], constructSignatures: readonly Signature[], stringIndexInfo: IndexInfo | undefined, numberIndexInfo: IndexInfo | undefined): ResolvedType { - return setStructuredTypeMembers(createObjectType(ObjectFlags.Anonymous, symbol), - members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); + function checkTruncationLength(context: NodeBuilderContext): boolean { + if (context.truncating) + return context.truncating; + return context.truncating = !(context.flags & NodeBuilderFlags.NoTruncation) && context.approximateLength > defaultMaximumTruncationLength; } - - function forEachSymbolTableInScope(enclosingDeclaration: Node | undefined, callback: (symbolTable: SymbolTable) => T): T { - let result: T; - for (let location = enclosingDeclaration; location; location = location.parent) { - // Locals of a source file are not in scope (because they get merged into the global symbol table) - if (location.locals && !isGlobalSourceFile(location)) { - if (result = callback(location.locals)) { - return result; - } - } - switch (location.kind) { - case SyntaxKind.SourceFile: - if (!isExternalOrCommonJsModule(location)) { - break; - } - // falls through - case SyntaxKind.ModuleDeclaration: - const sym = getSymbolOfNode(location as ModuleDeclaration); - // `sym` may not have exports if this module declaration is backed by the symbol for a `const` that's being rewritten - // into a namespace - in such cases, it's best to just let the namespace appear empty (the const members couldn't have referred - // to one another anyway) - if (result = callback(sym.exports || emptySymbols)) { - return result; - } - break; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - // Type parameters are bound into `members` lists so they can merge across declarations - // This is troublesome, since in all other respects, they behave like locals :cries: - // TODO: the below is shared with similar code in `resolveName` - in fact, rephrasing all this symbol - // lookup logic in terms of `resolveName` would be nice - // The below is used to lookup type parameters within a class or interface, as they are added to the class/interface locals - // These can never be latebound, so the symbol's raw members are sufficient. `getMembersOfNode` cannot be used, as it would - // trigger resolving late-bound names, which we may already be in the process of doing while we're here! - let table: UnderscoreEscapedMap | undefined; - // TODO: Should this filtered table be cached in some way? - (getSymbolOfNode(location as ClassLikeDeclaration | InterfaceDeclaration).members || emptySymbols).forEach((memberSymbol, key) => { - if (memberSymbol.flags & (SymbolFlags.Type & ~SymbolFlags.Assignment)) { - (table || (table = createSymbolTable())).set(key, memberSymbol); - } - }); - if (table && (result = callback(table))) { - return result; - } - break; - } + function typeToTypeNodeHelper(type: ts.Type, context: NodeBuilderContext): TypeNode { + if (cancellationToken && cancellationToken.throwIfCancellationRequested) { + cancellationToken.throwIfCancellationRequested(); } - - return callback(globals); - } - - function getQualifiedLeftMeaning(rightMeaning: SymbolFlags) { - // If we are looking in value space, the parent meaning is value, other wise it is namespace - return rightMeaning === SymbolFlags.Value ? SymbolFlags.Value : SymbolFlags.Namespace; - } - - function getAccessibleSymbolChain(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean, visitedSymbolTablesMap: Map = createMap()): Symbol[] | undefined { - if (!(symbol && !isPropertyOrMethodDeclarationSymbol(symbol))) { - return undefined; + const inTypeAlias = context.flags & NodeBuilderFlags.InTypeAlias; + context.flags &= ~NodeBuilderFlags.InTypeAlias; + if (!type) { + context.encounteredError = true; + return undefined!; // TODO: GH#18217 } - - const id = "" + getSymbolId(symbol); - let visitedSymbolTables = visitedSymbolTablesMap.get(id); - if (!visitedSymbolTables) { - visitedSymbolTablesMap.set(id, visitedSymbolTables = []); + if (type.flags & TypeFlags.Any) { + context.approximateLength += 3; + return createKeywordTypeNode(SyntaxKind.AnyKeyword); } - return forEachSymbolTableInScope(enclosingDeclaration, getAccessibleSymbolChainFromSymbolTable); - - /** - * @param {ignoreQualification} boolean Set when a symbol is being looked for through the exports of another symbol (meaning we have a route to qualify it already) - */ - function getAccessibleSymbolChainFromSymbolTable(symbols: SymbolTable, ignoreQualification?: boolean): Symbol[] | undefined { - if (!pushIfUnique(visitedSymbolTables!, symbols)) { - return undefined; - } - - const result = trySymbolTable(symbols, ignoreQualification); - visitedSymbolTables!.pop(); - return result; + if (type.flags & TypeFlags.Unknown) { + return createKeywordTypeNode(SyntaxKind.UnknownKeyword); } - - function canQualifySymbol(symbolFromSymbolTable: Symbol, meaning: SymbolFlags) { - // If the symbol is equivalent and doesn't need further qualification, this symbol is accessible - return !needsQualification(symbolFromSymbolTable, enclosingDeclaration, meaning) || - // If symbol needs qualification, make sure that parent is accessible, if it is then this symbol is accessible too - !!getAccessibleSymbolChain(symbolFromSymbolTable.parent, enclosingDeclaration, getQualifiedLeftMeaning(meaning), useOnlyExternalAliasing, visitedSymbolTablesMap); + if (type.flags & TypeFlags.String) { + context.approximateLength += 6; + return createKeywordTypeNode(SyntaxKind.StringKeyword); } - - function isAccessible(symbolFromSymbolTable: Symbol, resolvedAliasSymbol?: Symbol, ignoreQualification?: boolean) { - return (symbol === (resolvedAliasSymbol || symbolFromSymbolTable) || getMergedSymbol(symbol) === getMergedSymbol(resolvedAliasSymbol || symbolFromSymbolTable)) && - // if the symbolFromSymbolTable is not external module (it could be if it was determined as ambient external module and would be in globals table) - // and if symbolFromSymbolTable or alias resolution matches the symbol, - // check the symbol can be qualified, it is only then this symbol is accessible - !some(symbolFromSymbolTable.declarations, hasNonGlobalAugmentationExternalModuleSymbol) && - (ignoreQualification || canQualifySymbol(getMergedSymbol(symbolFromSymbolTable), meaning)); + if (type.flags & TypeFlags.Number) { + context.approximateLength += 6; + return createKeywordTypeNode(SyntaxKind.NumberKeyword); } - - function trySymbolTable(symbols: SymbolTable, ignoreQualification: boolean | undefined): Symbol[] | undefined { - // If symbol is directly available by its name in the symbol table - if (isAccessible(symbols.get(symbol!.escapedName)!, /*resolvedAliasSymbol*/ undefined, ignoreQualification)) { - return [symbol!]; - } - - // Check if symbol is any of the aliases in scope - const result = forEachEntry(symbols, symbolFromSymbolTable => { - if (symbolFromSymbolTable.flags & SymbolFlags.Alias - && symbolFromSymbolTable.escapedName !== InternalSymbolName.ExportEquals - && symbolFromSymbolTable.escapedName !== InternalSymbolName.Default - && !(isUMDExportSymbol(symbolFromSymbolTable) && enclosingDeclaration && isExternalModule(getSourceFileOfNode(enclosingDeclaration))) - // If `!useOnlyExternalAliasing`, we can use any type of alias to get the name - && (!useOnlyExternalAliasing || some(symbolFromSymbolTable.declarations, isExternalModuleImportEqualsDeclaration)) - // While exports are generally considered to be in scope, export-specifier declared symbols are _not_ - // See similar comment in `resolveName` for details - && (ignoreQualification || !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier)) - ) { - - const resolvedImportedSymbol = resolveAlias(symbolFromSymbolTable); - const candidate = getCandidateListForSymbol(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification); - if (candidate) { - return candidate; - } - } - if (symbolFromSymbolTable.escapedName === symbol!.escapedName && symbolFromSymbolTable.exportSymbol) { - if (isAccessible(getMergedSymbol(symbolFromSymbolTable.exportSymbol), /*aliasSymbol*/ undefined, ignoreQualification)) { - return [symbol!]; - } - } - }); - - // If there's no result and we're looking at the global symbol table, treat `globalThis` like an alias and try to lookup thru that - return result || (symbols === globals ? getCandidateListForSymbol(globalThisSymbol, globalThisSymbol, ignoreQualification) : undefined); + if (type.flags & TypeFlags.BigInt) { + context.approximateLength += 6; + return createKeywordTypeNode(SyntaxKind.BigIntKeyword); } - - function getCandidateListForSymbol(symbolFromSymbolTable: Symbol, resolvedImportedSymbol: Symbol, ignoreQualification: boolean | undefined) { - if (isAccessible(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification)) { - return [symbolFromSymbolTable]; - } - - // Look in the exported members, if we can find accessibleSymbolChain, symbol is accessible using this chain - // but only if the symbolFromSymbolTable can be qualified - const candidateTable = getExportsOfSymbol(resolvedImportedSymbol); - const accessibleSymbolsFromExports = candidateTable && getAccessibleSymbolChainFromSymbolTable(candidateTable, /*ignoreQualification*/ true); - if (accessibleSymbolsFromExports && canQualifySymbol(symbolFromSymbolTable, getQualifiedLeftMeaning(meaning))) { - return [symbolFromSymbolTable].concat(accessibleSymbolsFromExports); - } + if (type.flags & TypeFlags.Boolean) { + context.approximateLength += 7; + return createKeywordTypeNode(SyntaxKind.BooleanKeyword); } - } - - function needsQualification(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags) { - let qualify = false; - forEachSymbolTableInScope(enclosingDeclaration, symbolTable => { - // If symbol of this name is not available in the symbol table we are ok - let symbolFromSymbolTable = getMergedSymbol(symbolTable.get(symbol.escapedName)); - if (!symbolFromSymbolTable) { - // Continue to the next symbol table - return false; - } - // If the symbol with this name is present it should refer to the symbol - if (symbolFromSymbolTable === symbol) { - // No need to qualify - return true; - } - - // Qualify if the symbol from symbol table has same meaning as expected - symbolFromSymbolTable = (symbolFromSymbolTable.flags & SymbolFlags.Alias && !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier)) ? resolveAlias(symbolFromSymbolTable) : symbolFromSymbolTable; - if (symbolFromSymbolTable.flags & meaning) { - qualify = true; - return true; - } - - // Continue to the next symbol table - return false; - }); - - return qualify; - } - - function isPropertyOrMethodDeclarationSymbol(symbol: Symbol) { - if (symbol.declarations && symbol.declarations.length) { - for (const declaration of symbol.declarations) { - switch (declaration.kind) { - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - continue; - default: - return false; - } - } - return true; + if (type.flags & TypeFlags.EnumLiteral && !(type.flags & TypeFlags.Union)) { + const parentSymbol = getParentOfSymbol(type.symbol)!; + const parentName = symbolToTypeNode(parentSymbol, context, SymbolFlags.Type); + const enumLiteralName = getDeclaredTypeOfSymbol(parentSymbol) === type + ? parentName + : appendReferenceToType((parentName as TypeReferenceNode | ImportTypeNode), createTypeReferenceNode(symbolName(type.symbol), /*typeArguments*/ undefined)); + return enumLiteralName; } - return false; - } - - function isTypeSymbolAccessible(typeSymbol: Symbol, enclosingDeclaration: Node | undefined): boolean { - const access = isSymbolAccessible(typeSymbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false); - return access.accessibility === SymbolAccessibility.Accessible; - } - - function isValueSymbolAccessible(typeSymbol: Symbol, enclosingDeclaration: Node | undefined): boolean { - const access = isSymbolAccessible(typeSymbol, enclosingDeclaration, SymbolFlags.Value, /*shouldComputeAliasesToMakeVisible*/ false); - return access.accessibility === SymbolAccessibility.Accessible; - } - - function isAnySymbolAccessible(symbols: Symbol[] | undefined, enclosingDeclaration: Node | undefined, initialSymbol: Symbol, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean): SymbolAccessibilityResult | undefined { - if (!length(symbols)) return; - - let hadAccessibleChain: Symbol | undefined; - for (const symbol of symbols!) { - // Symbol is accessible if it by itself is accessible - const accessibleSymbolChain = getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning, /*useOnlyExternalAliasing*/ false); - if (accessibleSymbolChain) { - hadAccessibleChain = symbol; - const hasAccessibleDeclarations = hasVisibleDeclarations(accessibleSymbolChain[0], shouldComputeAliasesToMakeVisible); - if (hasAccessibleDeclarations) { - return hasAccessibleDeclarations; - } - } - else { - if (some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { - // Any meaning of a module symbol is always accessible via an `import` type - return { - accessibility: SymbolAccessibility.Accessible - }; - } - } - - // If we haven't got the accessible symbol, it doesn't mean the symbol is actually inaccessible. - // It could be a qualified symbol and hence verify the path - // e.g.: - // module m { - // export class c { - // } - // } - // const x: typeof m.c - // In the above example when we start with checking if typeof m.c symbol is accessible, - // we are going to see if c can be accessed in scope directly. - // But it can't, hence the accessible is going to be undefined, but that doesn't mean m.c is inaccessible - // It is accessible if the parent m is accessible because then m.c can be accessed through qualification - - let containers = getContainersOfSymbol(symbol, enclosingDeclaration); - // If we're trying to reference some object literal in, eg `var a = { x: 1 }`, the symbol for the literal, `__object`, is distinct - // from the symbol of the declaration it is being assigned to. Since we can use the declaration to refer to the literal, however, - // we'd like to make that connection here - potentially causing us to paint the declaration's visibility, and therefore the literal. - const firstDecl: Node = first(symbol.declarations); - if (!length(containers) && meaning & SymbolFlags.Value && firstDecl && isObjectLiteralExpression(firstDecl)) { - if (firstDecl.parent && isVariableDeclaration(firstDecl.parent) && firstDecl === firstDecl.parent.initializer) { - containers = [getSymbolOfNode(firstDecl.parent)]; - } - } - const parentResult = isAnySymbolAccessible(containers, enclosingDeclaration, initialSymbol, initialSymbol === symbol ? getQualifiedLeftMeaning(meaning) : meaning, shouldComputeAliasesToMakeVisible); - if (parentResult) { - return parentResult; - } + if (type.flags & TypeFlags.EnumLike) { + return symbolToTypeNode(type.symbol, context, SymbolFlags.Type); } - - if (hadAccessibleChain) { - return { - accessibility: SymbolAccessibility.NotAccessible, - errorSymbolName: symbolToString(initialSymbol, enclosingDeclaration, meaning), - errorModuleName: hadAccessibleChain !== initialSymbol ? symbolToString(hadAccessibleChain, enclosingDeclaration, SymbolFlags.Namespace) : undefined, - }; + if (type.flags & TypeFlags.StringLiteral) { + context.approximateLength += ((type).value.length + 2); + return createLiteralTypeNode(setEmitFlags(createLiteral((type).value), EmitFlags.NoAsciiEscaping)); } - } - - /** - * Check if the given symbol in given enclosing declaration is accessible and mark all associated alias to be visible if requested - * - * @param symbol a Symbol to check if accessible - * @param enclosingDeclaration a Node containing reference to the symbol - * @param meaning a SymbolFlags to check if such meaning of the symbol is accessible - * @param shouldComputeAliasToMakeVisible a boolean value to indicate whether to return aliases to be mark visible in case the symbol is accessible - */ - function isSymbolAccessible(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean): SymbolAccessibilityResult { - if (symbol && enclosingDeclaration) { - const result = isAnySymbolAccessible([symbol], enclosingDeclaration, symbol, meaning, shouldComputeAliasesToMakeVisible); - if (result) { - return result; - } - - // This could be a symbol that is not exported in the external module - // or it could be a symbol from different external module that is not aliased and hence cannot be named - const symbolExternalModule = forEach(symbol.declarations, getExternalModuleContainer); - if (symbolExternalModule) { - const enclosingExternalModule = getExternalModuleContainer(enclosingDeclaration); - if (symbolExternalModule !== enclosingExternalModule) { - // name from different external module that is not visible - return { - accessibility: SymbolAccessibility.CannotBeNamed, - errorSymbolName: symbolToString(symbol, enclosingDeclaration, meaning), - errorModuleName: symbolToString(symbolExternalModule) - }; - } - } - - // Just a local name that is not accessible - return { - accessibility: SymbolAccessibility.NotAccessible, - errorSymbolName: symbolToString(symbol, enclosingDeclaration, meaning), - }; + if (type.flags & TypeFlags.NumberLiteral) { + const value = (type).value; + context.approximateLength += ("" + value).length; + return createLiteralTypeNode(value < 0 ? createPrefix(SyntaxKind.MinusToken, createLiteral(-value)) : createLiteral(value)); } - - return { accessibility: SymbolAccessibility.Accessible }; - } - - function getExternalModuleContainer(declaration: Node) { - const node = findAncestor(declaration, hasExternalModuleSymbol); - return node && getSymbolOfNode(node); - } - - function hasExternalModuleSymbol(declaration: Node) { - return isAmbientModule(declaration) || (declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(declaration)); - } - - function hasNonGlobalAugmentationExternalModuleSymbol(declaration: Node) { - return isModuleWithStringLiteralName(declaration) || (declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(declaration)); - } - - function hasVisibleDeclarations(symbol: Symbol, shouldComputeAliasToMakeVisible: boolean): SymbolVisibilityResult | undefined { - let aliasesToMakeVisible: LateVisibilityPaintedStatement[] | undefined; - if (!every(filter(symbol.declarations, d => d.kind !== SyntaxKind.Identifier), getIsDeclarationVisible)) { - return undefined; + if (type.flags & TypeFlags.BigIntLiteral) { + context.approximateLength += (pseudoBigIntToString((type).value).length) + 1; + return createLiteralTypeNode((createLiteral((type).value))); } - return { accessibility: SymbolAccessibility.Accessible, aliasesToMakeVisible }; - - function getIsDeclarationVisible(declaration: Declaration) { - if (!isDeclarationVisible(declaration)) { - // Mark the unexported alias as visible if its parent is visible - // because these kind of aliases can be used to name types in declaration file - - const anyImportSyntax = getAnyImportSyntax(declaration); - if (anyImportSyntax && - !hasModifier(anyImportSyntax, ModifierFlags.Export) && // import clause without export - isDeclarationVisible(anyImportSyntax.parent)) { - return addVisibleAlias(declaration, anyImportSyntax); - } - else if (isVariableDeclaration(declaration) && isVariableStatement(declaration.parent.parent) && - !hasModifier(declaration.parent.parent, ModifierFlags.Export) && // unexported variable statement - isDeclarationVisible(declaration.parent.parent.parent)) { - return addVisibleAlias(declaration, declaration.parent.parent); - } - else if (isLateVisibilityPaintedStatement(declaration) // unexported top-level statement - && !hasModifier(declaration, ModifierFlags.Export) - && isDeclarationVisible(declaration.parent)) { - return addVisibleAlias(declaration, declaration); - } - - // Declaration is not visible - return false; - } - - return true; + if (type.flags & TypeFlags.BooleanLiteral) { + context.approximateLength += (type).intrinsicName.length; + return (type).intrinsicName === "true" ? createTrue() : createFalse(); } - - function addVisibleAlias(declaration: Declaration, aliasingStatement: LateVisibilityPaintedStatement) { - // In function "buildTypeDisplay" where we decide whether to write type-alias or serialize types, - // we want to just check if type- alias is accessible or not but we don't care about emitting those alias at that time - // since we will do the emitting later in trackSymbol. - if (shouldComputeAliasToMakeVisible) { - getNodeLinks(declaration).isVisible = true; - aliasesToMakeVisible = appendIfUnique(aliasesToMakeVisible, aliasingStatement); + if (type.flags & TypeFlags.UniqueESSymbol) { + if (!(context.flags & NodeBuilderFlags.AllowUniqueESSymbolType)) { + if (isValueSymbolAccessible(type.symbol, context.enclosingDeclaration)) { + context.approximateLength += 6; + return symbolToTypeNode(type.symbol, context, SymbolFlags.Value); + } + if (context.tracker.reportInaccessibleUniqueSymbolError) { + context.tracker.reportInaccessibleUniqueSymbolError(); + } } - return true; + context.approximateLength += 13; + return createTypeOperatorNode(SyntaxKind.UniqueKeyword, createKeywordTypeNode(SyntaxKind.SymbolKeyword)); } - } - - function isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult { - // get symbol of the first identifier of the entityName - let meaning: SymbolFlags; - if (entityName.parent.kind === SyntaxKind.TypeQuery || - isExpressionWithTypeArgumentsInClassExtendsClause(entityName.parent) || - entityName.parent.kind === SyntaxKind.ComputedPropertyName) { - // Typeof value - meaning = SymbolFlags.Value | SymbolFlags.ExportValue; + if (type.flags & TypeFlags.Void) { + context.approximateLength += 4; + return createKeywordTypeNode(SyntaxKind.VoidKeyword); } - else if (entityName.kind === SyntaxKind.QualifiedName || entityName.kind === SyntaxKind.PropertyAccessExpression || - entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration) { - // Left identifier from type reference or TypeAlias - // Entity name of the import declaration - meaning = SymbolFlags.Namespace; + if (type.flags & TypeFlags.Undefined) { + context.approximateLength += 9; + return createKeywordTypeNode(SyntaxKind.UndefinedKeyword); } - else { - // Type Reference or TypeAlias entity = Identifier - meaning = SymbolFlags.Type; + if (type.flags & TypeFlags.Null) { + context.approximateLength += 4; + return createKeywordTypeNode(SyntaxKind.NullKeyword); } - - const firstIdentifier = getFirstIdentifier(entityName); - const symbol = resolveName(enclosingDeclaration, firstIdentifier.escapedText, meaning, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); - - // Verify if the symbol is accessible - return (symbol && hasVisibleDeclarations(symbol, /*shouldComputeAliasToMakeVisible*/ true)) || { - accessibility: SymbolAccessibility.NotAccessible, - errorSymbolName: getTextOfNode(firstIdentifier), - errorNode: firstIdentifier - }; - } - - function symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags: SymbolFormatFlags = SymbolFormatFlags.AllowAnyNodeKind, writer?: EmitTextWriter): string { - let nodeFlags = NodeBuilderFlags.IgnoreErrors; - if (flags & SymbolFormatFlags.UseOnlyExternalAliasing) { - nodeFlags |= NodeBuilderFlags.UseOnlyExternalAliasing; + if (type.flags & TypeFlags.Never) { + context.approximateLength += 5; + return createKeywordTypeNode(SyntaxKind.NeverKeyword); } - if (flags & SymbolFormatFlags.WriteTypeParametersOrArguments) { - nodeFlags |= NodeBuilderFlags.WriteTypeParametersInQualifiedName; + if (type.flags & TypeFlags.ESSymbol) { + context.approximateLength += 6; + return createKeywordTypeNode(SyntaxKind.SymbolKeyword); } - if (flags & SymbolFormatFlags.UseAliasDefinedOutsideCurrentScope) { - nodeFlags |= NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; + if (type.flags & TypeFlags.NonPrimitive) { + context.approximateLength += 6; + return createKeywordTypeNode(SyntaxKind.ObjectKeyword); } - if (flags & SymbolFormatFlags.DoNotIncludeSymbolChain) { - nodeFlags |= NodeBuilderFlags.DoNotIncludeSymbolChain; + if (isThisTypeParameter(type)) { + if (context.flags & NodeBuilderFlags.InObjectTypeLiteral) { + if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowThisInObjectLiteral)) { + context.encounteredError = true; + } + if (context.tracker.reportInaccessibleThisError) { + context.tracker.reportInaccessibleThisError(); + } + } + context.approximateLength += 4; + return createThis(); } - const builder = flags & SymbolFormatFlags.AllowAnyNodeKind ? nodeBuilder.symbolToExpression : nodeBuilder.symbolToEntityName; - return writer ? symbolToStringWorker(writer).getText() : usingSingleLineStringWriter(symbolToStringWorker); - - function symbolToStringWorker(writer: EmitTextWriter) { - const entity = builder(symbol, meaning!, enclosingDeclaration, nodeFlags)!; // TODO: GH#18217 - const printer = createPrinter({ removeComments: true }); - const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); - printer.writeNode(EmitHint.Unspecified, entity, /*sourceFile*/ sourceFile, writer); - return writer; + if (!inTypeAlias && type.aliasSymbol && (context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope || isTypeSymbolAccessible(type.aliasSymbol, context.enclosingDeclaration))) { + const typeArgumentNodes = mapToTypeNodes(type.aliasTypeArguments, context); + if (isReservedMemberName(type.aliasSymbol.escapedName) && !(type.aliasSymbol.flags & SymbolFlags.Class)) + return createTypeReferenceNode(createIdentifier(""), typeArgumentNodes); + return symbolToTypeNode(type.aliasSymbol, context, SymbolFlags.Type, typeArgumentNodes); } - } - - function signatureToString(signature: Signature, enclosingDeclaration?: Node, flags = TypeFormatFlags.None, kind?: SignatureKind, writer?: EmitTextWriter): string { - return writer ? signatureToStringWorker(writer).getText() : usingSingleLineStringWriter(signatureToStringWorker); - - function signatureToStringWorker(writer: EmitTextWriter) { - let sigOutput: SyntaxKind; - if (flags & TypeFormatFlags.WriteArrowStyleSignature) { - sigOutput = kind === SignatureKind.Construct ? SyntaxKind.ConstructorType : SyntaxKind.FunctionType; + const objectFlags = getObjectFlags(type); + if (objectFlags & ObjectFlags.Reference) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + return (type).node ? visitAndTransformType(type, typeReferenceToTypeNode) : typeReferenceToTypeNode((type)); + } + if (type.flags & TypeFlags.TypeParameter || objectFlags & ObjectFlags.ClassOrInterface) { + if (type.flags & TypeFlags.TypeParameter && contains(context.inferTypeParameters, type)) { + context.approximateLength += (symbolName(type.symbol).length + 6); + return createInferTypeNode(typeParameterToDeclarationWithConstraint((type as TypeParameter), context, /*constraintNode*/ undefined)); + } + if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && + type.flags & TypeFlags.TypeParameter && + !isTypeSymbolAccessible(type.symbol, context.enclosingDeclaration)) { + const name = typeParameterToName(type, context); + context.approximateLength += idText(name).length; + return createTypeReferenceNode(createIdentifier(idText(name)), /*typeArguments*/ undefined); + } + // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. + return type.symbol + ? symbolToTypeNode(type.symbol, context, SymbolFlags.Type) + : createTypeReferenceNode(createIdentifier("?"), /*typeArguments*/ undefined); + } + if (type.flags & (TypeFlags.Union | TypeFlags.Intersection)) { + const types = type.flags & TypeFlags.Union ? formatUnionTypes((type).types) : (type).types; + if (length(types) === 1) { + return typeToTypeNodeHelper(types[0], context); + } + const typeNodes = mapToTypeNodes(types, context, /*isBareList*/ true); + if (typeNodes && typeNodes.length > 0) { + const unionOrIntersectionTypeNode = createUnionOrIntersectionTypeNode(type.flags & TypeFlags.Union ? SyntaxKind.UnionType : SyntaxKind.IntersectionType, typeNodes); + return unionOrIntersectionTypeNode; } else { - sigOutput = kind === SignatureKind.Construct ? SyntaxKind.ConstructSignature : SyntaxKind.CallSignature; + if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) { + context.encounteredError = true; + } + return undefined!; // TODO: GH#18217 } - const sig = nodeBuilder.signatureToSignatureDeclaration(signature, sigOutput, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName); - const printer = createPrinter({ removeComments: true, omitTrailingSemicolon: true }); - const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); - printer.writeNode(EmitHint.Unspecified, sig!, /*sourceFile*/ sourceFile, getTrailingSemicolonDeferringWriter(writer)); // TODO: GH#18217 - return writer; } - } - - function typeToString(type: Type, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer: EmitTextWriter = createTextWriter("")): string { - const noTruncation = compilerOptions.noErrorTruncation || flags & TypeFormatFlags.NoTruncation; - const typeNode = nodeBuilder.typeToTypeNode(type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | (noTruncation ? NodeBuilderFlags.NoTruncation : 0), writer); - if (typeNode === undefined) return Debug.fail("should always get typenode"); - const options = { removeComments: true }; - const printer = createPrinter(options); - const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); - printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ sourceFile, writer); - const result = writer.getText(); - - const maxLength = noTruncation ? undefined : defaultMaximumTruncationLength * 2; - if (maxLength && result && result.length >= maxLength) { - return result.substr(0, maxLength - "...".length) + "..."; + if (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + // The type is an object literal type. + return createAnonymousTypeNode((type)); } - return result; - } - - function getTypeNamesForErrorDisplay(left: Type, right: Type): [string, string] { - let leftStr = symbolValueDeclarationIsContextSensitive(left.symbol) ? typeToString(left, left.symbol.valueDeclaration) : typeToString(left); - let rightStr = symbolValueDeclarationIsContextSensitive(right.symbol) ? typeToString(right, right.symbol.valueDeclaration) : typeToString(right); - if (leftStr === rightStr) { - leftStr = typeToString(left, /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType); - rightStr = typeToString(right, /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType); + if (type.flags & TypeFlags.Index) { + const indexedType = (type).type; + context.approximateLength += 6; + const indexTypeNode = typeToTypeNodeHelper(indexedType, context); + return createTypeOperatorNode(indexTypeNode); } - return [leftStr, rightStr]; - } - - function symbolValueDeclarationIsContextSensitive(symbol: Symbol): boolean { - return symbol && symbol.valueDeclaration && isExpression(symbol.valueDeclaration) && !isContextSensitive(symbol.valueDeclaration); - } - - function toNodeBuilderFlags(flags = TypeFormatFlags.None): NodeBuilderFlags { - return flags & TypeFormatFlags.NodeBuilderFlagsMask; - } - - function createNodeBuilder() { - return { - typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => typeToTypeNodeHelper(type, context)), - indexInfoToIndexSignatureDeclaration: (indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => indexInfoToIndexSignatureDeclarationHelper(indexInfo, kind, context)), - signatureToSignatureDeclaration: (signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => signatureToSignatureDeclarationHelper(signature, kind, context)), - symbolToEntityName: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => symbolToName(symbol, context, meaning, /*expectsIdentifier*/ false)), - symbolToExpression: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => symbolToExpression(symbol, context, meaning)), - symbolToTypeParameterDeclarations: (symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => typeParametersToTypeParameterDeclarations(symbol, context)), - symbolToParameterDeclaration: (symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => symbolToParameterDeclaration(symbol, context)), - typeParameterToDeclaration: (parameter: TypeParameter, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => typeParameterToDeclaration(parameter, context)), - symbolTableToDeclarationStatements: (symbolTable: SymbolTable, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker, bundled?: boolean) => - withContext(enclosingDeclaration, flags, tracker, context => symbolTableToDeclarationStatements(symbolTable, context, bundled)), - }; - - function withContext(enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined, tracker: SymbolTracker | undefined, cb: (context: NodeBuilderContext) => T): T | undefined { - Debug.assert(enclosingDeclaration === undefined || (enclosingDeclaration.flags & NodeFlags.Synthesized) === 0); - const context: NodeBuilderContext = { - enclosingDeclaration, - flags: flags || NodeBuilderFlags.None, - // If no full tracker is provided, fake up a dummy one with a basic limited-functionality moduleResolverHost - tracker: tracker && tracker.trackSymbol ? tracker : { trackSymbol: noop, moduleResolverHost: flags! & NodeBuilderFlags.DoNotIncludeSymbolChain ? { - getCommonSourceDirectory: (host as Program).getCommonSourceDirectory ? () => (host as Program).getCommonSourceDirectory() : () => "", - getSourceFiles: () => host.getSourceFiles(), - getCurrentDirectory: maybeBind(host, host.getCurrentDirectory), - getProbableSymlinks: maybeBind(host, host.getProbableSymlinks), - } : undefined }, - encounteredError: false, - visitedTypes: undefined, - symbolDepth: undefined, - inferTypeParameters: undefined, - approximateLength: 0 - }; - const resultingNode = cb(context); - return context.encounteredError ? undefined : resultingNode; + if (type.flags & TypeFlags.IndexedAccess) { + const objectTypeNode = typeToTypeNodeHelper((type).objectType, context); + const indexTypeNode = typeToTypeNodeHelper((type).indexType, context); + context.approximateLength += 2; + return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); } - - function checkTruncationLength(context: NodeBuilderContext): boolean { - if (context.truncating) return context.truncating; - return context.truncating = !(context.flags & NodeBuilderFlags.NoTruncation) && context.approximateLength > defaultMaximumTruncationLength; + if (type.flags & TypeFlags.Conditional) { + const checkTypeNode = typeToTypeNodeHelper((type).checkType, context); + const saveInferTypeParameters = context.inferTypeParameters; + context.inferTypeParameters = (type).root.inferTypeParameters; + const extendsTypeNode = typeToTypeNodeHelper((type).extendsType, context); + context.inferTypeParameters = saveInferTypeParameters; + const trueTypeNode = typeToTypeNodeHelper(getTrueTypeFromConditionalType((type)), context); + const falseTypeNode = typeToTypeNodeHelper(getFalseTypeFromConditionalType((type)), context); + context.approximateLength += 15; + return createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode); } - - function typeToTypeNodeHelper(type: Type, context: NodeBuilderContext): TypeNode { - if (cancellationToken && cancellationToken.throwIfCancellationRequested) { - cancellationToken.throwIfCancellationRequested(); + if (type.flags & TypeFlags.Substitution) { + return typeToTypeNodeHelper((type).typeVariable, context); + } + return Debug.fail("Should be unreachable."); + function createMappedTypeNodeFromType(type: MappedType) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + const readonlyToken = type.declaration.readonlyToken ? createToken(type.declaration.readonlyToken.kind) : undefined; + const questionToken = type.declaration.questionToken ? createToken(type.declaration.questionToken.kind) : undefined; + let appropriateConstraintTypeNode: TypeNode; + if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + // We have a { [P in keyof T]: X } + // We do this to ensure we retain the toplevel keyof-ness of the type which may be lost due to keyof distribution during `getConstraintTypeFromMappedType` + appropriateConstraintTypeNode = createTypeOperatorNode(typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context)); } - const inTypeAlias = context.flags & NodeBuilderFlags.InTypeAlias; - context.flags &= ~NodeBuilderFlags.InTypeAlias; - - if (!type) { - context.encounteredError = true; - return undefined!; // TODO: GH#18217 + else { + appropriateConstraintTypeNode = typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context); } - - if (type.flags & TypeFlags.Any) { - context.approximateLength += 3; - return createKeywordTypeNode(SyntaxKind.AnyKeyword); - } - if (type.flags & TypeFlags.Unknown) { - return createKeywordTypeNode(SyntaxKind.UnknownKeyword); - } - if (type.flags & TypeFlags.String) { - context.approximateLength += 6; - return createKeywordTypeNode(SyntaxKind.StringKeyword); - } - if (type.flags & TypeFlags.Number) { - context.approximateLength += 6; - return createKeywordTypeNode(SyntaxKind.NumberKeyword); - } - if (type.flags & TypeFlags.BigInt) { - context.approximateLength += 6; - return createKeywordTypeNode(SyntaxKind.BigIntKeyword); - } - if (type.flags & TypeFlags.Boolean) { - context.approximateLength += 7; - return createKeywordTypeNode(SyntaxKind.BooleanKeyword); - } - if (type.flags & TypeFlags.EnumLiteral && !(type.flags & TypeFlags.Union)) { - const parentSymbol = getParentOfSymbol(type.symbol)!; - const parentName = symbolToTypeNode(parentSymbol, context, SymbolFlags.Type); - const enumLiteralName = getDeclaredTypeOfSymbol(parentSymbol) === type - ? parentName - : appendReferenceToType( - parentName as TypeReferenceNode | ImportTypeNode, - createTypeReferenceNode(symbolName(type.symbol), /*typeArguments*/ undefined) - ); - return enumLiteralName; - } - if (type.flags & TypeFlags.EnumLike) { - return symbolToTypeNode(type.symbol, context, SymbolFlags.Type); - } - if (type.flags & TypeFlags.StringLiteral) { - context.approximateLength += ((type).value.length + 2); - return createLiteralTypeNode(setEmitFlags(createLiteral((type).value), EmitFlags.NoAsciiEscaping)); - } - if (type.flags & TypeFlags.NumberLiteral) { - const value = (type).value; - context.approximateLength += ("" + value).length; - return createLiteralTypeNode(value < 0 ? createPrefix(SyntaxKind.MinusToken, createLiteral(-value)) : createLiteral(value)); - } - if (type.flags & TypeFlags.BigIntLiteral) { - context.approximateLength += (pseudoBigIntToString((type).value).length) + 1; - return createLiteralTypeNode((createLiteral((type).value))); - } - if (type.flags & TypeFlags.BooleanLiteral) { - context.approximateLength += (type).intrinsicName.length; - return (type).intrinsicName === "true" ? createTrue() : createFalse(); - } - if (type.flags & TypeFlags.UniqueESSymbol) { - if (!(context.flags & NodeBuilderFlags.AllowUniqueESSymbolType)) { - if (isValueSymbolAccessible(type.symbol, context.enclosingDeclaration)) { - context.approximateLength += 6; - return symbolToTypeNode(type.symbol, context, SymbolFlags.Value); + const typeParameterNode = typeParameterToDeclarationWithConstraint(getTypeParameterFromMappedType(type), context, appropriateConstraintTypeNode); + const templateTypeNode = typeToTypeNodeHelper(getTemplateTypeFromMappedType(type), context); + const mappedTypeNode = createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode); + context.approximateLength += 10; + return setEmitFlags(mappedTypeNode, EmitFlags.SingleLine); + } + function createAnonymousTypeNode(type: ObjectType): TypeNode { + const typeId = "" + type.id; + const symbol = type.symbol; + if (symbol) { + if (isJSConstructor(symbol.valueDeclaration)) { + // Instance and static types share the same symbol; only add 'typeof' for the static side. + const isInstanceType = type === getDeclaredTypeOfClassOrInterface(symbol) ? SymbolFlags.Type : SymbolFlags.Value; + return symbolToTypeNode(symbol, context, isInstanceType); + } + // Always use 'typeof T' for type of class, enum, and module objects + else if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) && !(symbol.valueDeclaration.kind === SyntaxKind.ClassExpression && context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) || + symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) || + shouldWriteTypeOfFunctionSymbol()) { + return symbolToTypeNode(symbol, context, SymbolFlags.Value); + } + else if (context.visitedTypes && context.visitedTypes.has(typeId)) { + // If type is an anonymous type literal in a type alias declaration, use type alias name + const typeAlias = getTypeAliasForTypeLiteral(type); + if (typeAlias) { + // The specified symbol flags need to be reinterpreted as type flags + return symbolToTypeNode(typeAlias, context, SymbolFlags.Type); } - if (context.tracker.reportInaccessibleUniqueSymbolError) { - context.tracker.reportInaccessibleUniqueSymbolError(); + else { + return createElidedInformationPlaceholder(context); } } - context.approximateLength += 13; - return createTypeOperatorNode(SyntaxKind.UniqueKeyword, createKeywordTypeNode(SyntaxKind.SymbolKeyword)); + else { + return visitAndTransformType(type, createTypeNodeFromObjectType); + } } - if (type.flags & TypeFlags.Void) { - context.approximateLength += 4; - return createKeywordTypeNode(SyntaxKind.VoidKeyword); + else { + // Anonymous types without a symbol are never circular. + return createTypeNodeFromObjectType(type); + } + function shouldWriteTypeOfFunctionSymbol() { + const isStaticMethodSymbol = !!(symbol.flags & SymbolFlags.Method) && // typeof static method + some(symbol.declarations, declaration => hasModifier(declaration, ModifierFlags.Static)); + const isNonLocalFunctionSymbol = !!(symbol.flags & SymbolFlags.Function) && + (symbol.parent || // is exported function symbol + forEach(symbol.declarations, declaration => declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock)); + if (isStaticMethodSymbol || isNonLocalFunctionSymbol) { + // typeof is allowed only for static/non local functions + return (!!(context.flags & NodeBuilderFlags.UseTypeOfFunction) || (context.visitedTypes && context.visitedTypes.has(typeId))) && // it is type of the symbol uses itself recursively + (!(context.flags & NodeBuilderFlags.UseStructuralFallback) || isValueSymbolAccessible(symbol, context.enclosingDeclaration)); // And the build is going to succeed without visibility error or there is no structural fallback allowed + } + } + } + function visitAndTransformType(type: ts.Type, transform: (type: ts.Type) => T) { + const typeId = "" + type.id; + const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class; + const id = getObjectFlags(type) & ObjectFlags.Reference && (type).node ? "N" + getNodeId(((type).node!)) : + type.symbol ? (isConstructorObject ? "+" : "") + getSymbolId(type.symbol) : + undefined; + // Since instantiations of the same anonymous type have the same symbol, tracking symbols instead + // of types allows us to catch circular references to instantiations of the same anonymous type + if (!context.visitedTypes) { + context.visitedTypes = createMap(); } - if (type.flags & TypeFlags.Undefined) { - context.approximateLength += 9; - return createKeywordTypeNode(SyntaxKind.UndefinedKeyword); + if (id && !context.symbolDepth) { + context.symbolDepth = createMap(); } - if (type.flags & TypeFlags.Null) { - context.approximateLength += 4; - return createKeywordTypeNode(SyntaxKind.NullKeyword); + let depth: number | undefined; + if (id) { + depth = context.symbolDepth!.get(id) || 0; + if (depth > 10) { + return createElidedInformationPlaceholder(context); + } + context.symbolDepth!.set(id, depth + 1); } - if (type.flags & TypeFlags.Never) { - context.approximateLength += 5; - return createKeywordTypeNode(SyntaxKind.NeverKeyword); + context.visitedTypes.set(typeId, true); + const result = transform(type); + context.visitedTypes.delete(typeId); + if (id) { + context.symbolDepth!.set(id, depth!); } - if (type.flags & TypeFlags.ESSymbol) { - context.approximateLength += 6; - return createKeywordTypeNode(SyntaxKind.SymbolKeyword); + return result; + } + function createTypeNodeFromObjectType(type: ObjectType): TypeNode { + if (isGenericMappedType(type)) { + return createMappedTypeNodeFromType(type); + } + const resolved = resolveStructuredTypeMembers(type); + if (!resolved.properties.length && !resolved.stringIndexInfo && !resolved.numberIndexInfo) { + if (!resolved.callSignatures.length && !resolved.constructSignatures.length) { + context.approximateLength += 2; + return setEmitFlags(createTypeLiteralNode(/*members*/ undefined), EmitFlags.SingleLine); + } + if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { + const signature = resolved.callSignatures[0]; + const signatureNode = (signatureToSignatureDeclarationHelper(signature, SyntaxKind.FunctionType, context)); + return signatureNode; + } + if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { + const signature = resolved.constructSignatures[0]; + const signatureNode = (signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructorType, context)); + return signatureNode; + } + } + const savedFlags = context.flags; + context.flags |= NodeBuilderFlags.InObjectTypeLiteral; + const members = createTypeNodesFromResolvedType(resolved); + context.flags = savedFlags; + const typeLiteralNode = createTypeLiteralNode(members); + context.approximateLength += 2; + return setEmitFlags(typeLiteralNode, (context.flags & NodeBuilderFlags.MultilineObjectLiterals) ? 0 : EmitFlags.SingleLine); + } + function typeReferenceToTypeNode(type: TypeReference) { + const typeArguments: readonly ts.Type[] = getTypeArguments(type); + if (type.target === globalArrayType || type.target === globalReadonlyArrayType) { + if (context.flags & NodeBuilderFlags.WriteArrayAsGenericType) { + const typeArgumentNode = typeToTypeNodeHelper(typeArguments[0], context); + return createTypeReferenceNode(type.target === globalArrayType ? "Array" : "ReadonlyArray", [typeArgumentNode]); + } + const elementType = typeToTypeNodeHelper(typeArguments[0], context); + const arrayType = createArrayTypeNode(elementType); + return type.target === globalArrayType ? arrayType : createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, arrayType); + } + else if (type.target.objectFlags & ObjectFlags.Tuple) { + if (typeArguments.length > 0) { + const arity = getTypeReferenceArity(type); + const tupleConstituentNodes = mapToTypeNodes(typeArguments.slice(0, arity), context); + const hasRestElement = (type.target).hasRestElement; + if (tupleConstituentNodes) { + for (let i = (type.target).minLength; i < Math.min(arity, tupleConstituentNodes.length); i++) { + tupleConstituentNodes[i] = hasRestElement && i === arity - 1 ? + createRestTypeNode(createArrayTypeNode(tupleConstituentNodes[i])) : + createOptionalTypeNode(tupleConstituentNodes[i]); + } + const tupleTypeNode = createTupleTypeNode(tupleConstituentNodes); + return (type.target).readonly ? createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleTypeNode) : tupleTypeNode; + } + } + if (context.encounteredError || (context.flags & NodeBuilderFlags.AllowEmptyTuple)) { + const tupleTypeNode = createTupleTypeNode([]); + return (type.target).readonly ? createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleTypeNode) : tupleTypeNode; + } + context.encounteredError = true; + return undefined!; // TODO: GH#18217 } - if (type.flags & TypeFlags.NonPrimitive) { - context.approximateLength += 6; - return createKeywordTypeNode(SyntaxKind.ObjectKeyword); + else if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral && + type.symbol.valueDeclaration && + isClassLike(type.symbol.valueDeclaration) && + !isValueSymbolAccessible(type.symbol, context.enclosingDeclaration)) { + return createAnonymousTypeNode(type); } - if (isThisTypeParameter(type)) { - if (context.flags & NodeBuilderFlags.InObjectTypeLiteral) { - if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowThisInObjectLiteral)) { - context.encounteredError = true; - } - if (context.tracker.reportInaccessibleThisError) { - context.tracker.reportInaccessibleThisError(); + else { + const outerTypeParameters = type.target.outerTypeParameters; + let i = 0; + let resultType: TypeReferenceNode | ImportTypeNode | undefined; + if (outerTypeParameters) { + const length = outerTypeParameters.length; + while (i < length) { + // Find group of type arguments for type parameters with the same declaring container. + const start = i; + const parent = getParentSymbolOfTypeParameter(outerTypeParameters[i])!; + do { + i++; + } while (i < length && getParentSymbolOfTypeParameter(outerTypeParameters[i]) === parent); + // When type parameters are their own type arguments for the whole group (i.e. we have + // the default outer type arguments), we don't show the group. + if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { + const typeArgumentSlice = mapToTypeNodes(typeArguments.slice(start, i), context); + const flags = context.flags; + context.flags |= NodeBuilderFlags.ForbidIndexedAccessSymbolReferences; + const ref = (symbolToTypeNode(parent, context, SymbolFlags.Type, typeArgumentSlice) as TypeReferenceNode | ImportTypeNode); + context.flags = flags; + resultType = !resultType ? ref : appendReferenceToType(resultType, (ref as TypeReferenceNode)); + } } } - context.approximateLength += 4; - return createThis(); + let typeArgumentNodes: readonly TypeNode[] | undefined; + if (typeArguments.length > 0) { + const typeParameterCount = (type.target.typeParameters || emptyArray).length; + typeArgumentNodes = mapToTypeNodes(typeArguments.slice(i, typeParameterCount), context); + } + const flags = context.flags; + context.flags |= NodeBuilderFlags.ForbidIndexedAccessSymbolReferences; + const finalRef = symbolToTypeNode(type.symbol, context, SymbolFlags.Type, typeArgumentNodes); + context.flags = flags; + return !resultType ? finalRef : appendReferenceToType(resultType, (finalRef as TypeReferenceNode)); } - - if (!inTypeAlias && type.aliasSymbol && (context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope || isTypeSymbolAccessible(type.aliasSymbol, context.enclosingDeclaration))) { - const typeArgumentNodes = mapToTypeNodes(type.aliasTypeArguments, context); - if (isReservedMemberName(type.aliasSymbol.escapedName) && !(type.aliasSymbol.flags & SymbolFlags.Class)) return createTypeReferenceNode(createIdentifier(""), typeArgumentNodes); - return symbolToTypeNode(type.aliasSymbol, context, SymbolFlags.Type, typeArgumentNodes); + } + function appendReferenceToType(root: TypeReferenceNode | ImportTypeNode, ref: TypeReferenceNode): TypeReferenceNode | ImportTypeNode { + if (isImportTypeNode(root)) { + // first shift type arguments + const innerParams = root.typeArguments; + if (root.qualifier) { + (isIdentifier(root.qualifier) ? root.qualifier : root.qualifier.right).typeArguments = innerParams; + } + root.typeArguments = ref.typeArguments; + // then move qualifiers + const ids = getAccessStack(ref); + for (const id of ids) { + root.qualifier = root.qualifier ? createQualifiedName(root.qualifier, id) : id; + } + return root; } - - const objectFlags = getObjectFlags(type); - - if (objectFlags & ObjectFlags.Reference) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - return (type).node ? visitAndTransformType(type, typeReferenceToTypeNode) : typeReferenceToTypeNode(type); - } - if (type.flags & TypeFlags.TypeParameter || objectFlags & ObjectFlags.ClassOrInterface) { - if (type.flags & TypeFlags.TypeParameter && contains(context.inferTypeParameters, type)) { - context.approximateLength += (symbolName(type.symbol).length + 6); - return createInferTypeNode(typeParameterToDeclarationWithConstraint(type as TypeParameter, context, /*constraintNode*/ undefined)); - } - if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && - type.flags & TypeFlags.TypeParameter && - !isTypeSymbolAccessible(type.symbol, context.enclosingDeclaration)) { - const name = typeParameterToName(type, context); - context.approximateLength += idText(name).length; - return createTypeReferenceNode(createIdentifier(idText(name)), /*typeArguments*/ undefined); - } - // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. - return type.symbol - ? symbolToTypeNode(type.symbol, context, SymbolFlags.Type) - : createTypeReferenceNode(createIdentifier("?"), /*typeArguments*/ undefined); - } - if (type.flags & (TypeFlags.Union | TypeFlags.Intersection)) { - const types = type.flags & TypeFlags.Union ? formatUnionTypes((type).types) : (type).types; - if (length(types) === 1) { - return typeToTypeNodeHelper(types[0], context); - } - const typeNodes = mapToTypeNodes(types, context, /*isBareList*/ true); - if (typeNodes && typeNodes.length > 0) { - const unionOrIntersectionTypeNode = createUnionOrIntersectionTypeNode(type.flags & TypeFlags.Union ? SyntaxKind.UnionType : SyntaxKind.IntersectionType, typeNodes); - return unionOrIntersectionTypeNode; + else { + // first shift type arguments + const innerParams = root.typeArguments; + (isIdentifier(root.typeName) ? root.typeName : root.typeName.right).typeArguments = innerParams; + root.typeArguments = ref.typeArguments; + // then move qualifiers + const ids = getAccessStack(ref); + for (const id of ids) { + root.typeName = createQualifiedName(root.typeName, id); } - else { - if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) { - context.encounteredError = true; - } - return undefined!; // TODO: GH#18217 - } - } - if (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - // The type is an object literal type. - return createAnonymousTypeNode(type); - } - if (type.flags & TypeFlags.Index) { - const indexedType = (type).type; - context.approximateLength += 6; - const indexTypeNode = typeToTypeNodeHelper(indexedType, context); - return createTypeOperatorNode(indexTypeNode); - } - if (type.flags & TypeFlags.IndexedAccess) { - const objectTypeNode = typeToTypeNodeHelper((type).objectType, context); - const indexTypeNode = typeToTypeNodeHelper((type).indexType, context); - context.approximateLength += 2; - return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); - } - if (type.flags & TypeFlags.Conditional) { - const checkTypeNode = typeToTypeNodeHelper((type).checkType, context); - const saveInferTypeParameters = context.inferTypeParameters; - context.inferTypeParameters = (type).root.inferTypeParameters; - const extendsTypeNode = typeToTypeNodeHelper((type).extendsType, context); - context.inferTypeParameters = saveInferTypeParameters; - const trueTypeNode = typeToTypeNodeHelper(getTrueTypeFromConditionalType(type), context); - const falseTypeNode = typeToTypeNodeHelper(getFalseTypeFromConditionalType(type), context); - context.approximateLength += 15; - return createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode); - } - if (type.flags & TypeFlags.Substitution) { - return typeToTypeNodeHelper((type).typeVariable, context); + return root; } - - return Debug.fail("Should be unreachable."); - - function createMappedTypeNodeFromType(type: MappedType) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - const readonlyToken = type.declaration.readonlyToken ? createToken(type.declaration.readonlyToken.kind) : undefined; - const questionToken = type.declaration.questionToken ? createToken(type.declaration.questionToken.kind) : undefined; - let appropriateConstraintTypeNode: TypeNode; - if (isMappedTypeWithKeyofConstraintDeclaration(type)) { - // We have a { [P in keyof T]: X } - // We do this to ensure we retain the toplevel keyof-ness of the type which may be lost due to keyof distribution during `getConstraintTypeFromMappedType` - appropriateConstraintTypeNode = createTypeOperatorNode(typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context)); - } - else { - appropriateConstraintTypeNode = typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context); - } - const typeParameterNode = typeParameterToDeclarationWithConstraint(getTypeParameterFromMappedType(type), context, appropriateConstraintTypeNode); - const templateTypeNode = typeToTypeNodeHelper(getTemplateTypeFromMappedType(type), context); - const mappedTypeNode = createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode); - context.approximateLength += 10; - return setEmitFlags(mappedTypeNode, EmitFlags.SingleLine); + } + function getAccessStack(ref: TypeReferenceNode): Identifier[] { + let state = ref.typeName; + const ids = []; + while (!isIdentifier(state)) { + ids.unshift(state.right); + state = state.left; } - - function createAnonymousTypeNode(type: ObjectType): TypeNode { - const typeId = "" + type.id; - const symbol = type.symbol; - if (symbol) { - if (isJSConstructor(symbol.valueDeclaration)) { - // Instance and static types share the same symbol; only add 'typeof' for the static side. - const isInstanceType = type === getDeclaredTypeOfClassOrInterface(symbol) ? SymbolFlags.Type : SymbolFlags.Value; - return symbolToTypeNode(symbol, context, isInstanceType); - } - // Always use 'typeof T' for type of class, enum, and module objects - else if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) && !(symbol.valueDeclaration.kind === SyntaxKind.ClassExpression && context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) || - symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) || - shouldWriteTypeOfFunctionSymbol()) { - return symbolToTypeNode(symbol, context, SymbolFlags.Value); - } - else if (context.visitedTypes && context.visitedTypes.has(typeId)) { - // If type is an anonymous type literal in a type alias declaration, use type alias name - const typeAlias = getTypeAliasForTypeLiteral(type); - if (typeAlias) { - // The specified symbol flags need to be reinterpreted as type flags - return symbolToTypeNode(typeAlias, context, SymbolFlags.Type); - } - else { - return createElidedInformationPlaceholder(context); - } - } - else { - return visitAndTransformType(type, createTypeNodeFromObjectType); - } + ids.unshift(state); + return ids; + } + function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] | undefined { + if (checkTruncationLength(context)) { + return [createPropertySignature(/*modifiers*/ undefined, "...", /*questionToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined)]; + } + const typeElements: TypeElement[] = []; + for (const signature of resolvedType.callSignatures) { + typeElements.push((signatureToSignatureDeclarationHelper(signature, SyntaxKind.CallSignature, context))); + } + for (const signature of resolvedType.constructSignatures) { + typeElements.push((signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructSignature, context))); + } + if (resolvedType.stringIndexInfo) { + let indexSignature: IndexSignatureDeclaration; + if (resolvedType.objectFlags & ObjectFlags.ReverseMapped) { + indexSignature = indexInfoToIndexSignatureDeclarationHelper(createIndexInfo(anyType, resolvedType.stringIndexInfo.isReadonly, resolvedType.stringIndexInfo.declaration), IndexKind.String, context); + indexSignature.type = createElidedInformationPlaceholder(context); } else { - // Anonymous types without a symbol are never circular. - return createTypeNodeFromObjectType(type); - } - function shouldWriteTypeOfFunctionSymbol() { - const isStaticMethodSymbol = !!(symbol.flags & SymbolFlags.Method) && // typeof static method - some(symbol.declarations, declaration => hasModifier(declaration, ModifierFlags.Static)); - const isNonLocalFunctionSymbol = !!(symbol.flags & SymbolFlags.Function) && - (symbol.parent || // is exported function symbol - forEach(symbol.declarations, declaration => - declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock)); - if (isStaticMethodSymbol || isNonLocalFunctionSymbol) { - // typeof is allowed only for static/non local functions - return (!!(context.flags & NodeBuilderFlags.UseTypeOfFunction) || (context.visitedTypes && context.visitedTypes.has(typeId))) && // it is type of the symbol uses itself recursively - (!(context.flags & NodeBuilderFlags.UseStructuralFallback) || isValueSymbolAccessible(symbol, context.enclosingDeclaration)); // And the build is going to succeed without visibility error or there is no structural fallback allowed - } + indexSignature = indexInfoToIndexSignatureDeclarationHelper(resolvedType.stringIndexInfo, IndexKind.String, context); } + typeElements.push(indexSignature); } - - function visitAndTransformType(type: Type, transform: (type: Type) => T) { - const typeId = "" + type.id; - const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class; - const id = getObjectFlags(type) & ObjectFlags.Reference && (type).node ? "N" + getNodeId((type).node!) : - type.symbol ? (isConstructorObject ? "+" : "") + getSymbolId(type.symbol) : - undefined; - // Since instantiations of the same anonymous type have the same symbol, tracking symbols instead - // of types allows us to catch circular references to instantiations of the same anonymous type - if (!context.visitedTypes) { - context.visitedTypes = createMap(); - } - if (id && !context.symbolDepth) { - context.symbolDepth = createMap(); - } - - let depth: number | undefined; - if (id) { - depth = context.symbolDepth!.get(id) || 0; - if (depth > 10) { - return createElidedInformationPlaceholder(context); - } - context.symbolDepth!.set(id, depth + 1); - } - context.visitedTypes.set(typeId, true); - const result = transform(type); - context.visitedTypes.delete(typeId); - if (id) { - context.symbolDepth!.set(id, depth!); - } - return result; + if (resolvedType.numberIndexInfo) { + typeElements.push(indexInfoToIndexSignatureDeclarationHelper(resolvedType.numberIndexInfo, IndexKind.Number, context)); } - - function createTypeNodeFromObjectType(type: ObjectType): TypeNode { - if (isGenericMappedType(type)) { - return createMappedTypeNodeFromType(type); - } - - const resolved = resolveStructuredTypeMembers(type); - if (!resolved.properties.length && !resolved.stringIndexInfo && !resolved.numberIndexInfo) { - if (!resolved.callSignatures.length && !resolved.constructSignatures.length) { - context.approximateLength += 2; - return setEmitFlags(createTypeLiteralNode(/*members*/ undefined), EmitFlags.SingleLine); - } - - if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { - const signature = resolved.callSignatures[0]; - const signatureNode = signatureToSignatureDeclarationHelper(signature, SyntaxKind.FunctionType, context); - return signatureNode; - - } - - if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { - const signature = resolved.constructSignatures[0]; - const signatureNode = signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructorType, context); - return signatureNode; - } - } - - const savedFlags = context.flags; - context.flags |= NodeBuilderFlags.InObjectTypeLiteral; - const members = createTypeNodesFromResolvedType(resolved); - context.flags = savedFlags; - const typeLiteralNode = createTypeLiteralNode(members); - context.approximateLength += 2; - return setEmitFlags(typeLiteralNode, (context.flags & NodeBuilderFlags.MultilineObjectLiterals) ? 0 : EmitFlags.SingleLine); + const properties = resolvedType.properties; + if (!properties) { + return typeElements; } - - function typeReferenceToTypeNode(type: TypeReference) { - const typeArguments: readonly Type[] = getTypeArguments(type); - if (type.target === globalArrayType || type.target === globalReadonlyArrayType) { - if (context.flags & NodeBuilderFlags.WriteArrayAsGenericType) { - const typeArgumentNode = typeToTypeNodeHelper(typeArguments[0], context); - return createTypeReferenceNode(type.target === globalArrayType ? "Array" : "ReadonlyArray", [typeArgumentNode]); - } - const elementType = typeToTypeNodeHelper(typeArguments[0], context); - const arrayType = createArrayTypeNode(elementType); - return type.target === globalArrayType ? arrayType : createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, arrayType); - } - else if (type.target.objectFlags & ObjectFlags.Tuple) { - if (typeArguments.length > 0) { - const arity = getTypeReferenceArity(type); - const tupleConstituentNodes = mapToTypeNodes(typeArguments.slice(0, arity), context); - const hasRestElement = (type.target).hasRestElement; - if (tupleConstituentNodes) { - for (let i = (type.target).minLength; i < Math.min(arity, tupleConstituentNodes.length); i++) { - tupleConstituentNodes[i] = hasRestElement && i === arity - 1 ? - createRestTypeNode(createArrayTypeNode(tupleConstituentNodes[i])) : - createOptionalTypeNode(tupleConstituentNodes[i]); - } - const tupleTypeNode = createTupleTypeNode(tupleConstituentNodes); - return (type.target).readonly ? createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleTypeNode) : tupleTypeNode; - } + let i = 0; + for (const propertySymbol of properties) { + i++; + if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) { + if (propertySymbol.flags & SymbolFlags.Prototype) { + continue; } - if (context.encounteredError || (context.flags & NodeBuilderFlags.AllowEmptyTuple)) { - const tupleTypeNode = createTupleTypeNode([]); - return (type.target).readonly ? createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleTypeNode) : tupleTypeNode; + if (getDeclarationModifierFlagsFromSymbol(propertySymbol) & (ModifierFlags.Private | ModifierFlags.Protected) && context.tracker.reportPrivateInBaseOfClassExpression) { + context.tracker.reportPrivateInBaseOfClassExpression(unescapeLeadingUnderscores(propertySymbol.escapedName)); } - context.encounteredError = true; - return undefined!; // TODO: GH#18217 - } - else if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral && - type.symbol.valueDeclaration && - isClassLike(type.symbol.valueDeclaration) && - !isValueSymbolAccessible(type.symbol, context.enclosingDeclaration) - ) { - return createAnonymousTypeNode(type); } - else { - const outerTypeParameters = type.target.outerTypeParameters; - let i = 0; - let resultType: TypeReferenceNode | ImportTypeNode | undefined; - if (outerTypeParameters) { - const length = outerTypeParameters.length; - while (i < length) { - // Find group of type arguments for type parameters with the same declaring container. - const start = i; - const parent = getParentSymbolOfTypeParameter(outerTypeParameters[i])!; - do { - i++; - } while (i < length && getParentSymbolOfTypeParameter(outerTypeParameters[i]) === parent); - // When type parameters are their own type arguments for the whole group (i.e. we have - // the default outer type arguments), we don't show the group. - if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { - const typeArgumentSlice = mapToTypeNodes(typeArguments.slice(start, i), context); - const flags = context.flags; - context.flags |= NodeBuilderFlags.ForbidIndexedAccessSymbolReferences; - const ref = symbolToTypeNode(parent, context, SymbolFlags.Type, typeArgumentSlice) as TypeReferenceNode | ImportTypeNode; - context.flags = flags; - resultType = !resultType ? ref : appendReferenceToType(resultType, ref as TypeReferenceNode); - } - } - } - let typeArgumentNodes: readonly TypeNode[] | undefined; - if (typeArguments.length > 0) { - const typeParameterCount = (type.target.typeParameters || emptyArray).length; - typeArgumentNodes = mapToTypeNodes(typeArguments.slice(i, typeParameterCount), context); - } - const flags = context.flags; - context.flags |= NodeBuilderFlags.ForbidIndexedAccessSymbolReferences; - const finalRef = symbolToTypeNode(type.symbol, context, SymbolFlags.Type, typeArgumentNodes); - context.flags = flags; - return !resultType ? finalRef : appendReferenceToType(resultType, finalRef as TypeReferenceNode); + if (checkTruncationLength(context) && (i + 2 < properties.length - 1)) { + typeElements.push(createPropertySignature(/*modifiers*/ undefined, `... ${properties.length - i} more ...`, /*questionToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined)); + addPropertyToElementList(properties[properties.length - 1], context, typeElements); + break; } + addPropertyToElementList(propertySymbol, context, typeElements); } - - - function appendReferenceToType(root: TypeReferenceNode | ImportTypeNode, ref: TypeReferenceNode): TypeReferenceNode | ImportTypeNode { - if (isImportTypeNode(root)) { - // first shift type arguments - const innerParams = root.typeArguments; - if (root.qualifier) { - (isIdentifier(root.qualifier) ? root.qualifier : root.qualifier.right).typeArguments = innerParams; - } - root.typeArguments = ref.typeArguments; - // then move qualifiers - const ids = getAccessStack(ref); - for (const id of ids) { - root.qualifier = root.qualifier ? createQualifiedName(root.qualifier, id) : id; + return typeElements.length ? typeElements : undefined; + } + } + function createElidedInformationPlaceholder(context: NodeBuilderContext) { + context.approximateLength += 3; + if (!(context.flags & NodeBuilderFlags.NoTruncation)) { + return createTypeReferenceNode(createIdentifier("..."), /*typeArguments*/ undefined); + } + return createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + function addPropertyToElementList(propertySymbol: ts.Symbol, context: NodeBuilderContext, typeElements: TypeElement[]) { + const propertyIsReverseMapped = !!(getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped); + const propertyType = propertyIsReverseMapped && context.flags & NodeBuilderFlags.InReverseMappedType ? + anyType : getTypeOfSymbol(propertySymbol); + const saveEnclosingDeclaration = context.enclosingDeclaration; + context.enclosingDeclaration = undefined; + if (context.tracker.trackSymbol && getCheckFlags(propertySymbol) & CheckFlags.Late) { + const decl = first(propertySymbol.declarations); + if (hasLateBindableName(decl)) { + if (isBinaryExpression(decl)) { + const name = getNameOfDeclaration(decl); + if (name && isElementAccessExpression(name) && isPropertyAccessEntityNameExpression(name.argumentExpression)) { + trackComputedName(name.argumentExpression, saveEnclosingDeclaration, context); } - return root; } else { - // first shift type arguments - const innerParams = root.typeArguments; - (isIdentifier(root.typeName) ? root.typeName : root.typeName.right).typeArguments = innerParams; - root.typeArguments = ref.typeArguments; - // then move qualifiers - const ids = getAccessStack(ref); - for (const id of ids) { - root.typeName = createQualifiedName(root.typeName, id); - } - return root; - } - } - - function getAccessStack(ref: TypeReferenceNode): Identifier[] { - let state = ref.typeName; - const ids = []; - while (!isIdentifier(state)) { - ids.unshift(state.right); - state = state.left; + trackComputedName(decl.name.expression, saveEnclosingDeclaration, context); } - ids.unshift(state); - return ids; } - - function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] | undefined { - if (checkTruncationLength(context)) { - return [createPropertySignature(/*modifiers*/ undefined, "...", /*questionToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined)]; - } - const typeElements: TypeElement[] = []; - for (const signature of resolvedType.callSignatures) { - typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.CallSignature, context)); - } - for (const signature of resolvedType.constructSignatures) { - typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructSignature, context)); - } - if (resolvedType.stringIndexInfo) { - let indexSignature: IndexSignatureDeclaration; - if (resolvedType.objectFlags & ObjectFlags.ReverseMapped) { - indexSignature = indexInfoToIndexSignatureDeclarationHelper(createIndexInfo(anyType, resolvedType.stringIndexInfo.isReadonly, resolvedType.stringIndexInfo.declaration), IndexKind.String, context); - indexSignature.type = createElidedInformationPlaceholder(context); - } - else { - indexSignature = indexInfoToIndexSignatureDeclarationHelper(resolvedType.stringIndexInfo, IndexKind.String, context); - } - typeElements.push(indexSignature); - } - if (resolvedType.numberIndexInfo) { - typeElements.push(indexInfoToIndexSignatureDeclarationHelper(resolvedType.numberIndexInfo, IndexKind.Number, context)); - } - - const properties = resolvedType.properties; - if (!properties) { - return typeElements; - } - - let i = 0; - for (const propertySymbol of properties) { - i++; - if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) { - if (propertySymbol.flags & SymbolFlags.Prototype) { - continue; - } - if (getDeclarationModifierFlagsFromSymbol(propertySymbol) & (ModifierFlags.Private | ModifierFlags.Protected) && context.tracker.reportPrivateInBaseOfClassExpression) { - context.tracker.reportPrivateInBaseOfClassExpression(unescapeLeadingUnderscores(propertySymbol.escapedName)); - } - } - if (checkTruncationLength(context) && (i + 2 < properties.length - 1)) { - typeElements.push(createPropertySignature(/*modifiers*/ undefined, `... ${properties.length - i} more ...`, /*questionToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined)); - addPropertyToElementList(properties[properties.length - 1], context, typeElements); - break; - } - addPropertyToElementList(propertySymbol, context, typeElements); - - } - return typeElements.length ? typeElements : undefined; + } + context.enclosingDeclaration = saveEnclosingDeclaration; + const propertyName = getPropertyNameNodeForSymbol(propertySymbol, context); + context.approximateLength += (symbolName(propertySymbol).length + 1); + const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? createToken(SyntaxKind.QuestionToken) : undefined; + if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length && !isReadonlySymbol(propertySymbol)) { + const signatures = getSignaturesOfType(filterType(propertyType, t => !(t.flags & TypeFlags.Undefined)), SignatureKind.Call); + for (const signature of signatures) { + const methodDeclaration = (signatureToSignatureDeclarationHelper(signature, SyntaxKind.MethodSignature, context)); + methodDeclaration.name = propertyName; + methodDeclaration.questionToken = optionalToken; + typeElements.push(preserveCommentsOn(methodDeclaration)); } } - - function createElidedInformationPlaceholder(context: NodeBuilderContext) { - context.approximateLength += 3; - if (!(context.flags & NodeBuilderFlags.NoTruncation)) { - return createTypeReferenceNode(createIdentifier("..."), /*typeArguments*/ undefined); + else { + const savedFlags = context.flags; + context.flags |= propertyIsReverseMapped ? NodeBuilderFlags.InReverseMappedType : 0; + let propertyTypeNode: TypeNode; + if (propertyIsReverseMapped && !!(savedFlags & NodeBuilderFlags.InReverseMappedType)) { + propertyTypeNode = createElidedInformationPlaceholder(context); } - return createKeywordTypeNode(SyntaxKind.AnyKeyword); + else { + propertyTypeNode = propertyType ? typeToTypeNodeHelper(propertyType, context) : createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + context.flags = savedFlags; + const modifiers = isReadonlySymbol(propertySymbol) ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined; + if (modifiers) { + context.approximateLength += 9; + } + const propertySignature = createPropertySignature(modifiers, propertyName, optionalToken, propertyTypeNode, + /*initializer*/ undefined); + typeElements.push(preserveCommentsOn(propertySignature)); } - - function addPropertyToElementList(propertySymbol: Symbol, context: NodeBuilderContext, typeElements: TypeElement[]) { - const propertyIsReverseMapped = !!(getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped); - const propertyType = propertyIsReverseMapped && context.flags & NodeBuilderFlags.InReverseMappedType ? - anyType : getTypeOfSymbol(propertySymbol); - const saveEnclosingDeclaration = context.enclosingDeclaration; - context.enclosingDeclaration = undefined; - if (context.tracker.trackSymbol && getCheckFlags(propertySymbol) & CheckFlags.Late) { - const decl = first(propertySymbol.declarations); - if (hasLateBindableName(decl)) { - if (isBinaryExpression(decl)) { - const name = getNameOfDeclaration(decl); - if (name && isElementAccessExpression(name) && isPropertyAccessEntityNameExpression(name.argumentExpression)) { - trackComputedName(name.argumentExpression, saveEnclosingDeclaration, context); - } - } - else { - trackComputedName(decl.name.expression, saveEnclosingDeclaration, context); - } + function preserveCommentsOn(node: T) { + if (some(propertySymbol.declarations, d => d.kind === SyntaxKind.JSDocPropertyTag)) { + const d = (find(propertySymbol.declarations, d => d.kind === SyntaxKind.JSDocPropertyTag)! as JSDocPropertyTag); + const commentText = d.comment; + if (commentText) { + setSyntheticLeadingComments(node, [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "*\n * " + commentText.replace(/\n/g, "\n * ") + "\n ", pos: -1, end: -1, hasTrailingNewLine: true }]); } } - context.enclosingDeclaration = saveEnclosingDeclaration; - const propertyName = getPropertyNameNodeForSymbol(propertySymbol, context); - context.approximateLength += (symbolName(propertySymbol).length + 1); - const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? createToken(SyntaxKind.QuestionToken) : undefined; - if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length && !isReadonlySymbol(propertySymbol)) { - const signatures = getSignaturesOfType(filterType(propertyType, t => !(t.flags & TypeFlags.Undefined)), SignatureKind.Call); - for (const signature of signatures) { - const methodDeclaration = signatureToSignatureDeclarationHelper(signature, SyntaxKind.MethodSignature, context); - methodDeclaration.name = propertyName; - methodDeclaration.questionToken = optionalToken; - typeElements.push(preserveCommentsOn(methodDeclaration)); - } + else if (propertySymbol.valueDeclaration) { + // Copy comments to node for declaration emit + setCommentRange(node, propertySymbol.valueDeclaration); } - else { - const savedFlags = context.flags; - context.flags |= propertyIsReverseMapped ? NodeBuilderFlags.InReverseMappedType : 0; - let propertyTypeNode: TypeNode; - if (propertyIsReverseMapped && !!(savedFlags & NodeBuilderFlags.InReverseMappedType)) { - propertyTypeNode = createElidedInformationPlaceholder(context); + return node; + } + } + function mapToTypeNodes(types: readonly ts.Type[] | undefined, context: NodeBuilderContext, isBareList?: boolean): TypeNode[] | undefined { + if (some(types)) { + if (checkTruncationLength(context)) { + if (!isBareList) { + return [createTypeReferenceNode("...", /*typeArguments*/ undefined)]; } - else { - propertyTypeNode = propertyType ? typeToTypeNodeHelper(propertyType, context) : createKeywordTypeNode(SyntaxKind.AnyKeyword); + else if (types.length > 2) { + return [ + typeToTypeNodeHelper(types[0], context), + createTypeReferenceNode(`... ${types.length - 2} more ...`, /*typeArguments*/ undefined), + typeToTypeNodeHelper(types[types.length - 1], context) + ]; } - context.flags = savedFlags; - - const modifiers = isReadonlySymbol(propertySymbol) ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined; - if (modifiers) { - context.approximateLength += 9; - } - const propertySignature = createPropertySignature( - modifiers, - propertyName, - optionalToken, - propertyTypeNode, - /*initializer*/ undefined); - - typeElements.push(preserveCommentsOn(propertySignature)); } - - function preserveCommentsOn(node: T) { - if (some(propertySymbol.declarations, d => d.kind === SyntaxKind.JSDocPropertyTag)) { - const d = find(propertySymbol.declarations, d => d.kind === SyntaxKind.JSDocPropertyTag)! as JSDocPropertyTag; - const commentText = d.comment; - if (commentText) { - setSyntheticLeadingComments(node, [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "*\n * " + commentText.replace(/\n/g, "\n * ") + "\n ", pos: -1, end: -1, hasTrailingNewLine: true }]); + const result = []; + let i = 0; + for (const type of types) { + i++; + if (checkTruncationLength(context) && (i + 2 < types.length - 1)) { + result.push(createTypeReferenceNode(`... ${types.length - i} more ...`, /*typeArguments*/ undefined)); + const typeNode = typeToTypeNodeHelper(types[types.length - 1], context); + if (typeNode) { + result.push(typeNode); } + break; } - else if (propertySymbol.valueDeclaration) { - // Copy comments to node for declaration emit - setCommentRange(node, propertySymbol.valueDeclaration); + context.approximateLength += 2; // Account for whitespace + separator + const typeNode = typeToTypeNodeHelper(type, context); + if (typeNode) { + result.push(typeNode); } - return node; } + return result; } - - function mapToTypeNodes(types: readonly Type[] | undefined, context: NodeBuilderContext, isBareList?: boolean): TypeNode[] | undefined { - if (some(types)) { - if (checkTruncationLength(context)) { - if (!isBareList) { - return [createTypeReferenceNode("...", /*typeArguments*/ undefined)]; - } - else if (types.length > 2) { - return [ - typeToTypeNodeHelper(types[0], context), - createTypeReferenceNode(`... ${types.length - 2} more ...`, /*typeArguments*/ undefined), - typeToTypeNodeHelper(types[types.length - 1], context) - ]; + } + function indexInfoToIndexSignatureDeclarationHelper(indexInfo: IndexInfo, kind: IndexKind, context: NodeBuilderContext): IndexSignatureDeclaration { + const name = getNameFromIndexInfo(indexInfo) || "x"; + const indexerTypeNode = createKeywordTypeNode(kind === IndexKind.String ? SyntaxKind.StringKeyword : SyntaxKind.NumberKeyword); + const indexingParameter = createParameter( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, name, + /*questionToken*/ undefined, indexerTypeNode, + /*initializer*/ undefined); + const typeNode = typeToTypeNodeHelper(indexInfo.type || anyType, context); + if (!indexInfo.type && !(context.flags & NodeBuilderFlags.AllowEmptyIndexInfoType)) { + context.encounteredError = true; + } + context.approximateLength += (name.length + 4); + return createIndexSignature( + /*decorators*/ undefined, indexInfo.isReadonly ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined, [indexingParameter], typeNode); + } + function signatureToSignatureDeclarationHelper(signature: ts.Signature, kind: SyntaxKind, context: NodeBuilderContext): SignatureDeclaration { + let typeParameters: TypeParameterDeclaration[] | undefined; + let typeArguments: TypeNode[] | undefined; + if (context.flags & NodeBuilderFlags.WriteTypeArgumentsOfSignature && signature.target && signature.mapper && signature.target.typeParameters) { + typeArguments = signature.target.typeParameters.map(parameter => typeToTypeNodeHelper(instantiateType(parameter, signature.mapper), context)); + } + else { + typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter, context)); + } + const parameters = getExpandedParameters(signature).map(parameter => symbolToParameterDeclaration(parameter, context, kind === SyntaxKind.Constructor)); + if (signature.thisParameter) { + const thisParameter = symbolToParameterDeclaration(signature.thisParameter, context); + parameters.unshift(thisParameter); + } + let returnTypeNode: TypeNode | undefined; + const typePredicate = getTypePredicateOfSignature(signature); + if (typePredicate) { + const assertsModifier = typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? + createToken(SyntaxKind.AssertsKeyword) : + undefined; + const parameterName = typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? + setEmitFlags(createIdentifier(typePredicate.parameterName), EmitFlags.NoAsciiEscaping) : + createThisTypeNode(); + const typeNode = typePredicate.type && typeToTypeNodeHelper(typePredicate.type, context); + returnTypeNode = createTypePredicateNodeWithModifier(assertsModifier, parameterName, typeNode); + } + else { + const returnType = getReturnTypeOfSignature(signature); + returnTypeNode = returnType && typeToTypeNodeHelper(returnType, context); + } + if (context.flags & NodeBuilderFlags.SuppressAnyReturnType) { + if (returnTypeNode && returnTypeNode.kind === SyntaxKind.AnyKeyword) { + returnTypeNode = undefined; + } + } + else if (!returnTypeNode) { + returnTypeNode = createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + context.approximateLength += 3; // Usually a signature contributes a few more characters than this, but 3 is the minimum + return createSignatureDeclaration(kind, typeParameters, parameters, returnTypeNode, typeArguments); + } + function typeParameterToDeclarationWithConstraint(type: TypeParameter, context: NodeBuilderContext, constraintNode: TypeNode | undefined): TypeParameterDeclaration { + const savedContextFlags = context.flags; + context.flags &= ~NodeBuilderFlags.WriteTypeParametersInQualifiedName; // Avoids potential infinite loop when building for a claimspace with a generic + const name = typeParameterToName(type, context); + const defaultParameter = getDefaultFromTypeParameter(type); + const defaultParameterNode = defaultParameter && typeToTypeNodeHelper(defaultParameter, context); + context.flags = savedContextFlags; + return createTypeParameterDeclaration(name, constraintNode, defaultParameterNode); + } + function typeParameterToDeclaration(type: TypeParameter, context: NodeBuilderContext, constraint = getConstraintOfTypeParameter(type)): TypeParameterDeclaration { + const constraintNode = constraint && typeToTypeNodeHelper(constraint, context); + return typeParameterToDeclarationWithConstraint(type, context, constraintNode); + } + function symbolToParameterDeclaration(parameterSymbol: ts.Symbol, context: NodeBuilderContext, preserveModifierFlags?: boolean): ParameterDeclaration { + let parameterDeclaration: ParameterDeclaration | JSDocParameterTag | undefined = getDeclarationOfKind(parameterSymbol, SyntaxKind.Parameter); + if (!parameterDeclaration && !isTransientSymbol(parameterSymbol)) { + parameterDeclaration = getDeclarationOfKind(parameterSymbol, SyntaxKind.JSDocParameterTag); + } + let parameterType = getTypeOfSymbol(parameterSymbol); + if (parameterDeclaration && isRequiredInitializedParameter(parameterDeclaration)) { + parameterType = getOptionalType(parameterType); + } + const parameterTypeNode = typeToTypeNodeHelper(parameterType, context); + const modifiers = !(context.flags & NodeBuilderFlags.OmitParameterModifiers) && preserveModifierFlags && parameterDeclaration && parameterDeclaration.modifiers ? parameterDeclaration.modifiers.map(getSynthesizedClone) : undefined; + const isRest = parameterDeclaration && isRestParameter(parameterDeclaration) || getCheckFlags(parameterSymbol) & CheckFlags.RestParameter; + const dotDotDotToken = isRest ? createToken(SyntaxKind.DotDotDotToken) : undefined; + const name = parameterDeclaration ? parameterDeclaration.name ? + parameterDeclaration.name.kind === SyntaxKind.Identifier ? setEmitFlags(getSynthesizedClone(parameterDeclaration.name), EmitFlags.NoAsciiEscaping) : + parameterDeclaration.name.kind === SyntaxKind.QualifiedName ? setEmitFlags(getSynthesizedClone(parameterDeclaration.name.right), EmitFlags.NoAsciiEscaping) : + cloneBindingName(parameterDeclaration.name) : + symbolName(parameterSymbol) : + symbolName(parameterSymbol); + const isOptional = parameterDeclaration && isOptionalParameter(parameterDeclaration) || getCheckFlags(parameterSymbol) & CheckFlags.OptionalParameter; + const questionToken = isOptional ? createToken(SyntaxKind.QuestionToken) : undefined; + const parameterNode = createParameter( + /*decorators*/ undefined, modifiers, dotDotDotToken, name, questionToken, parameterTypeNode, + /*initializer*/ undefined); + context.approximateLength += symbolName(parameterSymbol).length + 3; + return parameterNode; + function cloneBindingName(node: BindingName): BindingName { + return elideInitializerAndSetEmitFlags(node); + function elideInitializerAndSetEmitFlags(node: Node): Node { + if (context.tracker.trackSymbol && isComputedPropertyName(node) && isLateBindableName(node)) { + trackComputedName(node.expression, context.enclosingDeclaration, context); + } + const visited = (visitEachChild(node, elideInitializerAndSetEmitFlags, nullTransformationContext, /*nodesVisitor*/ undefined, elideInitializerAndSetEmitFlags)!); + const clone = nodeIsSynthesized(visited) ? visited : getSynthesizedClone(visited); + if (clone.kind === SyntaxKind.BindingElement) { + (clone).initializer = undefined; + } + return setEmitFlags(clone, EmitFlags.SingleLine | EmitFlags.NoAsciiEscaping); + } + } + } + function trackComputedName(accessExpression: EntityNameOrEntityNameExpression, enclosingDeclaration: Node | undefined, context: NodeBuilderContext) { + if (!context.tracker.trackSymbol) + return; + // get symbol of the first identifier of the entityName + const firstIdentifier = getFirstIdentifier(accessExpression); + const name = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); + if (name) { + context.tracker.trackSymbol(name, enclosingDeclaration, SymbolFlags.Value); + } + } + function lookupSymbolChain(symbol: ts.Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) { + context.tracker.trackSymbol!(symbol, context.enclosingDeclaration, meaning); // TODO: GH#18217 + return lookupSymbolChainWorker(symbol, context, meaning, yieldModuleSymbol); + } + function lookupSymbolChainWorker(symbol: ts.Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) { + // Try to get qualified name if the symbol is not a type parameter and there is an enclosing declaration. + let chain: ts.Symbol[]; + const isTypeParameter = symbol.flags & SymbolFlags.TypeParameter; + if (!isTypeParameter && (context.enclosingDeclaration || context.flags & NodeBuilderFlags.UseFullyQualifiedType) && !(context.flags & NodeBuilderFlags.DoNotIncludeSymbolChain)) { + chain = Debug.assertDefined(getSymbolChain(symbol, meaning, /*endOfChain*/ true)); + Debug.assert(chain && chain.length > 0); + } + else { + chain = [symbol]; + } + return chain; + /** @param endOfChain Set to false for recursive calls; non-recursive calls should always output something. */ + function getSymbolChain(symbol: ts.Symbol, meaning: SymbolFlags, endOfChain: boolean): ts.Symbol[] | undefined { + let accessibleSymbolChain = getAccessibleSymbolChain(symbol, context.enclosingDeclaration, meaning, !!(context.flags & NodeBuilderFlags.UseOnlyExternalAliasing)); + let parentSpecifiers: (string | undefined)[]; + if (!accessibleSymbolChain || + needsQualification(accessibleSymbolChain[0], context.enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) { + // Go up and add our parent. + const parents = getContainersOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol, context.enclosingDeclaration); + if (length(parents)) { + parentSpecifiers = parents!.map(symbol => some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol) + ? getSpecifierForModuleSymbol(symbol, context) + : undefined); + const indices = parents!.map((_, i) => i); + indices.sort(sortByBestName); + const sortedParents = indices.map(i => parents![i]); + for (const parent of sortedParents) { + const parentChain = getSymbolChain(parent, getQualifiedLeftMeaning(meaning), /*endOfChain*/ false); + if (parentChain) { + if (parent.exports && parent.exports.get(InternalSymbolName.ExportEquals) && + getSymbolIfSameReference((parent.exports.get(InternalSymbolName.ExportEquals)!), symbol)) { + // parentChain root _is_ symbol - symbol is a module export=, so it kinda looks like it's own parent + // No need to lookup an alias for the symbol in itself + accessibleSymbolChain = parentChain; + break; + } + accessibleSymbolChain = parentChain.concat(accessibleSymbolChain || [getAliasForSymbolInContainer(parent, symbol) || symbol]); + break; + } } } - const result = []; - let i = 0; - for (const type of types) { - i++; - if (checkTruncationLength(context) && (i + 2 < types.length - 1)) { - result.push(createTypeReferenceNode(`... ${types.length - i} more ...`, /*typeArguments*/ undefined)); - const typeNode = typeToTypeNodeHelper(types[types.length - 1], context); - if (typeNode) { - result.push(typeNode); - } - break; + } + if (accessibleSymbolChain) { + return accessibleSymbolChain; + } + if ( + // If this is the last part of outputting the symbol, always output. The cases apply only to parent symbols. + endOfChain || + // If a parent symbol is an anonymous type, don't write it. + !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral))) { + // If a parent symbol is an external module, don't write it. (We prefer just `x` vs `"foo/bar".x`.) + if (!endOfChain && !yieldModuleSymbol && !!forEach(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { + return; + } + return [symbol]; + } + function sortByBestName(a: number, b: number) { + const specifierA = parentSpecifiers[a]; + const specifierB = parentSpecifiers[b]; + if (specifierA && specifierB) { + const isBRelative = pathIsRelative(specifierB); + if (pathIsRelative(specifierA) === isBRelative) { + // Both relative or both non-relative, sort by number of parts + return countPathComponents(specifierA) - countPathComponents(specifierB); } - context.approximateLength += 2; // Account for whitespace + separator - const typeNode = typeToTypeNodeHelper(type, context); - if (typeNode) { - result.push(typeNode); + if (isBRelative) { + // A is non-relative, B is relative: prefer A + return -1; } + // A is relative, B is non-relative: prefer B + return 1; } - - return result; + return 0; } } - - function indexInfoToIndexSignatureDeclarationHelper(indexInfo: IndexInfo, kind: IndexKind, context: NodeBuilderContext): IndexSignatureDeclaration { - const name = getNameFromIndexInfo(indexInfo) || "x"; - const indexerTypeNode = createKeywordTypeNode(kind === IndexKind.String ? SyntaxKind.StringKeyword : SyntaxKind.NumberKeyword); - - const indexingParameter = createParameter( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - name, - /*questionToken*/ undefined, - indexerTypeNode, - /*initializer*/ undefined); - const typeNode = typeToTypeNodeHelper(indexInfo.type || anyType, context); - if (!indexInfo.type && !(context.flags & NodeBuilderFlags.AllowEmptyIndexInfoType)) { - context.encounteredError = true; - } - context.approximateLength += (name.length + 4); - return createIndexSignature( - /*decorators*/ undefined, - indexInfo.isReadonly ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined, - [indexingParameter], - typeNode); + } + function typeParametersToTypeParameterDeclarations(symbol: ts.Symbol, context: NodeBuilderContext) { + let typeParameterNodes: NodeArray | undefined; + const targetSymbol = getTargetSymbol(symbol); + if (targetSymbol.flags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeAlias)) { + typeParameterNodes = createNodeArray(map(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol), tp => typeParameterToDeclaration(tp, context))); } - - function signatureToSignatureDeclarationHelper(signature: Signature, kind: SyntaxKind, context: NodeBuilderContext): SignatureDeclaration { - let typeParameters: TypeParameterDeclaration[] | undefined; - let typeArguments: TypeNode[] | undefined; - if (context.flags & NodeBuilderFlags.WriteTypeArgumentsOfSignature && signature.target && signature.mapper && signature.target.typeParameters) { - typeArguments = signature.target.typeParameters.map(parameter => typeToTypeNodeHelper(instantiateType(parameter, signature.mapper), context)); + return typeParameterNodes; + } + function lookupTypeParameterNodes(chain: ts.Symbol[], index: number, context: NodeBuilderContext) { + Debug.assert(chain && 0 <= index && index < chain.length); + const symbol = chain[index]; + const symbolId = "" + getSymbolId(symbol); + if (context.typeParameterSymbolList && context.typeParameterSymbolList.get(symbolId)) { + return undefined; + } + (context.typeParameterSymbolList || (context.typeParameterSymbolList = createMap())).set(symbolId, true); + let typeParameterNodes: readonly TypeNode[] | readonly TypeParameterDeclaration[] | undefined; + if (context.flags & NodeBuilderFlags.WriteTypeParametersInQualifiedName && index < (chain.length - 1)) { + const parentSymbol = symbol; + const nextSymbol = chain[index + 1]; + if (getCheckFlags(nextSymbol) & CheckFlags.Instantiated) { + const params = getTypeParametersOfClassOrInterface(parentSymbol.flags & SymbolFlags.Alias ? resolveAlias(parentSymbol) : parentSymbol); + typeParameterNodes = mapToTypeNodes(map(params, ((nextSymbol as TransientSymbol).mapper!)), context); } else { - typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter, context)); + typeParameterNodes = typeParametersToTypeParameterDeclarations(symbol, context); } - - const parameters = getExpandedParameters(signature).map(parameter => symbolToParameterDeclaration(parameter, context, kind === SyntaxKind.Constructor)); - if (signature.thisParameter) { - const thisParameter = symbolToParameterDeclaration(signature.thisParameter, context); - parameters.unshift(thisParameter); + } + return typeParameterNodes; + } + /** + * Given A[B][C][D], finds A[B] + */ + function getTopmostIndexedAccessType(top: IndexedAccessTypeNode): IndexedAccessTypeNode { + if (isIndexedAccessTypeNode(top.objectType)) { + return getTopmostIndexedAccessType(top.objectType); + } + return top; + } + function getSpecifierForModuleSymbol(symbol: ts.Symbol, context: NodeBuilderContext) { + const file = getDeclarationOfKind(symbol, SyntaxKind.SourceFile); + if (file && file.moduleName !== undefined) { + // Use the amd name if it is available + return file.moduleName; + } + if (!file) { + if (context.tracker.trackReferencedAmbientModule) { + const ambientDecls = filter(symbol.declarations, isAmbientModule); + if (length(ambientDecls)) { + for (const decl of ambientDecls) { + context.tracker.trackReferencedAmbientModule(decl, symbol); + } + } } - - let returnTypeNode: TypeNode | undefined; - const typePredicate = getTypePredicateOfSignature(signature); - if (typePredicate) { - const assertsModifier = typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? - createToken(SyntaxKind.AssertsKeyword) : - undefined; - const parameterName = typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? - setEmitFlags(createIdentifier(typePredicate.parameterName), EmitFlags.NoAsciiEscaping) : - createThisTypeNode(); - const typeNode = typePredicate.type && typeToTypeNodeHelper(typePredicate.type, context); - returnTypeNode = createTypePredicateNodeWithModifier(assertsModifier, parameterName, typeNode); + if (ambientModuleSymbolRegex.test(symbol.escapedName as string)) { + return (symbol.escapedName as string).substring(1, (symbol.escapedName as string).length - 1); } - else { - const returnType = getReturnTypeOfSignature(signature); - returnTypeNode = returnType && typeToTypeNodeHelper(returnType, context); + } + if (!context.enclosingDeclaration || !context.tracker.moduleResolverHost) { + // If there's no context declaration, we can't lookup a non-ambient specifier, so we just use the symbol name + if (ambientModuleSymbolRegex.test(symbol.escapedName as string)) { + return (symbol.escapedName as string).substring(1, (symbol.escapedName as string).length - 1); + } + return getSourceFileOfNode((getNonAugmentationDeclaration(symbol)!)).fileName; // A resolver may not be provided for baselines and errors - in those cases we use the fileName in full + } + const contextFile = getSourceFileOfNode(getOriginalNode(context.enclosingDeclaration)); + const links = getSymbolLinks(symbol); + let specifier = links.specifierCache && links.specifierCache.get(contextFile.path); + if (!specifier) { + const isBundle = (compilerOptions.out || compilerOptions.outFile); + // For declaration bundles, we need to generate absolute paths relative to the common source dir for imports, + // just like how the declaration emitter does for the ambient module declarations - we can easily accomplish this + // using the `baseUrl` compiler option (which we would otherwise never use in declaration emit) and a non-relative + // specifier preference + const { moduleResolverHost } = context.tracker; + const specifierCompilerOptions = isBundle ? { ...compilerOptions, baseUrl: moduleResolverHost.getCommonSourceDirectory() } : compilerOptions; + specifier = first(getModuleSpecifiers(symbol, specifierCompilerOptions, contextFile, moduleResolverHost, host.getSourceFiles(), { importModuleSpecifierPreference: isBundle ? "non-relative" : "relative" }, host.redirectTargetsMap)); + links.specifierCache = links.specifierCache || createMap(); + links.specifierCache.set(contextFile.path, specifier); + } + return specifier; + } + function symbolToTypeNode(symbol: ts.Symbol, context: NodeBuilderContext, meaning: SymbolFlags, overrideTypeArguments?: readonly TypeNode[]): TypeNode { + const chain = lookupSymbolChain(symbol, context, meaning, !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope)); // If we're using aliases outside the current scope, dont bother with the module + const isTypeOf = meaning === SymbolFlags.Value; + if (some(chain[0].declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { + // module is root, must use `ImportTypeNode` + const nonRootParts = chain.length > 1 ? createAccessFromSymbolChain(chain, chain.length - 1, 1) : undefined; + const typeParameterNodes = overrideTypeArguments || lookupTypeParameterNodes(chain, 0, context); + const specifier = getSpecifierForModuleSymbol(chain[0], context); + if (!(context.flags & NodeBuilderFlags.AllowNodeModulesRelativePaths) && getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs && specifier.indexOf("/node_modules/") >= 0) { + // If ultimately we can only name the symbol with a reference that dives into a `node_modules` folder, we should error + // since declaration files with these kinds of references are liable to fail when published :( + context.encounteredError = true; + if (context.tracker.reportLikelyUnsafeImportRequiredError) { + context.tracker.reportLikelyUnsafeImportRequiredError(specifier); + } } - if (context.flags & NodeBuilderFlags.SuppressAnyReturnType) { - if (returnTypeNode && returnTypeNode.kind === SyntaxKind.AnyKeyword) { - returnTypeNode = undefined; + const lit = createLiteralTypeNode(createLiteral(specifier)); + if (context.tracker.trackExternalModuleSymbolOfImportTypeNode) + context.tracker.trackExternalModuleSymbolOfImportTypeNode(chain[0]); + context.approximateLength += specifier.length + 10; // specifier + import("") + if (!nonRootParts || isEntityName(nonRootParts)) { + if (nonRootParts) { + const lastId = isIdentifier(nonRootParts) ? nonRootParts : nonRootParts.right; + lastId.typeArguments = undefined; } + return createImportTypeNode(lit, (nonRootParts as EntityName), (typeParameterNodes as readonly TypeNode[]), isTypeOf); } - else if (!returnTypeNode) { - returnTypeNode = createKeywordTypeNode(SyntaxKind.AnyKeyword); + else { + const splitNode = getTopmostIndexedAccessType(nonRootParts); + const qualifier = (splitNode.objectType as TypeReferenceNode).typeName; + return createIndexedAccessTypeNode(createImportTypeNode(lit, qualifier, (typeParameterNodes as readonly TypeNode[]), isTypeOf), splitNode.indexType); } - context.approximateLength += 3; // Usually a signature contributes a few more characters than this, but 3 is the minimum - return createSignatureDeclaration(kind, typeParameters, parameters, returnTypeNode, typeArguments); } - - function typeParameterToDeclarationWithConstraint(type: TypeParameter, context: NodeBuilderContext, constraintNode: TypeNode | undefined): TypeParameterDeclaration { - const savedContextFlags = context.flags; - context.flags &= ~NodeBuilderFlags.WriteTypeParametersInQualifiedName; // Avoids potential infinite loop when building for a claimspace with a generic - const name = typeParameterToName(type, context); - const defaultParameter = getDefaultFromTypeParameter(type); - const defaultParameterNode = defaultParameter && typeToTypeNodeHelper(defaultParameter, context); - context.flags = savedContextFlags; - return createTypeParameterDeclaration(name, constraintNode, defaultParameterNode); + const entityName = createAccessFromSymbolChain(chain, chain.length - 1, 0); + if (isIndexedAccessTypeNode(entityName)) { + return entityName; // Indexed accesses can never be `typeof` } - - function typeParameterToDeclaration(type: TypeParameter, context: NodeBuilderContext, constraint = getConstraintOfTypeParameter(type)): TypeParameterDeclaration { - const constraintNode = constraint && typeToTypeNodeHelper(constraint, context); - return typeParameterToDeclarationWithConstraint(type, context, constraintNode); + if (isTypeOf) { + return createTypeQueryNode(entityName); } - - function symbolToParameterDeclaration(parameterSymbol: Symbol, context: NodeBuilderContext, preserveModifierFlags?: boolean): ParameterDeclaration { - let parameterDeclaration: ParameterDeclaration | JSDocParameterTag | undefined = getDeclarationOfKind(parameterSymbol, SyntaxKind.Parameter); - if (!parameterDeclaration && !isTransientSymbol(parameterSymbol)) { - parameterDeclaration = getDeclarationOfKind(parameterSymbol, SyntaxKind.JSDocParameterTag); + else { + const lastId = isIdentifier(entityName) ? entityName : entityName.right; + const lastTypeArgs = lastId.typeArguments; + lastId.typeArguments = undefined; + return createTypeReferenceNode(entityName, (lastTypeArgs as NodeArray)); + } + function createAccessFromSymbolChain(chain: ts.Symbol[], index: number, stopper: number): EntityName | IndexedAccessTypeNode { + const typeParameterNodes = index === (chain.length - 1) ? overrideTypeArguments : lookupTypeParameterNodes(chain, index, context); + const symbol = chain[index]; + const parent = chain[index - 1]; + let symbolName: string | undefined; + if (index === 0) { + context.flags |= NodeBuilderFlags.InInitialEntityName; + symbolName = getNameOfSymbolAsWritten(symbol, context); + context.approximateLength += (symbolName ? symbolName.length : 0) + 1; + context.flags ^= NodeBuilderFlags.InInitialEntityName; } - - let parameterType = getTypeOfSymbol(parameterSymbol); - if (parameterDeclaration && isRequiredInitializedParameter(parameterDeclaration)) { - parameterType = getOptionalType(parameterType); + else { + if (parent && getExportsOfSymbol(parent)) { + const exports = getExportsOfSymbol(parent); + forEachEntry(exports, (ex, name) => { + if (getSymbolIfSameReference(ex, symbol) && !isLateBoundName(name) && name !== InternalSymbolName.ExportEquals) { + symbolName = unescapeLeadingUnderscores(name); + return true; + } + }); + } } - const parameterTypeNode = typeToTypeNodeHelper(parameterType, context); - - const modifiers = !(context.flags & NodeBuilderFlags.OmitParameterModifiers) && preserveModifierFlags && parameterDeclaration && parameterDeclaration.modifiers ? parameterDeclaration.modifiers.map(getSynthesizedClone) : undefined; - const isRest = parameterDeclaration && isRestParameter(parameterDeclaration) || getCheckFlags(parameterSymbol) & CheckFlags.RestParameter; - const dotDotDotToken = isRest ? createToken(SyntaxKind.DotDotDotToken) : undefined; - const name = parameterDeclaration ? parameterDeclaration.name ? - parameterDeclaration.name.kind === SyntaxKind.Identifier ? setEmitFlags(getSynthesizedClone(parameterDeclaration.name), EmitFlags.NoAsciiEscaping) : - parameterDeclaration.name.kind === SyntaxKind.QualifiedName ? setEmitFlags(getSynthesizedClone(parameterDeclaration.name.right), EmitFlags.NoAsciiEscaping) : - cloneBindingName(parameterDeclaration.name) : - symbolName(parameterSymbol) : - symbolName(parameterSymbol); - const isOptional = parameterDeclaration && isOptionalParameter(parameterDeclaration) || getCheckFlags(parameterSymbol) & CheckFlags.OptionalParameter; - const questionToken = isOptional ? createToken(SyntaxKind.QuestionToken) : undefined; - const parameterNode = createParameter( - /*decorators*/ undefined, - modifiers, - dotDotDotToken, - name, - questionToken, - parameterTypeNode, - /*initializer*/ undefined); - context.approximateLength += symbolName(parameterSymbol).length + 3; - return parameterNode; - - function cloneBindingName(node: BindingName): BindingName { - return elideInitializerAndSetEmitFlags(node); - function elideInitializerAndSetEmitFlags(node: Node): Node { - if (context.tracker.trackSymbol && isComputedPropertyName(node) && isLateBindableName(node)) { - trackComputedName(node.expression, context.enclosingDeclaration, context); - } - const visited = visitEachChild(node, elideInitializerAndSetEmitFlags, nullTransformationContext, /*nodesVisitor*/ undefined, elideInitializerAndSetEmitFlags)!; - const clone = nodeIsSynthesized(visited) ? visited : getSynthesizedClone(visited); - if (clone.kind === SyntaxKind.BindingElement) { - (clone).initializer = undefined; - } - return setEmitFlags(clone, EmitFlags.SingleLine | EmitFlags.NoAsciiEscaping); + if (!symbolName) { + symbolName = getNameOfSymbolAsWritten(symbol, context); + } + context.approximateLength += symbolName.length + 1; + if (!(context.flags & NodeBuilderFlags.ForbidIndexedAccessSymbolReferences) && parent && + getMembersOfSymbol(parent) && getMembersOfSymbol(parent).get(symbol.escapedName) && + getSymbolIfSameReference(getMembersOfSymbol(parent).get(symbol.escapedName)!, symbol)) { + // Should use an indexed access + const LHS = createAccessFromSymbolChain(chain, index - 1, stopper); + if (isIndexedAccessTypeNode(LHS)) { + return createIndexedAccessTypeNode(LHS, createLiteralTypeNode(createLiteral(symbolName))); + } + else { + return createIndexedAccessTypeNode(createTypeReferenceNode(LHS, (typeParameterNodes as readonly TypeNode[])), createLiteralTypeNode(createLiteral(symbolName))); + } + } + const identifier = setEmitFlags(createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); + identifier.symbol = symbol; + if (index > stopper) { + const LHS = createAccessFromSymbolChain(chain, index - 1, stopper); + if (!isEntityName(LHS)) { + return Debug.fail("Impossible construct - an export of an indexed access cannot be reachable"); } + return createQualifiedName(LHS, identifier); } + return identifier; } - - function trackComputedName(accessExpression: EntityNameOrEntityNameExpression, enclosingDeclaration: Node | undefined, context: NodeBuilderContext) { - if (!context.tracker.trackSymbol) return; - // get symbol of the first identifier of the entityName - const firstIdentifier = getFirstIdentifier(accessExpression); - const name = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); - if (name) { - context.tracker.trackSymbol(name, enclosingDeclaration, SymbolFlags.Value); + } + function typeParameterShadowsNameInScope(escapedName: __String, context: NodeBuilderContext) { + return !!resolveName(context.enclosingDeclaration, escapedName, SymbolFlags.Type, /*nameNotFoundArg*/ undefined, escapedName, /*isUse*/ false); + } + function typeParameterToName(type: TypeParameter, context: NodeBuilderContext) { + if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && context.typeParameterNames) { + const cached = context.typeParameterNames.get("" + getTypeId(type)); + if (cached) { + return cached; } } - - function lookupSymbolChain(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) { - context.tracker.trackSymbol!(symbol, context.enclosingDeclaration, meaning); // TODO: GH#18217 - return lookupSymbolChainWorker(symbol, context, meaning, yieldModuleSymbol); + let result = symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ true); + if (!(result.kind & SyntaxKind.Identifier)) { + return createIdentifier("(Missing type parameter)"); } - - function lookupSymbolChainWorker(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) { - // Try to get qualified name if the symbol is not a type parameter and there is an enclosing declaration. - let chain: Symbol[]; - const isTypeParameter = symbol.flags & SymbolFlags.TypeParameter; - if (!isTypeParameter && (context.enclosingDeclaration || context.flags & NodeBuilderFlags.UseFullyQualifiedType) && !(context.flags & NodeBuilderFlags.DoNotIncludeSymbolChain)) { - chain = Debug.assertDefined(getSymbolChain(symbol, meaning, /*endOfChain*/ true)); - Debug.assert(chain && chain.length > 0); - } - else { - chain = [symbol]; + if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) { + const rawtext = result.escapedText as string; + let i = 0; + let text = rawtext; + while ((context.typeParameterNamesByText && context.typeParameterNamesByText.get(text)) || typeParameterShadowsNameInScope((text as __String), context)) { + i++; + text = `${rawtext}_${i}`; } - return chain; - - /** @param endOfChain Set to false for recursive calls; non-recursive calls should always output something. */ - function getSymbolChain(symbol: Symbol, meaning: SymbolFlags, endOfChain: boolean): Symbol[] | undefined { - let accessibleSymbolChain = getAccessibleSymbolChain(symbol, context.enclosingDeclaration, meaning, !!(context.flags & NodeBuilderFlags.UseOnlyExternalAliasing)); - let parentSpecifiers: (string | undefined)[]; - if (!accessibleSymbolChain || - needsQualification(accessibleSymbolChain[0], context.enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) { - - // Go up and add our parent. - const parents = getContainersOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol, context.enclosingDeclaration); - if (length(parents)) { - parentSpecifiers = parents!.map(symbol => - some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol) - ? getSpecifierForModuleSymbol(symbol, context) - : undefined); - const indices = parents!.map((_, i) => i); - indices.sort(sortByBestName); - const sortedParents = indices.map(i => parents![i]); - for (const parent of sortedParents) { - const parentChain = getSymbolChain(parent, getQualifiedLeftMeaning(meaning), /*endOfChain*/ false); - if (parentChain) { - if (parent.exports && parent.exports.get(InternalSymbolName.ExportEquals) && - getSymbolIfSameReference(parent.exports.get(InternalSymbolName.ExportEquals)!, symbol)) { - // parentChain root _is_ symbol - symbol is a module export=, so it kinda looks like it's own parent - // No need to lookup an alias for the symbol in itself - accessibleSymbolChain = parentChain; - break; - } - accessibleSymbolChain = parentChain.concat(accessibleSymbolChain || [getAliasForSymbolInContainer(parent, symbol) || symbol]); - break; - } - } - } - } - - if (accessibleSymbolChain) { - return accessibleSymbolChain; - } - if ( - // If this is the last part of outputting the symbol, always output. The cases apply only to parent symbols. - endOfChain || - // If a parent symbol is an anonymous type, don't write it. - !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral))) { - // If a parent symbol is an external module, don't write it. (We prefer just `x` vs `"foo/bar".x`.) - if (!endOfChain && !yieldModuleSymbol && !!forEach(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { - return; - } - return [symbol]; - } - - function sortByBestName(a: number, b: number) { - const specifierA = parentSpecifiers[a]; - const specifierB = parentSpecifiers[b]; - if (specifierA && specifierB) { - const isBRelative = pathIsRelative(specifierB); - if (pathIsRelative(specifierA) === isBRelative) { - // Both relative or both non-relative, sort by number of parts - return moduleSpecifiers.countPathComponents(specifierA) - moduleSpecifiers.countPathComponents(specifierB); - } - if (isBRelative) { - // A is non-relative, B is relative: prefer A - return -1; - } - // A is relative, B is non-relative: prefer B - return 1; - } - return 0; - } + if (text !== rawtext) { + result = createIdentifier(text, result.typeArguments); } + (context.typeParameterNames || (context.typeParameterNames = createMap())).set("" + getTypeId(type), result); + (context.typeParameterNamesByText || (context.typeParameterNamesByText = createMap())).set((result.escapedText as string), true); } - - function typeParametersToTypeParameterDeclarations(symbol: Symbol, context: NodeBuilderContext) { - let typeParameterNodes: NodeArray | undefined; - const targetSymbol = getTargetSymbol(symbol); - if (targetSymbol.flags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeAlias)) { - typeParameterNodes = createNodeArray(map(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol), tp => typeParameterToDeclaration(tp, context))); + return result; + } + function symbolToName(symbol: ts.Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: true): Identifier; + function symbolToName(symbol: ts.Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: false): EntityName; + function symbolToName(symbol: ts.Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: boolean): EntityName { + const chain = lookupSymbolChain(symbol, context, meaning); + if (expectsIdentifier && chain.length !== 1 + && !context.encounteredError + && !(context.flags & NodeBuilderFlags.AllowQualifedNameInPlaceOfIdentifier)) { + context.encounteredError = true; + } + return createEntityNameFromSymbolChain(chain, chain.length - 1); + function createEntityNameFromSymbolChain(chain: ts.Symbol[], index: number): EntityName { + const typeParameterNodes = lookupTypeParameterNodes(chain, index, context); + const symbol = chain[index]; + if (index === 0) { + context.flags |= NodeBuilderFlags.InInitialEntityName; } - return typeParameterNodes; + const symbolName = getNameOfSymbolAsWritten(symbol, context); + if (index === 0) { + context.flags ^= NodeBuilderFlags.InInitialEntityName; + } + const identifier = setEmitFlags(createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); + identifier.symbol = symbol; + return index > 0 ? createQualifiedName(createEntityNameFromSymbolChain(chain, index - 1), identifier) : identifier; } - - function lookupTypeParameterNodes(chain: Symbol[], index: number, context: NodeBuilderContext) { - Debug.assert(chain && 0 <= index && index < chain.length); + } + function symbolToExpression(symbol: ts.Symbol, context: NodeBuilderContext, meaning: SymbolFlags) { + const chain = lookupSymbolChain(symbol, context, meaning); + return createExpressionFromSymbolChain(chain, chain.length - 1); + function createExpressionFromSymbolChain(chain: ts.Symbol[], index: number): Expression { + const typeParameterNodes = lookupTypeParameterNodes(chain, index, context); const symbol = chain[index]; - const symbolId = "" + getSymbolId(symbol); - if (context.typeParameterSymbolList && context.typeParameterSymbolList.get(symbolId)) { - return undefined; + if (index === 0) { + context.flags |= NodeBuilderFlags.InInitialEntityName; + } + let symbolName = getNameOfSymbolAsWritten(symbol, context); + if (index === 0) { + context.flags ^= NodeBuilderFlags.InInitialEntityName; + } + let firstChar = symbolName.charCodeAt(0); + if (isSingleOrDoubleQuote(firstChar) && some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { + return createLiteral(getSpecifierForModuleSymbol(symbol, context)); + } + const canUsePropertyAccess = isIdentifierStart(firstChar, languageVersion); + if (index === 0 || canUsePropertyAccess) { + const identifier = setEmitFlags(createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); + identifier.symbol = symbol; + return index > 0 ? createPropertyAccess(createExpressionFromSymbolChain(chain, index - 1), identifier) : identifier; } - (context.typeParameterSymbolList || (context.typeParameterSymbolList = createMap())).set(symbolId, true); - let typeParameterNodes: readonly TypeNode[] | readonly TypeParameterDeclaration[] | undefined; - if (context.flags & NodeBuilderFlags.WriteTypeParametersInQualifiedName && index < (chain.length - 1)) { - const parentSymbol = symbol; - const nextSymbol = chain[index + 1]; - if (getCheckFlags(nextSymbol) & CheckFlags.Instantiated) { - const params = getTypeParametersOfClassOrInterface( - parentSymbol.flags & SymbolFlags.Alias ? resolveAlias(parentSymbol) : parentSymbol - ); - typeParameterNodes = mapToTypeNodes(map(params, (nextSymbol as TransientSymbol).mapper!), context); + else { + if (firstChar === CharacterCodes.openBracket) { + symbolName = symbolName.substring(1, symbolName.length - 1); + firstChar = symbolName.charCodeAt(0); } - else { - typeParameterNodes = typeParametersToTypeParameterDeclarations(symbol, context); + let expression: Expression | undefined; + if (isSingleOrDoubleQuote(firstChar)) { + expression = createLiteral(symbolName.substring(1, symbolName.length - 1).replace(/\\./g, s => s.substring(1))); + (expression as StringLiteral).singleQuote = firstChar === CharacterCodes.singleQuote; + } + else if (("" + +symbolName) === symbolName) { + expression = createLiteral(+symbolName); } + if (!expression) { + expression = setEmitFlags(createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); + expression.symbol = symbol; + } + return createElementAccess(createExpressionFromSymbolChain(chain, index - 1), expression); } - return typeParameterNodes; } - - /** - * Given A[B][C][D], finds A[B] - */ - function getTopmostIndexedAccessType(top: IndexedAccessTypeNode): IndexedAccessTypeNode { - if (isIndexedAccessTypeNode(top.objectType)) { - return getTopmostIndexedAccessType(top.objectType); - } - return top; + } + function isSingleQuotedStringNamed(d: Declaration) { + const name = getNameOfDeclaration(d); + if (name && isStringLiteral(name) && (name.singleQuote || + (!nodeIsSynthesized(name) && startsWith(getTextOfNode(name, /*includeTrivia*/ false), "'")))) { + return true; } - - function getSpecifierForModuleSymbol(symbol: Symbol, context: NodeBuilderContext) { - const file = getDeclarationOfKind(symbol, SyntaxKind.SourceFile); - if (file && file.moduleName !== undefined) { - // Use the amd name if it is available - return file.moduleName; - } - if (!file) { - if (context.tracker.trackReferencedAmbientModule) { - const ambientDecls = filter(symbol.declarations, isAmbientModule); - if (length(ambientDecls)) { - for (const decl of ambientDecls) { - context.tracker.trackReferencedAmbientModule(decl, symbol); + return false; + } + function getPropertyNameNodeForSymbol(symbol: ts.Symbol, context: NodeBuilderContext) { + const singleQuote = !!length(symbol.declarations) && every(symbol.declarations, isSingleQuotedStringNamed); + const fromNameType = getPropertyNameNodeForSymbolFromNameType(symbol, context, singleQuote); + if (fromNameType) { + return fromNameType; + } + if (isKnownSymbol(symbol)) { + return createComputedPropertyName(createPropertyAccess(createIdentifier("Symbol"), (symbol.escapedName as string).substr(3))); + } + const rawName = unescapeLeadingUnderscores(symbol.escapedName); + return createPropertyNameNodeForIdentifierOrLiteral(rawName, singleQuote); + } + // See getNameForSymbolFromNameType for a stringy equivalent + function getPropertyNameNodeForSymbolFromNameType(symbol: ts.Symbol, context: NodeBuilderContext, singleQuote?: boolean) { + const nameType = symbol.nameType; + if (nameType) { + if (nameType.flags & TypeFlags.StringOrNumberLiteral) { + const name = "" + (nameType).value; + if (!isIdentifierText(name, compilerOptions.target) && !isNumericLiteralName(name)) { + return createLiteral(name, !!singleQuote); + } + if (isNumericLiteralName(name) && startsWith(name, "-")) { + return createComputedPropertyName(createLiteral(+name)); + } + return createPropertyNameNodeForIdentifierOrLiteral(name); + } + if (nameType.flags & TypeFlags.UniqueESSymbol) { + return createComputedPropertyName(symbolToExpression((nameType).symbol, context, SymbolFlags.Value)); + } + } + } + function createPropertyNameNodeForIdentifierOrLiteral(name: string, singleQuote?: boolean) { + return isIdentifierText(name, compilerOptions.target) ? createIdentifier(name) : createLiteral(isNumericLiteralName(name) ? +name : name, !!singleQuote); + } + function cloneNodeBuilderContext(context: NodeBuilderContext): NodeBuilderContext { + const initial: NodeBuilderContext = { ...context }; + // Make type parameters created within this context not consume the name outside this context + // The symbol serializer ends up creating many sibling scopes that all need "separate" contexts when + // it comes to naming things - within a normal `typeToTypeNode` call, the node builder only ever descends + // through the type tree, so the only cases where we could have used distinct sibling scopes was when there + // were multiple generic overloads with similar generated type parameter names + // The effect: + // When we write out + // export const x: (x: T) => T + // export const y: (x: T) => T + // we write it out like that, rather than as + // export const x: (x: T) => T + // export const y: (x: T_1) => T_1 + if (initial.typeParameterNames) { + initial.typeParameterNames = cloneMap(initial.typeParameterNames); + } + if (initial.typeParameterNamesByText) { + initial.typeParameterNamesByText = cloneMap(initial.typeParameterNamesByText); + } + if (initial.typeParameterSymbolList) { + initial.typeParameterSymbolList = cloneMap(initial.typeParameterSymbolList); + } + return initial; + } + function symbolTableToDeclarationStatements(symbolTable: SymbolTable, context: NodeBuilderContext, bundled?: boolean): Statement[] { + const serializePropertySymbolForClass = makeSerializePropertySymbol(createProperty, SyntaxKind.MethodDeclaration, /*useAcessors*/ true); + const serializePropertySymbolForInterfaceWorker = makeSerializePropertySymbol((_decorators, mods, name, question, type, initializer) => createPropertySignature(mods, name, question, type, initializer), SyntaxKind.MethodSignature, /*useAcessors*/ false); + // TODO: Use `setOriginalNode` on original declaration names where possible so these declarations see some kind of + // declaration mapping + // We save the enclosing declaration off here so it's not adjusted by well-meaning declaration + // emit codepaths which want to apply more specific contexts (so we can still refer to the root real declaration + // we're trying to emit from later on) + const enclosingDeclaration = context.enclosingDeclaration!; + let results: Statement[] = []; + const visitedSymbols: ts.Map = createMap(); + let deferredPrivates: ts.Map | undefined; + const oldcontext = context; + context = { + ...oldcontext, + usedSymbolNames: createMap(), + remappedSymbolNames: createMap(), + tracker: { + ...oldcontext.tracker, + trackSymbol: (sym, decl, meaning) => { + const accessibleResult = isSymbolAccessible(sym, decl, meaning, /*computeALiases*/ false); + if (accessibleResult.accessibility === SymbolAccessibility.Accessible) { + // Lookup the root symbol of the chain of refs we'll use to access it and serialize it + const chain = lookupSymbolChainWorker(sym, context, meaning); + if (!(sym.flags & SymbolFlags.Property)) { + includePrivateSymbol(chain[0]); } } + else if (oldcontext.tracker && oldcontext.tracker.trackSymbol) { + oldcontext.tracker.trackSymbol(sym, decl, meaning); + } } - if (ambientModuleSymbolRegex.test(symbol.escapedName as string)) { - return (symbol.escapedName as string).substring(1, (symbol.escapedName as string).length - 1); - } - } - if (!context.enclosingDeclaration || !context.tracker.moduleResolverHost) { - // If there's no context declaration, we can't lookup a non-ambient specifier, so we just use the symbol name - if (ambientModuleSymbolRegex.test(symbol.escapedName as string)) { - return (symbol.escapedName as string).substring(1, (symbol.escapedName as string).length - 1); - } - return getSourceFileOfNode(getNonAugmentationDeclaration(symbol)!).fileName; // A resolver may not be provided for baselines and errors - in those cases we use the fileName in full - } - const contextFile = getSourceFileOfNode(getOriginalNode(context.enclosingDeclaration)); - const links = getSymbolLinks(symbol); - let specifier = links.specifierCache && links.specifierCache.get(contextFile.path); - if (!specifier) { - const isBundle = (compilerOptions.out || compilerOptions.outFile); - // For declaration bundles, we need to generate absolute paths relative to the common source dir for imports, - // just like how the declaration emitter does for the ambient module declarations - we can easily accomplish this - // using the `baseUrl` compiler option (which we would otherwise never use in declaration emit) and a non-relative - // specifier preference - const { moduleResolverHost } = context.tracker; - const specifierCompilerOptions = isBundle ? { ...compilerOptions, baseUrl: moduleResolverHost.getCommonSourceDirectory() } : compilerOptions; - specifier = first(moduleSpecifiers.getModuleSpecifiers( - symbol, - specifierCompilerOptions, - contextFile, - moduleResolverHost, - host.getSourceFiles(), - { importModuleSpecifierPreference: isBundle ? "non-relative" : "relative" }, - host.redirectTargetsMap, - )); - links.specifierCache = links.specifierCache || createMap(); - links.specifierCache.set(contextFile.path, specifier); - } - return specifier; + } + }; + if (oldcontext.usedSymbolNames) { + oldcontext.usedSymbolNames.forEach((_, name) => { + context.usedSymbolNames!.set(name, true); + }); } - - function symbolToTypeNode(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, overrideTypeArguments?: readonly TypeNode[]): TypeNode { - const chain = lookupSymbolChain(symbol, context, meaning, !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope)); // If we're using aliases outside the current scope, dont bother with the module - - const isTypeOf = meaning === SymbolFlags.Value; - if (some(chain[0].declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { - // module is root, must use `ImportTypeNode` - const nonRootParts = chain.length > 1 ? createAccessFromSymbolChain(chain, chain.length - 1, 1) : undefined; - const typeParameterNodes = overrideTypeArguments || lookupTypeParameterNodes(chain, 0, context); - const specifier = getSpecifierForModuleSymbol(chain[0], context); - if (!(context.flags & NodeBuilderFlags.AllowNodeModulesRelativePaths) && getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs && specifier.indexOf("/node_modules/") >= 0) { - // If ultimately we can only name the symbol with a reference that dives into a `node_modules` folder, we should error - // since declaration files with these kinds of references are liable to fail when published :( - context.encounteredError = true; - if (context.tracker.reportLikelyUnsafeImportRequiredError) { - context.tracker.reportLikelyUnsafeImportRequiredError(specifier); + forEachEntry(symbolTable, (symbol, name) => { + const baseName = unescapeLeadingUnderscores(name); + void getInternalSymbolName(symbol, baseName); // Called to cache values into `usedSymbolNames` and `remappedSymbolNames` + }); + let addingDeclare = !bundled; + const exportEquals = symbolTable.get(InternalSymbolName.ExportEquals); + if (exportEquals && symbolTable.size > 1 && exportEquals.flags & SymbolFlags.Alias) { + symbolTable = createSymbolTable(); + // Remove extraneous elements from root symbol table (they'll be mixed back in when the target of the `export=` is looked up) + symbolTable.set(InternalSymbolName.ExportEquals, exportEquals); + } + visitSymbolTable(symbolTable); + return mergeRedundantStatements(results); + function isIdentifierAndNotUndefined(node: Node | undefined): node is Identifier { + return !!node && node.kind === SyntaxKind.Identifier; + } + function getNamesOfDeclaration(statement: Statement): Identifier[] { + if (isVariableStatement(statement)) { + return filter(map(statement.declarationList.declarations, getNameOfDeclaration), isIdentifierAndNotUndefined); + } + return filter([getNameOfDeclaration((statement as DeclarationStatement))], isIdentifierAndNotUndefined); + } + function flattenExportAssignedNamespace(statements: Statement[]) { + const exportAssignment = find(statements, isExportAssignment); + const ns = find(statements, isModuleDeclaration); + if (ns && exportAssignment && exportAssignment.isExportEquals && + isIdentifier(exportAssignment.expression) && isIdentifier(ns.name) && idText(ns.name) === idText(exportAssignment.expression) && + ns.body && isModuleBlock(ns.body)) { + // Pass 0: Correct situations where a module has both an `export = ns` and multiple top-level exports by stripping the export modifiers from + // the top-level exports and exporting them in the targeted ns, as can occur when a js file has both typedefs and `module.export` assignments + const excessExports = filter(statements, s => !!(getModifierFlags(s) & ModifierFlags.Export)); + if (length(excessExports)) { + ns.body.statements = createNodeArray([...ns.body.statements, createExportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, createNamedExports(map(flatMap(excessExports, e => getNamesOfDeclaration(e)), id => createExportSpecifier(/*alias*/ undefined, id))), + /*moduleSpecifier*/ undefined)]); + } + // Pass 1: Flatten `export namespace _exports {} export = _exports;` so long as the `export=` only points at a single namespace declaration + if (!find(statements, s => s !== ns && nodeHasName(s, (ns.name as Identifier)))) { + results = []; + forEach(ns.body.statements, s => { + addResult(s, ModifierFlags.None); // Recalculates the ambient (and export, if applicable from above) flag + }); + statements = [...filter(statements, s => s !== ns && s !== exportAssignment), ...results]; + } + } + return statements; + } + function mergeExportDeclarations(statements: Statement[]) { + // Pass 2: Combine all `export {}` declarations + const exports = (filter(statements, d => isExportDeclaration(d) && !d.moduleSpecifier && !!d.exportClause) as ExportDeclaration[]); + if (length(exports) > 1) { + const nonExports = filter(statements, d => !isExportDeclaration(d) || !!d.moduleSpecifier || !d.exportClause); + statements = [...nonExports, createExportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, createNamedExports(flatMap(exports, e => e.exportClause!.elements)), + /*moduleSpecifier*/ undefined)]; + } + // Pass 2b: Also combine all `export {} from "..."` declarations as needed + const reexports = (filter(statements, d => isExportDeclaration(d) && !!d.moduleSpecifier && !!d.exportClause) as ExportDeclaration[]); + if (length(reexports) > 1) { + const groups = group(reexports, decl => isStringLiteral((decl.moduleSpecifier!)) ? ">" + decl.moduleSpecifier.text : ">"); + if (groups.length !== reexports.length) { + for (const group of groups) { + if (group.length > 1) { + // remove group members from statements and then merge group members and add back to statements + statements = [ + ...filter(statements, s => group.indexOf((s as ExportDeclaration)) === -1), + createExportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, createNamedExports(flatMap(group, e => e.exportClause!.elements)), group[0].moduleSpecifier) + ]; + } } } - const lit = createLiteralTypeNode(createLiteral(specifier)); - if (context.tracker.trackExternalModuleSymbolOfImportTypeNode) context.tracker.trackExternalModuleSymbolOfImportTypeNode(chain[0]); - context.approximateLength += specifier.length + 10; // specifier + import("") - if (!nonRootParts || isEntityName(nonRootParts)) { - if (nonRootParts) { - const lastId = isIdentifier(nonRootParts) ? nonRootParts : nonRootParts.right; - lastId.typeArguments = undefined; + } + return statements; + } + function inlineExportModifiers(statements: Statement[]) { + // Pass 3: Move all `export {}`'s to `export` modifiers where possible + const exportDecl = (find(statements, d => isExportDeclaration(d) && !d.moduleSpecifier && !!d.exportClause) as ExportDeclaration | undefined); + if (exportDecl) { + const replacements = mapDefined(exportDecl.exportClause!.elements, e => { + if (!e.propertyName) { + // export {name} - look thru `statements` for `name`, and if all results can take an `export` modifier, do so and filter it + const associated = filter(statements, s => nodeHasName(s, e.name)); + if (length(associated) && every(associated, canHaveExportModifier)) { + forEach(associated, addExportModifier); + return undefined; + } } - return createImportTypeNode(lit, nonRootParts as EntityName, typeParameterNodes as readonly TypeNode[], isTypeOf); + return e; + }); + if (!length(replacements)) { + // all clauses removed, filter the export declaration + statements = filter(statements, s => s !== exportDecl); } else { - const splitNode = getTopmostIndexedAccessType(nonRootParts); - const qualifier = (splitNode.objectType as TypeReferenceNode).typeName; - return createIndexedAccessTypeNode(createImportTypeNode(lit, qualifier, typeParameterNodes as readonly TypeNode[], isTypeOf), splitNode.indexType); - } - } - - const entityName = createAccessFromSymbolChain(chain, chain.length - 1, 0); - if (isIndexedAccessTypeNode(entityName)) { - return entityName; // Indexed accesses can never be `typeof` + // some items filtered, others not - update the export declaration + // (mutating because why not, we're building a whole new tree here anyway) + exportDecl.exportClause!.elements = createNodeArray(replacements); + } + } + return statements; + } + function mergeRedundantStatements(statements: Statement[]) { + statements = flattenExportAssignedNamespace(statements); + statements = mergeExportDeclarations(statements); + statements = inlineExportModifiers(statements); + // Not a cleanup, but as a final step: If there is a mix of `export` and non-`export` declarations, but no `export =` or `export {}` add a `export {};` so + // declaration privacy is respected. + if (enclosingDeclaration && + ((isSourceFile(enclosingDeclaration) && isExternalOrCommonJsModule(enclosingDeclaration)) || isModuleDeclaration(enclosingDeclaration)) && + (!some(statements, isExternalModuleIndicator) || (!hasScopeMarker(statements) && some(statements, needsScopeMarker)))) { + statements.push(createEmptyExports()); + } + return statements; + } + function canHaveExportModifier(node: Statement) { + return isEnumDeclaration(node) || + isVariableStatement(node) || + isFunctionDeclaration(node) || + isClassDeclaration(node) || + (isModuleDeclaration(node) && !isExternalModuleAugmentation(node) && !isGlobalScopeAugmentation(node)) || + isInterfaceDeclaration(node) || + isTypeDeclaration(node); + } + function addExportModifier(statement: Statement) { + const flags = (getModifierFlags(statement) | ModifierFlags.Export) & ~ModifierFlags.Ambient; + statement.modifiers = createNodeArray(createModifiersFromModifierFlags(flags)); + statement.modifierFlagsCache = 0; + } + function visitSymbolTable(symbolTable: SymbolTable, suppressNewPrivateContext?: boolean, propertyAsAlias?: boolean) { + const oldDeferredPrivates = deferredPrivates; + if (!suppressNewPrivateContext) { + deferredPrivates = createMap(); + } + symbolTable.forEach((symbol: ts.Symbol) => { + serializeSymbol(symbol, /*isPrivate*/ false, !!propertyAsAlias); + }); + if (!suppressNewPrivateContext) { + // deferredPrivates will be filled up by visiting the symbol table + // And will continue to iterate as elements are added while visited `deferredPrivates` + // (As that's how a map iterator is defined to work) + deferredPrivates!.forEach((symbol: ts.Symbol) => { + serializeSymbol(symbol, /*isPrivate*/ true, !!propertyAsAlias); + }); } - if (isTypeOf) { - return createTypeQueryNode(entityName); + deferredPrivates = oldDeferredPrivates; + } + function serializeSymbol(symbol: ts.Symbol, isPrivate: boolean, propertyAsAlias: boolean) { + // cache visited list based on merged symbol, since we want to use the unmerged top-level symbol, but + // still skip reserializing it if we encounter the merged product later on + const visitedSym = getMergedSymbol(symbol); + if (visitedSymbols.has("" + getSymbolId(visitedSym))) { + return; // Already printed + } + visitedSymbols.set("" + getSymbolId(visitedSym), true); + // Only actually serialize symbols within the correct enclosing declaration, otherwise do nothing with the out-of-context symbol + const skipMembershipCheck = !isPrivate; // We only call this on exported symbols when we know they're in the correct scope + if (skipMembershipCheck || (!!length(symbol.declarations) && some(symbol.declarations, d => !!findAncestor(d, n => n === enclosingDeclaration)))) { + const oldContext = context; + context = cloneNodeBuilderContext(context); + const result = serializeSymbolWorker(symbol, isPrivate, propertyAsAlias); + context = oldContext; + return result; } - else { - const lastId = isIdentifier(entityName) ? entityName : entityName.right; - const lastTypeArgs = lastId.typeArguments; - lastId.typeArguments = undefined; - return createTypeReferenceNode(entityName, lastTypeArgs as NodeArray); + } + // Synthesize declarations for a symbol - might be an Interface, a Class, a Namespace, a Type, a Variable (const, let, or var), an Alias + // or a merge of some number of those. + // An interesting challenge is ensuring that when classes merge with namespaces and interfaces, is keeping + // each symbol in only one of the representations + // Also, synthesizing a default export of some kind + // If it's an alias: emit `export default ref` + // If it's a property: emit `export default _default` with a `_default` prop + // If it's a class/interface/function: emit a class/interface/function with a `default` modifier + // These forms can merge, eg (`export default 12; export default interface A {}`) + function serializeSymbolWorker(symbol: ts.Symbol, isPrivate: boolean, propertyAsAlias: boolean) { + const symbolName = unescapeLeadingUnderscores(symbol.escapedName); + const isDefault = symbol.escapedName === InternalSymbolName.Default; + if (isStringANonContextualKeyword(symbolName) && !isDefault) { + // Oh no. We cannot use this symbol's name as it's name... It's likely some jsdoc had an invalid name like `export` or `default` :( + context.encounteredError = true; + // TODO: Issue error via symbol tracker? + return; // If we need to emit a private with a keyword name, we're done for, since something else will try to refer to it by that name + } + const needsPostExportDefault = isDefault && !!(symbol.flags & SymbolFlags.ExportDoesNotSupportDefaultModifier + || (symbol.flags & SymbolFlags.Function && length(getPropertiesOfType(getTypeOfSymbol(symbol))))) && !(symbol.flags & SymbolFlags.Alias); // An alias symbol should preclude needing to make an alias ourselves + if (needsPostExportDefault) { + isPrivate = true; + } + const modifierFlags = (!isPrivate ? ModifierFlags.Export : 0) | (isDefault && !needsPostExportDefault ? ModifierFlags.Default : 0); + const isConstMergedWithNS = symbol.flags & SymbolFlags.Module && + symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property) && + symbol.escapedName !== InternalSymbolName.ExportEquals; + const isConstMergedWithNSPrintableAsSignatureMerge = isConstMergedWithNS && isTypeRepresentableAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol); + if (symbol.flags & SymbolFlags.Function || isConstMergedWithNSPrintableAsSignatureMerge) { + serializeAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); + } + if (symbol.flags & SymbolFlags.TypeAlias) { + serializeTypeAlias(symbol, symbolName, modifierFlags); + } + // Need to skip over export= symbols below - json source files get a single `Property` flagged + // symbol of name `export=` which needs to be handled like an alias. It's not great, but it is what it is. + if (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property) + && symbol.escapedName !== InternalSymbolName.ExportEquals + && !(symbol.flags & SymbolFlags.Prototype) + && !(symbol.flags & SymbolFlags.Class) + && !isConstMergedWithNSPrintableAsSignatureMerge) { + serializeVariableOrProperty(symbol, symbolName, isPrivate, needsPostExportDefault, propertyAsAlias, modifierFlags); + } + if (symbol.flags & SymbolFlags.Enum) { + serializeEnum(symbol, symbolName, modifierFlags); } - - function createAccessFromSymbolChain(chain: Symbol[], index: number, stopper: number): EntityName | IndexedAccessTypeNode { - const typeParameterNodes = index === (chain.length - 1) ? overrideTypeArguments : lookupTypeParameterNodes(chain, index, context); - const symbol = chain[index]; - - const parent = chain[index - 1]; - let symbolName: string | undefined; - if (index === 0) { - context.flags |= NodeBuilderFlags.InInitialEntityName; - symbolName = getNameOfSymbolAsWritten(symbol, context); - context.approximateLength += (symbolName ? symbolName.length : 0) + 1; - context.flags ^= NodeBuilderFlags.InInitialEntityName; + if (symbol.flags & SymbolFlags.Class) { + if (symbol.flags & SymbolFlags.Property) { + // Looks like a `module.exports.Sub = class {}` - if we serialize `symbol` as a class, the result will have no members, + // since the classiness is actually from the target of the effective alias the symbol is. yes. A BlockScopedVariable|Class|Property + // _really_ acts like an Alias, and none of a BlockScopedVariable, Class, or Property. This is the travesty of JS binding today. + serializeAsAlias(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); } else { - if (parent && getExportsOfSymbol(parent)) { - const exports = getExportsOfSymbol(parent); - forEachEntry(exports, (ex, name) => { - if (getSymbolIfSameReference(ex, symbol) && !isLateBoundName(name) && name !== InternalSymbolName.ExportEquals) { - symbolName = unescapeLeadingUnderscores(name); - return true; - } - }); - } - } - if (!symbolName) { - symbolName = getNameOfSymbolAsWritten(symbol, context); - } - context.approximateLength += symbolName.length + 1; - - if (!(context.flags & NodeBuilderFlags.ForbidIndexedAccessSymbolReferences) && parent && - getMembersOfSymbol(parent) && getMembersOfSymbol(parent).get(symbol.escapedName) && - getSymbolIfSameReference(getMembersOfSymbol(parent).get(symbol.escapedName)!, symbol)) { - // Should use an indexed access - const LHS = createAccessFromSymbolChain(chain, index - 1, stopper); - if (isIndexedAccessTypeNode(LHS)) { - return createIndexedAccessTypeNode(LHS, createLiteralTypeNode(createLiteral(symbolName))); - } - else { - return createIndexedAccessTypeNode(createTypeReferenceNode(LHS, typeParameterNodes as readonly TypeNode[]), createLiteralTypeNode(createLiteral(symbolName))); - } - } - - const identifier = setEmitFlags(createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); - identifier.symbol = symbol; - - if (index > stopper) { - const LHS = createAccessFromSymbolChain(chain, index - 1, stopper); - if (!isEntityName(LHS)) { - return Debug.fail("Impossible construct - an export of an indexed access cannot be reachable"); - } - return createQualifiedName(LHS, identifier); + serializeAsClass(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); } - return identifier; } - } - - function typeParameterShadowsNameInScope(escapedName: __String, context: NodeBuilderContext) { - return !!resolveName(context.enclosingDeclaration, escapedName, SymbolFlags.Type, /*nameNotFoundArg*/ undefined, escapedName, /*isUse*/ false); - } - - function typeParameterToName(type: TypeParameter, context: NodeBuilderContext) { - if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && context.typeParameterNames) { - const cached = context.typeParameterNames.get("" + getTypeId(type)); - if (cached) { - return cached; - } + if ((symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) && (!isConstMergedWithNS || isTypeOnlyNamespace(symbol))) || isConstMergedWithNSPrintableAsSignatureMerge) { + serializeModule(symbol, symbolName, modifierFlags); } - let result = symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ true); - if (!(result.kind & SyntaxKind.Identifier)) { - return createIdentifier("(Missing type parameter)"); + if (symbol.flags & SymbolFlags.Interface) { + serializeInterface(symbol, symbolName, modifierFlags); } - if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) { - const rawtext = result.escapedText as string; - let i = 0; - let text = rawtext; - while ((context.typeParameterNamesByText && context.typeParameterNamesByText.get(text)) || typeParameterShadowsNameInScope(text as __String, context)) { - i++; - text = `${rawtext}_${i}`; - } - if (text !== rawtext) { - result = createIdentifier(text, result.typeArguments); + if (symbol.flags & SymbolFlags.Alias) { + serializeAsAlias(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); + } + if (symbol.flags & SymbolFlags.Property && symbol.escapedName === InternalSymbolName.ExportEquals) { + serializeMaybeAliasAssignment(symbol); + } + if (symbol.flags & SymbolFlags.ExportStar) { + // synthesize export * from "moduleReference" + // Straightforward - only one thing to do - make an export declaration + for (const node of symbol.declarations) { + const resolvedModule = resolveExternalModuleName(node, ((node as ExportDeclaration).moduleSpecifier!)); + if (!resolvedModule) + continue; + addResult(createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*exportClause*/ undefined, createLiteral(getSpecifierForModuleSymbol(resolvedModule, context))), ModifierFlags.None); } - (context.typeParameterNames || (context.typeParameterNames = createMap())).set("" + getTypeId(type), result); - (context.typeParameterNamesByText || (context.typeParameterNamesByText = createMap())).set(result.escapedText as string, true); } - return result; + if (needsPostExportDefault) { + addResult(createExportAssignment(/*decorators*/ undefined, /*modifiers*/ undefined, /*isExportAssignment*/ false, createIdentifier(getInternalSymbolName(symbol, symbolName))), ModifierFlags.None); + } } - - function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: true): Identifier; - function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: false): EntityName; - function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: boolean): EntityName { - const chain = lookupSymbolChain(symbol, context, meaning); - - if (expectsIdentifier && chain.length !== 1 - && !context.encounteredError - && !(context.flags & NodeBuilderFlags.AllowQualifedNameInPlaceOfIdentifier)) { - context.encounteredError = true; + function includePrivateSymbol(symbol: ts.Symbol) { + if (some(symbol.declarations, isParameterDeclaration)) + return; + Debug.assertDefined(deferredPrivates); + getUnusedName(unescapeLeadingUnderscores(symbol.escapedName), symbol); // Call to cache unique name for symbol + deferredPrivates!.set("" + getSymbolId(symbol), symbol); + } + function isExportingScope(enclosingDeclaration: Node) { + return ((isSourceFile(enclosingDeclaration) && (isExternalOrCommonJsModule(enclosingDeclaration) || isJsonSourceFile(enclosingDeclaration))) || + (isAmbientModule(enclosingDeclaration) && !isGlobalScopeAugmentation(enclosingDeclaration))); + } + // Prepends a `declare` and/or `export` modifier if the context requires it, and then adds `node` to `result` and returns `node` + // Note: This _mutates_ `node` without using `updateNode` - the assumption being that all nodes should be manufactured fresh by the node builder + function addResult(node: Statement, additionalModifierFlags: ModifierFlags) { + let newModifierFlags: ModifierFlags = ModifierFlags.None; + if (additionalModifierFlags & ModifierFlags.Export && + enclosingDeclaration && + isExportingScope(enclosingDeclaration) && + canHaveExportModifier(node)) { + // Classes, namespaces, variables, functions, interfaces, and types should all be `export`ed in a module context if not private + newModifierFlags |= ModifierFlags.Export; + } + if (addingDeclare && !(newModifierFlags & ModifierFlags.Export) && + (!enclosingDeclaration || !(enclosingDeclaration.flags & NodeFlags.Ambient)) && + (isEnumDeclaration(node) || isVariableStatement(node) || isFunctionDeclaration(node) || isClassDeclaration(node) || isModuleDeclaration(node))) { + // Classes, namespaces, variables, enums, and functions all need `declare` modifiers to be valid in a declaration file top-level scope + newModifierFlags |= ModifierFlags.Ambient; + } + if ((additionalModifierFlags & ModifierFlags.Default) && (isClassDeclaration(node) || isInterfaceDeclaration(node) || isFunctionDeclaration(node))) { + newModifierFlags |= ModifierFlags.Default; + } + if (newModifierFlags) { + node.modifiers = createNodeArray(createModifiersFromModifierFlags(newModifierFlags | getModifierFlags(node))); + node.modifierFlagsCache = 0; // Reset computed flags cache + } + results.push(node); + } + function serializeTypeAlias(symbol: ts.Symbol, symbolName: string, modifierFlags: ModifierFlags) { + const aliasType = getDeclaredTypeOfTypeAlias(symbol); + const typeParams = getSymbolLinks(symbol).typeParameters; + const typeParamDecls = map(typeParams, p => typeParameterToDeclaration(p, context)); + const jsdocAliasDecl = find(symbol.declarations, isJSDocTypeAlias); + const commentText = jsdocAliasDecl ? jsdocAliasDecl.comment || jsdocAliasDecl.parent.comment : undefined; + const oldFlags = context.flags; + context.flags |= NodeBuilderFlags.InTypeAlias; + addResult(setSyntheticLeadingComments(createTypeAliasDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, getInternalSymbolName(symbol, symbolName), typeParamDecls, typeToTypeNodeHelper(aliasType, context)), !commentText ? [] : [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "*\n * " + commentText.replace(/\n/g, "\n * ") + "\n ", pos: -1, end: -1, hasTrailingNewLine: true }]), modifierFlags); + context.flags = oldFlags; + } + function serializeInterface(symbol: ts.Symbol, symbolName: string, modifierFlags: ModifierFlags) { + const interfaceType = getDeclaredTypeOfClassOrInterface(symbol); + const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); + const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context)); + const baseTypes = getBaseTypes(interfaceType); + const baseType = length(baseTypes) ? getIntersectionType(baseTypes) : undefined; + const members = flatMap(getPropertiesOfType(interfaceType), p => serializePropertySymbolForInterface(p, baseType)); + const callSignatures = (serializeSignatures(SignatureKind.Call, interfaceType, baseType, SyntaxKind.CallSignature) as CallSignatureDeclaration[]); + const constructSignatures = (serializeSignatures(SignatureKind.Construct, interfaceType, baseType, SyntaxKind.ConstructSignature) as ConstructSignatureDeclaration[]); + const indexSignatures = serializeIndexSignatures(interfaceType, baseType); + const heritageClauses = !length(baseTypes) ? undefined : [createHeritageClause(SyntaxKind.ExtendsKeyword, mapDefined(baseTypes, b => trySerializeAsTypeReference(b)))]; + addResult(createInterfaceDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, getInternalSymbolName(symbol, symbolName), typeParamDecls, heritageClauses, [...indexSignatures, ...constructSignatures, ...callSignatures, ...members]), modifierFlags); + } + function getNamespaceMembersForSerialization(symbol: ts.Symbol) { + return !symbol.exports ? [] : filter(arrayFrom((symbol.exports).values()), p => !((p.flags & SymbolFlags.Prototype) || (p.escapedName === "prototype"))); + } + function isTypeOnlyNamespace(symbol: ts.Symbol) { + return every(getNamespaceMembersForSerialization(symbol), m => !(resolveSymbol(m).flags & SymbolFlags.Value)); + } + function serializeModule(symbol: ts.Symbol, symbolName: string, modifierFlags: ModifierFlags) { + const members = getNamespaceMembersForSerialization(symbol); + // Split NS members up by declaration - members whose parent symbol is the ns symbol vs those whose is not (but were added in later via merging) + const locationMap = arrayToMultiMap(members, m => m.parent && m.parent === symbol ? "real" : "merged"); + const realMembers = locationMap.get("real") || emptyArray; + const mergedMembers = locationMap.get("merged") || emptyArray; + // TODO: `suppressNewPrivateContext` is questionable -we need to simply be emitting privates in whatever scope they were declared in, rather + // than whatever scope we traverse to them in. That's a bit of a complex rewrite, since we're not _actually_ tracking privates at all in advance, + // so we don't even have placeholders to fill in. + if (length(realMembers)) { + const localName = getInternalSymbolName(symbol, symbolName); + serializeAsNamespaceDeclaration(realMembers, localName, modifierFlags, !!(symbol.flags & (SymbolFlags.Function | SymbolFlags.Assignment))); + } + if (length(mergedMembers)) { + const localName = getInternalSymbolName(symbol, symbolName); + const nsBody = createModuleBlock([createExportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, createNamedExports(map(filter(mergedMembers, n => n.escapedName !== InternalSymbolName.ExportEquals), s => { + const name = unescapeLeadingUnderscores(s.escapedName); + const localName = getInternalSymbolName(s, name); + const aliasDecl = s.declarations && getDeclarationOfAliasSymbol(s); + const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true); + includePrivateSymbol(target || s); + const targetName = target ? getInternalSymbolName(target, unescapeLeadingUnderscores(target.escapedName)) : localName; + return createExportSpecifier(name === targetName ? undefined : targetName, name); + })))]); + addResult(createModuleDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, createIdentifier(localName), nsBody, NodeFlags.Namespace), ModifierFlags.None); + } + } + function serializeEnum(symbol: ts.Symbol, symbolName: string, modifierFlags: ModifierFlags) { + addResult(createEnumDeclaration( + /*decorators*/ undefined, createModifiersFromModifierFlags(isConstEnumSymbol(symbol) ? ModifierFlags.Const : 0), getInternalSymbolName(symbol, symbolName), map(filter(getPropertiesOfType(getTypeOfSymbol(symbol)), p => !!(p.flags & SymbolFlags.EnumMember)), p => { + // TODO: Handle computed names + // I hate that to get the initialized value we need to walk back to the declarations here; but there's no + // other way to get the possible const value of an enum member that I'm aware of, as the value is cached + // _on the declaration_, not on the declaration's symbol... + const initializedValue = p.declarations && p.declarations[0] && isEnumMember(p.declarations[0]) && getConstantValue((p.declarations[0] as EnumMember)); + return createEnumMember(unescapeLeadingUnderscores(p.escapedName), initializedValue === undefined ? undefined : createLiteral(initializedValue)); + })), modifierFlags); + } + function serializeVariableOrProperty(symbol: ts.Symbol, symbolName: string, isPrivate: boolean, needsPostExportDefault: boolean, propertyAsAlias: boolean | undefined, modifierFlags: ModifierFlags) { + if (propertyAsAlias) { + serializeMaybeAliasAssignment(symbol); } - return createEntityNameFromSymbolChain(chain, chain.length - 1); - - function createEntityNameFromSymbolChain(chain: Symbol[], index: number): EntityName { - const typeParameterNodes = lookupTypeParameterNodes(chain, index, context); - const symbol = chain[index]; - - if (index === 0) { - context.flags |= NodeBuilderFlags.InInitialEntityName; - } - const symbolName = getNameOfSymbolAsWritten(symbol, context); - if (index === 0) { - context.flags ^= NodeBuilderFlags.InInitialEntityName; + else { + const type = getTypeOfSymbol(symbol); + const localName = getInternalSymbolName(symbol, symbolName); + if (!(symbol.flags & SymbolFlags.Function) && isTypeRepresentableAsFunctionNamespaceMerge(type, symbol)) { + // If the type looks like a function declaration + ns could represent it, and it's type is sourced locally, rewrite it into a function declaration + ns + serializeAsFunctionNamespaceMerge(type, symbol, localName, modifierFlags); } - - const identifier = setEmitFlags(createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); - identifier.symbol = symbol; - - return index > 0 ? createQualifiedName(createEntityNameFromSymbolChain(chain, index - 1), identifier) : identifier; + else { + // A Class + Property merge is made for a `module.exports.Member = class {}`, and it doesn't serialize well as either a class _or_ a property symbol - in fact, _it behaves like an alias!_ + // `var` is `FunctionScopedVariable`, `const` and `let` are `BlockScopedVariable`, and `module.exports.thing =` is `Property` + const flags = !(symbol.flags & SymbolFlags.BlockScopedVariable) ? undefined + : isConstVariable(symbol) ? NodeFlags.Const + : NodeFlags.Let; + const name = (needsPostExportDefault || !(symbol.flags & SymbolFlags.Property)) ? localName : getUnusedName(localName, symbol); + let textRange: Node | undefined = symbol.declarations && find(symbol.declarations, d => isVariableDeclaration(d)); + if (textRange && isVariableDeclarationList(textRange.parent) && textRange.parent.declarations.length === 1) { + textRange = textRange.parent.parent; + } + const statement = setTextRange(createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([ + createVariableDeclaration(name, serializeTypeForDeclaration(type, symbol)) + ], flags)), textRange); + addResult(statement, name !== localName ? modifierFlags & ~ModifierFlags.Export : modifierFlags); + if (name !== localName && !isPrivate) { + // We rename the variable declaration we generate for Property symbols since they may have a name which + // conflicts with a local declaration. For example, given input: + // ``` + // function g() {} + // module.exports.g = g + // ``` + // In such a situation, we have a local variable named `g`, and a seperate exported variable named `g`. + // Naively, we would emit + // ``` + // function g() {} + // export const g: typeof g; + // ``` + // That's obviously incorrect - the `g` in the type annotation needs to refer to the local `g`, but + // the export declaration shadows it. + // To work around that, we instead write + // ``` + // function g() {} + // const g_1: typeof g; + // export { g_1 as g }; + // ``` + // To create an export named `g` that does _not_ shadow the local `g` + addResult(createExportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, createNamedExports([createExportSpecifier(name, localName)])), ModifierFlags.None); + } + } + } + } + function serializeAsFunctionNamespaceMerge(type: ts.Type, symbol: ts.Symbol, localName: string, modifierFlags: ModifierFlags) { + const signatures = getSignaturesOfType(type, SignatureKind.Call); + for (const sig of signatures) { + // Each overload becomes a separate function declaration, in order + const decl = (signatureToSignatureDeclarationHelper(sig, SyntaxKind.FunctionDeclaration, context) as FunctionDeclaration); + decl.name = createIdentifier(localName); + addResult(setTextRange(decl, sig.declaration), modifierFlags); + } + // Module symbol emit will take care of module-y members, provided it has exports + if (!(symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) && !!symbol.exports && !!symbol.exports.size)) { + const props = filter(getPropertiesOfType(type), p => !((p.flags & SymbolFlags.Prototype) || (p.escapedName === "prototype"))); + serializeAsNamespaceDeclaration(props, localName, modifierFlags, /*suppressNewPrivateContext*/ true); + } + } + function serializeAsNamespaceDeclaration(props: readonly ts.Symbol[], localName: string, modifierFlags: ModifierFlags, suppressNewPrivateContext: boolean) { + if (length(props)) { + const localVsRemoteMap = arrayToMultiMap(props, p => !length(p.declarations) || some(p.declarations, d => getSourceFileOfNode(d) === getSourceFileOfNode((context.enclosingDeclaration!))) ? "local" : "remote"); + const localProps = localVsRemoteMap.get("local") || emptyArray; + // handle remote props first - we need to make an `import` declaration that points at the module containing each remote + // prop in the outermost scope (TODO: a namespace within a namespace would need to be appropriately handled by this) + // Example: + // import Foo_1 = require("./exporter"); + // export namespace ns { + // import Foo = Foo_1.Foo; + // export { Foo }; + // export const c: number; + // } + // This is needed because in JS, statements like `const x = require("./f")` support both type and value lookup, even if they're + // normally just value lookup (so it functions kinda like an alias even when it's not an alias) + // _Usually_, we'll simply print the top-level as an alias instead of a `var` in such situations, however is is theoretically + // possible to encounter a situation where a type has members from both the current file and other files - in those situations, + // emit akin to the above would be needed. + // Add a namespace + const fakespace = createModuleDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createIdentifier(localName), createModuleBlock([]), NodeFlags.Namespace); + fakespace.flags ^= NodeFlags.Synthesized; // unset synthesized so it is usable as an enclosing declaration + fakespace.parent = (enclosingDeclaration as SourceFile | NamespaceDeclaration); + fakespace.locals = createSymbolTable(props); + fakespace.symbol = props[0].parent!; + const oldResults = results; + results = []; + const oldAddingDeclare = addingDeclare; + addingDeclare = false; + const subcontext = { ...context, enclosingDeclaration: fakespace }; + const oldContext = context; + context = subcontext; + // TODO: implement handling for the localVsRemoteMap.get("remote") - should be difficult to trigger (see comment above), as only interesting cross-file js merges should make this possible + visitSymbolTable(createSymbolTable(localProps), suppressNewPrivateContext, /*propertyAsAlias*/ true); + context = oldContext; + addingDeclare = oldAddingDeclare; + const declarations = results; + results = oldResults; + fakespace.flags ^= NodeFlags.Synthesized; // reset synthesized + fakespace.parent = undefined!; + fakespace.locals = undefined!; + fakespace.symbol = undefined!; + fakespace.body = createModuleBlock(declarations); + addResult(fakespace, modifierFlags); // namespaces can never be default exported + } + } + function serializeAsClass(symbol: ts.Symbol, localName: string, modifierFlags: ModifierFlags) { + const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); + const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context)); + const classType = getDeclaredTypeOfClassOrInterface(symbol); + const baseTypes = getBaseTypes(classType); + const staticType = getTypeOfSymbol(symbol); + const staticBaseType = getBaseConstructorTypeOfClass((staticType as InterfaceType)); + const heritageClauses = !length(baseTypes) ? undefined : [createHeritageClause(SyntaxKind.ExtendsKeyword, map(baseTypes, b => serializeBaseType(b, staticBaseType, localName)))]; + const members = flatMap(getPropertiesOfType(classType), p => serializePropertySymbolForClass(p, /*isStatic*/ false, baseTypes[0])); + // Consider static members empty if symbol also has function or module meaning - function namespacey emit will handle statics + const staticMembers = symbol.flags & (SymbolFlags.Function | SymbolFlags.ValueModule) + ? [] + : flatMap(filter(getPropertiesOfType(staticType), p => !(p.flags & SymbolFlags.Prototype) && p.escapedName !== "prototype"), p => serializePropertySymbolForClass(p, /*isStatic*/ true, staticBaseType)); + const constructors = (serializeSignatures(SignatureKind.Construct, staticType, baseTypes[0], SyntaxKind.Constructor) as ConstructorDeclaration[]); + for (const c of constructors) { + // A constructor's return type and type parameters are supposed to be controlled by the enclosing class declaration + // `signatureToSignatureDeclarationHelper` appends them regardless, so for now we delete them here + c.type = undefined; + c.typeParameters = undefined; + } + const indexSignatures = serializeIndexSignatures(classType, baseTypes[0]); + addResult(setTextRange(createClassDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, localName, typeParamDecls, heritageClauses, [...indexSignatures, ...staticMembers, ...constructors, ...members]), symbol.declarations && filter(symbol.declarations, d => isClassDeclaration(d) || isClassExpression(d))[0]), modifierFlags); + } + function serializeAsAlias(symbol: ts.Symbol, localName: string, modifierFlags: ModifierFlags) { + // synthesize an alias, eg `export { symbolName as Name }` + // need to mark the alias `symbol` points + // at as something we need to serialize as a private declaration as well + const node = getDeclarationOfAliasSymbol(symbol); + if (!node) + return Debug.fail(); + const target = getMergedSymbol(getTargetOfAliasDeclaration(node, /*dontRecursivelyResolve*/ true)); + if (!target) { + return; + } + let verbatimTargetName = unescapeLeadingUnderscores(target.escapedName); + if (verbatimTargetName === InternalSymbolName.ExportEquals && (compilerOptions.esModuleInterop || compilerOptions.allowSyntheticDefaultImports)) { + // target refers to an `export=` symbol that was hoisted into a synthetic default - rename here to match + verbatimTargetName = InternalSymbolName.Default; + } + const targetName = getInternalSymbolName(target, verbatimTargetName); + includePrivateSymbol(target); // the target may be within the same scope - attempt to serialize it first + switch (node.kind) { + case SyntaxKind.ImportEqualsDeclaration: + // Could be a local `import localName = ns.member` or + // an external `import localName = require("whatever")` + const isLocalImport = !(target.flags & SymbolFlags.ValueModule); + addResult(createImportEqualsDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, createIdentifier(localName), isLocalImport + ? symbolToName(target, context, SymbolFlags.All, /*expectsIdentifier*/ false) + : createExternalModuleReference(createLiteral(getSpecifierForModuleSymbol(symbol, context)))), isLocalImport ? modifierFlags : ModifierFlags.None); + break; + case SyntaxKind.NamespaceExportDeclaration: + // export as namespace foo + // TODO: Not part of a file's local or export symbol tables + // Is bound into file.symbol.globalExports instead, which we don't currently traverse + addResult(createNamespaceExportDeclaration(idText((node as NamespaceExportDeclaration).name)), ModifierFlags.None); + break; + case SyntaxKind.ImportClause: + addResult(createImportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, createImportClause(createIdentifier(localName), /*namedBindings*/ undefined), + // We use `target.parent || target` below as `target.parent` is unset when the target is a module which has been export assigned + // And then made into a default by the `esModuleInterop` or `allowSyntheticDefaultImports` flag + // In such cases, the `target` refers to the module itself already + createLiteral(getSpecifierForModuleSymbol(target.parent || target, context))), ModifierFlags.None); + break; + case SyntaxKind.NamespaceImport: + addResult(createImportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, createImportClause(/*importClause*/ undefined, createNamespaceImport(createIdentifier(localName))), createLiteral(getSpecifierForModuleSymbol(target, context))), ModifierFlags.None); + break; + case SyntaxKind.ImportSpecifier: + addResult(createImportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, createImportClause(/*importClause*/ undefined, createNamedImports([ + createImportSpecifier(localName !== verbatimTargetName ? createIdentifier(verbatimTargetName) : undefined, createIdentifier(localName)) + ])), createLiteral(getSpecifierForModuleSymbol(target.parent || target, context))), ModifierFlags.None); + break; + case SyntaxKind.ExportSpecifier: + // does not use localName because the symbol name in this case refers to the name in the exports table, + // which we must exactly preserve + const specifier = (node.parent.parent as ExportDeclaration).moduleSpecifier; + // targetName is only used when the target is local, as otherwise the target is an alias that points at + // another file + serializeExportSpecifier(unescapeLeadingUnderscores(symbol.escapedName), specifier ? verbatimTargetName : targetName, specifier && isStringLiteralLike(specifier) ? createLiteral(specifier.text) : undefined); + break; + case SyntaxKind.ExportAssignment: + serializeMaybeAliasAssignment(symbol); + break; + case SyntaxKind.BinaryExpression: + case SyntaxKind.PropertyAccessExpression: + // Could be best encoded as though an export specifier or as though an export assignment + // If name is default or export=, do an export assignment + // Otherwise do an export specifier + if (symbol.escapedName === InternalSymbolName.Default || symbol.escapedName === InternalSymbolName.ExportEquals) { + serializeMaybeAliasAssignment(symbol); + } + else { + serializeExportSpecifier(localName, targetName); + } + break; + default: + return Debug.failBadSyntaxKind(node, "Unhandled alias declaration kind in symbol serializer!"); } } - - function symbolToExpression(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags) { - const chain = lookupSymbolChain(symbol, context, meaning); - - return createExpressionFromSymbolChain(chain, chain.length - 1); - - function createExpressionFromSymbolChain(chain: Symbol[], index: number): Expression { - const typeParameterNodes = lookupTypeParameterNodes(chain, index, context); - const symbol = chain[index]; - - if (index === 0) { - context.flags |= NodeBuilderFlags.InInitialEntityName; - } - let symbolName = getNameOfSymbolAsWritten(symbol, context); - if (index === 0) { - context.flags ^= NodeBuilderFlags.InInitialEntityName; - } - let firstChar = symbolName.charCodeAt(0); - if (isSingleOrDoubleQuote(firstChar) && some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { - return createLiteral(getSpecifierForModuleSymbol(symbol, context)); - } - const canUsePropertyAccess = isIdentifierStart(firstChar, languageVersion); - if (index === 0 || canUsePropertyAccess) { - const identifier = setEmitFlags(createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); - identifier.symbol = symbol; - - return index > 0 ? createPropertyAccess(createExpressionFromSymbolChain(chain, index - 1), identifier) : identifier; + function serializeExportSpecifier(localName: string, targetName: string, specifier?: Expression) { + addResult(createExportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, createNamedExports([createExportSpecifier(localName !== targetName ? targetName : undefined, localName)]), specifier), ModifierFlags.None); + } + function serializeMaybeAliasAssignment(symbol: ts.Symbol) { + if (symbol.flags & SymbolFlags.Prototype) { + return; + } + const name = unescapeLeadingUnderscores(symbol.escapedName); + const isExportEquals = name === InternalSymbolName.ExportEquals; + const isDefault = name === InternalSymbolName.Default; + const isExportAssignment = isExportEquals || isDefault; + // synthesize export = ref + // ref should refer to either be a locally scoped symbol which we need to emit, or + // a reference to another namespace/module which we may need to emit an `import` statement for + const aliasDecl = symbol.declarations && getDeclarationOfAliasSymbol(symbol); + // serialize what the alias points to, preserve the declaration's initializer + const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true); + // If the target resolves and resolves to a thing defined in this file, emit as an alias, otherwise emit as a const + if (target && length(target.declarations) && some(target.declarations, d => getSourceFileOfNode(d) === getSourceFileOfNode(enclosingDeclaration))) { + // In case `target` refers to a namespace member, look at the declaration and serialize the leftmost symbol in it + // eg, `namespace A { export class B {} }; exports = A.B;` + // Technically, this is all that's required in the case where the assignment is an entity name expression + const expr = isExportAssignment ? getExportAssignmentExpression((aliasDecl as ExportAssignment | BinaryExpression)) : getPropertyAssignmentAliasLikeExpression((aliasDecl as ShorthandPropertyAssignment | PropertyAssignment | PropertyAccessExpression)); + const first = isEntityNameExpression(expr) ? getFirstNonModuleExportsIdentifier(expr) : undefined; + const referenced = first && resolveEntityName(first, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, enclosingDeclaration); + if (referenced || target) { + includePrivateSymbol(referenced || target); + } + // We disable the context's symbol traker for the duration of this name serialization + // as, by virtue of being here, the name is required to print something, and we don't want to + // issue a visibility error on it. Only anonymous classes that an alias points at _would_ issue + // a visibility error here (as they're not visible within any scope), but we want to hoist them + // into the containing scope anyway, so we want to skip the visibility checks. + const oldTrack = context.tracker.trackSymbol; + context.tracker.trackSymbol = noop; + if (isExportAssignment) { + results.push(createExportAssignment( + /*decorators*/ undefined, + /*modifiers*/ undefined, isExportEquals, symbolToExpression(target, context, SymbolFlags.All))); } else { - if (firstChar === CharacterCodes.openBracket) { - symbolName = symbolName.substring(1, symbolName.length - 1); - firstChar = symbolName.charCodeAt(0); + if (first === expr) { + // serialize as `export {target as name}` + serializeExportSpecifier(name, idText(first)); } - let expression: Expression | undefined; - if (isSingleOrDoubleQuote(firstChar)) { - expression = createLiteral(symbolName.substring(1, symbolName.length - 1).replace(/\\./g, s => s.substring(1))); - (expression as StringLiteral).singleQuote = firstChar === CharacterCodes.singleQuote; + else if (isClassExpression(expr)) { + serializeExportSpecifier(name, getInternalSymbolName(target, symbolName(target))); } - else if (("" + +symbolName) === symbolName) { - expression = createLiteral(+symbolName); - } - if (!expression) { - expression = setEmitFlags(createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); - expression.symbol = symbol; + else { + // serialize as `import _Ref = t.arg.et; export { _Ref as name }` + const varName = getUnusedName(name, symbol); + addResult(createImportEqualsDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, createIdentifier(varName), symbolToName(target, context, SymbolFlags.All, /*expectsIdentifier*/ false)), ModifierFlags.None); + serializeExportSpecifier(name, varName); } - return createElementAccess(createExpressionFromSymbolChain(chain, index - 1), expression); } + context.tracker.trackSymbol = oldTrack; } - } - - function isSingleQuotedStringNamed(d: Declaration) { - const name = getNameOfDeclaration(d); - if (name && isStringLiteral(name) && ( - name.singleQuote || - (!nodeIsSynthesized(name) && startsWith(getTextOfNode(name, /*includeTrivia*/ false), "'")) - )) { - return true; + else { + // serialize as an anonymous property declaration + const varName = getUnusedName(name, symbol); + // We have to use `getWidenedType` here since the object within a json file is unwidened within the file + // (Unwidened types can only exist in expression contexts and should never be serialized) + const typeToSerialize = getWidenedType(getTypeOfSymbol(symbol)); + if (isTypeRepresentableAsFunctionNamespaceMerge(typeToSerialize, symbol)) { + // If there are no index signatures and `typeToSerialize` is an object type, emit as a namespace instead of a const + serializeAsFunctionNamespaceMerge(typeToSerialize, symbol, varName, isExportAssignment ? ModifierFlags.None : ModifierFlags.Export); + } + else { + const statement = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([ + createVariableDeclaration(varName, serializeTypeForDeclaration(typeToSerialize, symbol)) + ], NodeFlags.Const)); + addResult(statement, name === varName ? ModifierFlags.Export : ModifierFlags.None); + } + if (isExportAssignment) { + results.push(createExportAssignment( + /*decorators*/ undefined, + /*modifiers*/ undefined, isExportEquals, createIdentifier(varName))); + } + else if (name !== varName) { + serializeExportSpecifier(name, varName); + } } - return false; } - - function getPropertyNameNodeForSymbol(symbol: Symbol, context: NodeBuilderContext) { - const singleQuote = !!length(symbol.declarations) && every(symbol.declarations, isSingleQuotedStringNamed); - const fromNameType = getPropertyNameNodeForSymbolFromNameType(symbol, context, singleQuote); - if (fromNameType) { - return fromNameType; - } - if (isKnownSymbol(symbol)) { - return createComputedPropertyName(createPropertyAccess(createIdentifier("Symbol"), (symbol.escapedName as string).substr(3))); - } - const rawName = unescapeLeadingUnderscores(symbol.escapedName); - return createPropertyNameNodeForIdentifierOrLiteral(rawName, singleQuote); + function isTypeRepresentableAsFunctionNamespaceMerge(typeToSerialize: ts.Type, hostSymbol: ts.Symbol) { + // Only object types which are not constructable, or indexable, whose members all come from the + // context source file, and whose property names are all valid identifiers and not late-bound, _and_ + // whose input is not type annotated (if the input symbol has an annotation we can reuse, we should prefer it) + const ctxSrc = getSourceFileOfNode(context.enclosingDeclaration); + return getObjectFlags(typeToSerialize) & (ObjectFlags.Anonymous | ObjectFlags.Mapped) && + !getIndexInfoOfType(typeToSerialize, IndexKind.String) && + !getIndexInfoOfType(typeToSerialize, IndexKind.Number) && + !!(length(getPropertiesOfType(typeToSerialize)) || length(getSignaturesOfType(typeToSerialize, SignatureKind.Call))) && + !length(getSignaturesOfType(typeToSerialize, SignatureKind.Construct)) && // TODO: could probably serialize as function + ns + class, now that that's OK + !getDeclarationWithTypeAnnotation(hostSymbol) && + !(typeToSerialize.symbol && some(typeToSerialize.symbol.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) && + !some(getPropertiesOfType(typeToSerialize), p => isLateBoundName(p.escapedName)) && + !some(getPropertiesOfType(typeToSerialize), p => some(p.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) && + every(getPropertiesOfType(typeToSerialize), p => isIdentifierText(symbolName(p), languageVersion) && !isStringAKeyword(symbolName(p))); } - - // See getNameForSymbolFromNameType for a stringy equivalent - function getPropertyNameNodeForSymbolFromNameType(symbol: Symbol, context: NodeBuilderContext, singleQuote?: boolean) { - const nameType = symbol.nameType; - if (nameType) { - if (nameType.flags & TypeFlags.StringOrNumberLiteral) { - const name = "" + (nameType).value; - if (!isIdentifierText(name, compilerOptions.target) && !isNumericLiteralName(name)) { - return createLiteral(name, !!singleQuote); - } - if (isNumericLiteralName(name) && startsWith(name, "-")) { - return createComputedPropertyName(createLiteral(+name)); + function makeSerializePropertySymbol(createProperty: (decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | PropertyName, questionOrExclamationToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined) => T, methodKind: SyntaxKind, useAccessors: true): (p: ts.Symbol, isStatic: boolean, baseType: ts.Type | undefined) => (T | AccessorDeclaration | (T | AccessorDeclaration)[]); + function makeSerializePropertySymbol(createProperty: (decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | PropertyName, questionOrExclamationToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined) => T, methodKind: SyntaxKind, useAccessors: false): (p: ts.Symbol, isStatic: boolean, baseType: ts.Type | undefined) => (T | T[]); + function makeSerializePropertySymbol(createProperty: (decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | PropertyName, questionOrExclamationToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined) => T, methodKind: SyntaxKind, useAccessors: boolean): (p: ts.Symbol, isStatic: boolean, baseType: ts.Type | undefined) => (T | AccessorDeclaration | (T | AccessorDeclaration)[]) { + return function serializePropertySymbol(p: ts.Symbol, isStatic: boolean, baseType: ts.Type | undefined) { + if (isStatic && (p.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias))) { + // Only value-only-meaning symbols can be correctly encoded as class statics, type/namespace/alias meaning symbols + // need to be merged namespace members + return []; + } + if (p.flags & SymbolFlags.Prototype || (baseType && getPropertyOfType(baseType, p.escapedName) + && isReadonlySymbol(getPropertyOfType(baseType, p.escapedName)!) === isReadonlySymbol(p) + && (p.flags & SymbolFlags.Optional) === (getPropertyOfType(baseType, p.escapedName)!.flags & SymbolFlags.Optional) + && isTypeIdenticalTo(getTypeOfSymbol(p), getTypeOfPropertyOfType(baseType, p.escapedName)!))) { + return []; + } + const staticFlag = isStatic ? ModifierFlags.Static : 0; + const name = getPropertyNameNodeForSymbol(p, context); + const firstPropertyLikeDecl = find(p.declarations, or(isPropertyDeclaration, isAccessor, isVariableDeclaration, isPropertySignature, isBinaryExpression, isPropertyAccessExpression)); + if (p.flags & SymbolFlags.Accessor && useAccessors) { + const result: AccessorDeclaration[] = []; + if (p.flags & SymbolFlags.SetAccessor) { + result.push(setTextRange(createSetAccessor( + /*decorators*/ undefined, createModifiersFromModifierFlags(staticFlag), name, [createParameter( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, "arg", + /*questionToken*/ undefined, serializeTypeForDeclaration(getTypeOfSymbol(p), p))], + /*body*/ undefined), find(p.declarations, isSetAccessor) || firstPropertyLikeDecl)); + } + if (p.flags & SymbolFlags.GetAccessor) { + result.push(setTextRange(createGetAccessor( + /*decorators*/ undefined, createModifiersFromModifierFlags(staticFlag), name, [], serializeTypeForDeclaration(getTypeOfSymbol(p), p), + /*body*/ undefined), find(p.declarations, isGetAccessor) || firstPropertyLikeDecl)); } - return createPropertyNameNodeForIdentifierOrLiteral(name); + return result; } - if (nameType.flags & TypeFlags.UniqueESSymbol) { - return createComputedPropertyName(symbolToExpression((nameType).symbol, context, SymbolFlags.Value)); + // This is an else/if as accessors and properties can't merge in TS, but might in JS + // If this happens, we assume the accessor takes priority, as it imposes more constraints + else if (p.flags & (SymbolFlags.Property | SymbolFlags.Variable)) { + return setTextRange(createProperty( + /*decorators*/ undefined, createModifiersFromModifierFlags((isReadonlySymbol(p) ? ModifierFlags.Readonly : 0) | staticFlag), name, p.flags & SymbolFlags.Optional ? createToken(SyntaxKind.QuestionToken) : undefined, serializeTypeForDeclaration(getTypeOfSymbol(p), p), + // TODO: https://github.com/microsoft/TypeScript/pull/32372#discussion_r328386357 + // interface members can't have initializers, however class members _can_ + /*initializer*/ undefined), find(p.declarations, or(isPropertyDeclaration, isVariableDeclaration)) || firstPropertyLikeDecl); + } + if (p.flags & (SymbolFlags.Method | SymbolFlags.Function)) { + const type = getTypeOfSymbol(p); + const signatures = getSignaturesOfType(type, SignatureKind.Call); + const results = []; + for (const sig of signatures) { + // Each overload becomes a separate method declaration, in order + const decl = (signatureToSignatureDeclarationHelper(sig, methodKind, context) as MethodDeclaration); + decl.name = name; // TODO: Clone + if (staticFlag) { + decl.modifiers = createNodeArray(createModifiersFromModifierFlags(staticFlag)); + } + if (p.flags & SymbolFlags.Optional) { + decl.questionToken = createToken(SyntaxKind.QuestionToken); + } + results.push(setTextRange(decl, sig.declaration)); + } + return results as unknown as T[]; } - } + // The `Constructor`'s symbol isn't in the class's properties lists, obviously, since it's a signature on the static + return Debug.fail(`Unhandled class member kind! ${(p as any).__debugFlags || p.flags}`); + }; } - - function createPropertyNameNodeForIdentifierOrLiteral(name: string, singleQuote?: boolean) { - return isIdentifierText(name, compilerOptions.target) ? createIdentifier(name) : createLiteral(isNumericLiteralName(name) ? +name : name, !!singleQuote); + function serializePropertySymbolForInterface(p: ts.Symbol, baseType: ts.Type | undefined) { + return serializePropertySymbolForInterfaceWorker(p, /*isStatic*/ false, baseType); } - - function cloneNodeBuilderContext(context: NodeBuilderContext): NodeBuilderContext { - const initial: NodeBuilderContext = { ...context }; - // Make type parameters created within this context not consume the name outside this context - // The symbol serializer ends up creating many sibling scopes that all need "separate" contexts when - // it comes to naming things - within a normal `typeToTypeNode` call, the node builder only ever descends - // through the type tree, so the only cases where we could have used distinct sibling scopes was when there - // were multiple generic overloads with similar generated type parameter names - // The effect: - // When we write out - // export const x: (x: T) => T - // export const y: (x: T) => T - // we write it out like that, rather than as - // export const x: (x: T) => T - // export const y: (x: T_1) => T_1 - if (initial.typeParameterNames) { - initial.typeParameterNames = cloneMap(initial.typeParameterNames); - } - if (initial.typeParameterNamesByText) { - initial.typeParameterNamesByText = cloneMap(initial.typeParameterNamesByText); - } - if (initial.typeParameterSymbolList) { - initial.typeParameterSymbolList = cloneMap(initial.typeParameterSymbolList); - } - return initial; + function getDeclarationWithTypeAnnotation(symbol: ts.Symbol) { + return find(symbol.declarations, s => !!getEffectiveTypeAnnotationNode(s) && !!findAncestor(s, n => n === enclosingDeclaration)); } - - function symbolTableToDeclarationStatements(symbolTable: SymbolTable, context: NodeBuilderContext, bundled?: boolean): Statement[] { - const serializePropertySymbolForClass = makeSerializePropertySymbol(createProperty, SyntaxKind.MethodDeclaration, /*useAcessors*/ true); - const serializePropertySymbolForInterfaceWorker = makeSerializePropertySymbol((_decorators, mods, name, question, type, initializer) => createPropertySignature(mods, name, question, type, initializer), SyntaxKind.MethodSignature, /*useAcessors*/ false); - - // TODO: Use `setOriginalNode` on original declaration names where possible so these declarations see some kind of - // declaration mapping - - // We save the enclosing declaration off here so it's not adjusted by well-meaning declaration - // emit codepaths which want to apply more specific contexts (so we can still refer to the root real declaration - // we're trying to emit from later on) - const enclosingDeclaration = context.enclosingDeclaration!; - let results: Statement[] = []; - const visitedSymbols: Map = createMap(); - let deferredPrivates: Map | undefined; - const oldcontext = context; - context = { - ...oldcontext, - usedSymbolNames: createMap(), - remappedSymbolNames: createMap(), - tracker: { - ...oldcontext.tracker, - trackSymbol: (sym, decl, meaning) => { - const accessibleResult = isSymbolAccessible(sym, decl, meaning, /*computeALiases*/ false); - if (accessibleResult.accessibility === SymbolAccessibility.Accessible) { - // Lookup the root symbol of the chain of refs we'll use to access it and serialize it - const chain = lookupSymbolChainWorker(sym, context, meaning); - if (!(sym.flags & SymbolFlags.Property)) { - includePrivateSymbol(chain[0]); + /** + * Unlike `typeToTypeNodeHelper`, this handles setting up the `AllowUniqueESSymbolType` flag + * so a `unique symbol` is returned when appropriate for the input symbol, rather than `typeof sym` + */ + function serializeTypeForDeclaration(type: ts.Type, symbol: ts.Symbol) { + const declWithExistingAnnotation = getDeclarationWithTypeAnnotation(symbol); + if (declWithExistingAnnotation && !isFunctionLikeDeclaration(declWithExistingAnnotation)) { + // try to reuse the existing annotation + const existing = (getEffectiveTypeAnnotationNode(declWithExistingAnnotation)!); + const transformed = visitNode(existing, visitExistingNodeTreeSymbols); + return transformed === existing ? getMutableClone(existing) : transformed; + } + const oldFlags = context.flags; + if (type.flags & TypeFlags.UniqueESSymbol && + type.symbol === symbol) { + context.flags |= NodeBuilderFlags.AllowUniqueESSymbolType; + } + const result = typeToTypeNodeHelper(type, context); + context.flags = oldFlags; + return result; + function visitExistingNodeTreeSymbols(node: T): Node { + if (isJSDocAllType(node)) { + return createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + if (isJSDocUnknownType(node)) { + return createKeywordTypeNode(SyntaxKind.UnknownKeyword); + } + if (isJSDocNullableType(node)) { + return createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols), createKeywordTypeNode(SyntaxKind.NullKeyword)]); + } + if (isJSDocOptionalType(node)) { + return createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols), createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); + } + if (isJSDocNonNullableType(node)) { + return visitNode(node.type, visitExistingNodeTreeSymbols); + } + if ((isExpressionWithTypeArguments(node) || isTypeReferenceNode(node)) && isJSDocIndexSignature(node)) { + return createTypeLiteralNode([createIndexSignature( + /*decorators*/ undefined, + /*modifiers*/ undefined, [createParameter( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotdotdotToken*/ undefined, "x", + /*questionToken*/ undefined, visitNode(node.typeArguments![0], visitExistingNodeTreeSymbols))], visitNode(node.typeArguments![1], visitExistingNodeTreeSymbols))]); + } + if (isJSDocFunctionType(node)) { + if (isJSDocConstructSignature(node)) { + let newTypeNode: TypeNode | undefined; + return createConstructorTypeNode(visitNodes(node.typeParameters, visitExistingNodeTreeSymbols), mapDefined(node.parameters, (p, i) => p.name && isIdentifier(p.name) && p.name.escapedText === "new" ? (newTypeNode = p.type, undefined) : createParameter( + /*decorators*/ undefined, + /*modifiers*/ undefined, p.dotDotDotToken, p.name || p.dotDotDotToken ? `args` : `arg${i}`, p.questionToken, visitNode(p.type, visitExistingNodeTreeSymbols), + /*initializer*/ undefined)), visitNode(newTypeNode || node.type, visitExistingNodeTreeSymbols)); + } + else { + return createFunctionTypeNode(visitNodes(node.typeParameters, visitExistingNodeTreeSymbols), map(node.parameters, (p, i) => createParameter( + /*decorators*/ undefined, + /*modifiers*/ undefined, p.dotDotDotToken, p.name || p.dotDotDotToken ? `args` : `arg${i}`, p.questionToken, visitNode(p.type, visitExistingNodeTreeSymbols), + /*initializer*/ undefined)), visitNode(node.type, visitExistingNodeTreeSymbols)); + } + } + if (isLiteralImportTypeNode(node)) { + return updateImportTypeNode(node, updateLiteralTypeNode(node.argument, rewriteModuleSpecifier(node, node.argument.literal)), node.qualifier, visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode), node.isTypeOf); + } + if (isEntityName(node) || isEntityNameExpression(node)) { + const leftmost = getFirstIdentifier(node); + const sym = resolveEntityName(leftmost, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveALias*/ true); + if (sym) { + includePrivateSymbol(sym); + if (isIdentifier(node) && sym.flags & SymbolFlags.TypeParameter) { + const name = typeParameterToName(getDeclaredTypeOfSymbol(sym), context); + if (idText(name) !== idText(node)) { + return name; } - } - else if (oldcontext.tracker && oldcontext.tracker.trackSymbol) { - oldcontext.tracker.trackSymbol(sym, decl, meaning); + return node; } } } - }; - if (oldcontext.usedSymbolNames) { - oldcontext.usedSymbolNames.forEach((_, name) => { - context.usedSymbolNames!.set(name, true); - }); - } - forEachEntry(symbolTable, (symbol, name) => { - const baseName = unescapeLeadingUnderscores(name); - void getInternalSymbolName(symbol, baseName); // Called to cache values into `usedSymbolNames` and `remappedSymbolNames` - }); - let addingDeclare = !bundled; - const exportEquals = symbolTable.get(InternalSymbolName.ExportEquals); - if (exportEquals && symbolTable.size > 1 && exportEquals.flags & SymbolFlags.Alias) { - symbolTable = createSymbolTable(); - // Remove extraneous elements from root symbol table (they'll be mixed back in when the target of the `export=` is looked up) - symbolTable.set(InternalSymbolName.ExportEquals, exportEquals); - } - - visitSymbolTable(symbolTable); - return mergeRedundantStatements(results); - - function isIdentifierAndNotUndefined(node: Node | undefined): node is Identifier { - return !!node && node.kind === SyntaxKind.Identifier; - } - - function getNamesOfDeclaration(statement: Statement): Identifier[] { - if (isVariableStatement(statement)) { - return filter(map(statement.declarationList.declarations, getNameOfDeclaration), isIdentifierAndNotUndefined); - } - return filter([getNameOfDeclaration(statement as DeclarationStatement)], isIdentifierAndNotUndefined); - } - - function flattenExportAssignedNamespace(statements: Statement[]) { - const exportAssignment = find(statements, isExportAssignment); - const ns = find(statements, isModuleDeclaration); - if (ns && exportAssignment && exportAssignment.isExportEquals && - isIdentifier(exportAssignment.expression) && isIdentifier(ns.name) && idText(ns.name) === idText(exportAssignment.expression) && - ns.body && isModuleBlock(ns.body)) { - // Pass 0: Correct situations where a module has both an `export = ns` and multiple top-level exports by stripping the export modifiers from - // the top-level exports and exporting them in the targeted ns, as can occur when a js file has both typedefs and `module.export` assignments - const excessExports = filter(statements, s => !!(getModifierFlags(s) & ModifierFlags.Export)); - if (length(excessExports)) { - ns.body.statements = createNodeArray([...ns.body.statements, createExportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - createNamedExports(map(flatMap(excessExports, e => getNamesOfDeclaration(e)), id => createExportSpecifier(/*alias*/ undefined, id))), - /*moduleSpecifier*/ undefined - )]); + return visitEachChild(node, visitExistingNodeTreeSymbols, nullTransformationContext); + } + function rewriteModuleSpecifier(parent: ImportTypeNode, lit: StringLiteral) { + if (bundled) { + if (context.tracker && context.tracker.moduleResolverHost) { + const targetFile = getExternalModuleFileFromDeclaration(parent); + if (targetFile) { + const getCanonicalFileName = createGetCanonicalFileName(!!host.useCaseSensitiveFileNames); + const resolverHost = { + getCanonicalFileName, + getCurrentDirectory: context.tracker.moduleResolverHost.getCurrentDirectory ? () => context.tracker.moduleResolverHost!.getCurrentDirectory!() : () => "", + getCommonSourceDirectory: () => context.tracker.moduleResolverHost!.getCommonSourceDirectory() + }; + const newName = getResolvedExternalModuleName(resolverHost, targetFile); + return createLiteral(newName); + } } - - - // Pass 1: Flatten `export namespace _exports {} export = _exports;` so long as the `export=` only points at a single namespace declaration - if (!find(statements, s => s !== ns && nodeHasName(s, ns.name as Identifier))) { - results = []; - forEach(ns.body.statements, s => { - addResult(s, ModifierFlags.None); // Recalculates the ambient (and export, if applicable from above) flag - }); - statements = [...filter(statements, s => s !== ns && s !== exportAssignment), ...results]; + } + else { + if (context.tracker && context.tracker.trackExternalModuleSymbolOfImportTypeNode) { + const moduleSym = resolveExternalModuleNameWorker(lit, lit, /*moduleNotFoundError*/ undefined); + if (moduleSym) { + context.tracker.trackExternalModuleSymbolOfImportTypeNode(moduleSym); + } } } - return statements; + return lit; } - - function mergeExportDeclarations(statements: Statement[]) { - // Pass 2: Combine all `export {}` declarations - const exports = filter(statements, d => isExportDeclaration(d) && !d.moduleSpecifier && !!d.exportClause) as ExportDeclaration[]; - if (length(exports) > 1) { - const nonExports = filter(statements, d => !isExportDeclaration(d) || !!d.moduleSpecifier || !d.exportClause); - statements = [...nonExports, createExportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - createNamedExports(flatMap(exports, e => e.exportClause!.elements)), - /*moduleSpecifier*/ undefined - )]; - } - // Pass 2b: Also combine all `export {} from "..."` declarations as needed - const reexports = filter(statements, d => isExportDeclaration(d) && !!d.moduleSpecifier && !!d.exportClause) as ExportDeclaration[]; - if (length(reexports) > 1) { - const groups = group(reexports, decl => isStringLiteral(decl.moduleSpecifier!) ? ">" + decl.moduleSpecifier.text : ">"); - if (groups.length !== reexports.length) { - for (const group of groups) { - if (group.length > 1) { - // remove group members from statements and then merge group members and add back to statements - statements = [ - ...filter(statements, s => group.indexOf(s as ExportDeclaration) === -1), - createExportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - createNamedExports(flatMap(group, e => e.exportClause!.elements)), - group[0].moduleSpecifier - ) - ]; + } + function serializeSignatures(kind: SignatureKind, input: ts.Type, baseType: ts.Type | undefined, outputKind: SyntaxKind) { + const signatures = getSignaturesOfType(input, kind); + if (kind === SignatureKind.Construct) { + if (!baseType && every(signatures, s => length(s.parameters) === 0)) { + return []; // No base type, every constructor is empty - elide the extraneous `constructor()` + } + if (baseType) { + // If there is a base type, if every signature in the class is identical to a signature in the baseType, elide all the declarations + const baseSigs = getSignaturesOfType(baseType, SignatureKind.Construct); + if (!length(baseSigs) && every(signatures, s => length(s.parameters) === 0)) { + return []; // Base had no explicit signatures, if all our signatures are also implicit, return an empty list + } + if (baseSigs.length === signatures.length) { + let failed = false; + for (let i = 0; i < baseSigs.length; i++) { + if (!compareSignaturesIdentical(signatures[i], baseSigs[i], /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true, compareTypesIdentical)) { + failed = true; + break; } } + if (!failed) { + return []; // Every signature was identical - elide constructor list as it is inherited + } } } - return statements; } - - function inlineExportModifiers(statements: Statement[]) { - // Pass 3: Move all `export {}`'s to `export` modifiers where possible - const exportDecl = find(statements, d => isExportDeclaration(d) && !d.moduleSpecifier && !!d.exportClause) as ExportDeclaration | undefined; - if (exportDecl) { - const replacements = mapDefined(exportDecl.exportClause!.elements, e => { - if (!e.propertyName) { - // export {name} - look thru `statements` for `name`, and if all results can take an `export` modifier, do so and filter it - const associated = filter(statements, s => nodeHasName(s, e.name)); - if (length(associated) && every(associated, canHaveExportModifier)) { - forEach(associated, addExportModifier); - return undefined; + const results = []; + for (const sig of signatures) { + // Each overload becomes a separate constructor declaration, in order + const decl = signatureToSignatureDeclarationHelper(sig, outputKind, context); + results.push(setTextRange(decl, sig.declaration)); + } + return results; + } + function serializeIndexSignatures(input: ts.Type, baseType: ts.Type | undefined) { + const results: IndexSignatureDeclaration[] = []; + for (const type of [IndexKind.String, IndexKind.Number]) { + const info = getIndexInfoOfType(input, type); + if (info) { + if (baseType) { + const baseInfo = getIndexInfoOfType(baseType, type); + if (baseInfo) { + if (isTypeIdenticalTo(info.type, baseInfo.type)) { + continue; // elide identical index signatures } } - return e; - }); - if (!length(replacements)) { - // all clauses removed, filter the export declaration - statements = filter(statements, s => s !== exportDecl); - } - else { - // some items filtered, others not - update the export declaration - // (mutating because why not, we're building a whole new tree here anyway) - exportDecl.exportClause!.elements = createNodeArray(replacements); } + results.push(indexInfoToIndexSignatureDeclarationHelper(info, type, context)); } - return statements; - } - - function mergeRedundantStatements(statements: Statement[]) { - statements = flattenExportAssignedNamespace(statements); - statements = mergeExportDeclarations(statements); - statements = inlineExportModifiers(statements); - - // Not a cleanup, but as a final step: If there is a mix of `export` and non-`export` declarations, but no `export =` or `export {}` add a `export {};` so - // declaration privacy is respected. - if (enclosingDeclaration && - ((isSourceFile(enclosingDeclaration) && isExternalOrCommonJsModule(enclosingDeclaration)) || isModuleDeclaration(enclosingDeclaration)) && - (!some(statements, isExternalModuleIndicator) || (!hasScopeMarker(statements) && some(statements, needsScopeMarker)))) { - statements.push(createEmptyExports()); - } - return statements; } - - function canHaveExportModifier(node: Statement) { - return isEnumDeclaration(node) || - isVariableStatement(node) || - isFunctionDeclaration(node) || - isClassDeclaration(node) || - (isModuleDeclaration(node) && !isExternalModuleAugmentation(node) && !isGlobalScopeAugmentation(node)) || - isInterfaceDeclaration(node) || - isTypeDeclaration(node); + return results; + } + function serializeBaseType(t: ts.Type, staticType: ts.Type, rootName: string) { + const ref = trySerializeAsTypeReference(t); + if (ref) { + return ref; + } + const tempName = getUnusedName(`${rootName}_base`); + const statement = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([ + createVariableDeclaration(tempName, typeToTypeNodeHelper(staticType, context)) + ], NodeFlags.Const)); + addResult(statement, ModifierFlags.None); + return createExpressionWithTypeArguments(/*typeArgs*/ undefined, createIdentifier(tempName)); + } + function trySerializeAsTypeReference(t: ts.Type) { + let typeArgs: TypeNode[] | undefined; + let reference: Expression | undefined; + // We don't use `isValueSymbolAccessible` below. since that considers alternative containers (like modules) + // which we can't write out in a syntactically valid way as an expression + if ((t as TypeReference).target && getAccessibleSymbolChain((t as TypeReference).target.symbol, enclosingDeclaration, SymbolFlags.Value, /*useOnlyExternalAliasing*/ false)) { + typeArgs = map(getTypeArguments((t as TypeReference)), t => typeToTypeNodeHelper(t, context)); + reference = symbolToExpression((t as TypeReference).target.symbol, context, SymbolFlags.Type); + } + else if (t.symbol && getAccessibleSymbolChain(t.symbol, enclosingDeclaration, SymbolFlags.Value, /*useOnlyExternalAliasing*/ false)) { + reference = symbolToExpression(t.symbol, context, SymbolFlags.Type); } - - function addExportModifier(statement: Statement) { - const flags = (getModifierFlags(statement) | ModifierFlags.Export) & ~ModifierFlags.Ambient; - statement.modifiers = createNodeArray(createModifiersFromModifierFlags(flags)); - statement.modifierFlagsCache = 0; + if (reference) { + return createExpressionWithTypeArguments(typeArgs, reference); } - - function visitSymbolTable(symbolTable: SymbolTable, suppressNewPrivateContext?: boolean, propertyAsAlias?: boolean) { - const oldDeferredPrivates = deferredPrivates; - if (!suppressNewPrivateContext) { - deferredPrivates = createMap(); - } - symbolTable.forEach((symbol: Symbol) => { - serializeSymbol(symbol, /*isPrivate*/ false, !!propertyAsAlias); - }); - if (!suppressNewPrivateContext) { - // deferredPrivates will be filled up by visiting the symbol table - // And will continue to iterate as elements are added while visited `deferredPrivates` - // (As that's how a map iterator is defined to work) - deferredPrivates!.forEach((symbol: Symbol) => { - serializeSymbol(symbol, /*isPrivate*/ true, !!propertyAsAlias); - }); + } + function getUnusedName(input: string, symbol?: ts.Symbol): string { + if (symbol) { + if (context.remappedSymbolNames!.has("" + getSymbolId(symbol))) { + return context.remappedSymbolNames!.get("" + getSymbolId(symbol))!; } - deferredPrivates = oldDeferredPrivates; } - - function serializeSymbol(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean) { - // cache visited list based on merged symbol, since we want to use the unmerged top-level symbol, but - // still skip reserializing it if we encounter the merged product later on - const visitedSym = getMergedSymbol(symbol); - if (visitedSymbols.has("" + getSymbolId(visitedSym))) { - return; // Already printed - } - visitedSymbols.set("" + getSymbolId(visitedSym), true); - // Only actually serialize symbols within the correct enclosing declaration, otherwise do nothing with the out-of-context symbol - const skipMembershipCheck = !isPrivate; // We only call this on exported symbols when we know they're in the correct scope - if (skipMembershipCheck || (!!length(symbol.declarations) && some(symbol.declarations, d => !!findAncestor(d, n => n === enclosingDeclaration)))) { - const oldContext = context; - context = cloneNodeBuilderContext(context); - const result = serializeSymbolWorker(symbol, isPrivate, propertyAsAlias); - context = oldContext; - return result; - } + if (symbol) { + input = getNameCandidateWorker(symbol, input); } - - // Synthesize declarations for a symbol - might be an Interface, a Class, a Namespace, a Type, a Variable (const, let, or var), an Alias - // or a merge of some number of those. - // An interesting challenge is ensuring that when classes merge with namespaces and interfaces, is keeping - // each symbol in only one of the representations - // Also, synthesizing a default export of some kind - // If it's an alias: emit `export default ref` - // If it's a property: emit `export default _default` with a `_default` prop - // If it's a class/interface/function: emit a class/interface/function with a `default` modifier - // These forms can merge, eg (`export default 12; export default interface A {}`) - function serializeSymbolWorker(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean) { - const symbolName = unescapeLeadingUnderscores(symbol.escapedName); - const isDefault = symbol.escapedName === InternalSymbolName.Default; - if (isStringANonContextualKeyword(symbolName) && !isDefault) { - // Oh no. We cannot use this symbol's name as it's name... It's likely some jsdoc had an invalid name like `export` or `default` :( - context.encounteredError = true; - // TODO: Issue error via symbol tracker? - return; // If we need to emit a private with a keyword name, we're done for, since something else will try to refer to it by that name - } - const needsPostExportDefault = isDefault && !!( - symbol.flags & SymbolFlags.ExportDoesNotSupportDefaultModifier - || (symbol.flags & SymbolFlags.Function && length(getPropertiesOfType(getTypeOfSymbol(symbol)))) - ) && !(symbol.flags & SymbolFlags.Alias); // An alias symbol should preclude needing to make an alias ourselves - if (needsPostExportDefault) { - isPrivate = true; - } - const modifierFlags = (!isPrivate ? ModifierFlags.Export : 0) | (isDefault && !needsPostExportDefault ? ModifierFlags.Default : 0); - const isConstMergedWithNS = symbol.flags & SymbolFlags.Module && - symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property) && - symbol.escapedName !== InternalSymbolName.ExportEquals; - const isConstMergedWithNSPrintableAsSignatureMerge = isConstMergedWithNS && isTypeRepresentableAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol); - if (symbol.flags & SymbolFlags.Function || isConstMergedWithNSPrintableAsSignatureMerge) { - serializeAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); - } - if (symbol.flags & SymbolFlags.TypeAlias) { - serializeTypeAlias(symbol, symbolName, modifierFlags); - } - // Need to skip over export= symbols below - json source files get a single `Property` flagged - // symbol of name `export=` which needs to be handled like an alias. It's not great, but it is what it is. - if (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property) - && symbol.escapedName !== InternalSymbolName.ExportEquals - && !(symbol.flags & SymbolFlags.Prototype) - && !(symbol.flags & SymbolFlags.Class) - && !isConstMergedWithNSPrintableAsSignatureMerge) { - serializeVariableOrProperty(symbol, symbolName, isPrivate, needsPostExportDefault, propertyAsAlias, modifierFlags); - } - if (symbol.flags & SymbolFlags.Enum) { - serializeEnum(symbol, symbolName, modifierFlags); - } - if (symbol.flags & SymbolFlags.Class) { - if (symbol.flags & SymbolFlags.Property) { - // Looks like a `module.exports.Sub = class {}` - if we serialize `symbol` as a class, the result will have no members, - // since the classiness is actually from the target of the effective alias the symbol is. yes. A BlockScopedVariable|Class|Property - // _really_ acts like an Alias, and none of a BlockScopedVariable, Class, or Property. This is the travesty of JS binding today. - serializeAsAlias(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); - } - else { - serializeAsClass(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); - } - } - if ((symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) && (!isConstMergedWithNS || isTypeOnlyNamespace(symbol))) || isConstMergedWithNSPrintableAsSignatureMerge) { - serializeModule(symbol, symbolName, modifierFlags); - } - if (symbol.flags & SymbolFlags.Interface) { - serializeInterface(symbol, symbolName, modifierFlags); - } - if (symbol.flags & SymbolFlags.Alias) { - serializeAsAlias(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); - } - if (symbol.flags & SymbolFlags.Property && symbol.escapedName === InternalSymbolName.ExportEquals) { - serializeMaybeAliasAssignment(symbol); - } - if (symbol.flags & SymbolFlags.ExportStar) { - // synthesize export * from "moduleReference" - // Straightforward - only one thing to do - make an export declaration - for (const node of symbol.declarations) { - const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!); - if (!resolvedModule) continue; - addResult(createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*exportClause*/ undefined, createLiteral(getSpecifierForModuleSymbol(resolvedModule, context))), ModifierFlags.None); - } - } - if (needsPostExportDefault) { - addResult(createExportAssignment(/*decorators*/ undefined, /*modifiers*/ undefined, /*isExportAssignment*/ false, createIdentifier(getInternalSymbolName(symbol, symbolName))), ModifierFlags.None); - } + let i = 0; + const original = input; + while (context.usedSymbolNames!.has(input)) { + i++; + input = `${original}_${i}`; } - - function includePrivateSymbol(symbol: Symbol) { - if (some(symbol.declarations, isParameterDeclaration)) return; - Debug.assertDefined(deferredPrivates); - getUnusedName(unescapeLeadingUnderscores(symbol.escapedName), symbol); // Call to cache unique name for symbol - deferredPrivates!.set("" + getSymbolId(symbol), symbol); + context.usedSymbolNames!.set(input, true); + if (symbol) { + context.remappedSymbolNames!.set("" + getSymbolId(symbol), input); } - - function isExportingScope(enclosingDeclaration: Node) { - return ((isSourceFile(enclosingDeclaration) && (isExternalOrCommonJsModule(enclosingDeclaration) || isJsonSourceFile(enclosingDeclaration))) || - (isAmbientModule(enclosingDeclaration) && !isGlobalScopeAugmentation(enclosingDeclaration))); + return input; + } + function getNameCandidateWorker(symbol: ts.Symbol, localName: string) { + if (localName === InternalSymbolName.Default || localName === InternalSymbolName.Class || localName === InternalSymbolName.Function) { + const flags = context.flags; + context.flags |= NodeBuilderFlags.InInitialEntityName; + const nameCandidate = getNameOfSymbolAsWritten(symbol, context); + context.flags = flags; + localName = nameCandidate.length > 0 && isSingleOrDoubleQuote(nameCandidate.charCodeAt(0)) ? stripQuotes(nameCandidate) : nameCandidate; } - - // Prepends a `declare` and/or `export` modifier if the context requires it, and then adds `node` to `result` and returns `node` - // Note: This _mutates_ `node` without using `updateNode` - the assumption being that all nodes should be manufactured fresh by the node builder - function addResult(node: Statement, additionalModifierFlags: ModifierFlags) { - let newModifierFlags: ModifierFlags = ModifierFlags.None; - if (additionalModifierFlags & ModifierFlags.Export && - enclosingDeclaration && - isExportingScope(enclosingDeclaration) && - canHaveExportModifier(node) - ) { - // Classes, namespaces, variables, functions, interfaces, and types should all be `export`ed in a module context if not private - newModifierFlags |= ModifierFlags.Export; - } - if (addingDeclare && !(newModifierFlags & ModifierFlags.Export) && - (!enclosingDeclaration || !(enclosingDeclaration.flags & NodeFlags.Ambient)) && - (isEnumDeclaration(node) || isVariableStatement(node) || isFunctionDeclaration(node) || isClassDeclaration(node) || isModuleDeclaration(node))) { - // Classes, namespaces, variables, enums, and functions all need `declare` modifiers to be valid in a declaration file top-level scope - newModifierFlags |= ModifierFlags.Ambient; - } - if ((additionalModifierFlags & ModifierFlags.Default) && (isClassDeclaration(node) || isInterfaceDeclaration(node) || isFunctionDeclaration(node))) { - newModifierFlags |= ModifierFlags.Default; - } - if (newModifierFlags) { - node.modifiers = createNodeArray(createModifiersFromModifierFlags(newModifierFlags | getModifierFlags(node))); - node.modifierFlagsCache = 0; // Reset computed flags cache - } - results.push(node); + if (localName === InternalSymbolName.Default) { + localName = "_default"; } - - function serializeTypeAlias(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { - const aliasType = getDeclaredTypeOfTypeAlias(symbol); - const typeParams = getSymbolLinks(symbol).typeParameters; - const typeParamDecls = map(typeParams, p => typeParameterToDeclaration(p, context)); - const jsdocAliasDecl = find(symbol.declarations, isJSDocTypeAlias); - const commentText = jsdocAliasDecl ? jsdocAliasDecl.comment || jsdocAliasDecl.parent.comment : undefined; - const oldFlags = context.flags; - context.flags |= NodeBuilderFlags.InTypeAlias; - addResult(setSyntheticLeadingComments( - createTypeAliasDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, getInternalSymbolName(symbol, symbolName), typeParamDecls, typeToTypeNodeHelper(aliasType, context)), - !commentText ? [] : [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "*\n * " + commentText.replace(/\n/g, "\n * ") + "\n ", pos: -1, end: -1, hasTrailingNewLine: true }] - ), modifierFlags); - context.flags = oldFlags; - } - - function serializeInterface(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { - const interfaceType = getDeclaredTypeOfClassOrInterface(symbol); - const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); - const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context)); - const baseTypes = getBaseTypes(interfaceType); - const baseType = length(baseTypes) ? getIntersectionType(baseTypes) : undefined; - const members = flatMap(getPropertiesOfType(interfaceType), p => serializePropertySymbolForInterface(p, baseType)); - const callSignatures = serializeSignatures(SignatureKind.Call, interfaceType, baseType, SyntaxKind.CallSignature) as CallSignatureDeclaration[]; - const constructSignatures = serializeSignatures(SignatureKind.Construct, interfaceType, baseType, SyntaxKind.ConstructSignature) as ConstructSignatureDeclaration[]; - const indexSignatures = serializeIndexSignatures(interfaceType, baseType); - - const heritageClauses = !length(baseTypes) ? undefined : [createHeritageClause(SyntaxKind.ExtendsKeyword, mapDefined(baseTypes, b => trySerializeAsTypeReference(b)))]; - addResult(createInterfaceDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - getInternalSymbolName(symbol, symbolName), - typeParamDecls, - heritageClauses, - [...indexSignatures, ...constructSignatures, ...callSignatures, ...members] - ), modifierFlags); - } - - function getNamespaceMembersForSerialization(symbol: Symbol) { - return !symbol.exports ? [] : filter(arrayFrom((symbol.exports).values()), p => !((p.flags & SymbolFlags.Prototype) || (p.escapedName === "prototype"))); - } - - function isTypeOnlyNamespace(symbol: Symbol) { - return every(getNamespaceMembersForSerialization(symbol), m => !(resolveSymbol(m).flags & SymbolFlags.Value)); - } - - function serializeModule(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { - const members = getNamespaceMembersForSerialization(symbol); - // Split NS members up by declaration - members whose parent symbol is the ns symbol vs those whose is not (but were added in later via merging) - const locationMap = arrayToMultiMap(members, m => m.parent && m.parent === symbol ? "real" : "merged"); - const realMembers = locationMap.get("real") || emptyArray; - const mergedMembers = locationMap.get("merged") || emptyArray; - // TODO: `suppressNewPrivateContext` is questionable -we need to simply be emitting privates in whatever scope they were declared in, rather - // than whatever scope we traverse to them in. That's a bit of a complex rewrite, since we're not _actually_ tracking privates at all in advance, - // so we don't even have placeholders to fill in. - if (length(realMembers)) { - const localName = getInternalSymbolName(symbol, symbolName); - serializeAsNamespaceDeclaration(realMembers, localName, modifierFlags, !!(symbol.flags & (SymbolFlags.Function | SymbolFlags.Assignment))); - } - if (length(mergedMembers)) { - const localName = getInternalSymbolName(symbol, symbolName); - const nsBody = createModuleBlock([createExportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - createNamedExports(map(filter(mergedMembers, n => n.escapedName !== InternalSymbolName.ExportEquals), s => { - const name = unescapeLeadingUnderscores(s.escapedName); - const localName = getInternalSymbolName(s, name); - const aliasDecl = s.declarations && getDeclarationOfAliasSymbol(s); - const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true); - includePrivateSymbol(target || s); - const targetName = target ? getInternalSymbolName(target, unescapeLeadingUnderscores(target.escapedName)) : localName; - return createExportSpecifier(name === targetName ? undefined : targetName, name); - })) - )]); - addResult(createModuleDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - createIdentifier(localName), - nsBody, - NodeFlags.Namespace - ), ModifierFlags.None); - } - } - - function serializeEnum(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { - addResult(createEnumDeclaration( - /*decorators*/ undefined, - createModifiersFromModifierFlags(isConstEnumSymbol(symbol) ? ModifierFlags.Const : 0), - getInternalSymbolName(symbol, symbolName), - map(filter(getPropertiesOfType(getTypeOfSymbol(symbol)), p => !!(p.flags & SymbolFlags.EnumMember)), p => { - // TODO: Handle computed names - // I hate that to get the initialized value we need to walk back to the declarations here; but there's no - // other way to get the possible const value of an enum member that I'm aware of, as the value is cached - // _on the declaration_, not on the declaration's symbol... - const initializedValue = p.declarations && p.declarations[0] && isEnumMember(p.declarations[0]) && getConstantValue(p.declarations[0] as EnumMember); - return createEnumMember(unescapeLeadingUnderscores(p.escapedName), initializedValue === undefined ? undefined : createLiteral(initializedValue)); - }) - ), modifierFlags); - } - - function serializeVariableOrProperty(symbol: Symbol, symbolName: string, isPrivate: boolean, needsPostExportDefault: boolean, propertyAsAlias: boolean | undefined, modifierFlags: ModifierFlags) { - if (propertyAsAlias) { - serializeMaybeAliasAssignment(symbol); - } - else { - const type = getTypeOfSymbol(symbol); - const localName = getInternalSymbolName(symbol, symbolName); - if (!(symbol.flags & SymbolFlags.Function) && isTypeRepresentableAsFunctionNamespaceMerge(type, symbol)) { - // If the type looks like a function declaration + ns could represent it, and it's type is sourced locally, rewrite it into a function declaration + ns - serializeAsFunctionNamespaceMerge(type, symbol, localName, modifierFlags); - } - else { - // A Class + Property merge is made for a `module.exports.Member = class {}`, and it doesn't serialize well as either a class _or_ a property symbol - in fact, _it behaves like an alias!_ - // `var` is `FunctionScopedVariable`, `const` and `let` are `BlockScopedVariable`, and `module.exports.thing =` is `Property` - const flags = !(symbol.flags & SymbolFlags.BlockScopedVariable) ? undefined - : isConstVariable(symbol) ? NodeFlags.Const - : NodeFlags.Let; - const name = (needsPostExportDefault || !(symbol.flags & SymbolFlags.Property)) ? localName : getUnusedName(localName, symbol); - let textRange: Node | undefined = symbol.declarations && find(symbol.declarations, d => isVariableDeclaration(d)); - if (textRange && isVariableDeclarationList(textRange.parent) && textRange.parent.declarations.length === 1) { - textRange = textRange.parent.parent; - } - const statement = setTextRange(createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([ - createVariableDeclaration(name, serializeTypeForDeclaration(type, symbol)) - ], flags)), textRange); - addResult(statement, name !== localName ? modifierFlags & ~ModifierFlags.Export : modifierFlags); - if (name !== localName && !isPrivate) { - // We rename the variable declaration we generate for Property symbols since they may have a name which - // conflicts with a local declaration. For example, given input: - // ``` - // function g() {} - // module.exports.g = g - // ``` - // In such a situation, we have a local variable named `g`, and a seperate exported variable named `g`. - // Naively, we would emit - // ``` - // function g() {} - // export const g: typeof g; - // ``` - // That's obviously incorrect - the `g` in the type annotation needs to refer to the local `g`, but - // the export declaration shadows it. - // To work around that, we instead write - // ``` - // function g() {} - // const g_1: typeof g; - // export { g_1 as g }; - // ``` - // To create an export named `g` that does _not_ shadow the local `g` - addResult( - createExportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - createNamedExports([createExportSpecifier(name, localName)]) - ), - ModifierFlags.None - ); - } - } - } - } - - function serializeAsFunctionNamespaceMerge(type: Type, symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { - const signatures = getSignaturesOfType(type, SignatureKind.Call); - for (const sig of signatures) { - // Each overload becomes a separate function declaration, in order - const decl = signatureToSignatureDeclarationHelper(sig, SyntaxKind.FunctionDeclaration, context) as FunctionDeclaration; - decl.name = createIdentifier(localName); - addResult(setTextRange(decl, sig.declaration), modifierFlags); - } - // Module symbol emit will take care of module-y members, provided it has exports - if (!(symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) && !!symbol.exports && !!symbol.exports.size)) { - const props = filter(getPropertiesOfType(type), p => !((p.flags & SymbolFlags.Prototype) || (p.escapedName === "prototype"))); - serializeAsNamespaceDeclaration(props, localName, modifierFlags, /*suppressNewPrivateContext*/ true); - } - } - - function serializeAsNamespaceDeclaration(props: readonly Symbol[], localName: string, modifierFlags: ModifierFlags, suppressNewPrivateContext: boolean) { - if (length(props)) { - const localVsRemoteMap = arrayToMultiMap(props, p => - !length(p.declarations) || some(p.declarations, d => - getSourceFileOfNode(d) === getSourceFileOfNode(context.enclosingDeclaration!) - ) ? "local" : "remote" - ); - const localProps = localVsRemoteMap.get("local") || emptyArray; - // handle remote props first - we need to make an `import` declaration that points at the module containing each remote - // prop in the outermost scope (TODO: a namespace within a namespace would need to be appropriately handled by this) - // Example: - // import Foo_1 = require("./exporter"); - // export namespace ns { - // import Foo = Foo_1.Foo; - // export { Foo }; - // export const c: number; - // } - // This is needed because in JS, statements like `const x = require("./f")` support both type and value lookup, even if they're - // normally just value lookup (so it functions kinda like an alias even when it's not an alias) - // _Usually_, we'll simply print the top-level as an alias instead of a `var` in such situations, however is is theoretically - // possible to encounter a situation where a type has members from both the current file and other files - in those situations, - // emit akin to the above would be needed. - - // Add a namespace - const fakespace = createModuleDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createIdentifier(localName), createModuleBlock([]), NodeFlags.Namespace); - fakespace.flags ^= NodeFlags.Synthesized; // unset synthesized so it is usable as an enclosing declaration - fakespace.parent = enclosingDeclaration as SourceFile | NamespaceDeclaration; - fakespace.locals = createSymbolTable(props); - fakespace.symbol = props[0].parent!; - const oldResults = results; - results = []; - const oldAddingDeclare = addingDeclare; - addingDeclare = false; - const subcontext = { ...context, enclosingDeclaration: fakespace }; - const oldContext = context; - context = subcontext; - // TODO: implement handling for the localVsRemoteMap.get("remote") - should be difficult to trigger (see comment above), as only interesting cross-file js merges should make this possible - visitSymbolTable(createSymbolTable(localProps), suppressNewPrivateContext, /*propertyAsAlias*/ true); - context = oldContext; - addingDeclare = oldAddingDeclare; - const declarations = results; - results = oldResults; - fakespace.flags ^= NodeFlags.Synthesized; // reset synthesized - fakespace.parent = undefined!; - fakespace.locals = undefined!; - fakespace.symbol = undefined!; - fakespace.body = createModuleBlock(declarations); - addResult(fakespace, modifierFlags); // namespaces can never be default exported - } - } - - function serializeAsClass(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { - const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); - const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context)); - const classType = getDeclaredTypeOfClassOrInterface(symbol); - const baseTypes = getBaseTypes(classType); - const staticType = getTypeOfSymbol(symbol); - const staticBaseType = getBaseConstructorTypeOfClass(staticType as InterfaceType); - const heritageClauses = !length(baseTypes) ? undefined : [createHeritageClause(SyntaxKind.ExtendsKeyword, map(baseTypes, b => serializeBaseType(b, staticBaseType, localName)))]; - const members = flatMap(getPropertiesOfType(classType), p => serializePropertySymbolForClass(p, /*isStatic*/ false, baseTypes[0])); - // Consider static members empty if symbol also has function or module meaning - function namespacey emit will handle statics - const staticMembers = symbol.flags & (SymbolFlags.Function | SymbolFlags.ValueModule) - ? [] - : flatMap(filter( - getPropertiesOfType(staticType), - p => !(p.flags & SymbolFlags.Prototype) && p.escapedName !== "prototype" - ), p => serializePropertySymbolForClass(p, /*isStatic*/ true, staticBaseType)); - const constructors = serializeSignatures(SignatureKind.Construct, staticType, baseTypes[0], SyntaxKind.Constructor) as ConstructorDeclaration[]; - for (const c of constructors) { - // A constructor's return type and type parameters are supposed to be controlled by the enclosing class declaration - // `signatureToSignatureDeclarationHelper` appends them regardless, so for now we delete them here - c.type = undefined; - c.typeParameters = undefined; - } - const indexSignatures = serializeIndexSignatures(classType, baseTypes[0]); - addResult(setTextRange(createClassDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - localName, - typeParamDecls, - heritageClauses, - [...indexSignatures, ...staticMembers, ...constructors, ...members] - ), symbol.declarations && filter(symbol.declarations, d => isClassDeclaration(d) || isClassExpression(d))[0]), modifierFlags); - } - - function serializeAsAlias(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { - // synthesize an alias, eg `export { symbolName as Name }` - // need to mark the alias `symbol` points - // at as something we need to serialize as a private declaration as well - const node = getDeclarationOfAliasSymbol(symbol); - if (!node) return Debug.fail(); - const target = getMergedSymbol(getTargetOfAliasDeclaration(node, /*dontRecursivelyResolve*/ true)); - if (!target) { - return; - } - let verbatimTargetName = unescapeLeadingUnderscores(target.escapedName); - if (verbatimTargetName === InternalSymbolName.ExportEquals && (compilerOptions.esModuleInterop || compilerOptions.allowSyntheticDefaultImports)) { - // target refers to an `export=` symbol that was hoisted into a synthetic default - rename here to match - verbatimTargetName = InternalSymbolName.Default; - } - const targetName = getInternalSymbolName(target, verbatimTargetName); - includePrivateSymbol(target); // the target may be within the same scope - attempt to serialize it first - switch (node.kind) { - case SyntaxKind.ImportEqualsDeclaration: - // Could be a local `import localName = ns.member` or - // an external `import localName = require("whatever")` - const isLocalImport = !(target.flags & SymbolFlags.ValueModule); - addResult(createImportEqualsDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - createIdentifier(localName), - isLocalImport - ? symbolToName(target, context, SymbolFlags.All, /*expectsIdentifier*/ false) - : createExternalModuleReference(createLiteral(getSpecifierForModuleSymbol(symbol, context))) - ), isLocalImport ? modifierFlags : ModifierFlags.None); - break; - case SyntaxKind.NamespaceExportDeclaration: - // export as namespace foo - // TODO: Not part of a file's local or export symbol tables - // Is bound into file.symbol.globalExports instead, which we don't currently traverse - addResult(createNamespaceExportDeclaration(idText((node as NamespaceExportDeclaration).name)), ModifierFlags.None); - break; - case SyntaxKind.ImportClause: - addResult(createImportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - createImportClause(createIdentifier(localName), /*namedBindings*/ undefined), - // We use `target.parent || target` below as `target.parent` is unset when the target is a module which has been export assigned - // And then made into a default by the `esModuleInterop` or `allowSyntheticDefaultImports` flag - // In such cases, the `target` refers to the module itself already - createLiteral(getSpecifierForModuleSymbol(target.parent || target, context)) - ), ModifierFlags.None); - break; - case SyntaxKind.NamespaceImport: - addResult(createImportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - createImportClause(/*importClause*/ undefined, createNamespaceImport(createIdentifier(localName))), - createLiteral(getSpecifierForModuleSymbol(target, context)) - ), ModifierFlags.None); - break; - case SyntaxKind.ImportSpecifier: - addResult(createImportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - createImportClause(/*importClause*/ undefined, createNamedImports([ - createImportSpecifier( - localName !== verbatimTargetName ? createIdentifier(verbatimTargetName) : undefined, - createIdentifier(localName) - ) - ])), - createLiteral(getSpecifierForModuleSymbol(target.parent || target, context)) - ), ModifierFlags.None); - break; - case SyntaxKind.ExportSpecifier: - // does not use localName because the symbol name in this case refers to the name in the exports table, - // which we must exactly preserve - const specifier = (node.parent.parent as ExportDeclaration).moduleSpecifier; - // targetName is only used when the target is local, as otherwise the target is an alias that points at - // another file - serializeExportSpecifier( - unescapeLeadingUnderscores(symbol.escapedName), - specifier ? verbatimTargetName : targetName, - specifier && isStringLiteralLike(specifier) ? createLiteral(specifier.text) : undefined - ); - break; - case SyntaxKind.ExportAssignment: - serializeMaybeAliasAssignment(symbol); - break; - case SyntaxKind.BinaryExpression: - case SyntaxKind.PropertyAccessExpression: - // Could be best encoded as though an export specifier or as though an export assignment - // If name is default or export=, do an export assignment - // Otherwise do an export specifier - if (symbol.escapedName === InternalSymbolName.Default || symbol.escapedName === InternalSymbolName.ExportEquals) { - serializeMaybeAliasAssignment(symbol); - } - else { - serializeExportSpecifier(localName, targetName); - } - break; - default: - return Debug.failBadSyntaxKind(node, "Unhandled alias declaration kind in symbol serializer!"); - } - } - - function serializeExportSpecifier(localName: string, targetName: string, specifier?: Expression) { - addResult(createExportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - createNamedExports([createExportSpecifier(localName !== targetName ? targetName : undefined, localName)]), - specifier - ), ModifierFlags.None); - } - - function serializeMaybeAliasAssignment(symbol: Symbol) { - if (symbol.flags & SymbolFlags.Prototype) { - return; - } - const name = unescapeLeadingUnderscores(symbol.escapedName); - const isExportEquals = name === InternalSymbolName.ExportEquals; - const isDefault = name === InternalSymbolName.Default; - const isExportAssignment = isExportEquals || isDefault; - // synthesize export = ref - // ref should refer to either be a locally scoped symbol which we need to emit, or - // a reference to another namespace/module which we may need to emit an `import` statement for - const aliasDecl = symbol.declarations && getDeclarationOfAliasSymbol(symbol); - // serialize what the alias points to, preserve the declaration's initializer - const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true); - // If the target resolves and resolves to a thing defined in this file, emit as an alias, otherwise emit as a const - if (target && length(target.declarations) && some(target.declarations, d => getSourceFileOfNode(d) === getSourceFileOfNode(enclosingDeclaration))) { - // In case `target` refers to a namespace member, look at the declaration and serialize the leftmost symbol in it - // eg, `namespace A { export class B {} }; exports = A.B;` - // Technically, this is all that's required in the case where the assignment is an entity name expression - const expr = isExportAssignment ? getExportAssignmentExpression(aliasDecl as ExportAssignment | BinaryExpression) : getPropertyAssignmentAliasLikeExpression(aliasDecl as ShorthandPropertyAssignment | PropertyAssignment | PropertyAccessExpression); - const first = isEntityNameExpression(expr) ? getFirstNonModuleExportsIdentifier(expr) : undefined; - const referenced = first && resolveEntityName(first, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, enclosingDeclaration); - if (referenced || target) { - includePrivateSymbol(referenced || target); - } - - // We disable the context's symbol traker for the duration of this name serialization - // as, by virtue of being here, the name is required to print something, and we don't want to - // issue a visibility error on it. Only anonymous classes that an alias points at _would_ issue - // a visibility error here (as they're not visible within any scope), but we want to hoist them - // into the containing scope anyway, so we want to skip the visibility checks. - const oldTrack = context.tracker.trackSymbol; - context.tracker.trackSymbol = noop; - if (isExportAssignment) { - results.push(createExportAssignment( - /*decorators*/ undefined, - /*modifiers*/ undefined, - isExportEquals, - symbolToExpression(target, context, SymbolFlags.All) - )); - } - else { - if (first === expr) { - // serialize as `export {target as name}` - serializeExportSpecifier(name, idText(first)); - } - else if (isClassExpression(expr)) { - serializeExportSpecifier(name, getInternalSymbolName(target, symbolName(target))); - } - else { - // serialize as `import _Ref = t.arg.et; export { _Ref as name }` - const varName = getUnusedName(name, symbol); - addResult(createImportEqualsDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - createIdentifier(varName), - symbolToName(target, context, SymbolFlags.All, /*expectsIdentifier*/ false) - ), ModifierFlags.None); - serializeExportSpecifier(name, varName); - } - } - context.tracker.trackSymbol = oldTrack; - } - else { - // serialize as an anonymous property declaration - const varName = getUnusedName(name, symbol); - // We have to use `getWidenedType` here since the object within a json file is unwidened within the file - // (Unwidened types can only exist in expression contexts and should never be serialized) - const typeToSerialize = getWidenedType(getTypeOfSymbol(symbol)); - if (isTypeRepresentableAsFunctionNamespaceMerge(typeToSerialize, symbol)) { - // If there are no index signatures and `typeToSerialize` is an object type, emit as a namespace instead of a const - serializeAsFunctionNamespaceMerge(typeToSerialize, symbol, varName, isExportAssignment ? ModifierFlags.None : ModifierFlags.Export); - } - else { - const statement = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([ - createVariableDeclaration(varName, serializeTypeForDeclaration(typeToSerialize, symbol)) - ], NodeFlags.Const)); - addResult(statement, name === varName ? ModifierFlags.Export : ModifierFlags.None); - } - if (isExportAssignment) { - results.push(createExportAssignment( - /*decorators*/ undefined, - /*modifiers*/ undefined, - isExportEquals, - createIdentifier(varName) - )); - } - else if (name !== varName) { - serializeExportSpecifier(name, varName); - } - } - } - - function isTypeRepresentableAsFunctionNamespaceMerge(typeToSerialize: Type, hostSymbol: Symbol) { - // Only object types which are not constructable, or indexable, whose members all come from the - // context source file, and whose property names are all valid identifiers and not late-bound, _and_ - // whose input is not type annotated (if the input symbol has an annotation we can reuse, we should prefer it) - const ctxSrc = getSourceFileOfNode(context.enclosingDeclaration); - return getObjectFlags(typeToSerialize) & (ObjectFlags.Anonymous | ObjectFlags.Mapped) && - !getIndexInfoOfType(typeToSerialize, IndexKind.String) && - !getIndexInfoOfType(typeToSerialize, IndexKind.Number) && - !!(length(getPropertiesOfType(typeToSerialize)) || length(getSignaturesOfType(typeToSerialize, SignatureKind.Call))) && - !length(getSignaturesOfType(typeToSerialize, SignatureKind.Construct)) && // TODO: could probably serialize as function + ns + class, now that that's OK - !getDeclarationWithTypeAnnotation(hostSymbol) && - !(typeToSerialize.symbol && some(typeToSerialize.symbol.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) && - !some(getPropertiesOfType(typeToSerialize), p => isLateBoundName(p.escapedName)) && - !some(getPropertiesOfType(typeToSerialize), p => some(p.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) && - every(getPropertiesOfType(typeToSerialize), p => isIdentifierText(symbolName(p), languageVersion) && !isStringAKeyword(symbolName(p))); - } - - function makeSerializePropertySymbol(createProperty: ( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - questionOrExclamationToken: QuestionToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined - ) => T, methodKind: SyntaxKind, useAccessors: true): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | AccessorDeclaration | (T | AccessorDeclaration)[]); - function makeSerializePropertySymbol(createProperty: ( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - questionOrExclamationToken: QuestionToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined - ) => T, methodKind: SyntaxKind, useAccessors: false): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | T[]); - function makeSerializePropertySymbol(createProperty: ( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - questionOrExclamationToken: QuestionToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined - ) => T, methodKind: SyntaxKind, useAccessors: boolean): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | AccessorDeclaration | (T | AccessorDeclaration)[]) { - return function serializePropertySymbol(p: Symbol, isStatic: boolean, baseType: Type | undefined) { - if (isStatic && (p.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias))) { - // Only value-only-meaning symbols can be correctly encoded as class statics, type/namespace/alias meaning symbols - // need to be merged namespace members - return []; - } - if (p.flags & SymbolFlags.Prototype || (baseType && getPropertyOfType(baseType, p.escapedName) - && isReadonlySymbol(getPropertyOfType(baseType, p.escapedName)!) === isReadonlySymbol(p) - && (p.flags & SymbolFlags.Optional) === (getPropertyOfType(baseType, p.escapedName)!.flags & SymbolFlags.Optional) - && isTypeIdenticalTo(getTypeOfSymbol(p), getTypeOfPropertyOfType(baseType, p.escapedName)!))) { - return []; - } - const staticFlag = isStatic ? ModifierFlags.Static : 0; - const name = getPropertyNameNodeForSymbol(p, context); - const firstPropertyLikeDecl = find(p.declarations, or(isPropertyDeclaration, isAccessor, isVariableDeclaration, isPropertySignature, isBinaryExpression, isPropertyAccessExpression)); - if (p.flags & SymbolFlags.Accessor && useAccessors) { - const result: AccessorDeclaration[] = []; - if (p.flags & SymbolFlags.SetAccessor) { - result.push(setTextRange(createSetAccessor( - /*decorators*/ undefined, - createModifiersFromModifierFlags(staticFlag), - name, - [createParameter( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - "arg", - /*questionToken*/ undefined, - serializeTypeForDeclaration(getTypeOfSymbol(p), p) - )], - /*body*/ undefined - ), find(p.declarations, isSetAccessor) || firstPropertyLikeDecl)); - } - if (p.flags & SymbolFlags.GetAccessor) { - result.push(setTextRange(createGetAccessor( - /*decorators*/ undefined, - createModifiersFromModifierFlags(staticFlag), - name, - [], - serializeTypeForDeclaration(getTypeOfSymbol(p), p), - /*body*/ undefined - ), find(p.declarations, isGetAccessor) || firstPropertyLikeDecl)); - } - return result; - } - // This is an else/if as accessors and properties can't merge in TS, but might in JS - // If this happens, we assume the accessor takes priority, as it imposes more constraints - else if (p.flags & (SymbolFlags.Property | SymbolFlags.Variable)) { - return setTextRange(createProperty( - /*decorators*/ undefined, - createModifiersFromModifierFlags((isReadonlySymbol(p) ? ModifierFlags.Readonly : 0) | staticFlag), - name, - p.flags & SymbolFlags.Optional ? createToken(SyntaxKind.QuestionToken) : undefined, - serializeTypeForDeclaration(getTypeOfSymbol(p), p), - // TODO: https://github.com/microsoft/TypeScript/pull/32372#discussion_r328386357 - // interface members can't have initializers, however class members _can_ - /*initializer*/ undefined - ), find(p.declarations, or(isPropertyDeclaration, isVariableDeclaration)) || firstPropertyLikeDecl); - } - if (p.flags & (SymbolFlags.Method | SymbolFlags.Function)) { - const type = getTypeOfSymbol(p); - const signatures = getSignaturesOfType(type, SignatureKind.Call); - const results = []; - for (const sig of signatures) { - // Each overload becomes a separate method declaration, in order - const decl = signatureToSignatureDeclarationHelper(sig, methodKind, context) as MethodDeclaration; - decl.name = name; // TODO: Clone - if (staticFlag) { - decl.modifiers = createNodeArray(createModifiersFromModifierFlags(staticFlag)); - } - if (p.flags & SymbolFlags.Optional) { - decl.questionToken = createToken(SyntaxKind.QuestionToken); - } - results.push(setTextRange(decl, sig.declaration)); - } - return results as unknown as T[]; - } - // The `Constructor`'s symbol isn't in the class's properties lists, obviously, since it's a signature on the static - return Debug.fail(`Unhandled class member kind! ${(p as any).__debugFlags || p.flags}`); - }; - } - - function serializePropertySymbolForInterface(p: Symbol, baseType: Type | undefined) { - return serializePropertySymbolForInterfaceWorker(p, /*isStatic*/ false, baseType); - } - - function getDeclarationWithTypeAnnotation(symbol: Symbol) { - return find(symbol.declarations, s => !!getEffectiveTypeAnnotationNode(s) && !!findAncestor(s, n => n === enclosingDeclaration)); - } - - /** - * Unlike `typeToTypeNodeHelper`, this handles setting up the `AllowUniqueESSymbolType` flag - * so a `unique symbol` is returned when appropriate for the input symbol, rather than `typeof sym` - */ - function serializeTypeForDeclaration(type: Type, symbol: Symbol) { - const declWithExistingAnnotation = getDeclarationWithTypeAnnotation(symbol); - if (declWithExistingAnnotation && !isFunctionLikeDeclaration(declWithExistingAnnotation)) { - // try to reuse the existing annotation - const existing = getEffectiveTypeAnnotationNode(declWithExistingAnnotation)!; - const transformed = visitNode(existing, visitExistingNodeTreeSymbols); - return transformed === existing ? getMutableClone(existing) : transformed; - } - const oldFlags = context.flags; - if (type.flags & TypeFlags.UniqueESSymbol && - type.symbol === symbol) { - context.flags |= NodeBuilderFlags.AllowUniqueESSymbolType; - } - const result = typeToTypeNodeHelper(type, context); - context.flags = oldFlags; - return result; - - function visitExistingNodeTreeSymbols(node: T): Node { - if (isJSDocAllType(node)) { - return createKeywordTypeNode(SyntaxKind.AnyKeyword); - } - if (isJSDocUnknownType(node)) { - return createKeywordTypeNode(SyntaxKind.UnknownKeyword); - } - if (isJSDocNullableType(node)) { - return createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols), createKeywordTypeNode(SyntaxKind.NullKeyword)]); - } - if (isJSDocOptionalType(node)) { - return createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols), createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); - } - if (isJSDocNonNullableType(node)) { - return visitNode(node.type, visitExistingNodeTreeSymbols); - } - if ((isExpressionWithTypeArguments(node) || isTypeReferenceNode(node)) && isJSDocIndexSignature(node)) { - return createTypeLiteralNode([createIndexSignature( - /*decorators*/ undefined, - /*modifiers*/ undefined, - [createParameter( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotdotdotToken*/ undefined, - "x", - /*questionToken*/ undefined, - visitNode(node.typeArguments![0], visitExistingNodeTreeSymbols) - )], - visitNode(node.typeArguments![1], visitExistingNodeTreeSymbols) - )]); - } - if (isJSDocFunctionType(node)) { - if (isJSDocConstructSignature(node)) { - let newTypeNode: TypeNode | undefined; - return createConstructorTypeNode( - visitNodes(node.typeParameters, visitExistingNodeTreeSymbols), - mapDefined(node.parameters, (p, i) => p.name && isIdentifier(p.name) && p.name.escapedText === "new" ? (newTypeNode = p.type, undefined) : createParameter( - /*decorators*/ undefined, - /*modifiers*/ undefined, - p.dotDotDotToken, - p.name || p.dotDotDotToken ? `args` : `arg${i}`, - p.questionToken, - visitNode(p.type, visitExistingNodeTreeSymbols), - /*initializer*/ undefined - )), - visitNode(newTypeNode || node.type, visitExistingNodeTreeSymbols) - ); - } - else { - return createFunctionTypeNode( - visitNodes(node.typeParameters, visitExistingNodeTreeSymbols), - map(node.parameters, (p, i) => createParameter( - /*decorators*/ undefined, - /*modifiers*/ undefined, - p.dotDotDotToken, - p.name || p.dotDotDotToken ? `args` : `arg${i}`, - p.questionToken, - visitNode(p.type, visitExistingNodeTreeSymbols), - /*initializer*/ undefined - )), - visitNode(node.type, visitExistingNodeTreeSymbols) - ); - } - } - if (isLiteralImportTypeNode(node)) { - return updateImportTypeNode( - node, - updateLiteralTypeNode(node.argument, rewriteModuleSpecifier(node, node.argument.literal)), - node.qualifier, - visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode), - node.isTypeOf - ); - } - - if (isEntityName(node) || isEntityNameExpression(node)) { - const leftmost = getFirstIdentifier(node); - const sym = resolveEntityName(leftmost, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveALias*/ true); - if (sym) { - includePrivateSymbol(sym); - if (isIdentifier(node) && sym.flags & SymbolFlags.TypeParameter) { - const name = typeParameterToName(getDeclaredTypeOfSymbol(sym), context); - if (idText(name) !== idText(node)) { - return name; - } - return node; - } - } - } - - return visitEachChild(node, visitExistingNodeTreeSymbols, nullTransformationContext); - } - - function rewriteModuleSpecifier(parent: ImportTypeNode, lit: StringLiteral) { - if (bundled) { - if (context.tracker && context.tracker.moduleResolverHost) { - const targetFile = getExternalModuleFileFromDeclaration(parent); - if (targetFile) { - const getCanonicalFileName = createGetCanonicalFileName(!!host.useCaseSensitiveFileNames); - const resolverHost = { - getCanonicalFileName, - getCurrentDirectory: context.tracker.moduleResolverHost.getCurrentDirectory ? () => context.tracker.moduleResolverHost!.getCurrentDirectory!() : () => "", - getCommonSourceDirectory: () => context.tracker.moduleResolverHost!.getCommonSourceDirectory() - }; - const newName = getResolvedExternalModuleName(resolverHost, targetFile); - return createLiteral(newName); - } - } - } - else { - if (context.tracker && context.tracker.trackExternalModuleSymbolOfImportTypeNode) { - const moduleSym = resolveExternalModuleNameWorker(lit, lit, /*moduleNotFoundError*/ undefined); - if (moduleSym) { - context.tracker.trackExternalModuleSymbolOfImportTypeNode(moduleSym); - } - } - } - return lit; - } - } - - function serializeSignatures(kind: SignatureKind, input: Type, baseType: Type | undefined, outputKind: SyntaxKind) { - const signatures = getSignaturesOfType(input, kind); - if (kind === SignatureKind.Construct) { - if (!baseType && every(signatures, s => length(s.parameters) === 0)) { - return []; // No base type, every constructor is empty - elide the extraneous `constructor()` - } - if (baseType) { - // If there is a base type, if every signature in the class is identical to a signature in the baseType, elide all the declarations - const baseSigs = getSignaturesOfType(baseType, SignatureKind.Construct); - if (!length(baseSigs) && every(signatures, s => length(s.parameters) === 0)) { - return []; // Base had no explicit signatures, if all our signatures are also implicit, return an empty list - } - if (baseSigs.length === signatures.length) { - let failed = false; - for (let i = 0; i < baseSigs.length; i++) { - if (!compareSignaturesIdentical(signatures[i], baseSigs[i], /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true, compareTypesIdentical)) { - failed = true; - break; - } - } - if (!failed) { - return []; // Every signature was identical - elide constructor list as it is inherited - } - } - } - } - - const results = []; - for (const sig of signatures) { - // Each overload becomes a separate constructor declaration, in order - const decl = signatureToSignatureDeclarationHelper(sig, outputKind, context); - results.push(setTextRange(decl, sig.declaration)); - } - return results; - } - - function serializeIndexSignatures(input: Type, baseType: Type | undefined) { - const results: IndexSignatureDeclaration[] = []; - for (const type of [IndexKind.String, IndexKind.Number]) { - const info = getIndexInfoOfType(input, type); - if (info) { - if (baseType) { - const baseInfo = getIndexInfoOfType(baseType, type); - if (baseInfo) { - if (isTypeIdenticalTo(info.type, baseInfo.type)) { - continue; // elide identical index signatures - } - } - } - results.push(indexInfoToIndexSignatureDeclarationHelper(info, type, context)); - } - } - return results; - } - - function serializeBaseType(t: Type, staticType: Type, rootName: string) { - const ref = trySerializeAsTypeReference(t); - if (ref) { - return ref; - } - const tempName = getUnusedName(`${rootName}_base`); - const statement = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([ - createVariableDeclaration(tempName, typeToTypeNodeHelper(staticType, context)) - ], NodeFlags.Const)); - addResult(statement, ModifierFlags.None); - return createExpressionWithTypeArguments(/*typeArgs*/ undefined, createIdentifier(tempName)); - } - - function trySerializeAsTypeReference(t: Type) { - let typeArgs: TypeNode[] | undefined; - let reference: Expression | undefined; - // We don't use `isValueSymbolAccessible` below. since that considers alternative containers (like modules) - // which we can't write out in a syntactically valid way as an expression - if ((t as TypeReference).target && getAccessibleSymbolChain((t as TypeReference).target.symbol, enclosingDeclaration, SymbolFlags.Value, /*useOnlyExternalAliasing*/ false)) { - typeArgs = map(getTypeArguments(t as TypeReference), t => typeToTypeNodeHelper(t, context)); - reference = symbolToExpression((t as TypeReference).target.symbol, context, SymbolFlags.Type); - } - else if (t.symbol && getAccessibleSymbolChain(t.symbol, enclosingDeclaration, SymbolFlags.Value, /*useOnlyExternalAliasing*/ false)) { - reference = symbolToExpression(t.symbol, context, SymbolFlags.Type); - } - if (reference) { - return createExpressionWithTypeArguments(typeArgs, reference); - } - } - - function getUnusedName(input: string, symbol?: Symbol): string { - if (symbol) { - if (context.remappedSymbolNames!.has("" + getSymbolId(symbol))) { - return context.remappedSymbolNames!.get("" + getSymbolId(symbol))!; - } - } - if (symbol) { - input = getNameCandidateWorker(symbol, input); - } - let i = 0; - const original = input; - while (context.usedSymbolNames!.has(input)) { - i++; - input = `${original}_${i}`; - } - context.usedSymbolNames!.set(input, true); - if (symbol) { - context.remappedSymbolNames!.set("" + getSymbolId(symbol), input); - } - return input; - } - - function getNameCandidateWorker(symbol: Symbol, localName: string) { - if (localName === InternalSymbolName.Default || localName === InternalSymbolName.Class || localName === InternalSymbolName.Function) { - const flags = context.flags; - context.flags |= NodeBuilderFlags.InInitialEntityName; - const nameCandidate = getNameOfSymbolAsWritten(symbol, context); - context.flags = flags; - localName = nameCandidate.length > 0 && isSingleOrDoubleQuote(nameCandidate.charCodeAt(0)) ? stripQuotes(nameCandidate) : nameCandidate; - } - if (localName === InternalSymbolName.Default) { - localName = "_default"; - } - else if (localName === InternalSymbolName.ExportEquals) { - localName = "_exports"; - } - localName = isIdentifierText(localName, languageVersion) && !isStringANonContextualKeyword(localName) ? localName : "_" + localName.replace(/[^a-zA-Z0-9]/g, "_"); - return localName; - } - - function getInternalSymbolName(symbol: Symbol, localName: string) { - if (context.remappedSymbolNames!.has("" + getSymbolId(symbol))) { - return context.remappedSymbolNames!.get("" + getSymbolId(symbol))!; - } - localName = getNameCandidateWorker(symbol, localName); - // The result of this is going to be used as the symbol's name - lock it in, so `getUnusedName` will also pick it up - context.remappedSymbolNames!.set("" + getSymbolId(symbol), localName); - return localName; - } - } - } - - function typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer?: EmitTextWriter): string { - return writer ? typePredicateToStringWorker(writer).getText() : usingSingleLineStringWriter(typePredicateToStringWorker); - - function typePredicateToStringWorker(writer: EmitTextWriter) { - const predicate = createTypePredicateNodeWithModifier( - typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? createToken(SyntaxKind.AssertsKeyword) : undefined, - typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? createIdentifier(typePredicate.parameterName) : createThisTypeNode(), - typePredicate.type && nodeBuilder.typeToTypeNode(typePredicate.type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName)! // TODO: GH#18217 - ); - const printer = createPrinter({ removeComments: true }); - const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); - printer.writeNode(EmitHint.Unspecified, predicate, /*sourceFile*/ sourceFile, writer); - return writer; - } - } - - function formatUnionTypes(types: readonly Type[]): Type[] { - const result: Type[] = []; - let flags: TypeFlags = 0; - for (let i = 0; i < types.length; i++) { - const t = types[i]; - flags |= t.flags; - if (!(t.flags & TypeFlags.Nullable)) { - if (t.flags & (TypeFlags.BooleanLiteral | TypeFlags.EnumLiteral)) { - const baseType = t.flags & TypeFlags.BooleanLiteral ? booleanType : getBaseTypeOfEnumLiteralType(t); - if (baseType.flags & TypeFlags.Union) { - const count = (baseType).types.length; - if (i + count <= types.length && getRegularTypeOfLiteralType(types[i + count - 1]) === getRegularTypeOfLiteralType((baseType).types[count - 1])) { - result.push(baseType); - i += count - 1; - continue; - } - } - } - result.push(t); - } - } - if (flags & TypeFlags.Null) result.push(nullType); - if (flags & TypeFlags.Undefined) result.push(undefinedType); - return result || types; - } - - function visibilityToString(flags: ModifierFlags): string | undefined { - if (flags === ModifierFlags.Private) { - return "private"; - } - if (flags === ModifierFlags.Protected) { - return "protected"; - } - return "public"; - } - - function getTypeAliasForTypeLiteral(type: Type): Symbol | undefined { - if (type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral) { - const node = findAncestor(type.symbol.declarations[0].parent, n => n.kind !== SyntaxKind.ParenthesizedType)!; - if (node.kind === SyntaxKind.TypeAliasDeclaration) { - return getSymbolOfNode(node); - } - } - return undefined; - } - - function isTopLevelInExternalModuleAugmentation(node: Node): boolean { - return node && node.parent && - node.parent.kind === SyntaxKind.ModuleBlock && - isExternalModuleAugmentation(node.parent.parent); - } - - interface NodeBuilderContext { - enclosingDeclaration: Node | undefined; - flags: NodeBuilderFlags; - tracker: SymbolTracker; - - // State - encounteredError: boolean; - visitedTypes: Map | undefined; - symbolDepth: Map | undefined; - inferTypeParameters: TypeParameter[] | undefined; - approximateLength: number; - truncating?: boolean; - typeParameterSymbolList?: Map; - typeParameterNames?: Map; - typeParameterNamesByText?: Map; - usedSymbolNames?: Map; - remappedSymbolNames?: Map; - } - - function isDefaultBindingContext(location: Node) { - return location.kind === SyntaxKind.SourceFile || isAmbientModule(location); - } - - function getNameOfSymbolFromNameType(symbol: Symbol, context?: NodeBuilderContext) { - const nameType = symbol.nameType; - if (nameType) { - if (nameType.flags & TypeFlags.StringOrNumberLiteral) { - const name = "" + (nameType).value; - if (!isIdentifierText(name, compilerOptions.target) && !isNumericLiteralName(name)) { - return `"${escapeString(name, CharacterCodes.doubleQuote)}"`; - } - if (isNumericLiteralName(name) && startsWith(name, "-")) { - return `[${name}]`; - } - return name; - } - if (nameType.flags & TypeFlags.UniqueESSymbol) { - return `[${getNameOfSymbolAsWritten((nameType).symbol, context)}]`; - } - } - } - - /** - * Gets a human-readable name for a symbol. - * Should *not* be used for the right-hand side of a `.` -- use `symbolName(symbol)` for that instead. - * - * Unlike `symbolName(symbol)`, this will include quotes if the name is from a string literal. - * It will also use a representation of a number as written instead of a decimal form, e.g. `0o11` instead of `9`. - */ - function getNameOfSymbolAsWritten(symbol: Symbol, context?: NodeBuilderContext): string { - if (context && symbol.escapedName === InternalSymbolName.Default && !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope) && - // If it's not the first part of an entity name, it must print as `default` - (!(context.flags & NodeBuilderFlags.InInitialEntityName) || - // if the symbol is synthesized, it will only be referenced externally it must print as `default` - !symbol.declarations || - // if not in the same binding context (source file, module declaration), it must print as `default` - (context.enclosingDeclaration && findAncestor(symbol.declarations[0], isDefaultBindingContext) !== findAncestor(context.enclosingDeclaration, isDefaultBindingContext)))) { - return "default"; - } - if (symbol.declarations && symbol.declarations.length) { - let declaration = firstDefined(symbol.declarations, d => getNameOfDeclaration(d) ? d : undefined); // Try using a declaration with a name, first - const name = declaration && getNameOfDeclaration(declaration); - if (declaration && name) { - if (isCallExpression(declaration) && isBindableObjectDefinePropertyCall(declaration)) { - return symbolName(symbol); - } - if (isComputedPropertyName(name) && !(getCheckFlags(symbol) & CheckFlags.Late) && symbol.nameType && symbol.nameType.flags & TypeFlags.StringOrNumberLiteral) { - // Computed property name isn't late bound, but has a well-known name type - use name type to generate a symbol name - const result = getNameOfSymbolFromNameType(symbol, context); - if (result !== undefined) { - return result; - } - } - return declarationNameToString(name); - } - if (!declaration) { - declaration = symbol.declarations[0]; // Declaration may be nameless, but we'll try anyway - } - if (declaration.parent && declaration.parent.kind === SyntaxKind.VariableDeclaration) { - return declarationNameToString((declaration.parent).name); - } - switch (declaration.kind) { - case SyntaxKind.ClassExpression: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - if (context && !context.encounteredError && !(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier)) { - context.encounteredError = true; - } - return declaration.kind === SyntaxKind.ClassExpression ? "(Anonymous class)" : "(Anonymous function)"; - } - } - const name = getNameOfSymbolFromNameType(symbol, context); - return name !== undefined ? name : symbolName(symbol); - } - - function isDeclarationVisible(node: Node): boolean { - if (node) { - const links = getNodeLinks(node); - if (links.isVisible === undefined) { - links.isVisible = !!determineIfDeclarationIsVisible(); - } - return links.isVisible; - } - - return false; - - function determineIfDeclarationIsVisible() { - switch (node.kind) { - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocEnumTag: - // Top-level jsdoc type aliases are considered exported - // First parent is comment node, second is hosting declaration or token; we only care about those tokens or declarations whose parent is a source file - return !!(node.parent && node.parent.parent && node.parent.parent.parent && isSourceFile(node.parent.parent.parent)); - case SyntaxKind.BindingElement: - return isDeclarationVisible(node.parent.parent); - case SyntaxKind.VariableDeclaration: - if (isBindingPattern((node as VariableDeclaration).name) && - !((node as VariableDeclaration).name as BindingPattern).elements.length) { - // If the binding pattern is empty, this variable declaration is not visible - return false; - } - // falls through - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - // external module augmentation is always visible - if (isExternalModuleAugmentation(node)) { - return true; - } - const parent = getDeclarationContainer(node); - // If the node is not exported or it is not ambient module element (except import declaration) - if (!(getCombinedModifierFlags(node as Declaration) & ModifierFlags.Export) && - !(node.kind !== SyntaxKind.ImportEqualsDeclaration && parent.kind !== SyntaxKind.SourceFile && parent.flags & NodeFlags.Ambient)) { - return isGlobalSourceFile(parent); - } - // Exported members/ambient module elements (exception import declaration) are visible if parent is visible - return isDeclarationVisible(parent); - - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - if (hasModifier(node, ModifierFlags.Private | ModifierFlags.Protected)) { - // Private/protected properties/methods are not visible - return false; - } - // Public properties/methods are visible if its parents are visible, so: - // falls through - - case SyntaxKind.Constructor: - case SyntaxKind.ConstructSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.Parameter: - case SyntaxKind.ModuleBlock: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.TypeLiteral: - case SyntaxKind.TypeReference: - case SyntaxKind.ArrayType: - case SyntaxKind.TupleType: - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: - case SyntaxKind.ParenthesizedType: - return isDeclarationVisible(node.parent); - - // Default binding, import specifier and namespace import is visible - // only on demand so by default it is not visible - case SyntaxKind.ImportClause: - case SyntaxKind.NamespaceImport: - case SyntaxKind.ImportSpecifier: - return false; - - // Type parameters are always visible - case SyntaxKind.TypeParameter: - - // Source file and namespace export are always visible - // falls through - case SyntaxKind.SourceFile: - case SyntaxKind.NamespaceExportDeclaration: - return true; - - // Export assignments do not create name bindings outside the module - case SyntaxKind.ExportAssignment: - return false; - - default: - return false; - } - } - } - - function collectLinkedAliases(node: Identifier, setVisibility?: boolean): Node[] | undefined { - let exportSymbol: Symbol | undefined; - if (node.parent && node.parent.kind === SyntaxKind.ExportAssignment) { - exportSymbol = resolveName(node, node.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, node, /*isUse*/ false); - } - else if (node.parent.kind === SyntaxKind.ExportSpecifier) { - exportSymbol = getTargetOfExportSpecifier(node.parent, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); - } - let result: Node[] | undefined; - let visited: Map | undefined; - if (exportSymbol) { - visited = createMap(); - visited.set("" + getSymbolId(exportSymbol), true); - buildVisibleNodeList(exportSymbol.declarations); - } - return result; - - function buildVisibleNodeList(declarations: Declaration[]) { - forEach(declarations, declaration => { - const resultNode = getAnyImportSyntax(declaration) || declaration; - if (setVisibility) { - getNodeLinks(declaration).isVisible = true; - } - else { - result = result || []; - pushIfUnique(result, resultNode); - } - - if (isInternalModuleImportEqualsDeclaration(declaration)) { - // Add the referenced top container visible - const internalModuleReference = declaration.moduleReference; - const firstIdentifier = getFirstIdentifier(internalModuleReference); - const importSymbol = resolveName(declaration, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, - undefined, undefined, /*isUse*/ false); - const id = importSymbol && "" + getSymbolId(importSymbol); - if (importSymbol && !visited!.has(id!)) { - visited!.set(id!, true); - buildVisibleNodeList(importSymbol.declarations); - } - } - }); - } - } - - /** - * Push an entry on the type resolution stack. If an entry with the given target and the given property name - * is already on the stack, and no entries in between already have a type, then a circularity has occurred. - * In this case, the result values of the existing entry and all entries pushed after it are changed to false, - * and the value false is returned. Otherwise, the new entry is just pushed onto the stack, and true is returned. - * In order to see if the same query has already been done before, the target object and the propertyName both - * must match the one passed in. - * - * @param target The symbol, type, or signature whose type is being queried - * @param propertyName The property name that should be used to query the target for its type - */ - function pushTypeResolution(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean { - const resolutionCycleStartIndex = findResolutionCycleStartIndex(target, propertyName); - if (resolutionCycleStartIndex >= 0) { - // A cycle was found - const { length } = resolutionTargets; - for (let i = resolutionCycleStartIndex; i < length; i++) { - resolutionResults[i] = false; - } - return false; - } - resolutionTargets.push(target); - resolutionResults.push(/*items*/ true); - resolutionPropertyNames.push(propertyName); - return true; - } - - function findResolutionCycleStartIndex(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): number { - for (let i = resolutionTargets.length - 1; i >= 0; i--) { - if (hasType(resolutionTargets[i], resolutionPropertyNames[i])) { - return -1; - } - if (resolutionTargets[i] === target && resolutionPropertyNames[i] === propertyName) { - return i; - } - } - return -1; - } - - function hasType(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean { - switch (propertyName) { - case TypeSystemPropertyName.Type: - return !!getSymbolLinks(target).type; - case TypeSystemPropertyName.EnumTagType: - return !!(getNodeLinks(target as JSDocEnumTag).resolvedEnumType); - case TypeSystemPropertyName.DeclaredType: - return !!getSymbolLinks(target).declaredType; - case TypeSystemPropertyName.ResolvedBaseConstructorType: - return !!(target).resolvedBaseConstructorType; - case TypeSystemPropertyName.ResolvedReturnType: - return !!(target).resolvedReturnType; - case TypeSystemPropertyName.ImmediateBaseConstraint: - return !!(target).immediateBaseConstraint; - case TypeSystemPropertyName.JSDocTypeReference: - return !!getSymbolLinks(target as Symbol).resolvedJSDocType; - case TypeSystemPropertyName.ResolvedTypeArguments: - return !!(target as TypeReference).resolvedTypeArguments; - } - return Debug.assertNever(propertyName); - } - - /** - * Pop an entry from the type resolution stack and return its associated result value. The result value will - * be true if no circularities were detected, or false if a circularity was found. - */ - function popTypeResolution(): boolean { - resolutionTargets.pop(); - resolutionPropertyNames.pop(); - return resolutionResults.pop()!; - } - - function getDeclarationContainer(node: Node): Node { - return findAncestor(getRootDeclaration(node), node => { - switch (node.kind) { - case SyntaxKind.VariableDeclaration: - case SyntaxKind.VariableDeclarationList: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.NamedImports: - case SyntaxKind.NamespaceImport: - case SyntaxKind.ImportClause: - return false; - default: - return true; - } - })!.parent; - } - - function getTypeOfPrototypeProperty(prototype: Symbol): Type { - // TypeScript 1.0 spec (April 2014): 8.4 - // Every class automatically contains a static property member named 'prototype', - // the type of which is an instantiation of the class type with type Any supplied as a type argument for each type parameter. - // It is an error to explicitly declare a static property member with the name 'prototype'. - const classType = getDeclaredTypeOfSymbol(getParentOfSymbol(prototype)!); - return classType.typeParameters ? createTypeReference(classType, map(classType.typeParameters, _ => anyType)) : classType; - } - - // Return the type of the given property in the given type, or undefined if no such property exists - function getTypeOfPropertyOfType(type: Type, name: __String): Type | undefined { - const prop = getPropertyOfType(type, name); - return prop ? getTypeOfSymbol(prop) : undefined; - } - - function getTypeOfPropertyOrIndexSignature(type: Type, name: __String): Type { - return getTypeOfPropertyOfType(type, name) || isNumericLiteralName(name) && getIndexTypeOfType(type, IndexKind.Number) || getIndexTypeOfType(type, IndexKind.String) || unknownType; - } - - function isTypeAny(type: Type | undefined) { - return type && (type.flags & TypeFlags.Any) !== 0; - } - - // Return the type of a binding element parent. We check SymbolLinks first to see if a type has been - // assigned by contextual typing. - function getTypeForBindingElementParent(node: BindingElementGrandparent) { - const symbol = getSymbolOfNode(node); - return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false); - } - - function isComputedNonLiteralName(name: PropertyName): boolean { - return name.kind === SyntaxKind.ComputedPropertyName && !isStringOrNumericLiteralLike(name.expression); - } - - function getRestType(source: Type, properties: PropertyName[], symbol: Symbol | undefined): Type { - source = filterType(source, t => !(t.flags & TypeFlags.Nullable)); - if (source.flags & TypeFlags.Never) { - return emptyObjectType; - } - if (source.flags & TypeFlags.Union) { - return mapType(source, t => getRestType(t, properties, symbol)); - } - const omitKeyType = getUnionType(map(properties, getLiteralTypeFromPropertyName)); - if (isGenericObjectType(source) || isGenericIndexType(omitKeyType)) { - if (omitKeyType.flags & TypeFlags.Never) { - return source; - } - - const omitTypeAlias = getGlobalOmitSymbol(); - if (!omitTypeAlias) { - return errorType; - } - return getTypeAliasInstantiation(omitTypeAlias, [source, omitKeyType]); - } - const members = createSymbolTable(); - for (const prop of getPropertiesOfType(source)) { - if (!isTypeAssignableTo(getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique), omitKeyType) - && !(getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) - && isSpreadableProperty(prop)) { - members.set(prop.escapedName, getSpreadSymbol(prop, /*readonly*/ false)); - } - } - const stringIndexInfo = getIndexInfoOfType(source, IndexKind.String); - const numberIndexInfo = getIndexInfoOfType(source, IndexKind.Number); - const result = createAnonymousType(symbol, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); - result.objectFlags |= ObjectFlags.ObjectRestType; - return result; - } - - // Determine the control flow type associated with a destructuring declaration or assignment. The following - // forms of destructuring are possible: - // let { x } = obj; // BindingElement - // let [ x ] = obj; // BindingElement - // { x } = obj; // ShorthandPropertyAssignment - // { x: v } = obj; // PropertyAssignment - // [ x ] = obj; // Expression - // We construct a synthetic element access expression corresponding to 'obj.x' such that the control - // flow analyzer doesn't have to handle all the different syntactic forms. - function getFlowTypeOfDestructuring(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression, declaredType: Type) { - const reference = getSyntheticElementAccess(node); - return reference ? getFlowTypeOfReference(reference, declaredType) : declaredType; - } - - function getSyntheticElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression): ElementAccessExpression | undefined { - const parentAccess = getParentElementAccess(node); - if (parentAccess && parentAccess.flowNode) { - const propName = getDestructuringPropertyName(node); - if (propName) { - const result = createNode(SyntaxKind.ElementAccessExpression, node.pos, node.end); - result.parent = node; - result.expression = parentAccess; - const literal = createNode(SyntaxKind.StringLiteral, node.pos, node.end); - literal.parent = result; - literal.text = propName; - result.argumentExpression = literal; - result.flowNode = parentAccess.flowNode; - return result; - } - } - } - - function getParentElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) { - const ancestor = node.parent.parent; - switch (ancestor.kind) { - case SyntaxKind.BindingElement: - case SyntaxKind.PropertyAssignment: - return getSyntheticElementAccess(ancestor); - case SyntaxKind.ArrayLiteralExpression: - return getSyntheticElementAccess(node.parent); - case SyntaxKind.VariableDeclaration: - return (ancestor).initializer; - case SyntaxKind.BinaryExpression: - return (ancestor).right; - } - } - - function getDestructuringPropertyName(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) { - const parent = node.parent; - if (node.kind === SyntaxKind.BindingElement && parent.kind === SyntaxKind.ObjectBindingPattern) { - return getLiteralPropertyNameText((node).propertyName || (node).name); - } - if (node.kind === SyntaxKind.PropertyAssignment || node.kind === SyntaxKind.ShorthandPropertyAssignment) { - return getLiteralPropertyNameText((node).name); - } - return "" + (>(parent).elements).indexOf(node); - } - - function getLiteralPropertyNameText(name: PropertyName) { - const type = getLiteralTypeFromPropertyName(name); - return type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral) ? "" + (type).value : undefined; - } - - /** Return the inferred type for a binding element */ - function getTypeForBindingElement(declaration: BindingElement): Type | undefined { - const pattern = declaration.parent; - let parentType = getTypeForBindingElementParent(pattern.parent); - // If no type or an any type was inferred for parent, infer that for the binding element - if (!parentType || isTypeAny(parentType)) { - return parentType; - } - // Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation - if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) { - parentType = getNonNullableType(parentType); - } - - let type: Type | undefined; - if (pattern.kind === SyntaxKind.ObjectBindingPattern) { - if (declaration.dotDotDotToken) { - if (parentType.flags & TypeFlags.Unknown || !isValidSpreadType(parentType)) { - error(declaration, Diagnostics.Rest_types_may_only_be_created_from_object_types); - return errorType; - } - const literalMembers: PropertyName[] = []; - for (const element of pattern.elements) { - if (!element.dotDotDotToken) { - literalMembers.push(element.propertyName || element.name as Identifier); - } - } - type = getRestType(parentType, literalMembers, declaration.symbol); - } - else { - // Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form) - const name = declaration.propertyName || declaration.name; - const indexType = getLiteralTypeFromPropertyName(name); - const declaredType = getConstraintForLocation(getIndexedAccessType(parentType, indexType, name), declaration.name); - type = getFlowTypeOfDestructuring(declaration, declaredType); - } - } - else { - // This elementType will be used if the specific property corresponding to this index is not - // present (aka the tuple element property). This call also checks that the parentType is in - // fact an iterable or array (depending on target language). - const elementType = checkIteratedTypeOrElementType(IterationUse.Destructuring, parentType, undefinedType, pattern); - const index = pattern.elements.indexOf(declaration); - if (declaration.dotDotDotToken) { - // If the parent is a tuple type, the rest element has a tuple type of the - // remaining tuple element types. Otherwise, the rest element has an array type with same - // element type as the parent type. - type = everyType(parentType, isTupleType) ? - mapType(parentType, t => sliceTupleType(t, index)) : - createArrayType(elementType); - } - else if (isArrayLikeType(parentType)) { - const indexType = getLiteralType(index); - const accessFlags = hasDefaultValue(declaration) ? AccessFlags.NoTupleBoundsCheck : 0; - const declaredType = getConstraintForLocation(getIndexedAccessTypeOrUndefined(parentType, indexType, declaration.name, accessFlags) || errorType, declaration.name); - type = getFlowTypeOfDestructuring(declaration, declaredType); - } - else { - type = elementType; - } - } - if (!declaration.initializer) { - return type; - } - if (getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration))) { - // In strict null checking mode, if a default value of a non-undefined type is specified, remove - // undefined from the final type. - return strictNullChecks && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined) ? - getTypeWithFacts(type, TypeFacts.NEUndefined) : - type; - } - return getUnionType([getTypeWithFacts(type, TypeFacts.NEUndefined), checkDeclarationInitializer(declaration)], UnionReduction.Subtype); - } - - function getTypeForDeclarationFromJSDocComment(declaration: Node) { - const jsdocType = getJSDocType(declaration); - if (jsdocType) { - return getTypeFromTypeNode(jsdocType); - } - return undefined; - } - - function isNullOrUndefined(node: Expression) { - const expr = skipParentheses(node); - return expr.kind === SyntaxKind.NullKeyword || expr.kind === SyntaxKind.Identifier && getResolvedSymbol(expr) === undefinedSymbol; - } - - function isEmptyArrayLiteral(node: Expression) { - const expr = skipParentheses(node); - return expr.kind === SyntaxKind.ArrayLiteralExpression && (expr).elements.length === 0; - } - - function addOptionality(type: Type, optional = true): Type { - return strictNullChecks && optional ? getOptionalType(type) : type; - } - - function isParameterOfContextuallyTypedFunction(node: Declaration) { - return node.kind === SyntaxKind.Parameter && - (node.parent.kind === SyntaxKind.FunctionExpression || node.parent.kind === SyntaxKind.ArrowFunction) && - !!getContextualType(node.parent); - } - - // Return the inferred type for a variable, parameter, or property declaration - function getTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement, includeOptionality: boolean): Type | undefined { - // A variable declared in a for..in statement is of type string, or of type keyof T when the - // right hand expression is of a type parameter type. - if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForInStatement) { - const indexType = getIndexType(getNonNullableTypeIfNeeded(checkExpression(declaration.parent.parent.expression))); - return indexType.flags & (TypeFlags.TypeParameter | TypeFlags.Index) ? getExtractStringType(indexType) : stringType; - } - - if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForOfStatement) { - // checkRightHandSideOfForOf will return undefined if the for-of expression type was - // missing properties/signatures required to get its iteratedType (like - // [Symbol.iterator] or next). This may be because we accessed properties from anyType, - // or it may have led to an error inside getElementTypeOfIterable. - const forOfStatement = declaration.parent.parent; - return checkRightHandSideOfForOf(forOfStatement.expression, forOfStatement.awaitModifier) || anyType; - } - - if (isBindingPattern(declaration.parent)) { - return getTypeForBindingElement(declaration); - } - - const isOptional = includeOptionality && ( - isParameter(declaration) && isJSDocOptionalParameter(declaration) - || !isBindingElement(declaration) && !isVariableDeclaration(declaration) && !!declaration.questionToken); - - // Use type from type annotation if one is present - const declaredType = tryGetTypeFromEffectiveTypeNode(declaration); - if (declaredType) { - return addOptionality(declaredType, isOptional); - } - - if ((noImplicitAny || isInJSFile(declaration)) && - declaration.kind === SyntaxKind.VariableDeclaration && !isBindingPattern(declaration.name) && - !(getCombinedModifierFlags(declaration) & ModifierFlags.Export) && !(declaration.flags & NodeFlags.Ambient)) { - // If --noImplicitAny is on or the declaration is in a Javascript file, - // use control flow tracked 'any' type for non-ambient, non-exported var or let variables with no - // initializer or a 'null' or 'undefined' initializer. - if (!(getCombinedNodeFlags(declaration) & NodeFlags.Const) && (!declaration.initializer || isNullOrUndefined(declaration.initializer))) { - return autoType; - } - // Use control flow tracked 'any[]' type for non-ambient, non-exported variables with an empty array - // literal initializer. - if (declaration.initializer && isEmptyArrayLiteral(declaration.initializer)) { - return autoArrayType; - } - } - - if (declaration.kind === SyntaxKind.Parameter) { - const func = declaration.parent; - // For a parameter of a set accessor, use the type of the get accessor if one is present - if (func.kind === SyntaxKind.SetAccessor && !hasNonBindableDynamicName(func)) { - const getter = getDeclarationOfKind(getSymbolOfNode(declaration.parent), SyntaxKind.GetAccessor); - if (getter) { - const getterSignature = getSignatureFromDeclaration(getter); - const thisParameter = getAccessorThisParameter(func as AccessorDeclaration); - if (thisParameter && declaration === thisParameter) { - // Use the type from the *getter* - Debug.assert(!thisParameter.type); - return getTypeOfSymbol(getterSignature.thisParameter!); - } - return getReturnTypeOfSignature(getterSignature); - } - } - if (isInJSFile(declaration)) { - const typeTag = getJSDocType(func); - if (typeTag && isFunctionTypeNode(typeTag)) { - return getTypeAtPosition(getSignatureFromDeclaration(typeTag), func.parameters.indexOf(declaration)); - } - } - // Use contextual parameter type if one is available - const type = declaration.symbol.escapedName === InternalSymbolName.This ? getContextualThisParameterType(func) : getContextuallyTypedParameterType(declaration, /*forCache*/ true); - if (type) { - return addOptionality(type, isOptional); - } - } - else if (isInJSFile(declaration)) { - const containerObjectType = getJSContainerObjectType(declaration, getSymbolOfNode(declaration), getDeclaredExpandoInitializer(declaration)); - if (containerObjectType) { - return containerObjectType; - } - } - - // Use the type of the initializer expression if one is present and the declaration is - // not a parameter of a contextually typed function - if (declaration.initializer && !isParameterOfContextuallyTypedFunction(declaration)) { - const type = checkDeclarationInitializer(declaration); - return addOptionality(type, isOptional); - } - - if (isJsxAttribute(declaration)) { - // if JSX attribute doesn't have initializer, by default the attribute will have boolean value of true. - // I.e is sugar for - return trueType; - } - - // If the declaration specifies a binding pattern and is not a parameter of a contextually - // typed function, use the type implied by the binding pattern - if (isBindingPattern(declaration.name) && !isParameterOfContextuallyTypedFunction(declaration)) { - return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ false, /*reportErrors*/ true); - } - - // No type specified and nothing can be inferred - return undefined; - } - - function getWidenedTypeForAssignmentDeclaration(symbol: Symbol, resolvedSymbol?: Symbol) { - // function/class/{} initializers are themselves containers, so they won't merge in the same way as other initializers - const container = getAssignedExpandoInitializer(symbol.valueDeclaration); - if (container) { - const tag = getJSDocTypeTag(container); - if (tag && tag.typeExpression) { - return getTypeFromTypeNode(tag.typeExpression); - } - const containerObjectType = getJSContainerObjectType(symbol.valueDeclaration, symbol, container); - return containerObjectType || getWidenedLiteralType(checkExpressionCached(container)); - } - let definedInConstructor = false; - let definedInMethod = false; - let jsdocType: Type | undefined; - let types: Type[] | undefined; - for (const declaration of symbol.declarations) { - const expression = (isBinaryExpression(declaration) || isCallExpression(declaration)) ? declaration : - isAccessExpression(declaration) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration : - undefined; - if (!expression) { - continue; // Non-assignment declaration merged in (eg, an Identifier to mark the thing as a namespace) - skip over it and pull type info from elsewhere - } - - const kind = isAccessExpression(expression) - ? getAssignmentDeclarationPropertyAccessKind(expression) - : getAssignmentDeclarationKind(expression); - if (kind === AssignmentDeclarationKind.ThisProperty) { - if (isDeclarationInConstructor(expression)) { - definedInConstructor = true; - } - else { - definedInMethod = true; - } - } - if (!isCallExpression(expression)) { - jsdocType = getAnnotatedTypeForAssignmentDeclaration(jsdocType, expression, symbol, declaration); - } - if (!jsdocType) { - (types || (types = [])).push((isBinaryExpression(expression) || isCallExpression(expression)) ? getInitializerTypeFromAssignmentDeclaration(symbol, resolvedSymbol, expression, kind) : neverType); - } - } - let type = jsdocType; - if (!type) { - if (!length(types)) { - return errorType; // No types from any declarations :( - } - let constructorTypes = definedInConstructor ? getConstructorDefinedThisAssignmentTypes(types!, symbol.declarations) : undefined; - // use only the constructor types unless they were only assigned null | undefined (including widening variants) - if (definedInMethod) { - const propType = getTypeOfAssignmentDeclarationPropertyOfBaseType(symbol); - if (propType) { - (constructorTypes || (constructorTypes = [])).push(propType); - definedInConstructor = true; - } - } - const sourceTypes = some(constructorTypes, t => !!(t.flags & ~TypeFlags.Nullable)) ? constructorTypes : types; // TODO: GH#18217 - type = getUnionType(sourceTypes!, UnionReduction.Subtype); - } - const widened = getWidenedType(addOptionality(type, definedInMethod && !definedInConstructor)); - if (filterType(widened, t => !!(t.flags & ~TypeFlags.Nullable)) === neverType) { - reportImplicitAny(symbol.valueDeclaration, anyType); - return anyType; - } - return widened; - } - - function getJSContainerObjectType(decl: Node, symbol: Symbol, init: Expression | undefined): Type | undefined { - if (!isInJSFile(decl) || !init || !isObjectLiteralExpression(init) || init.properties.length) { - return undefined; - } - const exports = createSymbolTable(); - while (isBinaryExpression(decl) || isPropertyAccessExpression(decl)) { - const s = getSymbolOfNode(decl); - if (s && hasEntries(s.exports)) { - mergeSymbolTable(exports, s.exports); - } - decl = isBinaryExpression(decl) ? decl.parent : decl.parent.parent; - } - const s = getSymbolOfNode(decl); - if (s && hasEntries(s.exports)) { - mergeSymbolTable(exports, s.exports); - } - const type = createAnonymousType(symbol, exports, emptyArray, emptyArray, undefined, undefined); - type.objectFlags |= ObjectFlags.JSLiteral; - return type; - } - - function getAnnotatedTypeForAssignmentDeclaration(declaredType: Type | undefined, expression: Expression, symbol: Symbol, declaration: Declaration) { - const typeNode = getEffectiveTypeAnnotationNode(expression.parent); - if (typeNode) { - const type = getWidenedType(getTypeFromTypeNode(typeNode)); - if (!declaredType) { - return type; - } - else if (declaredType !== errorType && type !== errorType && !isTypeIdenticalTo(declaredType, type)) { - errorNextVariableOrPropertyDeclarationMustHaveSameType(/*firstDeclaration*/ undefined, declaredType, declaration, type); - } - } - if (symbol.parent) { - const typeNode = getEffectiveTypeAnnotationNode(symbol.parent.valueDeclaration); - if (typeNode) { - return getTypeOfPropertyOfType(getTypeFromTypeNode(typeNode), symbol.escapedName); - } - } - - return declaredType; - } - - /** If we don't have an explicit JSDoc type, get the type from the initializer. */ - function getInitializerTypeFromAssignmentDeclaration(symbol: Symbol, resolvedSymbol: Symbol | undefined, expression: BinaryExpression | CallExpression, kind: AssignmentDeclarationKind) { - if (isCallExpression(expression)) { - if (resolvedSymbol) { - return getTypeOfSymbol(resolvedSymbol); // This shouldn't happen except under some hopefully forbidden merges of export assignments and object define assignments - } - const objectLitType = checkExpressionCached((expression as BindableObjectDefinePropertyCall).arguments[2]); - const valueType = getTypeOfPropertyOfType(objectLitType, "value" as __String); - if (valueType) { - return valueType; - } - const getFunc = getTypeOfPropertyOfType(objectLitType, "get" as __String); - if (getFunc) { - const getSig = getSingleCallSignature(getFunc); - if (getSig) { - return getReturnTypeOfSignature(getSig); - } - } - const setFunc = getTypeOfPropertyOfType(objectLitType, "set" as __String); - if (setFunc) { - const setSig = getSingleCallSignature(setFunc); - if (setSig) { - return getTypeOfFirstParameterOfSignature(setSig); - } - } - return anyType; - } - const type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol) : getWidenedLiteralType(checkExpressionCached(expression.right)); - if (type.flags & TypeFlags.Object && - kind === AssignmentDeclarationKind.ModuleExports && - symbol.escapedName === InternalSymbolName.ExportEquals) { - const exportedType = resolveStructuredTypeMembers(type as ObjectType); - const members = createSymbolTable(); - copyEntries(exportedType.members, members); - if (resolvedSymbol && !resolvedSymbol.exports) { - resolvedSymbol.exports = createSymbolTable(); - } - (resolvedSymbol || symbol).exports!.forEach((s, name) => { - if (members.has(name)) { - const exportedMember = exportedType.members.get(name)!; - const union = createSymbol(s.flags | exportedMember.flags, name); - union.type = getUnionType([getTypeOfSymbol(s), getTypeOfSymbol(exportedMember)]); - members.set(name, union); - } - else { - members.set(name, s); - } - }); - const result = createAnonymousType( - exportedType.symbol, - members, - exportedType.callSignatures, - exportedType.constructSignatures, - exportedType.stringIndexInfo, - exportedType.numberIndexInfo); - result.objectFlags |= (getObjectFlags(type) & ObjectFlags.JSLiteral); // Propagate JSLiteral flag - return result; - } - if (isEmptyArrayLiteralType(type)) { - reportImplicitAny(expression, anyArrayType); - return anyArrayType; - } - return type; - } - - function isDeclarationInConstructor(expression: Expression) { - const thisContainer = getThisContainer(expression, /*includeArrowFunctions*/ false); - // Properties defined in a constructor (or base constructor, or javascript constructor function) don't get undefined added. - // Function expressions that are assigned to the prototype count as methods. - return thisContainer.kind === SyntaxKind.Constructor || - thisContainer.kind === SyntaxKind.FunctionDeclaration || - (thisContainer.kind === SyntaxKind.FunctionExpression && !isPrototypePropertyAssignment(thisContainer.parent)); - } - - function getConstructorDefinedThisAssignmentTypes(types: Type[], declarations: Declaration[]): Type[] | undefined { - Debug.assert(types.length === declarations.length); - return types.filter((_, i) => { - const declaration = declarations[i]; - const expression = isBinaryExpression(declaration) ? declaration : - isBinaryExpression(declaration.parent) ? declaration.parent : undefined; - return expression && isDeclarationInConstructor(expression); - }); - } - - /** check for definition in base class if any declaration is in a class */ - function getTypeOfAssignmentDeclarationPropertyOfBaseType(property: Symbol) { - const parentDeclaration = forEach(property.declarations, d => { - const parent = getThisContainer(d, /*includeArrowFunctions*/ false).parent; - return isClassLike(parent) && parent; - }); - if (parentDeclaration) { - const classType = getDeclaredTypeOfSymbol(getSymbolOfNode(parentDeclaration)) as InterfaceType; - const baseClassType = classType && getBaseTypes(classType)[0]; - if (baseClassType) { - return getTypeOfPropertyOfType(baseClassType, property.escapedName); - } - } - } - - // Return the type implied by a binding pattern element. This is the type of the initializer of the element if - // one is present. Otherwise, if the element is itself a binding pattern, it is the type implied by the binding - // pattern. Otherwise, it is the type any. - function getTypeFromBindingElement(element: BindingElement, includePatternInType?: boolean, reportErrors?: boolean): Type { - if (element.initializer) { - return addOptionality(checkDeclarationInitializer(element)); - } - if (isBindingPattern(element.name)) { - return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors); - } - if (reportErrors && !declarationBelongsToPrivateAmbientMember(element)) { - reportImplicitAny(element, anyType); - } - // When we're including the pattern in the type (an indication we're obtaining a contextual type), we - // use the non-inferrable any type. Inference will never directly infer this type, but it is possible - // to infer a type that contains it, e.g. for a binding pattern like [foo] or { foo }. In such cases, - // widening of the binding pattern type substitutes a regular any for the non-inferrable any. - return includePatternInType ? nonInferrableAnyType : anyType; - } - - // Return the type implied by an object binding pattern - function getTypeFromObjectBindingPattern(pattern: ObjectBindingPattern, includePatternInType: boolean, reportErrors: boolean): Type { - const members = createSymbolTable(); - let stringIndexInfo: IndexInfo | undefined; - let objectFlags = ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; - forEach(pattern.elements, e => { - const name = e.propertyName || e.name; - if (e.dotDotDotToken) { - stringIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false); - return; - } - - const exprType = getLiteralTypeFromPropertyName(name); - if (!isTypeUsableAsPropertyName(exprType)) { - // do not include computed properties in the implied type - objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties; - return; - } - const text = getPropertyNameFromType(exprType); - const flags = SymbolFlags.Property | (e.initializer ? SymbolFlags.Optional : 0); - const symbol = createSymbol(flags, text); - symbol.type = getTypeFromBindingElement(e, includePatternInType, reportErrors); - symbol.bindingElement = e; - members.set(symbol.escapedName, symbol); - }); - const result = createAnonymousType(undefined, members, emptyArray, emptyArray, stringIndexInfo, undefined); - result.objectFlags |= objectFlags; - if (includePatternInType) { - result.pattern = pattern; - result.objectFlags |= ObjectFlags.ContainsObjectOrArrayLiteral; - } - return result; - } - - // Return the type implied by an array binding pattern - function getTypeFromArrayBindingPattern(pattern: BindingPattern, includePatternInType: boolean, reportErrors: boolean): Type { - const elements = pattern.elements; - const lastElement = lastOrUndefined(elements); - const hasRestElement = !!(lastElement && lastElement.kind === SyntaxKind.BindingElement && lastElement.dotDotDotToken); - if (elements.length === 0 || elements.length === 1 && hasRestElement) { - return languageVersion >= ScriptTarget.ES2015 ? createIterableType(anyType) : anyArrayType; - } - const elementTypes = map(elements, e => isOmittedExpression(e) ? anyType : getTypeFromBindingElement(e, includePatternInType, reportErrors)); - const minLength = findLastIndex(elements, e => !isOmittedExpression(e) && !hasDefaultValue(e), elements.length - (hasRestElement ? 2 : 1)) + 1; - let result = createTupleType(elementTypes, minLength, hasRestElement); - if (includePatternInType) { - result = cloneTypeReference(result); - result.pattern = pattern; - result.objectFlags |= ObjectFlags.ContainsObjectOrArrayLiteral; - } - return result; - } - - // Return the type implied by a binding pattern. This is the type implied purely by the binding pattern itself - // and without regard to its context (i.e. without regard any type annotation or initializer associated with the - // declaration in which the binding pattern is contained). For example, the implied type of [x, y] is [any, any] - // and the implied type of { x, y: z = 1 } is { x: any; y: number; }. The type implied by a binding pattern is - // used as the contextual type of an initializer associated with the binding pattern. Also, for a destructuring - // parameter with no type annotation or initializer, the type implied by the binding pattern becomes the type of - // the parameter. - function getTypeFromBindingPattern(pattern: BindingPattern, includePatternInType = false, reportErrors = false): Type { - return pattern.kind === SyntaxKind.ObjectBindingPattern - ? getTypeFromObjectBindingPattern(pattern, includePatternInType, reportErrors) - : getTypeFromArrayBindingPattern(pattern, includePatternInType, reportErrors); - } - - // Return the type associated with a variable, parameter, or property declaration. In the simple case this is the type - // specified in a type annotation or inferred from an initializer. However, in the case of a destructuring declaration it - // is a bit more involved. For example: - // - // var [x, s = ""] = [1, "one"]; - // - // Here, the array literal [1, "one"] is contextually typed by the type [any, string], which is the implied type of the - // binding pattern [x, s = ""]. Because the contextual type is a tuple type, the resulting type of [1, "one"] is the - // tuple type [number, string]. Thus, the type inferred for 'x' is number and the type inferred for 's' is string. - function getWidenedTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement, reportErrors?: boolean): Type { - return widenTypeForVariableLikeDeclaration(getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true), declaration, reportErrors); - } - - function widenTypeForVariableLikeDeclaration(type: Type | undefined, declaration: any, reportErrors?: boolean) { - if (type) { - if (reportErrors) { - reportErrorsFromWidening(declaration, type); - } - - // always widen a 'unique symbol' type if the type was created for a different declaration. - if (type.flags & TypeFlags.UniqueESSymbol && (isBindingElement(declaration) || !declaration.type) && type.symbol !== getSymbolOfNode(declaration)) { - type = esSymbolType; - } - - return getWidenedType(type); - } - - // Rest parameters default to type any[], other parameters default to type any - type = isParameter(declaration) && declaration.dotDotDotToken ? anyArrayType : anyType; - - // Report implicit any errors unless this is a private property within an ambient declaration - if (reportErrors) { - if (!declarationBelongsToPrivateAmbientMember(declaration)) { - reportImplicitAny(declaration, type); - } - } - return type; - } - - function declarationBelongsToPrivateAmbientMember(declaration: VariableLikeDeclaration) { - const root = getRootDeclaration(declaration); - const memberDeclaration = root.kind === SyntaxKind.Parameter ? root.parent : root; - return isPrivateWithinAmbient(memberDeclaration); - } - - function tryGetTypeFromEffectiveTypeNode(declaration: Declaration) { - const typeNode = getEffectiveTypeAnnotationNode(declaration); - if (typeNode) { - return getTypeFromTypeNode(typeNode); - } - } - - function getTypeOfVariableOrParameterOrProperty(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - if (!links.type) { - const type = getTypeOfVariableOrParameterOrPropertyWorker(symbol); - // For a contextually typed parameter it is possible that a type has already - // been assigned (in assignTypeToParameterAndFixTypeParameters), and we want - // to preserve this type. - if (!links.type) { - links.type = type; - } - } - return links.type; - } - - function getTypeOfVariableOrParameterOrPropertyWorker(symbol: Symbol) { - // Handle prototype property - if (symbol.flags & SymbolFlags.Prototype) { - return getTypeOfPrototypeProperty(symbol); - } - // CommonsJS require and module both have type any. - if (symbol === requireSymbol) { - return anyType; - } - if (symbol.flags & SymbolFlags.ModuleExports) { - const fileSymbol = getSymbolOfNode(getSourceFileOfNode(symbol.valueDeclaration)); - const members = createSymbolTable(); - members.set("exports" as __String, fileSymbol); - return createAnonymousType(symbol, members, emptyArray, emptyArray, undefined, undefined); - } - // Handle catch clause variables - const declaration = symbol.valueDeclaration; - if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) { - return anyType; - } - // Handle export default expressions - if (isSourceFile(declaration) && isJsonSourceFile(declaration)) { - if (!declaration.statements.length) { - return emptyObjectType; - } - return getWidenedType(getWidenedLiteralType(checkExpression(declaration.statements[0].expression))); - } - - // Handle variable, parameter or property - if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { - // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` - if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) { - return getTypeOfFuncClassEnumModule(symbol); - } - return reportCircularityError(symbol); - } - let type: Type | undefined; - if (declaration.kind === SyntaxKind.ExportAssignment) { - type = widenTypeForVariableLikeDeclaration(checkExpressionCached((declaration).expression), declaration); - } - else if ( - isBinaryExpression(declaration) || - (isInJSFile(declaration) && - (isCallExpression(declaration) || (isPropertyAccessExpression(declaration) || isBindableStaticElementAccessExpression(declaration)) && isBinaryExpression(declaration.parent)))) { - type = getWidenedTypeForAssignmentDeclaration(symbol); - } - else if (isJSDocPropertyLikeTag(declaration) - || isPropertyAccessExpression(declaration) - || isElementAccessExpression(declaration) - || isIdentifier(declaration) - || isStringLiteralLike(declaration) - || isNumericLiteral(declaration) - || isClassDeclaration(declaration) - || isFunctionDeclaration(declaration) - || (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration)) - || isMethodSignature(declaration) - || isSourceFile(declaration)) { - // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { - return getTypeOfFuncClassEnumModule(symbol); - } - type = isBinaryExpression(declaration.parent) ? - getWidenedTypeForAssignmentDeclaration(symbol) : - tryGetTypeFromEffectiveTypeNode(declaration) || anyType; - } - else if (isPropertyAssignment(declaration)) { - type = tryGetTypeFromEffectiveTypeNode(declaration) || checkPropertyAssignment(declaration); - } - else if (isJsxAttribute(declaration)) { - type = tryGetTypeFromEffectiveTypeNode(declaration) || checkJsxAttribute(declaration); - } - else if (isShorthandPropertyAssignment(declaration)) { - type = tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionForMutableLocation(declaration.name, CheckMode.Normal); - } - else if (isObjectLiteralMethod(declaration)) { - type = tryGetTypeFromEffectiveTypeNode(declaration) || checkObjectLiteralMethod(declaration, CheckMode.Normal); - } - else if (isParameter(declaration) - || isPropertyDeclaration(declaration) - || isPropertySignature(declaration) - || isVariableDeclaration(declaration) - || isBindingElement(declaration)) { - type = getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true); - } - // getTypeOfSymbol dispatches some JS merges incorrectly because their symbol flags are not mutually exclusive. - // Re-dispatch based on valueDeclaration.kind instead. - else if (isEnumDeclaration(declaration)) { - type = getTypeOfFuncClassEnumModule(symbol); - } - else if (isEnumMember(declaration)) { - type = getTypeOfEnumMember(symbol); - } - else if (isAccessor(declaration)) { - type = resolveTypeOfAccessors(symbol); - } - else { - return Debug.fail("Unhandled declaration kind! " + Debug.formatSyntaxKind(declaration.kind) + " for " + Debug.formatSymbol(symbol)); - } - - if (!popTypeResolution()) { - // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` - if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) { - return getTypeOfFuncClassEnumModule(symbol); - } - return reportCircularityError(symbol); - } - return type; - } - - function getAnnotatedAccessorTypeNode(accessor: AccessorDeclaration | undefined): TypeNode | undefined { - if (accessor) { - if (accessor.kind === SyntaxKind.GetAccessor) { - const getterTypeAnnotation = getEffectiveReturnTypeNode(accessor); - return getterTypeAnnotation; - } - else { - const setterTypeAnnotation = getEffectiveSetAccessorTypeAnnotationNode(accessor); - return setterTypeAnnotation; - } - } - return undefined; - } - - function getAnnotatedAccessorType(accessor: AccessorDeclaration | undefined): Type | undefined { - const node = getAnnotatedAccessorTypeNode(accessor); - return node && getTypeFromTypeNode(node); - } - - function getAnnotatedAccessorThisParameter(accessor: AccessorDeclaration): Symbol | undefined { - const parameter = getAccessorThisParameter(accessor); - return parameter && parameter.symbol; - } - - function getThisTypeOfDeclaration(declaration: SignatureDeclaration): Type | undefined { - return getThisTypeOfSignature(getSignatureFromDeclaration(declaration)); - } - - function getTypeOfAccessors(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - return links.type || (links.type = getTypeOfAccessorsWorker(symbol)); - } - - function getTypeOfAccessorsWorker(symbol: Symbol): Type { - if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { - return errorType; - } - - let type = resolveTypeOfAccessors(symbol); - - if (!popTypeResolution()) { - type = anyType; - if (noImplicitAny) { - const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); - error(getter, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, symbolToString(symbol)); - } - } - return type; - } - - function resolveTypeOfAccessors(symbol: Symbol) { - const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); - const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor); - - if (getter && isInJSFile(getter)) { - const jsDocType = getTypeForDeclarationFromJSDocComment(getter); - if (jsDocType) { - return jsDocType; - } - } - // First try to see if the user specified a return type on the get-accessor. - const getterReturnType = getAnnotatedAccessorType(getter); - if (getterReturnType) { - return getterReturnType; - } - else { - // If the user didn't specify a return type, try to use the set-accessor's parameter type. - const setterParameterType = getAnnotatedAccessorType(setter); - if (setterParameterType) { - return setterParameterType; - } - else { - // If there are no specified types, try to infer it from the body of the get accessor if it exists. - if (getter && getter.body) { - return getReturnTypeFromBody(getter); - } - // Otherwise, fall back to 'any'. - else { - if (setter) { - if (!isPrivateWithinAmbient(setter)) { - errorOrSuggestion(noImplicitAny, setter, Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation, symbolToString(symbol)); - } - } - else { - Debug.assert(!!getter, "there must exist a getter as we are current checking either setter or getter in this function"); - if (!isPrivateWithinAmbient(getter!)) { - errorOrSuggestion(noImplicitAny, getter!, Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation, symbolToString(symbol)); - } - } - return anyType; - } - } - } - } - - function getBaseTypeVariableOfClass(symbol: Symbol) { - const baseConstructorType = getBaseConstructorTypeOfClass(getDeclaredTypeOfClassOrInterface(symbol)); - return baseConstructorType.flags & TypeFlags.TypeVariable ? baseConstructorType : - baseConstructorType.flags & TypeFlags.Intersection ? find((baseConstructorType as IntersectionType).types, t => !!(t.flags & TypeFlags.TypeVariable)) : - undefined; - } - - function getTypeOfFuncClassEnumModule(symbol: Symbol): Type { - let links = getSymbolLinks(symbol); - const originalLinks = links; - if (!links.type) { - const jsDeclaration = getDeclarationOfExpando(symbol.valueDeclaration); - if (jsDeclaration) { - const merged = mergeJSSymbols(symbol, getSymbolOfNode(jsDeclaration)); - if (merged) { - // note:we overwrite links because we just cloned the symbol - symbol = links = merged; - } - } - originalLinks.type = links.type = getTypeOfFuncClassEnumModuleWorker(symbol); - } - return links.type; - } - - function getTypeOfFuncClassEnumModuleWorker(symbol: Symbol): Type { - const declaration = symbol.valueDeclaration; - if (symbol.flags & SymbolFlags.Module && isShorthandAmbientModuleSymbol(symbol)) { - return anyType; - } - else if (declaration.kind === SyntaxKind.BinaryExpression || - (declaration.kind === SyntaxKind.PropertyAccessExpression || declaration.kind === SyntaxKind.ElementAccessExpression) && - declaration.parent.kind === SyntaxKind.BinaryExpression) { - return getWidenedTypeForAssignmentDeclaration(symbol); - } - else if (symbol.flags & SymbolFlags.ValueModule && declaration && isSourceFile(declaration) && declaration.commonJsModuleIndicator) { - const resolvedModule = resolveExternalModuleSymbol(symbol); - if (resolvedModule !== symbol) { - if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { - return errorType; - } - const exportEquals = getMergedSymbol(symbol.exports!.get(InternalSymbolName.ExportEquals)!); - const type = getWidenedTypeForAssignmentDeclaration(exportEquals, exportEquals === resolvedModule ? undefined : resolvedModule); - if (!popTypeResolution()) { - return reportCircularityError(symbol); - } - return type; - } - } - const type = createObjectType(ObjectFlags.Anonymous, symbol); - if (symbol.flags & SymbolFlags.Class) { - const baseTypeVariable = getBaseTypeVariableOfClass(symbol); - return baseTypeVariable ? getIntersectionType([type, baseTypeVariable]) : type; - } - else { - return strictNullChecks && symbol.flags & SymbolFlags.Optional ? getOptionalType(type) : type; - } - } - - function getTypeOfEnumMember(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - return links.type || (links.type = getDeclaredTypeOfEnumMember(symbol)); - } - - function getTypeOfAlias(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - if (!links.type) { - const targetSymbol = resolveAlias(symbol); - - // It only makes sense to get the type of a value symbol. If the result of resolving - // the alias is not a value, then it has no type. To get the type associated with a - // type symbol, call getDeclaredTypeOfSymbol. - // This check is important because without it, a call to getTypeOfSymbol could end - // up recursively calling getTypeOfAlias, causing a stack overflow. - links.type = targetSymbol.flags & SymbolFlags.Value - ? getTypeOfSymbol(targetSymbol) - : errorType; - } - return links.type; - } - - function getTypeOfInstantiatedSymbol(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - if (!links.type) { - if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { - return links.type = errorType; - } - let type = instantiateType(getTypeOfSymbol(links.target!), links.mapper); - if (!popTypeResolution()) { - type = reportCircularityError(symbol); - } - links.type = type; - } - return links.type; - } - - function reportCircularityError(symbol: Symbol) { - const declaration = symbol.valueDeclaration; - // Check if variable has type annotation that circularly references the variable itself - if (getEffectiveTypeAnnotationNode(declaration)) { - error(symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, - symbolToString(symbol)); - return errorType; - } - // Check if variable has initializer that circularly references the variable itself - if (noImplicitAny && (declaration.kind !== SyntaxKind.Parameter || (declaration).initializer)) { - error(symbol.valueDeclaration, Diagnostics._0_implicitly_has_type_any_because_it_does_not_have_a_type_annotation_and_is_referenced_directly_or_indirectly_in_its_own_initializer, - symbolToString(symbol)); - } - // Circularities could also result from parameters in function expressions that end up - // having themselves as contextual types following type argument inference. In those cases - // we have already reported an implicit any error so we don't report anything here. - return anyType; - } - - function getTypeOfSymbolWithDeferredType(symbol: Symbol) { - const links = getSymbolLinks(symbol); - if (!links.type) { - Debug.assertDefined(links.deferralParent); - Debug.assertDefined(links.deferralConstituents); - links.type = links.deferralParent!.flags & TypeFlags.Union ? getUnionType(links.deferralConstituents!) : getIntersectionType(links.deferralConstituents!); - } - return links.type; - } - - function getTypeOfSymbol(symbol: Symbol): Type { - if (getCheckFlags(symbol) & CheckFlags.DeferredType) { - return getTypeOfSymbolWithDeferredType(symbol); - } - if (getCheckFlags(symbol) & CheckFlags.Instantiated) { - return getTypeOfInstantiatedSymbol(symbol); - } - if (getCheckFlags(symbol) & CheckFlags.ReverseMapped) { - return getTypeOfReverseMappedSymbol(symbol as ReverseMappedSymbol); - } - if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { - return getTypeOfVariableOrParameterOrProperty(symbol); - } - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { - return getTypeOfFuncClassEnumModule(symbol); - } - if (symbol.flags & SymbolFlags.EnumMember) { - return getTypeOfEnumMember(symbol); - } - if (symbol.flags & SymbolFlags.Accessor) { - return getTypeOfAccessors(symbol); - } - if (symbol.flags & SymbolFlags.Alias) { - return getTypeOfAlias(symbol); - } - return errorType; - } - - function isReferenceToType(type: Type, target: Type) { - return type !== undefined - && target !== undefined - && (getObjectFlags(type) & ObjectFlags.Reference) !== 0 - && (type).target === target; - } - - function getTargetType(type: Type): Type { - return getObjectFlags(type) & ObjectFlags.Reference ? (type).target : type; - } - - // TODO: GH#18217 If `checkBase` is undefined, we should not call this because this will always return false. - function hasBaseType(type: Type, checkBase: Type | undefined) { - return check(type); - function check(type: Type): boolean { - if (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference)) { - const target = getTargetType(type); - return target === checkBase || some(getBaseTypes(target), check); - } - else if (type.flags & TypeFlags.Intersection) { - return some((type).types, check); - } - return false; - } - } - - // Appends the type parameters given by a list of declarations to a set of type parameters and returns the resulting set. - // The function allocates a new array if the input type parameter set is undefined, but otherwise it modifies the set - // in-place and returns the same array. - function appendTypeParameters(typeParameters: TypeParameter[] | undefined, declarations: readonly TypeParameterDeclaration[]): TypeParameter[] | undefined { - for (const declaration of declarations) { - typeParameters = appendIfUnique(typeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode(declaration))); - } - return typeParameters; - } - - // Return the outer type parameters of a node or undefined if the node has no outer type parameters. - function getOuterTypeParameters(node: Node, includeThisTypes?: boolean): TypeParameter[] | undefined { - while (true) { - node = node.parent; // TODO: GH#18217 Use SourceFile kind check instead - if (node && isBinaryExpression(node)) { - // prototype assignments get the outer type parameters of their constructor function - const assignmentKind = getAssignmentDeclarationKind(node); - if (assignmentKind === AssignmentDeclarationKind.Prototype || assignmentKind === AssignmentDeclarationKind.PrototypeProperty) { - const symbol = getSymbolOfNode(node.left); - if (symbol && symbol.parent && !findAncestor(symbol.parent.valueDeclaration, d => node === d)) { - node = symbol.parent.valueDeclaration; - } - } - } - if (!node) { - return undefined; - } - switch (node.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.MethodSignature: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.JSDocFunctionType: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.JSDocTemplateTag: - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocEnumTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.MappedType: - case SyntaxKind.ConditionalType: - const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes); - if (node.kind === SyntaxKind.MappedType) { - return append(outerTypeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode((node).typeParameter))); - } - else if (node.kind === SyntaxKind.ConditionalType) { - return concatenate(outerTypeParameters, getInferTypeParameters(node)); - } - const outerAndOwnTypeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(node)); - const thisType = includeThisTypes && - (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.InterfaceDeclaration || isJSConstructor(node)) && - getDeclaredTypeOfClassOrInterface(getSymbolOfNode(node as ClassLikeDeclaration | InterfaceDeclaration)).thisType; - return thisType ? append(outerAndOwnTypeParameters, thisType) : outerAndOwnTypeParameters; - } - } - } - - // The outer type parameters are those defined by enclosing generic classes, methods, or functions. - function getOuterTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] | undefined { - const declaration = symbol.flags & SymbolFlags.Class ? symbol.valueDeclaration : getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration)!; - Debug.assert(!!declaration, "Class was missing valueDeclaration -OR- non-class had no interface declarations"); - return getOuterTypeParameters(declaration); - } - - // The local type parameters are the combined set of type parameters from all declarations of the class, - // interface, or type alias. - function getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): TypeParameter[] | undefined { - let result: TypeParameter[] | undefined; - for (const node of symbol.declarations) { - if (node.kind === SyntaxKind.InterfaceDeclaration || - node.kind === SyntaxKind.ClassDeclaration || - node.kind === SyntaxKind.ClassExpression || - isJSConstructor(node) || - isTypeAlias(node)) { - const declaration = node; - result = appendTypeParameters(result, getEffectiveTypeParameterDeclarations(declaration)); - } - } - return result; - } - - // The full set of type parameters for a generic class or interface type consists of its outer type parameters plus - // its locally declared type parameters. - function getTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] | undefined { - return concatenate(getOuterTypeParametersOfClassOrInterface(symbol), getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol)); - } - - // A type is a mixin constructor if it has a single construct signature taking no type parameters and a single - // rest parameter of type any[]. - function isMixinConstructorType(type: Type) { - const signatures = getSignaturesOfType(type, SignatureKind.Construct); - if (signatures.length === 1) { - const s = signatures[0]; - return !s.typeParameters && s.parameters.length === 1 && signatureHasRestParameter(s) && getElementTypeOfArrayType(getTypeOfParameter(s.parameters[0])) === anyType; - } - return false; - } - - function isConstructorType(type: Type): boolean { - if (getSignaturesOfType(type, SignatureKind.Construct).length > 0) { - return true; - } - if (type.flags & TypeFlags.TypeVariable) { - const constraint = getBaseConstraintOfType(type); - return !!constraint && isMixinConstructorType(constraint); - } - return false; - } - - function getBaseTypeNodeOfClass(type: InterfaceType): ExpressionWithTypeArguments | undefined { - return getEffectiveBaseTypeNode(type.symbol.valueDeclaration as ClassLikeDeclaration); - } - - function getConstructorsForTypeArguments(type: Type, typeArgumentNodes: readonly TypeNode[] | undefined, location: Node): readonly Signature[] { - const typeArgCount = length(typeArgumentNodes); - const isJavascript = isInJSFile(location); - return filter(getSignaturesOfType(type, SignatureKind.Construct), - sig => (isJavascript || typeArgCount >= getMinTypeArgumentCount(sig.typeParameters)) && typeArgCount <= length(sig.typeParameters)); - } - - function getInstantiatedConstructorsForTypeArguments(type: Type, typeArgumentNodes: readonly TypeNode[] | undefined, location: Node): readonly Signature[] { - const signatures = getConstructorsForTypeArguments(type, typeArgumentNodes, location); - const typeArguments = map(typeArgumentNodes, getTypeFromTypeNode); - return sameMap(signatures, sig => some(sig.typeParameters) ? getSignatureInstantiation(sig, typeArguments, isInJSFile(location)) : sig); - } - - /** - * The base constructor of a class can resolve to - * * undefinedType if the class has no extends clause, - * * unknownType if an error occurred during resolution of the extends expression, - * * nullType if the extends expression is the null value, - * * anyType if the extends expression has type any, or - * * an object type with at least one construct signature. - */ - function getBaseConstructorTypeOfClass(type: InterfaceType): Type { - if (!type.resolvedBaseConstructorType) { - const decl = type.symbol.valueDeclaration; - const extended = getEffectiveBaseTypeNode(decl); - const baseTypeNode = getBaseTypeNodeOfClass(type); - if (!baseTypeNode) { - return type.resolvedBaseConstructorType = undefinedType; - } - if (!pushTypeResolution(type, TypeSystemPropertyName.ResolvedBaseConstructorType)) { - return errorType; - } - const baseConstructorType = checkExpression(baseTypeNode.expression); - if (extended && baseTypeNode !== extended) { - Debug.assert(!extended.typeArguments); // Because this is in a JS file, and baseTypeNode is in an @extends tag - checkExpression(extended.expression); - } - if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection)) { - // Resolving the members of a class requires us to resolve the base class of that class. - // We force resolution here such that we catch circularities now. - resolveStructuredTypeMembers(baseConstructorType); - } - if (!popTypeResolution()) { - error(type.symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_base_expression, symbolToString(type.symbol)); - return type.resolvedBaseConstructorType = errorType; - } - if (!(baseConstructorType.flags & TypeFlags.Any) && baseConstructorType !== nullWideningType && !isConstructorType(baseConstructorType)) { - const err = error(baseTypeNode.expression, Diagnostics.Type_0_is_not_a_constructor_function_type, typeToString(baseConstructorType)); - if (baseConstructorType.flags & TypeFlags.TypeParameter) { - const constraint = getConstraintFromTypeParameter(baseConstructorType); - let ctorReturn: Type = unknownType; - if (constraint) { - const ctorSig = getSignaturesOfType(constraint, SignatureKind.Construct); - if (ctorSig[0]) { - ctorReturn = getReturnTypeOfSignature(ctorSig[0]); - } - } - addRelatedInfo(err, createDiagnosticForNode(baseConstructorType.symbol.declarations[0], Diagnostics.Did_you_mean_for_0_to_be_constrained_to_type_new_args_Colon_any_1, symbolToString(baseConstructorType.symbol), typeToString(ctorReturn))); - } - return type.resolvedBaseConstructorType = errorType; - } - type.resolvedBaseConstructorType = baseConstructorType; - } - return type.resolvedBaseConstructorType; - } - - function getBaseTypes(type: InterfaceType): BaseType[] { - if (!type.resolvedBaseTypes) { - if (type.objectFlags & ObjectFlags.Tuple) { - type.resolvedBaseTypes = [createArrayType(getUnionType(type.typeParameters || emptyArray), (type).readonly)]; - } - else if (type.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - if (type.symbol.flags & SymbolFlags.Class) { - resolveBaseTypesOfClass(type); - } - if (type.symbol.flags & SymbolFlags.Interface) { - resolveBaseTypesOfInterface(type); - } - } - else { - Debug.fail("type must be class or interface"); - } - } - return type.resolvedBaseTypes; - } - - function resolveBaseTypesOfClass(type: InterfaceType) { - type.resolvedBaseTypes = resolvingEmptyArray; - const baseConstructorType = getApparentType(getBaseConstructorTypeOfClass(type)); - if (!(baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Any))) { - return type.resolvedBaseTypes = emptyArray; - } - const baseTypeNode = getBaseTypeNodeOfClass(type)!; - let baseType: Type; - const originalBaseType = baseConstructorType.symbol ? getDeclaredTypeOfSymbol(baseConstructorType.symbol) : undefined; - if (baseConstructorType.symbol && baseConstructorType.symbol.flags & SymbolFlags.Class && - areAllOuterTypeParametersApplied(originalBaseType!)) { - // When base constructor type is a class with no captured type arguments we know that the constructors all have the same type parameters as the - // class and all return the instance type of the class. There is no need for further checks and we can apply the - // type arguments in the same manner as a type reference to get the same error reporting experience. - baseType = getTypeFromClassOrInterfaceReference(baseTypeNode, baseConstructorType.symbol); - } - else if (baseConstructorType.flags & TypeFlags.Any) { - baseType = baseConstructorType; - } - else { - // The class derives from a "class-like" constructor function, check that we have at least one construct signature - // with a matching number of type parameters and use the return type of the first instantiated signature. Elsewhere - // we check that all instantiated signatures return the same type. - const constructors = getInstantiatedConstructorsForTypeArguments(baseConstructorType, baseTypeNode.typeArguments, baseTypeNode); - if (!constructors.length) { - error(baseTypeNode.expression, Diagnostics.No_base_constructor_has_the_specified_number_of_type_arguments); - return type.resolvedBaseTypes = emptyArray; - } - baseType = getReturnTypeOfSignature(constructors[0]); - } - - if (baseType === errorType) { - return type.resolvedBaseTypes = emptyArray; - } - if (!isValidBaseType(baseType)) { - error(baseTypeNode.expression, Diagnostics.Base_constructor_return_type_0_is_not_an_object_type_or_intersection_of_object_types_with_statically_known_members, typeToString(baseType)); - return type.resolvedBaseTypes = emptyArray; - } - if (type === baseType || hasBaseType(baseType, type)) { - error(type.symbol.valueDeclaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, - typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); - return type.resolvedBaseTypes = emptyArray; - } - if (type.resolvedBaseTypes === resolvingEmptyArray) { - // Circular reference, likely through instantiation of default parameters - // (otherwise there'd be an error from hasBaseType) - this is fine, but `.members` should be reset - // as `getIndexedAccessType` via `instantiateType` via `getTypeFromClassOrInterfaceReference` forces a - // partial instantiation of the members without the base types fully resolved - type.members = undefined; - } - return type.resolvedBaseTypes = [baseType]; - } - - function areAllOuterTypeParametersApplied(type: Type): boolean { // TODO: GH#18217 Shouldn't this take an InterfaceType? - // An unapplied type parameter has its symbol still the same as the matching argument symbol. - // Since parameters are applied outer-to-inner, only the last outer parameter needs to be checked. - const outerTypeParameters = (type).outerTypeParameters; - if (outerTypeParameters) { - const last = outerTypeParameters.length - 1; - const typeArguments = getTypeArguments(type); - return outerTypeParameters[last].symbol !== typeArguments[last].symbol; - } - return true; - } - - // A valid base type is `any`, an object type or intersection of object types. - function isValidBaseType(type: Type): type is BaseType { - if (type.flags & TypeFlags.TypeParameter) { - const constraint = getBaseConstraintOfType(type); - if (constraint) { - return isValidBaseType(constraint); - } - } - // TODO: Given that we allow type parmeters here now, is this `!isGenericMappedType(type)` check really needed? - // There's no reason a `T` should be allowed while a `Readonly` should not. - return !!(type.flags & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.Any)) && !isGenericMappedType(type) || - !!(type.flags & TypeFlags.Intersection) && every((type).types, isValidBaseType); - } - - function resolveBaseTypesOfInterface(type: InterfaceType): void { - type.resolvedBaseTypes = type.resolvedBaseTypes || emptyArray; - for (const declaration of type.symbol.declarations) { - if (declaration.kind === SyntaxKind.InterfaceDeclaration && getInterfaceBaseTypeNodes(declaration)) { - for (const node of getInterfaceBaseTypeNodes(declaration)!) { - const baseType = getTypeFromTypeNode(node); - if (baseType !== errorType) { - if (isValidBaseType(baseType)) { - if (type !== baseType && !hasBaseType(baseType, type)) { - if (type.resolvedBaseTypes === emptyArray) { - type.resolvedBaseTypes = [baseType]; - } - else { - type.resolvedBaseTypes.push(baseType); - } - } - else { - error(declaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); - } - } - else { - error(node, Diagnostics.An_interface_can_only_extend_an_object_type_or_intersection_of_object_types_with_statically_known_members); - } - } - } - } - } - } - - /** - * Returns true if the interface given by the symbol is free of "this" references. - * - * Specifically, the result is true if the interface itself contains no references - * to "this" in its body, if all base types are interfaces, - * and if none of the base interfaces have a "this" type. - */ - function isThislessInterface(symbol: Symbol): boolean { - for (const declaration of symbol.declarations) { - if (declaration.kind === SyntaxKind.InterfaceDeclaration) { - if (declaration.flags & NodeFlags.ContainsThis) { - return false; - } - const baseTypeNodes = getInterfaceBaseTypeNodes(declaration); - if (baseTypeNodes) { - for (const node of baseTypeNodes) { - if (isEntityNameExpression(node.expression)) { - const baseSymbol = resolveEntityName(node.expression, SymbolFlags.Type, /*ignoreErrors*/ true); - if (!baseSymbol || !(baseSymbol.flags & SymbolFlags.Interface) || getDeclaredTypeOfClassOrInterface(baseSymbol).thisType) { - return false; - } - } - } - } - } - } - return true; - } - - function getDeclaredTypeOfClassOrInterface(symbol: Symbol): InterfaceType { - let links = getSymbolLinks(symbol); - const originalLinks = links; - if (!links.declaredType) { - const kind = symbol.flags & SymbolFlags.Class ? ObjectFlags.Class : ObjectFlags.Interface; - const merged = mergeJSSymbols(symbol, getAssignedClassSymbol(symbol.valueDeclaration)); - if (merged) { - // note:we overwrite links because we just cloned the symbol - symbol = links = merged; - } - - const type = originalLinks.declaredType = links.declaredType = createObjectType(kind, symbol); - const outerTypeParameters = getOuterTypeParametersOfClassOrInterface(symbol); - const localTypeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); - // A class or interface is generic if it has type parameters or a "this" type. We always give classes a "this" type - // because it is not feasible to analyze all members to determine if the "this" type escapes the class (in particular, - // property types inferred from initializers and method return types inferred from return statements are very hard - // to exhaustively analyze). We give interfaces a "this" type if we can't definitely determine that they are free of - // "this" references. - if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || !isThislessInterface(symbol)) { - type.objectFlags |= ObjectFlags.Reference; - type.typeParameters = concatenate(outerTypeParameters, localTypeParameters); - type.outerTypeParameters = outerTypeParameters; - type.localTypeParameters = localTypeParameters; - (type).instantiations = createMap(); - (type).instantiations.set(getTypeListId(type.typeParameters), type); - (type).target = type; - (type).resolvedTypeArguments = type.typeParameters; - type.thisType = createTypeParameter(symbol); - type.thisType.isThisType = true; - type.thisType.constraint = type; - } - } - return links.declaredType; - } - - function getDeclaredTypeOfTypeAlias(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - if (!links.declaredType) { - // Note that we use the links object as the target here because the symbol object is used as the unique - // identity for resolution of the 'type' property in SymbolLinks. - if (!pushTypeResolution(symbol, TypeSystemPropertyName.DeclaredType)) { - return errorType; - } - - const declaration = find(symbol.declarations, isTypeAlias); - if (!declaration) { - return Debug.fail("Type alias symbol with no valid declaration found"); - } - const typeNode = isJSDocTypeAlias(declaration) ? declaration.typeExpression : declaration.type; - // If typeNode is missing, we will error in checkJSDocTypedefTag. - let type = typeNode ? getTypeFromTypeNode(typeNode) : errorType; - - if (popTypeResolution()) { - const typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); - if (typeParameters) { - // Initialize the instantiation cache for generic type aliases. The declared type corresponds to - // an instantiation of the type alias with the type parameters supplied as type arguments. - links.typeParameters = typeParameters; - links.instantiations = createMap(); - links.instantiations.set(getTypeListId(typeParameters), type); - } - } - else { - type = errorType; - error(isJSDocEnumTag(declaration) ? declaration : declaration.name || declaration, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol)); - } - links.declaredType = type; - } - return links.declaredType; - } - - function isStringConcatExpression(expr: Node): boolean { - if (isStringLiteralLike(expr)) { - return true; - } - else if (expr.kind === SyntaxKind.BinaryExpression) { - return isStringConcatExpression((expr).left) && isStringConcatExpression((expr).right); - } - return false; - } - - function isLiteralEnumMember(member: EnumMember) { - const expr = member.initializer; - if (!expr) { - return !(member.flags & NodeFlags.Ambient); - } - switch (expr.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - return true; - case SyntaxKind.PrefixUnaryExpression: - return (expr).operator === SyntaxKind.MinusToken && - (expr).operand.kind === SyntaxKind.NumericLiteral; - case SyntaxKind.Identifier: - return nodeIsMissing(expr) || !!getSymbolOfNode(member.parent).exports!.get((expr).escapedText); - case SyntaxKind.BinaryExpression: - return isStringConcatExpression(expr); - default: - return false; - } - } - - function getEnumKind(symbol: Symbol): EnumKind { - const links = getSymbolLinks(symbol); - if (links.enumKind !== undefined) { - return links.enumKind; - } - let hasNonLiteralMember = false; - for (const declaration of symbol.declarations) { - if (declaration.kind === SyntaxKind.EnumDeclaration) { - for (const member of (declaration).members) { - if (member.initializer && isStringLiteralLike(member.initializer)) { - return links.enumKind = EnumKind.Literal; - } - if (!isLiteralEnumMember(member)) { - hasNonLiteralMember = true; - } - } - } - } - return links.enumKind = hasNonLiteralMember ? EnumKind.Numeric : EnumKind.Literal; - } - - function getBaseTypeOfEnumLiteralType(type: Type) { - return type.flags & TypeFlags.EnumLiteral && !(type.flags & TypeFlags.Union) ? getDeclaredTypeOfSymbol(getParentOfSymbol(type.symbol)!) : type; - } - - function getDeclaredTypeOfEnum(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - if (links.declaredType) { - return links.declaredType; - } - if (getEnumKind(symbol) === EnumKind.Literal) { - enumCount++; - const memberTypeList: Type[] = []; - for (const declaration of symbol.declarations) { - if (declaration.kind === SyntaxKind.EnumDeclaration) { - for (const member of (declaration).members) { - const value = getEnumMemberValue(member); - const memberType = getFreshTypeOfLiteralType(getLiteralType(value !== undefined ? value : 0, enumCount, getSymbolOfNode(member))); - getSymbolLinks(getSymbolOfNode(member)).declaredType = memberType; - memberTypeList.push(getRegularTypeOfLiteralType(memberType)); - } - } - } - if (memberTypeList.length) { - const enumType = getUnionType(memberTypeList, UnionReduction.Literal, symbol, /*aliasTypeArguments*/ undefined); - if (enumType.flags & TypeFlags.Union) { - enumType.flags |= TypeFlags.EnumLiteral; - enumType.symbol = symbol; - } - return links.declaredType = enumType; - } - } - const enumType = createType(TypeFlags.Enum); - enumType.symbol = symbol; - return links.declaredType = enumType; - } - - function getDeclaredTypeOfEnumMember(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - if (!links.declaredType) { - const enumType = getDeclaredTypeOfEnum(getParentOfSymbol(symbol)!); - if (!links.declaredType) { - links.declaredType = enumType; - } - } - return links.declaredType; - } - - function getDeclaredTypeOfTypeParameter(symbol: Symbol): TypeParameter { - const links = getSymbolLinks(symbol); - return links.declaredType || (links.declaredType = createTypeParameter(symbol)); - } - - function getDeclaredTypeOfAlias(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - return links.declaredType || (links.declaredType = getDeclaredTypeOfSymbol(resolveAlias(symbol))); - } - - function getDeclaredTypeOfSymbol(symbol: Symbol): Type { - return tryGetDeclaredTypeOfSymbol(symbol) || errorType; - } - - function tryGetDeclaredTypeOfSymbol(symbol: Symbol): Type | undefined { - if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - return getDeclaredTypeOfClassOrInterface(symbol); - } - if (symbol.flags & SymbolFlags.TypeAlias) { - return getDeclaredTypeOfTypeAlias(symbol); - } - if (symbol.flags & SymbolFlags.TypeParameter) { - return getDeclaredTypeOfTypeParameter(symbol); - } - if (symbol.flags & SymbolFlags.Enum) { - return getDeclaredTypeOfEnum(symbol); - } - if (symbol.flags & SymbolFlags.EnumMember) { - return getDeclaredTypeOfEnumMember(symbol); - } - if (symbol.flags & SymbolFlags.Alias) { - return getDeclaredTypeOfAlias(symbol); - } - return undefined; - } - - /** - * A type is free of this references if it's the any, string, number, boolean, symbol, or void keyword, a string - * literal type, an array with an element type that is free of this references, or a type reference that is - * free of this references. - */ - function isThislessType(node: TypeNode): boolean { - switch (node.kind) { - case SyntaxKind.AnyKeyword: - case SyntaxKind.UnknownKeyword: - case SyntaxKind.StringKeyword: - case SyntaxKind.NumberKeyword: - case SyntaxKind.BigIntKeyword: - case SyntaxKind.BooleanKeyword: - case SyntaxKind.SymbolKeyword: - case SyntaxKind.ObjectKeyword: - case SyntaxKind.VoidKeyword: - case SyntaxKind.UndefinedKeyword: - case SyntaxKind.NullKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.LiteralType: - return true; - case SyntaxKind.ArrayType: - return isThislessType((node).elementType); - case SyntaxKind.TypeReference: - return !(node as TypeReferenceNode).typeArguments || (node as TypeReferenceNode).typeArguments!.every(isThislessType); - } - return false; - } - - /** A type parameter is thisless if its constraint is thisless, or if it has no constraint. */ - function isThislessTypeParameter(node: TypeParameterDeclaration) { - const constraint = getEffectiveConstraintOfTypeParameter(node); - return !constraint || isThislessType(constraint); - } - - /** - * A variable-like declaration is free of this references if it has a type annotation - * that is thisless, or if it has no type annotation and no initializer (and is thus of type any). - */ - function isThislessVariableLikeDeclaration(node: VariableLikeDeclaration): boolean { - const typeNode = getEffectiveTypeAnnotationNode(node); - return typeNode ? isThislessType(typeNode) : !hasInitializer(node); - } - - /** - * A function-like declaration is considered free of `this` references if it has a return type - * annotation that is free of this references and if each parameter is thisless and if - * each type parameter (if present) is thisless. - */ - function isThislessFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { - const returnType = getEffectiveReturnTypeNode(node); - const typeParameters = getEffectiveTypeParameterDeclarations(node); - return (node.kind === SyntaxKind.Constructor || (!!returnType && isThislessType(returnType))) && - node.parameters.every(isThislessVariableLikeDeclaration) && - typeParameters.every(isThislessTypeParameter); - } - - /** - * Returns true if the class or interface member given by the symbol is free of "this" references. The - * function may return false for symbols that are actually free of "this" references because it is not - * feasible to perform a complete analysis in all cases. In particular, property members with types - * inferred from their initializers and function members with inferred return types are conservatively - * assumed not to be free of "this" references. - */ - function isThisless(symbol: Symbol): boolean { - if (symbol.declarations && symbol.declarations.length === 1) { - const declaration = symbol.declarations[0]; - if (declaration) { - switch (declaration.kind) { - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - return isThislessVariableLikeDeclaration(declaration); - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return isThislessFunctionLikeDeclaration(declaration); - } - } - } - return false; - } - - // The mappingThisOnly flag indicates that the only type parameter being mapped is "this". When the flag is true, - // we check symbols to see if we can quickly conclude they are free of "this" references, thus needing no instantiation. - function createInstantiatedSymbolTable(symbols: Symbol[], mapper: TypeMapper, mappingThisOnly: boolean): SymbolTable { - const result = createSymbolTable(); - for (const symbol of symbols) { - result.set(symbol.escapedName, mappingThisOnly && isThisless(symbol) ? symbol : instantiateSymbol(symbol, mapper)); - } - return result; - } - - function addInheritedMembers(symbols: SymbolTable, baseSymbols: Symbol[]) { - for (const s of baseSymbols) { - if (!symbols.has(s.escapedName)) { - symbols.set(s.escapedName, s); - } - } - } - - function resolveDeclaredMembers(type: InterfaceType): InterfaceTypeWithDeclaredMembers { - if (!(type).declaredProperties) { - const symbol = type.symbol; - const members = getMembersOfSymbol(symbol); - (type).declaredProperties = getNamedMembers(members); - // Start with signatures at empty array in case of recursive types - (type).declaredCallSignatures = emptyArray; - (type).declaredConstructSignatures = emptyArray; - - (type).declaredCallSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.Call)); - (type).declaredConstructSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.New)); - (type).declaredStringIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.String); - (type).declaredNumberIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.Number); - } - return type; - } - - /** - * Indicates whether a type can be used as a property name. - */ - function isTypeUsableAsPropertyName(type: Type): type is StringLiteralType | NumberLiteralType | UniqueESSymbolType { - return !!(type.flags & TypeFlags.StringOrNumberLiteralOrUnique); - } - - /** - * Indicates whether a declaration name is definitely late-bindable. - * A declaration name is only late-bindable if: - * - It is a `ComputedPropertyName`. - * - Its expression is an `Identifier` or either a `PropertyAccessExpression` an - * `ElementAccessExpression` consisting only of these same three types of nodes. - * - The type of its expression is a string or numeric literal type, or is a `unique symbol` type. - */ - function isLateBindableName(node: DeclarationName): node is LateBoundName { - if (!isComputedPropertyName(node) && !isElementAccessExpression(node)) { - return false; - } - const expr = isComputedPropertyName(node) ? node.expression : node.argumentExpression; - return isEntityNameExpression(expr) - && isTypeUsableAsPropertyName(isComputedPropertyName(node) ? checkComputedPropertyName(node) : checkExpressionCached(expr)); - } - - function isLateBoundName(name: __String): boolean { - return (name as string).charCodeAt(0) === CharacterCodes._ && - (name as string).charCodeAt(1) === CharacterCodes._ && - (name as string).charCodeAt(2) === CharacterCodes.at; - } - - /** - * Indicates whether a declaration has a late-bindable dynamic name. - */ - function hasLateBindableName(node: Declaration): node is LateBoundDeclaration | LateBoundBinaryExpressionDeclaration { - const name = getNameOfDeclaration(node); - return !!name && isLateBindableName(name); - } - - /** - * Indicates whether a declaration has a dynamic name that cannot be late-bound. - */ - function hasNonBindableDynamicName(node: Declaration) { - return hasDynamicName(node) && !hasLateBindableName(node); - } - - /** - * Indicates whether a declaration name is a dynamic name that cannot be late-bound. - */ - function isNonBindableDynamicName(node: DeclarationName) { - return isDynamicName(node) && !isLateBindableName(node); - } - - /** - * Gets the symbolic name for a member from its type. - */ - function getPropertyNameFromType(type: StringLiteralType | NumberLiteralType | UniqueESSymbolType): __String { - if (type.flags & TypeFlags.UniqueESSymbol) { - return (type).escapedName; - } - if (type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { - return escapeLeadingUnderscores("" + (type).value); - } - return Debug.fail(); - } - - /** - * Adds a declaration to a late-bound dynamic member. This performs the same function for - * late-bound members that `addDeclarationToSymbol` in binder.ts performs for early-bound - * members. - */ - function addDeclarationToLateBoundSymbol(symbol: Symbol, member: LateBoundDeclaration | BinaryExpression, symbolFlags: SymbolFlags) { - Debug.assert(!!(getCheckFlags(symbol) & CheckFlags.Late), "Expected a late-bound symbol."); - symbol.flags |= symbolFlags; - getSymbolLinks(member.symbol).lateSymbol = symbol; - if (!symbol.declarations) { - symbol.declarations = [member]; - } - else { - symbol.declarations.push(member); - } - if (symbolFlags & SymbolFlags.Value) { - if (!symbol.valueDeclaration || symbol.valueDeclaration.kind !== member.kind) { - symbol.valueDeclaration = member; - } - } - } - - /** - * Performs late-binding of a dynamic member. This performs the same function for - * late-bound members that `declareSymbol` in binder.ts performs for early-bound - * members. - * - * If a symbol is a dynamic name from a computed property, we perform an additional "late" - * binding phase to attempt to resolve the name for the symbol from the type of the computed - * property's expression. If the type of the expression is a string-literal, numeric-literal, - * or unique symbol type, we can use that type as the name of the symbol. - * - * For example, given: - * - * const x = Symbol(); - * - * interface I { - * [x]: number; - * } - * - * The binder gives the property `[x]: number` a special symbol with the name "__computed". - * In the late-binding phase we can type-check the expression `x` and see that it has a - * unique symbol type which we can then use as the name of the member. This allows users - * to define custom symbols that can be used in the members of an object type. - * - * @param parent The containing symbol for the member. - * @param earlySymbols The early-bound symbols of the parent. - * @param lateSymbols The late-bound symbols of the parent. - * @param decl The member to bind. - */ - function lateBindMember(parent: Symbol, earlySymbols: SymbolTable | undefined, lateSymbols: SymbolTable, decl: LateBoundDeclaration | LateBoundBinaryExpressionDeclaration) { - Debug.assert(!!decl.symbol, "The member is expected to have a symbol."); - const links = getNodeLinks(decl); - if (!links.resolvedSymbol) { - // In the event we attempt to resolve the late-bound name of this member recursively, - // fall back to the early-bound name of this member. - links.resolvedSymbol = decl.symbol; - const declName = isBinaryExpression(decl) ? decl.left : decl.name; - const type = isElementAccessExpression(declName) ? checkExpressionCached(declName.argumentExpression) : checkComputedPropertyName(declName); - if (isTypeUsableAsPropertyName(type)) { - const memberName = getPropertyNameFromType(type); - const symbolFlags = decl.symbol.flags; - - // Get or add a late-bound symbol for the member. This allows us to merge late-bound accessor declarations. - let lateSymbol = lateSymbols.get(memberName); - if (!lateSymbol) lateSymbols.set(memberName, lateSymbol = createSymbol(SymbolFlags.None, memberName, CheckFlags.Late)); - - // Report an error if a late-bound member has the same name as an early-bound member, - // or if we have another early-bound symbol declaration with the same name and - // conflicting flags. - const earlySymbol = earlySymbols && earlySymbols.get(memberName); - if (lateSymbol.flags & getExcludedSymbolFlags(symbolFlags) || earlySymbol) { - // If we have an existing early-bound member, combine its declarations so that we can - // report an error at each declaration. - const declarations = earlySymbol ? concatenate(earlySymbol.declarations, lateSymbol.declarations) : lateSymbol.declarations; - const name = !(type.flags & TypeFlags.UniqueESSymbol) && unescapeLeadingUnderscores(memberName) || declarationNameToString(declName); - forEach(declarations, declaration => error(getNameOfDeclaration(declaration) || declaration, Diagnostics.Property_0_was_also_declared_here, name)); - error(declName || decl, Diagnostics.Duplicate_property_0, name); - lateSymbol = createSymbol(SymbolFlags.None, memberName, CheckFlags.Late); - } - lateSymbol.nameType = type; - addDeclarationToLateBoundSymbol(lateSymbol, decl, symbolFlags); - if (lateSymbol.parent) { - Debug.assert(lateSymbol.parent === parent, "Existing symbol parent should match new one"); - } - else { - lateSymbol.parent = parent; - } - return links.resolvedSymbol = lateSymbol; - } - } - return links.resolvedSymbol; - } - - function getResolvedMembersOrExportsOfSymbol(symbol: Symbol, resolutionKind: MembersOrExportsResolutionKind): UnderscoreEscapedMap { - const links = getSymbolLinks(symbol); - if (!links[resolutionKind]) { - const isStatic = resolutionKind === MembersOrExportsResolutionKind.resolvedExports; - const earlySymbols = !isStatic ? symbol.members : - symbol.flags & SymbolFlags.Module ? getExportsOfModuleWorker(symbol) : - symbol.exports; - - // In the event we recursively resolve the members/exports of the symbol, we - // set the initial value of resolvedMembers/resolvedExports to the early-bound - // members/exports of the symbol. - links[resolutionKind] = earlySymbols || emptySymbols; - - // fill in any as-yet-unresolved late-bound members. - const lateSymbols = createSymbolTable(); - for (const decl of symbol.declarations) { - const members = getMembersOfDeclaration(decl); - if (members) { - for (const member of members) { - if (isStatic === hasStaticModifier(member) && hasLateBindableName(member)) { - lateBindMember(symbol, earlySymbols, lateSymbols, member); - } - } - } - } - const assignments = symbol.assignmentDeclarationMembers; - if (assignments) { - const decls = arrayFrom(assignments.values()); - for (const member of decls) { - const assignmentKind = getAssignmentDeclarationKind(member as BinaryExpression | CallExpression); - const isInstanceMember = assignmentKind === AssignmentDeclarationKind.PrototypeProperty - || assignmentKind === AssignmentDeclarationKind.ThisProperty - || assignmentKind === AssignmentDeclarationKind.ObjectDefinePrototypeProperty - || assignmentKind === AssignmentDeclarationKind.Prototype; // A straight `Prototype` assignment probably can never have a computed name - if (isStatic === !isInstanceMember && hasLateBindableName(member)) { - lateBindMember(symbol, earlySymbols, lateSymbols, member); - } - } - } - - links[resolutionKind] = combineSymbolTables(earlySymbols, lateSymbols) || emptySymbols; - } - - return links[resolutionKind]!; - } - - /** - * Gets a SymbolTable containing both the early- and late-bound members of a symbol. - * - * For a description of late-binding, see `lateBindMember`. - */ - function getMembersOfSymbol(symbol: Symbol) { - return symbol.flags & SymbolFlags.LateBindingContainer - ? getResolvedMembersOrExportsOfSymbol(symbol, MembersOrExportsResolutionKind.resolvedMembers) - : symbol.members || emptySymbols; - } - - /** - * If a symbol is the dynamic name of the member of an object type, get the late-bound - * symbol of the member. - * - * For a description of late-binding, see `lateBindMember`. - */ - function getLateBoundSymbol(symbol: Symbol): Symbol { - if (symbol.flags & SymbolFlags.ClassMember && symbol.escapedName === InternalSymbolName.Computed) { - const links = getSymbolLinks(symbol); - if (!links.lateSymbol && some(symbol.declarations, hasLateBindableName)) { - // force late binding of members/exports. This will set the late-bound symbol - const parent = getMergedSymbol(symbol.parent)!; - if (some(symbol.declarations, hasStaticModifier)) { - getExportsOfSymbol(parent); - } - else { - getMembersOfSymbol(parent); - } - } - return links.lateSymbol || (links.lateSymbol = symbol); - } - return symbol; - } - - function getTypeWithThisArgument(type: Type, thisArgument?: Type, needApparentType?: boolean): Type { - if (getObjectFlags(type) & ObjectFlags.Reference) { - const target = (type).target; - const typeArguments = getTypeArguments(type); - if (length(target.typeParameters) === length(typeArguments)) { - const ref = createTypeReference(target, concatenate(typeArguments, [thisArgument || target.thisType!])); - return needApparentType ? getApparentType(ref) : ref; - } - } - else if (type.flags & TypeFlags.Intersection) { - return getIntersectionType(map((type).types, t => getTypeWithThisArgument(t, thisArgument, needApparentType))); - } - return needApparentType ? getApparentType(type) : type; - } - - function resolveObjectTypeMembers(type: ObjectType, source: InterfaceTypeWithDeclaredMembers, typeParameters: readonly TypeParameter[], typeArguments: readonly Type[]) { - let mapper: TypeMapper; - let members: SymbolTable; - let callSignatures: readonly Signature[]; - let constructSignatures: readonly Signature[] | undefined; - let stringIndexInfo: IndexInfo | undefined; - let numberIndexInfo: IndexInfo | undefined; - if (rangeEquals(typeParameters, typeArguments, 0, typeParameters.length)) { - mapper = identityMapper; - members = source.symbol ? getMembersOfSymbol(source.symbol) : createSymbolTable(source.declaredProperties); - callSignatures = source.declaredCallSignatures; - constructSignatures = source.declaredConstructSignatures; - stringIndexInfo = source.declaredStringIndexInfo; - numberIndexInfo = source.declaredNumberIndexInfo; - } - else { - mapper = createTypeMapper(typeParameters, typeArguments); - members = createInstantiatedSymbolTable(source.declaredProperties, mapper, /*mappingThisOnly*/ typeParameters.length === 1); - callSignatures = instantiateSignatures(source.declaredCallSignatures, mapper); - constructSignatures = instantiateSignatures(source.declaredConstructSignatures, mapper); - stringIndexInfo = instantiateIndexInfo(source.declaredStringIndexInfo, mapper); - numberIndexInfo = instantiateIndexInfo(source.declaredNumberIndexInfo, mapper); - } - const baseTypes = getBaseTypes(source); - if (baseTypes.length) { - if (source.symbol && members === getMembersOfSymbol(source.symbol)) { - members = createSymbolTable(source.declaredProperties); - } - setStructuredTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); - const thisArgument = lastOrUndefined(typeArguments); - for (const baseType of baseTypes) { - const instantiatedBaseType = thisArgument ? getTypeWithThisArgument(instantiateType(baseType, mapper), thisArgument) : baseType; - addInheritedMembers(members, getPropertiesOfType(instantiatedBaseType)); - callSignatures = concatenate(callSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Call)); - constructSignatures = concatenate(constructSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Construct)); - if (!stringIndexInfo) { - stringIndexInfo = instantiatedBaseType === anyType ? - createIndexInfo(anyType, /*isReadonly*/ false) : - getIndexInfoOfType(instantiatedBaseType, IndexKind.String); - } - numberIndexInfo = numberIndexInfo || getIndexInfoOfType(instantiatedBaseType, IndexKind.Number); - } - } - setStructuredTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); - } - - function resolveClassOrInterfaceMembers(type: InterfaceType): void { - resolveObjectTypeMembers(type, resolveDeclaredMembers(type), emptyArray, emptyArray); - } - - function resolveTypeReferenceMembers(type: TypeReference): void { - const source = resolveDeclaredMembers(type.target); - const typeParameters = concatenate(source.typeParameters!, [source.thisType!]); - const typeArguments = getTypeArguments(type); - const paddedTypeArguments = typeArguments.length === typeParameters.length ? typeArguments : concatenate(typeArguments, [type]); - resolveObjectTypeMembers(type, source, typeParameters, paddedTypeArguments); - } - - function createSignature( - declaration: SignatureDeclaration | JSDocSignature | undefined, - typeParameters: readonly TypeParameter[] | undefined, - thisParameter: Symbol | undefined, - parameters: readonly Symbol[], - resolvedReturnType: Type | undefined, - resolvedTypePredicate: TypePredicate | undefined, - minArgumentCount: number, - flags: SignatureFlags - ): Signature { - const sig = new Signature(checker, flags); - sig.declaration = declaration; - sig.typeParameters = typeParameters; - sig.parameters = parameters; - sig.thisParameter = thisParameter; - sig.resolvedReturnType = resolvedReturnType; - sig.resolvedTypePredicate = resolvedTypePredicate; - sig.minArgumentCount = minArgumentCount; - sig.target = undefined; - sig.mapper = undefined; - return sig; - } - - function cloneSignature(sig: Signature): Signature { - const result = createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, /*resolvedReturnType*/ undefined, - /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.flags & SignatureFlags.PropagatingFlags); - result.target = sig.target; - result.mapper = sig.mapper; - return result; - } - - function createUnionSignature(signature: Signature, unionSignatures: Signature[]) { - const result = cloneSignature(signature); - result.unionSignatures = unionSignatures; - result.target = undefined; - result.mapper = undefined; - return result; - } - - function getOptionalCallSignature(signature: Signature, callChainFlags: SignatureFlags): Signature { - if ((signature.flags & SignatureFlags.CallChainFlags) === callChainFlags) { - return signature; - } - if (!signature.optionalCallSignatureCache) { - signature.optionalCallSignatureCache = {}; - } - const key = callChainFlags === SignatureFlags.IsInnerCallChain ? "inner" : "outer"; - return signature.optionalCallSignatureCache[key] - || (signature.optionalCallSignatureCache[key] = createOptionalCallSignature(signature, callChainFlags)); - } - - function createOptionalCallSignature(signature: Signature, callChainFlags: SignatureFlags) { - Debug.assert(callChainFlags === SignatureFlags.IsInnerCallChain || callChainFlags === SignatureFlags.IsOuterCallChain, - "An optional call signature can either be for an inner call chain or an outer call chain, but not both."); - const result = cloneSignature(signature); - result.flags |= callChainFlags; - return result; - } - - function getExpandedParameters(sig: Signature): readonly Symbol[] { - if (signatureHasRestParameter(sig)) { - const restIndex = sig.parameters.length - 1; - const restParameter = sig.parameters[restIndex]; - const restType = getTypeOfSymbol(restParameter); - if (isTupleType(restType)) { - const elementTypes = getTypeArguments(restType); - const minLength = restType.target.minLength; - const tupleRestIndex = restType.target.hasRestElement ? elementTypes.length - 1 : -1; - const restParams = map(elementTypes, (t, i) => { - const name = getParameterNameAtPosition(sig, restIndex + i); - const checkFlags = i === tupleRestIndex ? CheckFlags.RestParameter : - i >= minLength ? CheckFlags.OptionalParameter : 0; - const symbol = createSymbol(SymbolFlags.FunctionScopedVariable, name, checkFlags); - symbol.type = i === tupleRestIndex ? createArrayType(t) : t; - return symbol; - }); - return concatenate(sig.parameters.slice(0, restIndex), restParams); - } - } - return sig.parameters; - } - - function getDefaultConstructSignatures(classType: InterfaceType): Signature[] { - const baseConstructorType = getBaseConstructorTypeOfClass(classType); - const baseSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct); - if (baseSignatures.length === 0) { - return [createSignature(undefined, classType.localTypeParameters, undefined, emptyArray, classType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None)]; - } - const baseTypeNode = getBaseTypeNodeOfClass(classType)!; - const isJavaScript = isInJSFile(baseTypeNode); - const typeArguments = typeArgumentsFromTypeReferenceNode(baseTypeNode); - const typeArgCount = length(typeArguments); - const result: Signature[] = []; - for (const baseSig of baseSignatures) { - const minTypeArgumentCount = getMinTypeArgumentCount(baseSig.typeParameters); - const typeParamCount = length(baseSig.typeParameters); - if (isJavaScript || typeArgCount >= minTypeArgumentCount && typeArgCount <= typeParamCount) { - const sig = typeParamCount ? createSignatureInstantiation(baseSig, fillMissingTypeArguments(typeArguments, baseSig.typeParameters, minTypeArgumentCount, isJavaScript)) : cloneSignature(baseSig); - sig.typeParameters = classType.localTypeParameters; - sig.resolvedReturnType = classType; - result.push(sig); - } - } - return result; - } - - function findMatchingSignature(signatureList: readonly Signature[], signature: Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean): Signature | undefined { - for (const s of signatureList) { - if (compareSignaturesIdentical(s, signature, partialMatch, ignoreThisTypes, ignoreReturnTypes, partialMatch ? compareTypesSubtypeOf : compareTypesIdentical)) { - return s; - } - } - } - - function findMatchingSignatures(signatureLists: readonly (readonly Signature[])[], signature: Signature, listIndex: number): Signature[] | undefined { - if (signature.typeParameters) { - // We require an exact match for generic signatures, so we only return signatures from the first - // signature list and only if they have exact matches in the other signature lists. - if (listIndex > 0) { - return undefined; - } - for (let i = 1; i < signatureLists.length; i++) { - if (!findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false)) { - return undefined; - } - } - return [signature]; - } - let result: Signature[] | undefined; - for (let i = 0; i < signatureLists.length; i++) { - // Allow matching non-generic signatures to have excess parameters and different return types. - // Prefer matching this types if possible. - const match = i === listIndex ? signature : findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ true, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true); - if (!match) { - return undefined; - } - result = appendIfUnique(result, match); - } - return result; - } - - // The signatures of a union type are those signatures that are present in each of the constituent types. - // Generic signatures must match exactly, but non-generic signatures are allowed to have extra optional - // parameters and may differ in return types. When signatures differ in return types, the resulting return - // type is the union of the constituent return types. - function getUnionSignatures(signatureLists: readonly (readonly Signature[])[]): Signature[] { - let result: Signature[] | undefined; - let indexWithLengthOverOne: number | undefined; - for (let i = 0; i < signatureLists.length; i++) { - if (signatureLists[i].length === 0) return emptyArray; - if (signatureLists[i].length > 1) { - indexWithLengthOverOne = indexWithLengthOverOne === undefined ? i : -1; // -1 is a signal there are multiple overload sets - } - for (const signature of signatureLists[i]) { - // Only process signatures with parameter lists that aren't already in the result list - if (!result || !findMatchingSignature(result, signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true)) { - const unionSignatures = findMatchingSignatures(signatureLists, signature, i); - if (unionSignatures) { - let s = signature; - // Union the result types when more than one signature matches - if (unionSignatures.length > 1) { - let thisParameter = signature.thisParameter; - const firstThisParameterOfUnionSignatures = forEach(unionSignatures, sig => sig.thisParameter); - if (firstThisParameterOfUnionSignatures) { - const thisType = getIntersectionType(mapDefined(unionSignatures, sig => sig.thisParameter && getTypeOfSymbol(sig.thisParameter))); - thisParameter = createSymbolWithType(firstThisParameterOfUnionSignatures, thisType); - } - s = createUnionSignature(signature, unionSignatures); - s.thisParameter = thisParameter; - } - (result || (result = [])).push(s); - } - } - } - } - if (!length(result) && indexWithLengthOverOne !== -1) { - // No sufficiently similar signature existed to subsume all the other signatures in the union - time to see if we can make a single - // signature that handles all over them. We only do this when there are overloads in only one constituent. - // (Overloads are conditional in nature and having overloads in multiple constituents would necessitate making a power set of - // signatures from the type, whose ordering would be non-obvious) - const masterList = signatureLists[indexWithLengthOverOne !== undefined ? indexWithLengthOverOne : 0]; - let results: Signature[] | undefined = masterList.slice(); - for (const signatures of signatureLists) { - if (signatures !== masterList) { - const signature = signatures[0]; - Debug.assert(!!signature, "getUnionSignatures bails early on empty signature lists and should not have empty lists on second pass"); - results = signature.typeParameters && some(results, s => !!s.typeParameters) ? undefined : map(results, sig => combineSignaturesOfUnionMembers(sig, signature)); - if (!results) { - break; - } - } - } - result = results; - } - return result || emptyArray; - } - - function combineUnionThisParam(left: Symbol | undefined, right: Symbol | undefined): Symbol | undefined { - if (!left || !right) { - return left || right; - } - // A signature `this` type might be a read or a write position... It's very possible that it should be invariant - // and we should refuse to merge signatures if there are `this` types and they do not match. However, so as to be - // permissive when calling, for now, we'll intersect the `this` types just like we do for param types in union signatures. - const thisType = getIntersectionType([getTypeOfSymbol(left), getTypeOfSymbol(right)]); - return createSymbolWithType(left, thisType); - } - - function combineUnionParameters(left: Signature, right: Signature) { - const leftCount = getParameterCount(left); - const rightCount = getParameterCount(right); - const longest = leftCount >= rightCount ? left : right; - const shorter = longest === left ? right : left; - const longestCount = longest === left ? leftCount : rightCount; - const eitherHasEffectiveRest = (hasEffectiveRestParameter(left) || hasEffectiveRestParameter(right)); - const needsExtraRestElement = eitherHasEffectiveRest && !hasEffectiveRestParameter(longest); - const params = new Array(longestCount + (needsExtraRestElement ? 1 : 0)); - for (let i = 0; i < longestCount; i++) { - const longestParamType = tryGetTypeAtPosition(longest, i)!; - const shorterParamType = tryGetTypeAtPosition(shorter, i) || unknownType; - const unionParamType = getIntersectionType([longestParamType, shorterParamType]); - const isRestParam = eitherHasEffectiveRest && !needsExtraRestElement && i === (longestCount - 1); - const isOptional = i >= getMinArgumentCount(longest) && i >= getMinArgumentCount(shorter); - const leftName = i >= leftCount ? undefined : getParameterNameAtPosition(left, i); - const rightName = i >= rightCount ? undefined : getParameterNameAtPosition(right, i); - - const paramName = leftName === rightName ? leftName : - !leftName ? rightName : - !rightName ? leftName : - undefined; - const paramSymbol = createSymbol( - SymbolFlags.FunctionScopedVariable | (isOptional && !isRestParam ? SymbolFlags.Optional : 0), - paramName || `arg${i}` as __String - ); - paramSymbol.type = isRestParam ? createArrayType(unionParamType) : unionParamType; - params[i] = paramSymbol; - } - if (needsExtraRestElement) { - const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String); - restParamSymbol.type = createArrayType(getTypeAtPosition(shorter, longestCount)); - params[longestCount] = restParamSymbol; - } - return params; - } - - function combineSignaturesOfUnionMembers(left: Signature, right: Signature): Signature { - const declaration = left.declaration; - const params = combineUnionParameters(left, right); - const thisParam = combineUnionThisParam(left.thisParameter, right.thisParameter); - const minArgCount = Math.max(left.minArgumentCount, right.minArgumentCount); - const result = createSignature( - declaration, - left.typeParameters || right.typeParameters, - thisParam, - params, - /*resolvedReturnType*/ undefined, - /*resolvedTypePredicate*/ undefined, - minArgCount, - (left.flags | right.flags) & SignatureFlags.PropagatingFlags - ); - result.unionSignatures = concatenate(left.unionSignatures || [left], [right]); - return result; - } - - function getUnionIndexInfo(types: readonly Type[], kind: IndexKind): IndexInfo | undefined { - const indexTypes: Type[] = []; - let isAnyReadonly = false; - for (const type of types) { - const indexInfo = getIndexInfoOfType(type, kind); - if (!indexInfo) { - return undefined; - } - indexTypes.push(indexInfo.type); - isAnyReadonly = isAnyReadonly || indexInfo.isReadonly; - } - return createIndexInfo(getUnionType(indexTypes, UnionReduction.Subtype), isAnyReadonly); - } - - function resolveUnionTypeMembers(type: UnionType) { - // The members and properties collections are empty for union types. To get all properties of a union - // type use getPropertiesOfType (only the language service uses this). - const callSignatures = getUnionSignatures(map(type.types, t => t === globalFunctionType ? [unknownSignature] : getSignaturesOfType(t, SignatureKind.Call))); - const constructSignatures = getUnionSignatures(map(type.types, t => getSignaturesOfType(t, SignatureKind.Construct))); - const stringIndexInfo = getUnionIndexInfo(type.types, IndexKind.String); - const numberIndexInfo = getUnionIndexInfo(type.types, IndexKind.Number); - setStructuredTypeMembers(type, emptySymbols, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); - } - - function intersectTypes(type1: Type, type2: Type): Type; - function intersectTypes(type1: Type | undefined, type2: Type | undefined): Type | undefined; - function intersectTypes(type1: Type | undefined, type2: Type | undefined): Type | undefined { - return !type1 ? type2 : !type2 ? type1 : getIntersectionType([type1, type2]); - } - - function intersectIndexInfos(info1: IndexInfo | undefined, info2: IndexInfo | undefined): IndexInfo | undefined { - return !info1 ? info2 : !info2 ? info1 : createIndexInfo( - getIntersectionType([info1.type, info2.type]), info1.isReadonly && info2.isReadonly); - } - - function unionSpreadIndexInfos(info1: IndexInfo | undefined, info2: IndexInfo | undefined): IndexInfo | undefined { - return info1 && info2 && createIndexInfo( - getUnionType([info1.type, info2.type]), info1.isReadonly || info2.isReadonly); - } - - function findMixins(types: readonly Type[]): readonly boolean[] { - const constructorTypeCount = countWhere(types, (t) => getSignaturesOfType(t, SignatureKind.Construct).length > 0); - const mixinFlags = map(types, isMixinConstructorType); - if (constructorTypeCount > 0 && constructorTypeCount === countWhere(mixinFlags, (b) => b)) { - const firstMixinIndex = mixinFlags.indexOf(/*searchElement*/ true); - mixinFlags[firstMixinIndex] = false; - } - return mixinFlags; - } - - function includeMixinType(type: Type, types: readonly Type[], mixinFlags: readonly boolean[], index: number): Type { - const mixedTypes: Type[] = []; - for (let i = 0; i < types.length; i++) { - if (i === index) { - mixedTypes.push(type); - } - else if (mixinFlags[i]) { - mixedTypes.push(getReturnTypeOfSignature(getSignaturesOfType(types[i], SignatureKind.Construct)[0])); - } - } - return getIntersectionType(mixedTypes); - } - - function resolveIntersectionTypeMembers(type: IntersectionType) { - // The members and properties collections are empty for intersection types. To get all properties of an - // intersection type use getPropertiesOfType (only the language service uses this). - let callSignatures: Signature[] | undefined; - let constructSignatures: Signature[] | undefined; - let stringIndexInfo: IndexInfo | undefined; - let numberIndexInfo: IndexInfo | undefined; - const types = type.types; - const mixinFlags = findMixins(types); - const mixinCount = countWhere(mixinFlags, (b) => b); - for (let i = 0; i < types.length; i++) { - const t = type.types[i]; - // When an intersection type contains mixin constructor types, the construct signatures from - // those types are discarded and their return types are mixed into the return types of all - // other construct signatures in the intersection type. For example, the intersection type - // '{ new(...args: any[]) => A } & { new(s: string) => B }' has a single construct signature - // 'new(s: string) => A & B'. - if (!mixinFlags[i]) { - let signatures = getSignaturesOfType(t, SignatureKind.Construct); - if (signatures.length && mixinCount > 0) { - signatures = map(signatures, s => { - const clone = cloneSignature(s); - clone.resolvedReturnType = includeMixinType(getReturnTypeOfSignature(s), types, mixinFlags, i); - return clone; - }); - } - constructSignatures = appendSignatures(constructSignatures, signatures); - } - callSignatures = appendSignatures(callSignatures, getSignaturesOfType(t, SignatureKind.Call)); - stringIndexInfo = intersectIndexInfos(stringIndexInfo, getIndexInfoOfType(t, IndexKind.String)); - numberIndexInfo = intersectIndexInfos(numberIndexInfo, getIndexInfoOfType(t, IndexKind.Number)); - } - setStructuredTypeMembers(type, emptySymbols, callSignatures || emptyArray, constructSignatures || emptyArray, stringIndexInfo, numberIndexInfo); - } - - function appendSignatures(signatures: Signature[] | undefined, newSignatures: readonly Signature[]) { - for (const sig of newSignatures) { - if (!signatures || every(signatures, s => !compareSignaturesIdentical(s, sig, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false, compareTypesIdentical))) { - signatures = append(signatures, sig); - } - } - return signatures; - } - - /** - * Converts an AnonymousType to a ResolvedType. - */ - function resolveAnonymousTypeMembers(type: AnonymousType) { - const symbol = getMergedSymbol(type.symbol); - if (type.target) { - setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, undefined, undefined); - const members = createInstantiatedSymbolTable(getPropertiesOfObjectType(type.target), type.mapper!, /*mappingThisOnly*/ false); - const callSignatures = instantiateSignatures(getSignaturesOfType(type.target, SignatureKind.Call), type.mapper!); - const constructSignatures = instantiateSignatures(getSignaturesOfType(type.target, SignatureKind.Construct), type.mapper!); - const stringIndexInfo = instantiateIndexInfo(getIndexInfoOfType(type.target, IndexKind.String), type.mapper!); - const numberIndexInfo = instantiateIndexInfo(getIndexInfoOfType(type.target, IndexKind.Number), type.mapper!); - setStructuredTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); - } - else if (symbol.flags & SymbolFlags.TypeLiteral) { - setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, undefined, undefined); - const members = getMembersOfSymbol(symbol); - const callSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.Call)); - const constructSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.New)); - const stringIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.String); - const numberIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.Number); - setStructuredTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); - } - else { - // Combinations of function, class, enum and module - let members = emptySymbols; - let stringIndexInfo: IndexInfo | undefined; - if (symbol.exports) { - members = getExportsOfSymbol(symbol); - if (symbol === globalThisSymbol) { - const varsOnly = createMap() as SymbolTable; - members.forEach(p => { - if (!(p.flags & SymbolFlags.BlockScoped)) { - varsOnly.set(p.escapedName, p); - } - }); - members = varsOnly; - } - } - setStructuredTypeMembers(type, members, emptyArray, emptyArray, undefined, undefined); - if (symbol.flags & SymbolFlags.Class) { - const classType = getDeclaredTypeOfClassOrInterface(symbol); - const baseConstructorType = getBaseConstructorTypeOfClass(classType); - if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.TypeVariable)) { - members = createSymbolTable(getNamedMembers(members)); - addInheritedMembers(members, getPropertiesOfType(baseConstructorType)); - } - else if (baseConstructorType === anyType) { - stringIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false); - } - } - const numberIndexInfo = symbol.flags & SymbolFlags.Enum && (getDeclaredTypeOfSymbol(symbol).flags & TypeFlags.Enum || - some(type.properties, prop => !!(getTypeOfSymbol(prop).flags & TypeFlags.NumberLike))) ? enumNumberIndexInfo : undefined; - setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); - // We resolve the members before computing the signatures because a signature may use - // typeof with a qualified name expression that circularly references the type we are - // in the process of resolving (see issue #6072). The temporarily empty signature list - // will never be observed because a qualified name can't reference signatures. - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) { - type.callSignatures = getSignaturesOfSymbol(symbol); - } - // And likewise for construct signatures for classes - if (symbol.flags & SymbolFlags.Class) { - const classType = getDeclaredTypeOfClassOrInterface(symbol); - let constructSignatures = symbol.members ? getSignaturesOfSymbol(symbol.members.get(InternalSymbolName.Constructor)) : emptyArray; - if (symbol.flags & SymbolFlags.Function) { - constructSignatures = addRange(constructSignatures.slice(), mapDefined( - type.callSignatures, - sig => isJSConstructor(sig.declaration) ? - createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, classType, /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.flags & SignatureFlags.PropagatingFlags) : - undefined)); - } - if (!constructSignatures.length) { - constructSignatures = getDefaultConstructSignatures(classType); - } - type.constructSignatures = constructSignatures; - } - } - } - - function resolveReverseMappedTypeMembers(type: ReverseMappedType) { - const indexInfo = getIndexInfoOfType(type.source, IndexKind.String); - const modifiers = getMappedTypeModifiers(type.mappedType); - const readonlyMask = modifiers & MappedTypeModifiers.IncludeReadonly ? false : true; - const optionalMask = modifiers & MappedTypeModifiers.IncludeOptional ? 0 : SymbolFlags.Optional; - const stringIndexInfo = indexInfo && createIndexInfo(inferReverseMappedType(indexInfo.type, type.mappedType, type.constraintType), readonlyMask && indexInfo.isReadonly); - const members = createSymbolTable(); - for (const prop of getPropertiesOfType(type.source)) { - const checkFlags = CheckFlags.ReverseMapped | (readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0); - const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags) as ReverseMappedSymbol; - inferredProp.declarations = prop.declarations; - inferredProp.nameType = prop.nameType; - inferredProp.propertyType = getTypeOfSymbol(prop); - inferredProp.mappedType = type.mappedType; - inferredProp.constraintType = type.constraintType; - members.set(prop.escapedName, inferredProp); - } - setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined); - } - - // Return the lower bound of the key type in a mapped type. Intuitively, the lower - // bound includes those keys that are known to always be present, for example because - // because of constraints on type parameters (e.g. 'keyof T' for a constrained T). - function getLowerBoundOfKeyType(type: Type): Type { - if (type.flags & (TypeFlags.Any | TypeFlags.Primitive)) { - return type; - } - if (type.flags & TypeFlags.Index) { - return getIndexType(getApparentType((type).type)); - } - if (type.flags & TypeFlags.Conditional) { - if ((type).root.isDistributive) { - const checkType = (type).checkType; - const constraint = getLowerBoundOfKeyType(checkType); - if (constraint !== checkType) { - const mapper = makeUnaryTypeMapper((type).root.checkType, constraint); - return getConditionalTypeInstantiation(type, combineTypeMappers(mapper, (type).mapper)); - } - } - return type; - } - if (type.flags & TypeFlags.Union) { - return getUnionType(sameMap((type).types, getLowerBoundOfKeyType)); - } - if (type.flags & TypeFlags.Intersection) { - return getIntersectionType(sameMap((type).types, getLowerBoundOfKeyType)); - } - return neverType; - } - - /** Resolve the members of a mapped type { [P in K]: T } */ - function resolveMappedTypeMembers(type: MappedType) { - const members: SymbolTable = createSymbolTable(); - let stringIndexInfo: IndexInfo | undefined; - let numberIndexInfo: IndexInfo | undefined; - // Resolve upfront such that recursive references see an empty object type. - setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, undefined, undefined); - // In { [P in K]: T }, we refer to P as the type parameter type, K as the constraint type, - // and T as the template type. - const typeParameter = getTypeParameterFromMappedType(type); - const constraintType = getConstraintTypeFromMappedType(type); - const templateType = getTemplateTypeFromMappedType(type.target || type); - const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' - const templateModifiers = getMappedTypeModifiers(type); - const include = keyofStringsOnly ? TypeFlags.StringLiteral : TypeFlags.StringOrNumberLiteralOrUnique; - if (isMappedTypeWithKeyofConstraintDeclaration(type)) { - // We have a { [P in keyof T]: X } - for (const prop of getPropertiesOfType(modifiersType)) { - addMemberForKeyType(getLiteralTypeFromProperty(prop, include)); - } - if (modifiersType.flags & TypeFlags.Any || getIndexInfoOfType(modifiersType, IndexKind.String)) { - addMemberForKeyType(stringType); - } - if (!keyofStringsOnly && getIndexInfoOfType(modifiersType, IndexKind.Number)) { - addMemberForKeyType(numberType); - } - } - else { - forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType); - } - setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); - - function addMemberForKeyType(t: Type) { - // Create a mapper from T to the current iteration type constituent. Then, if the - // mapped type is itself an instantiated type, combine the iteration mapper with the - // instantiation mapper. - const templateMapper = combineTypeMappers(type.mapper, createTypeMapper([typeParameter], [t])); - const propType = instantiateType(templateType, templateMapper); - // If the current iteration type constituent is a string literal type, create a property. - // Otherwise, for type string create a string index signature. - if (isTypeUsableAsPropertyName(t)) { - const propName = getPropertyNameFromType(t); - const modifiersProp = getPropertyOfType(modifiersType, propName); - const isOptional = !!(templateModifiers & MappedTypeModifiers.IncludeOptional || - !(templateModifiers & MappedTypeModifiers.ExcludeOptional) && modifiersProp && modifiersProp.flags & SymbolFlags.Optional); - const isReadonly = !!(templateModifiers & MappedTypeModifiers.IncludeReadonly || - !(templateModifiers & MappedTypeModifiers.ExcludeReadonly) && modifiersProp && isReadonlySymbol(modifiersProp)); - const prop = createSymbol(SymbolFlags.Property | (isOptional ? SymbolFlags.Optional : 0), propName, isReadonly ? CheckFlags.Readonly : 0); - // When creating an optional property in strictNullChecks mode, if 'undefined' isn't assignable to the - // type, we include 'undefined' in the type. Similarly, when creating a non-optional property in strictNullChecks - // mode, if the underlying property is optional we remove 'undefined' from the type. - prop.type = strictNullChecks && isOptional && !isTypeAssignableTo(undefinedType, propType) ? getOptionalType(propType) : - strictNullChecks && !isOptional && modifiersProp && modifiersProp.flags & SymbolFlags.Optional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) : - propType; - if (modifiersProp) { - prop.syntheticOrigin = modifiersProp; - prop.declarations = modifiersProp.declarations; - } - prop.nameType = t; - members.set(propName, prop); - } - else if (t.flags & (TypeFlags.Any | TypeFlags.String)) { - stringIndexInfo = createIndexInfo(propType, !!(templateModifiers & MappedTypeModifiers.IncludeReadonly)); - } - else if (t.flags & (TypeFlags.Number | TypeFlags.Enum)) { - numberIndexInfo = createIndexInfo(numberIndexInfo ? getUnionType([numberIndexInfo.type, propType]) : propType, - !!(templateModifiers & MappedTypeModifiers.IncludeReadonly)); - } - } - } - - function getTypeParameterFromMappedType(type: MappedType) { - return type.typeParameter || - (type.typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(type.declaration.typeParameter))); - } - - function getConstraintTypeFromMappedType(type: MappedType) { - return type.constraintType || - (type.constraintType = getConstraintOfTypeParameter(getTypeParameterFromMappedType(type)) || errorType); - } - - function getTemplateTypeFromMappedType(type: MappedType) { - return type.templateType || - (type.templateType = type.declaration.type ? - instantiateType(addOptionality(getTypeFromTypeNode(type.declaration.type), !!(getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional)), type.mapper || identityMapper) : - errorType); - } - - function getConstraintDeclarationForMappedType(type: MappedType) { - return getEffectiveConstraintOfTypeParameter(type.declaration.typeParameter); - } - - function isMappedTypeWithKeyofConstraintDeclaration(type: MappedType) { - const constraintDeclaration = getConstraintDeclarationForMappedType(type)!; // TODO: GH#18217 - return constraintDeclaration.kind === SyntaxKind.TypeOperator && - (constraintDeclaration).operator === SyntaxKind.KeyOfKeyword; - } - - function getModifiersTypeFromMappedType(type: MappedType) { - if (!type.modifiersType) { - if (isMappedTypeWithKeyofConstraintDeclaration(type)) { - // If the constraint declaration is a 'keyof T' node, the modifiers type is T. We check - // AST nodes here because, when T is a non-generic type, the logic below eagerly resolves - // 'keyof T' to a literal union type and we can't recover T from that type. - type.modifiersType = instantiateType(getTypeFromTypeNode((getConstraintDeclarationForMappedType(type)).type), type.mapper || identityMapper); - } - else { - // Otherwise, get the declared constraint type, and if the constraint type is a type parameter, - // get the constraint of that type parameter. If the resulting type is an indexed type 'keyof T', - // the modifiers type is T. Otherwise, the modifiers type is unknown. - const declaredType = getTypeFromMappedTypeNode(type.declaration); - const constraint = getConstraintTypeFromMappedType(declaredType); - const extendedConstraint = constraint && constraint.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(constraint) : constraint; - type.modifiersType = extendedConstraint && extendedConstraint.flags & TypeFlags.Index ? instantiateType((extendedConstraint).type, type.mapper || identityMapper) : unknownType; - } - } - return type.modifiersType; - } - - function getMappedTypeModifiers(type: MappedType): MappedTypeModifiers { - const declaration = type.declaration; - return (declaration.readonlyToken ? declaration.readonlyToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeReadonly : MappedTypeModifiers.IncludeReadonly : 0) | - (declaration.questionToken ? declaration.questionToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeOptional : MappedTypeModifiers.IncludeOptional : 0); - } - - function getMappedTypeOptionality(type: MappedType): number { - const modifiers = getMappedTypeModifiers(type); - return modifiers & MappedTypeModifiers.ExcludeOptional ? -1 : modifiers & MappedTypeModifiers.IncludeOptional ? 1 : 0; - } - - function getCombinedMappedTypeOptionality(type: MappedType): number { - const optionality = getMappedTypeOptionality(type); - const modifiersType = getModifiersTypeFromMappedType(type); - return optionality || (isGenericMappedType(modifiersType) ? getMappedTypeOptionality(modifiersType) : 0); - } - - function isPartialMappedType(type: Type) { - return !!(getObjectFlags(type) & ObjectFlags.Mapped && getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional); - } - - function isGenericMappedType(type: Type): type is MappedType { - return !!(getObjectFlags(type) & ObjectFlags.Mapped) && isGenericIndexType(getConstraintTypeFromMappedType(type)); - } - - function resolveStructuredTypeMembers(type: StructuredType): ResolvedType { - if (!(type).members) { - if (type.flags & TypeFlags.Object) { - if ((type).objectFlags & ObjectFlags.Reference) { - resolveTypeReferenceMembers(type); - } - else if ((type).objectFlags & ObjectFlags.ClassOrInterface) { - resolveClassOrInterfaceMembers(type); - } - else if ((type).objectFlags & ObjectFlags.ReverseMapped) { - resolveReverseMappedTypeMembers(type as ReverseMappedType); - } - else if ((type).objectFlags & ObjectFlags.Anonymous) { - resolveAnonymousTypeMembers(type); - } - else if ((type).objectFlags & ObjectFlags.Mapped) { - resolveMappedTypeMembers(type); - } - } - else if (type.flags & TypeFlags.Union) { - resolveUnionTypeMembers(type); - } - else if (type.flags & TypeFlags.Intersection) { - resolveIntersectionTypeMembers(type); - } - } - return type; - } - - /** Return properties of an object type or an empty array for other types */ - function getPropertiesOfObjectType(type: Type): Symbol[] { - if (type.flags & TypeFlags.Object) { - return resolveStructuredTypeMembers(type).properties; - } - return emptyArray; - } - - /** If the given type is an object type and that type has a property by the given name, - * return the symbol for that property. Otherwise return undefined. - */ - function getPropertyOfObjectType(type: Type, name: __String): Symbol | undefined { - if (type.flags & TypeFlags.Object) { - const resolved = resolveStructuredTypeMembers(type); - const symbol = resolved.members.get(name); - if (symbol && symbolIsValue(symbol)) { - return symbol; - } - } - } - - function getPropertiesOfUnionOrIntersectionType(type: UnionOrIntersectionType): Symbol[] { - if (!type.resolvedProperties) { - const members = createSymbolTable(); - for (const current of type.types) { - for (const prop of getPropertiesOfType(current)) { - if (!members.has(prop.escapedName)) { - const combinedProp = getPropertyOfUnionOrIntersectionType(type, prop.escapedName); - if (combinedProp) { - members.set(prop.escapedName, combinedProp); - } - } - } - // The properties of a union type are those that are present in all constituent types, so - // we only need to check the properties of the first type - if (type.flags & TypeFlags.Union) { - break; - } - } - type.resolvedProperties = getNamedMembers(members); - } - return type.resolvedProperties; - } - - function getPropertiesOfType(type: Type): Symbol[] { - type = getApparentType(type); - return type.flags & TypeFlags.UnionOrIntersection ? - getPropertiesOfUnionOrIntersectionType(type) : - getPropertiesOfObjectType(type); - } - - function isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean { - const list = obj.properties as NodeArray; - return list.some(property => { - const nameType = property.name && getLiteralTypeFromPropertyName(property.name); - const name = nameType && isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined; - const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name); - return !!expected && isLiteralType(expected) && !isTypeAssignableTo(getTypeOfNode(property), expected); - }); - } - - function getAllPossiblePropertiesOfTypes(types: readonly Type[]): Symbol[] { - const unionType = getUnionType(types); - if (!(unionType.flags & TypeFlags.Union)) { - return getAugmentedPropertiesOfType(unionType); - } - - const props = createSymbolTable(); - for (const memberType of types) { - for (const { escapedName } of getAugmentedPropertiesOfType(memberType)) { - if (!props.has(escapedName)) { - const prop = createUnionOrIntersectionProperty(unionType as UnionType, escapedName); - // May be undefined if the property is private - if (prop) props.set(escapedName, prop); - } - } - } - return arrayFrom(props.values()); - } - - function getConstraintOfType(type: InstantiableType | UnionOrIntersectionType): Type | undefined { - return type.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(type) : - type.flags & TypeFlags.IndexedAccess ? getConstraintOfIndexedAccess(type) : - type.flags & TypeFlags.Conditional ? getConstraintOfConditionalType(type) : - getBaseConstraintOfType(type); - } - - function getConstraintOfTypeParameter(typeParameter: TypeParameter): Type | undefined { - return hasNonCircularBaseConstraint(typeParameter) ? getConstraintFromTypeParameter(typeParameter) : undefined; - } - - function getConstraintOfIndexedAccess(type: IndexedAccessType) { - return hasNonCircularBaseConstraint(type) ? getConstraintFromIndexedAccess(type) : undefined; - } - - function getSimplifiedTypeOrConstraint(type: Type) { - const simplified = getSimplifiedType(type, /*writing*/ false); - return simplified !== type ? simplified : getConstraintOfType(type); - } - - function getConstraintFromIndexedAccess(type: IndexedAccessType) { - const indexConstraint = getSimplifiedTypeOrConstraint(type.indexType); - if (indexConstraint && indexConstraint !== type.indexType) { - const indexedAccess = getIndexedAccessTypeOrUndefined(type.objectType, indexConstraint); - if (indexedAccess) { - return indexedAccess; - } - } - const objectConstraint = getSimplifiedTypeOrConstraint(type.objectType); - if (objectConstraint && objectConstraint !== type.objectType) { - return getIndexedAccessTypeOrUndefined(objectConstraint, type.indexType); - } - return undefined; - } - - function getDefaultConstraintOfConditionalType(type: ConditionalType) { - if (!type.resolvedDefaultConstraint) { - // An `any` branch of a conditional type would normally be viral - specifically, without special handling here, - // a conditional type with a single branch of type `any` would be assignable to anything, since it's constraint would simplify to - // just `any`. This result is _usually_ unwanted - so instead here we elide an `any` branch from the constraint type, - // in effect treating `any` like `never` rather than `unknown` in this location. - const trueConstraint = getInferredTrueTypeFromConditionalType(type); - const falseConstraint = getFalseTypeFromConditionalType(type); - type.resolvedDefaultConstraint = isTypeAny(trueConstraint) ? falseConstraint : isTypeAny(falseConstraint) ? trueConstraint : getUnionType([trueConstraint, falseConstraint]); - } - return type.resolvedDefaultConstraint; - } - - function getConstraintOfDistributiveConditionalType(type: ConditionalType): Type | undefined { - // Check if we have a conditional type of the form 'T extends U ? X : Y', where T is a constrained - // type parameter. If so, create an instantiation of the conditional type where T is replaced - // with its constraint. We do this because if the constraint is a union type it will be distributed - // over the conditional type and possibly reduced. For example, 'T extends undefined ? never : T' - // removes 'undefined' from T. - // We skip returning a distributive constraint for a restrictive instantiation of a conditional type - // as the constraint for all type params (check type included) have been replace with `unknown`, which - // is going to produce even more false positive/negative results than the distribute constraint already does. - // Please note: the distributive constraint is a kludge for emulating what a negated type could to do filter - // a union - once negated types exist and are applied to the conditional false branch, this "constraint" - // likely doesn't need to exist. - if (type.root.isDistributive && type.restrictiveInstantiation !== type) { - const simplified = getSimplifiedType(type.checkType, /*writing*/ false); - const constraint = simplified === type.checkType ? getConstraintOfType(simplified) : simplified; - if (constraint && constraint !== type.checkType) { - const mapper = makeUnaryTypeMapper(type.root.checkType, constraint); - const instantiated = getConditionalTypeInstantiation(type, combineTypeMappers(mapper, type.mapper)); - if (!(instantiated.flags & TypeFlags.Never)) { - return instantiated; - } - } - } - return undefined; - } - - function getConstraintFromConditionalType(type: ConditionalType) { - return getConstraintOfDistributiveConditionalType(type) || getDefaultConstraintOfConditionalType(type); - } - - function getConstraintOfConditionalType(type: ConditionalType) { - return hasNonCircularBaseConstraint(type) ? getConstraintFromConditionalType(type) : undefined; - } - - function getEffectiveConstraintOfIntersection(types: readonly Type[], targetIsUnion: boolean) { - let constraints: Type[] | undefined; - let hasDisjointDomainType = false; - for (const t of types) { - if (t.flags & TypeFlags.Instantiable) { - // We keep following constraints as long as we have an instantiable type that is known - // not to be circular or infinite (hence we stop on index access types). - let constraint = getConstraintOfType(t); - while (constraint && constraint.flags & (TypeFlags.TypeParameter | TypeFlags.Index | TypeFlags.Conditional)) { - constraint = getConstraintOfType(constraint); - } - if (constraint) { - constraints = append(constraints, constraint); - if (targetIsUnion) { - constraints = append(constraints, t); - } - } - } - else if (t.flags & TypeFlags.DisjointDomains) { - hasDisjointDomainType = true; - } - } - // If the target is a union type or if we are intersecting with types belonging to one of the - // disjoint domains, we may end up producing a constraint that hasn't been examined before. - if (constraints && (targetIsUnion || hasDisjointDomainType)) { - if (hasDisjointDomainType) { - // We add any types belong to one of the disjoint domains because they might cause the final - // intersection operation to reduce the union constraints. - for (const t of types) { - if (t.flags & TypeFlags.DisjointDomains) { - constraints = append(constraints, t); - } - } - } - return getIntersectionType(constraints); - } - return undefined; - } - - function getBaseConstraintOfType(type: Type): Type | undefined { - if (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.UnionOrIntersection)) { - const constraint = getResolvedBaseConstraint(type); - return constraint !== noConstraintType && constraint !== circularConstraintType ? constraint : undefined; - } - return type.flags & TypeFlags.Index ? keyofConstraintType : undefined; - } - - /** - * This is similar to `getBaseConstraintOfType` except it returns the input type if there's no base constraint, instead of `undefined` - * It also doesn't map indexes to `string`, as where this is used this would be unneeded (and likely undesirable) - */ - function getBaseConstraintOrType(type: Type) { - return getBaseConstraintOfType(type) || type; - } - - function hasNonCircularBaseConstraint(type: InstantiableType): boolean { - return getResolvedBaseConstraint(type) !== circularConstraintType; - } - - /** - * Return the resolved base constraint of a type variable. The noConstraintType singleton is returned if the - * type variable has no constraint, and the circularConstraintType singleton is returned if the constraint - * circularly references the type variable. - */ - function getResolvedBaseConstraint(type: InstantiableType | UnionOrIntersectionType): Type { - let nonTerminating = false; - return type.resolvedBaseConstraint || - (type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), type)); - - function getImmediateBaseConstraint(t: Type): Type { - if (!t.immediateBaseConstraint) { - if (!pushTypeResolution(t, TypeSystemPropertyName.ImmediateBaseConstraint)) { - return circularConstraintType; - } - if (constraintDepth >= 50) { - // We have reached 50 recursive invocations of getImmediateBaseConstraint and there is a - // very high likelihood we're dealing with an infinite generic type that perpetually generates - // new type identities as we descend into it. We stop the recursion here and mark this type - // and the outer types as having circular constraints. - error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); - nonTerminating = true; - return t.immediateBaseConstraint = noConstraintType; - } - constraintDepth++; - let result = computeBaseConstraint(getSimplifiedType(t, /*writing*/ false)); - constraintDepth--; - if (!popTypeResolution()) { - if (t.flags & TypeFlags.TypeParameter) { - const errorNode = getConstraintDeclaration(t); - if (errorNode) { - const diagnostic = error(errorNode, Diagnostics.Type_parameter_0_has_a_circular_constraint, typeToString(t)); - if (currentNode && !isNodeDescendantOf(errorNode, currentNode) && !isNodeDescendantOf(currentNode, errorNode)) { - addRelatedInfo(diagnostic, createDiagnosticForNode(currentNode, Diagnostics.Circularity_originates_in_type_at_this_location)); - } - } - } - result = circularConstraintType; - } - if (nonTerminating) { - result = circularConstraintType; - } - t.immediateBaseConstraint = result || noConstraintType; - } - return t.immediateBaseConstraint; - } - - function getBaseConstraint(t: Type): Type | undefined { - const c = getImmediateBaseConstraint(t); - return c !== noConstraintType && c !== circularConstraintType ? c : undefined; - } - - function computeBaseConstraint(t: Type): Type | undefined { - if (t.flags & TypeFlags.TypeParameter) { - const constraint = getConstraintFromTypeParameter(t); - return (t as TypeParameter).isThisType || !constraint ? - constraint : - getBaseConstraint(constraint); - } - if (t.flags & TypeFlags.UnionOrIntersection) { - const types = (t).types; - const baseTypes: Type[] = []; - for (const type of types) { - const baseType = getBaseConstraint(type); - if (baseType) { - baseTypes.push(baseType); - } - } - return t.flags & TypeFlags.Union && baseTypes.length === types.length ? getUnionType(baseTypes) : - t.flags & TypeFlags.Intersection && baseTypes.length ? getIntersectionType(baseTypes) : - undefined; - } - if (t.flags & TypeFlags.Index) { - return keyofConstraintType; - } - if (t.flags & TypeFlags.IndexedAccess) { - const baseObjectType = getBaseConstraint((t).objectType); - const baseIndexType = getBaseConstraint((t).indexType); - const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType); - return baseIndexedAccess && getBaseConstraint(baseIndexedAccess); - } - if (t.flags & TypeFlags.Conditional) { - const constraint = getConstraintFromConditionalType(t); - constraintDepth++; // Penalize repeating conditional types (this captures the recursion within getConstraintFromConditionalType and carries it forward) - const result = constraint && getBaseConstraint(constraint); - constraintDepth--; - return result; - } - if (t.flags & TypeFlags.Substitution) { - return getBaseConstraint((t).substitute); - } - return t; - } - } - - function getApparentTypeOfIntersectionType(type: IntersectionType) { - return type.resolvedApparentType || (type.resolvedApparentType = getTypeWithThisArgument(type, type, /*apparentType*/ true)); - } - - function getResolvedTypeParameterDefault(typeParameter: TypeParameter): Type | undefined { - if (!typeParameter.default) { - if (typeParameter.target) { - const targetDefault = getResolvedTypeParameterDefault(typeParameter.target); - typeParameter.default = targetDefault ? instantiateType(targetDefault, typeParameter.mapper) : noConstraintType; - } - else { - // To block recursion, set the initial value to the resolvingDefaultType. - typeParameter.default = resolvingDefaultType; - const defaultDeclaration = typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default); - const defaultType = defaultDeclaration ? getTypeFromTypeNode(defaultDeclaration) : noConstraintType; - if (typeParameter.default === resolvingDefaultType) { - // If we have not been called recursively, set the correct default type. - typeParameter.default = defaultType; - } - } - } - else if (typeParameter.default === resolvingDefaultType) { - // If we are called recursively for this type parameter, mark the default as circular. - typeParameter.default = circularConstraintType; - } - return typeParameter.default; - } - - /** - * Gets the default type for a type parameter. - * - * If the type parameter is the result of an instantiation, this gets the instantiated - * default type of its target. If the type parameter has no default type or the default is - * circular, `undefined` is returned. - */ - function getDefaultFromTypeParameter(typeParameter: TypeParameter): Type | undefined { - const defaultType = getResolvedTypeParameterDefault(typeParameter); - return defaultType !== noConstraintType && defaultType !== circularConstraintType ? defaultType : undefined; - } - - function hasNonCircularTypeParameterDefault(typeParameter: TypeParameter) { - return getResolvedTypeParameterDefault(typeParameter) !== circularConstraintType; - } - - /** - * Indicates whether the declaration of a typeParameter has a default type. - */ - function hasTypeParameterDefault(typeParameter: TypeParameter): boolean { - return !!(typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default)); - } - - function getApparentTypeOfMappedType(type: MappedType) { - return type.resolvedApparentType || (type.resolvedApparentType = getResolvedApparentTypeOfMappedType(type)); - } - - function getResolvedApparentTypeOfMappedType(type: MappedType) { - const typeVariable = getHomomorphicTypeVariable(type); - if (typeVariable) { - const constraint = getConstraintOfTypeParameter(typeVariable); - if (constraint && (isArrayType(constraint) || isTupleType(constraint))) { - const mapper = makeUnaryTypeMapper(typeVariable, constraint); - return instantiateType(type, combineTypeMappers(mapper, type.mapper)); - } - } - return type; - } - - /** - * For a type parameter, return the base constraint of the type parameter. For the string, number, - * boolean, and symbol primitive types, return the corresponding object types. Otherwise return the - * type itself. Note that the apparent type of a union type is the union type itself. - */ - function getApparentType(type: Type): Type { - const t = type.flags & TypeFlags.Instantiable ? getBaseConstraintOfType(type) || unknownType : type; - return getObjectFlags(t) & ObjectFlags.Mapped ? getApparentTypeOfMappedType(t) : - t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(t) : - t.flags & TypeFlags.StringLike ? globalStringType : - t.flags & TypeFlags.NumberLike ? globalNumberType : - t.flags & TypeFlags.BigIntLike ? getGlobalBigIntType(/*reportErrors*/ languageVersion >= ScriptTarget.ESNext) : - t.flags & TypeFlags.BooleanLike ? globalBooleanType : - t.flags & TypeFlags.ESSymbolLike ? getGlobalESSymbolType(/*reportErrors*/ languageVersion >= ScriptTarget.ES2015) : - t.flags & TypeFlags.NonPrimitive ? emptyObjectType : - t.flags & TypeFlags.Index ? keyofConstraintType : - t.flags & TypeFlags.Unknown && !strictNullChecks ? emptyObjectType : - t; - } - - function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String): Symbol | undefined { - const propSet = createMap(); - let indexTypes: Type[] | undefined; - const isUnion = containingType.flags & TypeFlags.Union; - const excludeModifiers = isUnion ? ModifierFlags.NonPublicAccessibilityModifier : 0; - // Flags we want to propagate to the result if they exist in all source symbols - let optionalFlag = isUnion ? SymbolFlags.None : SymbolFlags.Optional; - let syntheticFlag = CheckFlags.SyntheticMethod; - let checkFlags = 0; - for (const current of containingType.types) { - const type = getApparentType(current); - if (type !== errorType) { - const prop = getPropertyOfType(type, name); - const modifiers = prop ? getDeclarationModifierFlagsFromSymbol(prop) : 0; - if (prop && !(modifiers & excludeModifiers)) { - if (isUnion) { - optionalFlag |= (prop.flags & SymbolFlags.Optional); - } - else { - optionalFlag &= prop.flags; - } - const id = "" + getSymbolId(prop); - if (!propSet.has(id)) { - propSet.set(id, prop); - } - checkFlags |= (isReadonlySymbol(prop) ? CheckFlags.Readonly : 0) | - (!(modifiers & ModifierFlags.NonPublicAccessibilityModifier) ? CheckFlags.ContainsPublic : 0) | - (modifiers & ModifierFlags.Protected ? CheckFlags.ContainsProtected : 0) | - (modifiers & ModifierFlags.Private ? CheckFlags.ContainsPrivate : 0) | - (modifiers & ModifierFlags.Static ? CheckFlags.ContainsStatic : 0); - if (!isPrototypeProperty(prop)) { - syntheticFlag = CheckFlags.SyntheticProperty; - } - } - else if (isUnion) { - const indexInfo = !isLateBoundName(name) && (isNumericLiteralName(name) && getIndexInfoOfType(type, IndexKind.Number) || getIndexInfoOfType(type, IndexKind.String)); - if (indexInfo) { - checkFlags |= CheckFlags.WritePartial | (indexInfo.isReadonly ? CheckFlags.Readonly : 0); - indexTypes = append(indexTypes, isTupleType(type) ? getRestTypeOfTupleType(type) || undefinedType : indexInfo.type); - } - else if (isObjectLiteralType(type)) { - checkFlags |= CheckFlags.WritePartial; - indexTypes = append(indexTypes, undefinedType); - } - else { - checkFlags |= CheckFlags.ReadPartial; - } - } - } - } - if (!propSet.size) { - return undefined; - } - const props = arrayFrom(propSet.values()); - if (props.length === 1 && !(checkFlags & CheckFlags.ReadPartial) && !indexTypes) { - return props[0]; - } - let declarations: Declaration[] | undefined; - let firstType: Type | undefined; - let nameType: Type | undefined; - const propTypes: Type[] = []; - let firstValueDeclaration: Declaration | undefined; - let hasNonUniformValueDeclaration = false; - for (const prop of props) { - if (!firstValueDeclaration) { - firstValueDeclaration = prop.valueDeclaration; - } - else if (prop.valueDeclaration !== firstValueDeclaration) { - hasNonUniformValueDeclaration = true; - } - declarations = addRange(declarations, prop.declarations); - const type = getTypeOfSymbol(prop); - if (!firstType) { - firstType = type; - nameType = prop.nameType; - } - else if (type !== firstType) { - checkFlags |= CheckFlags.HasNonUniformType; - } - if (isLiteralType(type)) { - checkFlags |= CheckFlags.HasLiteralType; - } - propTypes.push(type); - } - addRange(propTypes, indexTypes); - const result = createSymbol(SymbolFlags.Property | optionalFlag, name, syntheticFlag | checkFlags); - result.containingType = containingType; - if (!hasNonUniformValueDeclaration && firstValueDeclaration) { - result.valueDeclaration = firstValueDeclaration; - - // Inherit information about parent type. - if (firstValueDeclaration.symbol.parent) { - result.parent = firstValueDeclaration.symbol.parent; - } - } - - result.declarations = declarations!; - result.nameType = nameType; - if (propTypes.length > 2) { - // When `propTypes` has the potential to explode in size when normalized, defer normalization until absolutely needed - result.checkFlags |= CheckFlags.DeferredType; - result.deferralParent = containingType; - result.deferralConstituents = propTypes; - } - else { - result.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes); - } - return result; - } - - // Return the symbol for a given property in a union or intersection type, or undefined if the property - // does not exist in any constituent type. Note that the returned property may only be present in some - // constituents, in which case the isPartial flag is set when the containing type is union type. We need - // these partial properties when identifying discriminant properties, but otherwise they are filtered out - // and do not appear to be present in the union type. - function getUnionOrIntersectionProperty(type: UnionOrIntersectionType, name: __String): Symbol | undefined { - const properties = type.propertyCache || (type.propertyCache = createSymbolTable()); - let property = properties.get(name); - if (!property) { - property = createUnionOrIntersectionProperty(type, name); - if (property) { - properties.set(name, property); - } - } - return property; - } - - function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: __String): Symbol | undefined { - const property = getUnionOrIntersectionProperty(type, name); - // We need to filter out partial properties in union types - return property && !(getCheckFlags(property) & CheckFlags.ReadPartial) ? property : undefined; - } - - /** - * Return the symbol for the property with the given name in the given type. Creates synthetic union properties when - * necessary, maps primitive types and type parameters are to their apparent types, and augments with properties from - * Object and Function as appropriate. - * - * @param type a type to look up property from - * @param name a name of property to look up in a given type - */ - function getPropertyOfType(type: Type, name: __String): Symbol | undefined { - type = getApparentType(type); - if (type.flags & TypeFlags.Object) { - const resolved = resolveStructuredTypeMembers(type); - const symbol = resolved.members.get(name); - if (symbol && symbolIsValue(symbol)) { - return symbol; - } - const functionType = resolved === anyFunctionType ? globalFunctionType : - resolved.callSignatures.length ? globalCallableFunctionType : - resolved.constructSignatures.length ? globalNewableFunctionType : - undefined; - if (functionType) { - const symbol = getPropertyOfObjectType(functionType, name); - if (symbol) { - return symbol; - } - } - return getPropertyOfObjectType(globalObjectType, name); - } - if (type.flags & TypeFlags.UnionOrIntersection) { - return getPropertyOfUnionOrIntersectionType(type, name); - } - return undefined; - } - - function getSignaturesOfStructuredType(type: Type, kind: SignatureKind): readonly Signature[] { - if (type.flags & TypeFlags.StructuredType) { - const resolved = resolveStructuredTypeMembers(type); - return kind === SignatureKind.Call ? resolved.callSignatures : resolved.constructSignatures; - } - return emptyArray; - } - - /** - * Return the signatures of the given kind in the given type. Creates synthetic union signatures when necessary and - * maps primitive types and type parameters are to their apparent types. - */ - function getSignaturesOfType(type: Type, kind: SignatureKind): readonly Signature[] { - return getSignaturesOfStructuredType(getApparentType(type), kind); - } - - function getIndexInfoOfStructuredType(type: Type, kind: IndexKind): IndexInfo | undefined { - if (type.flags & TypeFlags.StructuredType) { - const resolved = resolveStructuredTypeMembers(type); - return kind === IndexKind.String ? resolved.stringIndexInfo : resolved.numberIndexInfo; - } - } - - function getIndexTypeOfStructuredType(type: Type, kind: IndexKind): Type | undefined { - const info = getIndexInfoOfStructuredType(type, kind); - return info && info.type; - } - - // Return the indexing info of the given kind in the given type. Creates synthetic union index types when necessary and - // maps primitive types and type parameters are to their apparent types. - function getIndexInfoOfType(type: Type, kind: IndexKind): IndexInfo | undefined { - return getIndexInfoOfStructuredType(getApparentType(type), kind); - } - - // Return the index type of the given kind in the given type. Creates synthetic union index types when necessary and - // maps primitive types and type parameters are to their apparent types. - function getIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined { - return getIndexTypeOfStructuredType(getApparentType(type), kind); - } - - function getImplicitIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined { - if (isObjectTypeWithInferableIndex(type)) { - const propTypes: Type[] = []; - for (const prop of getPropertiesOfType(type)) { - if (kind === IndexKind.String || isNumericLiteralName(prop.escapedName)) { - propTypes.push(getTypeOfSymbol(prop)); - } - } - if (kind === IndexKind.String) { - append(propTypes, getIndexTypeOfType(type, IndexKind.Number)); - } - if (propTypes.length) { - return getUnionType(propTypes, UnionReduction.Subtype); - } - } - return undefined; - } - - // Return list of type parameters with duplicates removed (duplicate identifier errors are generated in the actual - // type checking functions). - function getTypeParametersFromDeclaration(declaration: DeclarationWithTypeParameters): TypeParameter[] | undefined { - let result: TypeParameter[] | undefined; - for (const node of getEffectiveTypeParameterDeclarations(declaration)) { - result = appendIfUnique(result, getDeclaredTypeOfTypeParameter(node.symbol)); - } - return result; - } - - function symbolsToArray(symbols: SymbolTable): Symbol[] { - const result: Symbol[] = []; - symbols.forEach((symbol, id) => { - if (!isReservedMemberName(id)) { - result.push(symbol); - } - }); - return result; - } - - function isJSDocOptionalParameter(node: ParameterDeclaration) { - return isInJSFile(node) && ( - // node.type should only be a JSDocOptionalType when node is a parameter of a JSDocFunctionType - node.type && node.type.kind === SyntaxKind.JSDocOptionalType - || getJSDocParameterTags(node).some(({ isBracketed, typeExpression }) => - isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType)); - } - - function tryFindAmbientModule(moduleName: string, withAugmentations: boolean) { - if (isExternalModuleNameRelative(moduleName)) { - return undefined; - } - const symbol = getSymbol(globals, '"' + moduleName + '"' as __String, SymbolFlags.ValueModule); - // merged symbol is module declaration symbol combined with all augmentations - return symbol && withAugmentations ? getMergedSymbol(symbol) : symbol; - } - - function isOptionalParameter(node: ParameterDeclaration | JSDocParameterTag) { - if (hasQuestionToken(node) || isOptionalJSDocParameterTag(node) || isJSDocOptionalParameter(node)) { - return true; - } - - if (node.initializer) { - const signature = getSignatureFromDeclaration(node.parent); - const parameterIndex = node.parent.parameters.indexOf(node); - Debug.assert(parameterIndex >= 0); - return parameterIndex >= getMinArgumentCount(signature); - } - const iife = getImmediatelyInvokedFunctionExpression(node.parent); - if (iife) { - return !node.type && - !node.dotDotDotToken && - node.parent.parameters.indexOf(node) >= iife.arguments.length; - } - - return false; - } - - function isOptionalJSDocParameterTag(node: Node): node is JSDocParameterTag { - if (!isJSDocParameterTag(node)) { - return false; - } - const { isBracketed, typeExpression } = node; - return isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType; - } - - function createTypePredicate(kind: TypePredicateKind, parameterName: string | undefined, parameterIndex: number | undefined, type: Type | undefined): TypePredicate { - return { kind, parameterName, parameterIndex, type } as TypePredicate; - } - - /** - * Gets the minimum number of type arguments needed to satisfy all non-optional type - * parameters. - */ - function getMinTypeArgumentCount(typeParameters: readonly TypeParameter[] | undefined): number { - let minTypeArgumentCount = 0; - if (typeParameters) { - for (let i = 0; i < typeParameters.length; i++) { - if (!hasTypeParameterDefault(typeParameters[i])) { - minTypeArgumentCount = i + 1; - } - } - } - return minTypeArgumentCount; - } - - /** - * Fill in default types for unsupplied type arguments. If `typeArguments` is undefined - * when a default type is supplied, a new array will be created and returned. - * - * @param typeArguments The supplied type arguments. - * @param typeParameters The requested type parameters. - * @param minTypeArgumentCount The minimum number of required type arguments. - */ - function fillMissingTypeArguments(typeArguments: readonly Type[], typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[]; - function fillMissingTypeArguments(typeArguments: readonly Type[] | undefined, typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[] | undefined; - function fillMissingTypeArguments(typeArguments: readonly Type[] | undefined, typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean) { - const numTypeParameters = length(typeParameters); - if (!numTypeParameters) { - return []; - } - const numTypeArguments = length(typeArguments); - if (isJavaScriptImplicitAny || (numTypeArguments >= minTypeArgumentCount && numTypeArguments <= numTypeParameters)) { - const result = typeArguments ? typeArguments.slice() : []; - // Map invalid forward references in default types to the error type - for (let i = numTypeArguments; i < numTypeParameters; i++) { - result[i] = errorType; - } - const baseDefaultType = getDefaultTypeArgumentType(isJavaScriptImplicitAny); - for (let i = numTypeArguments; i < numTypeParameters; i++) { - let defaultType = getDefaultFromTypeParameter(typeParameters![i]); - if (isJavaScriptImplicitAny && defaultType && (isTypeIdenticalTo(defaultType, unknownType) || isTypeIdenticalTo(defaultType, emptyObjectType))) { - defaultType = anyType; - } - result[i] = defaultType ? instantiateType(defaultType, createTypeMapper(typeParameters!, result)) : baseDefaultType; - } - result.length = typeParameters!.length; - return result; - } - return typeArguments && typeArguments.slice(); - } - - function getSignatureFromDeclaration(declaration: SignatureDeclaration | JSDocSignature): Signature { - const links = getNodeLinks(declaration); - if (!links.resolvedSignature) { - const parameters: Symbol[] = []; - let flags = SignatureFlags.None; - let minArgumentCount = 0; - let thisParameter: Symbol | undefined; - let hasThisParameter = false; - const iife = getImmediatelyInvokedFunctionExpression(declaration); - const isJSConstructSignature = isJSDocConstructSignature(declaration); - const isUntypedSignatureInJSFile = !iife && - isInJSFile(declaration) && - isValueSignatureDeclaration(declaration) && - !hasJSDocParameterTags(declaration) && - !getJSDocType(declaration); - - // If this is a JSDoc construct signature, then skip the first parameter in the - // parameter list. The first parameter represents the return type of the construct - // signature. - for (let i = isJSConstructSignature ? 1 : 0; i < declaration.parameters.length; i++) { - const param = declaration.parameters[i]; - - let paramSymbol = param.symbol; - const type = isJSDocParameterTag(param) ? (param.typeExpression && param.typeExpression.type) : param.type; - // Include parameter symbol instead of property symbol in the signature - if (paramSymbol && !!(paramSymbol.flags & SymbolFlags.Property) && !isBindingPattern(param.name)) { - const resolvedSymbol = resolveName(param, paramSymbol.escapedName, SymbolFlags.Value, undefined, undefined, /*isUse*/ false); - paramSymbol = resolvedSymbol!; - } - if (i === 0 && paramSymbol.escapedName === InternalSymbolName.This) { - hasThisParameter = true; - thisParameter = param.symbol; - } - else { - parameters.push(paramSymbol); - } - - if (type && type.kind === SyntaxKind.LiteralType) { - flags |= SignatureFlags.HasLiteralTypes; - } - - // Record a new minimum argument count if this is not an optional parameter - const isOptionalParameter = isOptionalJSDocParameterTag(param) || - param.initializer || param.questionToken || param.dotDotDotToken || - iife && parameters.length > iife.arguments.length && !type || - isUntypedSignatureInJSFile || - isJSDocOptionalParameter(param); - if (!isOptionalParameter) { - minArgumentCount = parameters.length; - } - } - - // If only one accessor includes a this-type annotation, the other behaves as if it had the same type annotation - if ((declaration.kind === SyntaxKind.GetAccessor || declaration.kind === SyntaxKind.SetAccessor) && - !hasNonBindableDynamicName(declaration) && - (!hasThisParameter || !thisParameter)) { - const otherKind = declaration.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; - const other = getDeclarationOfKind(getSymbolOfNode(declaration), otherKind); - if (other) { - thisParameter = getAnnotatedAccessorThisParameter(other); - } - } - - const classType = declaration.kind === SyntaxKind.Constructor ? - getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent).symbol)) - : undefined; - const typeParameters = classType ? classType.localTypeParameters : getTypeParametersFromDeclaration(declaration); - if (hasRestParameter(declaration) || isInJSFile(declaration) && maybeAddJsSyntheticRestParameter(declaration, parameters)) { - flags |= SignatureFlags.HasRestParameter; - } - links.resolvedSignature = createSignature(declaration, typeParameters, thisParameter, parameters, - /*resolvedReturnType*/ undefined, /*resolvedTypePredicate*/ undefined, - minArgumentCount, flags); - } - return links.resolvedSignature; - } - - /** - * A JS function gets a synthetic rest parameter if it references `arguments` AND: - * 1. It has no parameters but at least one `@param` with a type that starts with `...` - * OR - * 2. It has at least one parameter, and the last parameter has a matching `@param` with a type that starts with `...` - */ - function maybeAddJsSyntheticRestParameter(declaration: SignatureDeclaration | JSDocSignature, parameters: Symbol[]): boolean { - if (isJSDocSignature(declaration) || !containsArgumentsReference(declaration)) { - return false; - } - const lastParam = lastOrUndefined(declaration.parameters); - const lastParamTags = lastParam ? getJSDocParameterTags(lastParam) : getJSDocTags(declaration).filter(isJSDocParameterTag); - const lastParamVariadicType = firstDefined(lastParamTags, p => - p.typeExpression && isJSDocVariadicType(p.typeExpression.type) ? p.typeExpression.type : undefined); - - const syntheticArgsSymbol = createSymbol(SymbolFlags.Variable, "args" as __String, CheckFlags.RestParameter); - syntheticArgsSymbol.type = lastParamVariadicType ? createArrayType(getTypeFromTypeNode(lastParamVariadicType.type)) : anyArrayType; - if (lastParamVariadicType) { - // Replace the last parameter with a rest parameter. - parameters.pop(); - } - parameters.push(syntheticArgsSymbol); - return true; - } - - function getSignatureOfTypeTag(node: SignatureDeclaration | JSDocSignature) { - const typeTag = isInJSFile(node) ? getJSDocTypeTag(node) : undefined; - const signature = typeTag && typeTag.typeExpression && getSingleCallSignature(getTypeFromTypeNode(typeTag.typeExpression)); - return signature && getErasedSignature(signature); - } - - function getReturnTypeOfTypeTag(node: SignatureDeclaration | JSDocSignature) { - const signature = getSignatureOfTypeTag(node); - return signature && getReturnTypeOfSignature(signature); - } - - function containsArgumentsReference(declaration: SignatureDeclaration): boolean { - const links = getNodeLinks(declaration); - if (links.containsArgumentsReference === undefined) { - if (links.flags & NodeCheckFlags.CaptureArguments) { - links.containsArgumentsReference = true; - } - else { - links.containsArgumentsReference = traverse((declaration as FunctionLikeDeclaration).body!); - } - } - return links.containsArgumentsReference; - - function traverse(node: Node): boolean { - if (!node) return false; - switch (node.kind) { - case SyntaxKind.Identifier: - return (node).escapedText === "arguments" && isExpressionNode(node); - - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return (node).name!.kind === SyntaxKind.ComputedPropertyName - && traverse((node).name!); - - default: - return !nodeStartsNewLexicalEnvironment(node) && !isPartOfTypeNode(node) && !!forEachChild(node, traverse); - } - } - } - - function getSignaturesOfSymbol(symbol: Symbol | undefined): Signature[] { - if (!symbol) return emptyArray; - const result: Signature[] = []; - for (let i = 0; i < symbol.declarations.length; i++) { - const decl = symbol.declarations[i]; - if (!isFunctionLike(decl)) continue; - // Don't include signature if node is the implementation of an overloaded function. A node is considered - // an implementation node if it has a body and the previous node is of the same kind and immediately - // precedes the implementation node (i.e. has the same parent and ends where the implementation starts). - if (i > 0 && (decl as FunctionLikeDeclaration).body) { - const previous = symbol.declarations[i - 1]; - if (decl.parent === previous.parent && decl.kind === previous.kind && decl.pos === previous.end) { - continue; - } - } - result.push(getSignatureFromDeclaration(decl)); - } - return result; - } - - function resolveExternalModuleTypeByLiteral(name: StringLiteral) { - const moduleSym = resolveExternalModuleName(name, name); - if (moduleSym) { - const resolvedModuleSymbol = resolveExternalModuleSymbol(moduleSym); - if (resolvedModuleSymbol) { - return getTypeOfSymbol(resolvedModuleSymbol); - } - } - - return anyType; - } - - function getThisTypeOfSignature(signature: Signature): Type | undefined { - if (signature.thisParameter) { - return getTypeOfSymbol(signature.thisParameter); - } - } - - function getTypePredicateOfSignature(signature: Signature): TypePredicate | undefined { - if (!signature.resolvedTypePredicate) { - if (signature.target) { - const targetTypePredicate = getTypePredicateOfSignature(signature.target); - signature.resolvedTypePredicate = targetTypePredicate ? instantiateTypePredicate(targetTypePredicate, signature.mapper!) : noTypePredicate; - } - else if (signature.unionSignatures) { - signature.resolvedTypePredicate = getUnionTypePredicate(signature.unionSignatures) || noTypePredicate; - } - else { - const type = signature.declaration && getEffectiveReturnTypeNode(signature.declaration); - let jsdocPredicate: TypePredicate | undefined; - if (!type && isInJSFile(signature.declaration)) { - const jsdocSignature = getSignatureOfTypeTag(signature.declaration!); - if (jsdocSignature && signature !== jsdocSignature) { - jsdocPredicate = getTypePredicateOfSignature(jsdocSignature); - } - } - signature.resolvedTypePredicate = type && isTypePredicateNode(type) ? - createTypePredicateFromTypePredicateNode(type, signature) : - jsdocPredicate || noTypePredicate; - } - Debug.assert(!!signature.resolvedTypePredicate); - } - return signature.resolvedTypePredicate === noTypePredicate ? undefined : signature.resolvedTypePredicate; - } - - function createTypePredicateFromTypePredicateNode(node: TypePredicateNode, signature: Signature): TypePredicate { - const parameterName = node.parameterName; - const type = node.type && getTypeFromTypeNode(node.type); - return parameterName.kind === SyntaxKind.ThisType ? - createTypePredicate(node.assertsModifier ? TypePredicateKind.AssertsThis : TypePredicateKind.This, /*parameterName*/ undefined, /*parameterIndex*/ undefined, type) : - createTypePredicate(node.assertsModifier ? TypePredicateKind.AssertsIdentifier : TypePredicateKind.Identifier, parameterName.escapedText as string, - findIndex(signature.parameters, p => p.escapedName === parameterName.escapedText), type); - } - - function getReturnTypeOfSignature(signature: Signature): Type { - if (!signature.resolvedReturnType) { - if (!pushTypeResolution(signature, TypeSystemPropertyName.ResolvedReturnType)) { - return errorType; - } - let type = signature.target ? instantiateType(getReturnTypeOfSignature(signature.target), signature.mapper) : - signature.unionSignatures ? getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), UnionReduction.Subtype) : - getReturnTypeFromAnnotation(signature.declaration!) || - (nodeIsMissing((signature.declaration).body) ? anyType : getReturnTypeFromBody(signature.declaration)); - if (signature.flags & SignatureFlags.IsInnerCallChain) { - type = addOptionalTypeMarker(type); - } - else if (signature.flags & SignatureFlags.IsOuterCallChain) { - type = getOptionalType(type); - } - if (!popTypeResolution()) { - if (signature.declaration) { - const typeNode = getEffectiveReturnTypeNode(signature.declaration); - if (typeNode) { - error(typeNode, Diagnostics.Return_type_annotation_circularly_references_itself); - } - else if (noImplicitAny) { - const declaration = signature.declaration; - const name = getNameOfDeclaration(declaration); - if (name) { - error(name, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, declarationNameToString(name)); - } - else { - error(declaration, Diagnostics.Function_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions); - } - } - } - type = anyType; - } - signature.resolvedReturnType = type; - } - return signature.resolvedReturnType; - } - - function getReturnTypeFromAnnotation(declaration: SignatureDeclaration | JSDocSignature) { - if (declaration.kind === SyntaxKind.Constructor) { - return getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent).symbol)); - } - if (isJSDocConstructSignature(declaration)) { - return getTypeFromTypeNode((declaration.parameters[0] as ParameterDeclaration).type!); // TODO: GH#18217 - } - const typeNode = getEffectiveReturnTypeNode(declaration); - if (typeNode) { - return getTypeFromTypeNode(typeNode); - } - if (declaration.kind === SyntaxKind.GetAccessor && !hasNonBindableDynamicName(declaration)) { - const jsDocType = isInJSFile(declaration) && getTypeForDeclarationFromJSDocComment(declaration); - if (jsDocType) { - return jsDocType; - } - const setter = getDeclarationOfKind(getSymbolOfNode(declaration), SyntaxKind.SetAccessor); - const setterType = getAnnotatedAccessorType(setter); - if (setterType) { - return setterType; - } - } - return getReturnTypeOfTypeTag(declaration); - } - - function isResolvingReturnTypeOfSignature(signature: Signature) { - return !signature.resolvedReturnType && findResolutionCycleStartIndex(signature, TypeSystemPropertyName.ResolvedReturnType) >= 0; - } - - function getRestTypeOfSignature(signature: Signature): Type { - return tryGetRestTypeOfSignature(signature) || anyType; - } - - function tryGetRestTypeOfSignature(signature: Signature): Type | undefined { - if (signatureHasRestParameter(signature)) { - const sigRestType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); - const restType = isTupleType(sigRestType) ? getRestTypeOfTupleType(sigRestType) : sigRestType; - return restType && getIndexTypeOfType(restType, IndexKind.Number); - } - return undefined; - } - - function getSignatureInstantiation(signature: Signature, typeArguments: Type[] | undefined, isJavascript: boolean, inferredTypeParameters?: readonly TypeParameter[]): Signature { - const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, fillMissingTypeArguments(typeArguments, signature.typeParameters, getMinTypeArgumentCount(signature.typeParameters), isJavascript)); - if (inferredTypeParameters) { - const returnSignature = getSingleCallOrConstructSignature(getReturnTypeOfSignature(instantiatedSignature)); - if (returnSignature) { - const newReturnSignature = cloneSignature(returnSignature); - newReturnSignature.typeParameters = inferredTypeParameters; - const newInstantiatedSignature = cloneSignature(instantiatedSignature); - newInstantiatedSignature.resolvedReturnType = getOrCreateTypeFromSignature(newReturnSignature); - return newInstantiatedSignature; - } - } - return instantiatedSignature; - } - - function getSignatureInstantiationWithoutFillingInTypeArguments(signature: Signature, typeArguments: readonly Type[] | undefined): Signature { - const instantiations = signature.instantiations || (signature.instantiations = createMap()); - const id = getTypeListId(typeArguments); - let instantiation = instantiations.get(id); - if (!instantiation) { - instantiations.set(id, instantiation = createSignatureInstantiation(signature, typeArguments)); - } - return instantiation; - } - - function createSignatureInstantiation(signature: Signature, typeArguments: readonly Type[] | undefined): Signature { - return instantiateSignature(signature, createSignatureTypeMapper(signature, typeArguments), /*eraseTypeParameters*/ true); - } - - function createSignatureTypeMapper(signature: Signature, typeArguments: readonly Type[] | undefined): TypeMapper { - return createTypeMapper(signature.typeParameters!, typeArguments); - } - - function getErasedSignature(signature: Signature): Signature { - return signature.typeParameters ? - signature.erasedSignatureCache || (signature.erasedSignatureCache = createErasedSignature(signature)) : - signature; - } - - function createErasedSignature(signature: Signature) { - // Create an instantiation of the signature where all type arguments are the any type. - return instantiateSignature(signature, createTypeEraser(signature.typeParameters!), /*eraseTypeParameters*/ true); - } - - function getCanonicalSignature(signature: Signature): Signature { - return signature.typeParameters ? - signature.canonicalSignatureCache || (signature.canonicalSignatureCache = createCanonicalSignature(signature)) : - signature; - } - - function createCanonicalSignature(signature: Signature) { - // Create an instantiation of the signature where each unconstrained type parameter is replaced with - // its original. When a generic class or interface is instantiated, each generic method in the class or - // interface is instantiated with a fresh set of cloned type parameters (which we need to handle scenarios - // where different generations of the same type parameter are in scope). This leads to a lot of new type - // identities, and potentially a lot of work comparing those identities, so here we create an instantiation - // that uses the original type identities for all unconstrained type parameters. - return getSignatureInstantiation( - signature, - map(signature.typeParameters, tp => tp.target && !getConstraintOfTypeParameter(tp.target) ? tp.target : tp), - isInJSFile(signature.declaration)); - } - - function getBaseSignature(signature: Signature) { - const typeParameters = signature.typeParameters; - if (typeParameters) { - const typeEraser = createTypeEraser(typeParameters); - const baseConstraints = map(typeParameters, tp => instantiateType(getBaseConstraintOfType(tp), typeEraser) || unknownType); - return instantiateSignature(signature, createTypeMapper(typeParameters, baseConstraints), /*eraseTypeParameters*/ true); - } - return signature; - } - - function getOrCreateTypeFromSignature(signature: Signature): ObjectType { - // There are two ways to declare a construct signature, one is by declaring a class constructor - // using the constructor keyword, and the other is declaring a bare construct signature in an - // object type literal or interface (using the new keyword). Each way of declaring a constructor - // will result in a different declaration kind. - if (!signature.isolatedSignatureType) { - const kind = signature.declaration ? signature.declaration.kind : SyntaxKind.Unknown; - const isConstructor = kind === SyntaxKind.Constructor || kind === SyntaxKind.ConstructSignature || kind === SyntaxKind.ConstructorType; - const type = createObjectType(ObjectFlags.Anonymous); - type.members = emptySymbols; - type.properties = emptyArray; - type.callSignatures = !isConstructor ? [signature] : emptyArray; - type.constructSignatures = isConstructor ? [signature] : emptyArray; - signature.isolatedSignatureType = type; - } - - return signature.isolatedSignatureType; - } - - function getIndexSymbol(symbol: Symbol): Symbol | undefined { - return symbol.members!.get(InternalSymbolName.Index); - } - - function getIndexDeclarationOfSymbol(symbol: Symbol, kind: IndexKind): IndexSignatureDeclaration | undefined { - const syntaxKind = kind === IndexKind.Number ? SyntaxKind.NumberKeyword : SyntaxKind.StringKeyword; - const indexSymbol = getIndexSymbol(symbol); - if (indexSymbol) { - for (const decl of indexSymbol.declarations) { - const node = cast(decl, isIndexSignatureDeclaration); - if (node.parameters.length === 1) { - const parameter = node.parameters[0]; - if (parameter.type && parameter.type.kind === syntaxKind) { - return node; - } - } - } - } - - return undefined; - } - - function createIndexInfo(type: Type, isReadonly: boolean, declaration?: IndexSignatureDeclaration): IndexInfo { - return { type, isReadonly, declaration }; - } - - function getIndexInfoOfSymbol(symbol: Symbol, kind: IndexKind): IndexInfo | undefined { - const declaration = getIndexDeclarationOfSymbol(symbol, kind); - if (declaration) { - return createIndexInfo(declaration.type ? getTypeFromTypeNode(declaration.type) : anyType, - hasModifier(declaration, ModifierFlags.Readonly), declaration); - } - return undefined; - } - - function getConstraintDeclaration(type: TypeParameter): TypeNode | undefined { - return mapDefined(filter(type.symbol && type.symbol.declarations, isTypeParameterDeclaration), getEffectiveConstraintOfTypeParameter)[0]; - } - - function getInferredTypeParameterConstraint(typeParameter: TypeParameter) { - let inferences: Type[] | undefined; - if (typeParameter.symbol) { - for (const declaration of typeParameter.symbol.declarations) { - if (declaration.parent.kind === SyntaxKind.InferType) { - // When an 'infer T' declaration is immediately contained in a type reference node - // (such as 'Foo'), T's constraint is inferred from the constraint of the - // corresponding type parameter in 'Foo'. When multiple 'infer T' declarations are - // present, we form an intersection of the inferred constraint types. - const grandParent = declaration.parent.parent; - if (grandParent.kind === SyntaxKind.TypeReference) { - const typeReference = grandParent; - const typeParameters = getTypeParametersForTypeReference(typeReference); - if (typeParameters) { - const index = typeReference.typeArguments!.indexOf(declaration.parent); - if (index < typeParameters.length) { - const declaredConstraint = getConstraintOfTypeParameter(typeParameters[index]); - if (declaredConstraint) { - // Type parameter constraints can reference other type parameters so - // constraints need to be instantiated. If instantiation produces the - // type parameter itself, we discard that inference. For example, in - // type Foo = [T, U]; - // type Bar = T extends Foo ? Foo : T; - // the instantiated constraint for U is X, so we discard that inference. - const mapper = createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReference, typeParameters)); - const constraint = instantiateType(declaredConstraint, mapper); - if (constraint !== typeParameter) { - inferences = append(inferences, constraint); - } - } - } - } - } - // When an 'infer T' declaration is immediately contained in a rest parameter - // declaration, we infer an 'unknown[]' constraint. - else if (grandParent.kind === SyntaxKind.Parameter && (grandParent).dotDotDotToken) { - inferences = append(inferences, createArrayType(unknownType)); - } - } - } - } - return inferences && getIntersectionType(inferences); - } - - /** This is a worker function. Use getConstraintOfTypeParameter which guards against circular constraints. */ - function getConstraintFromTypeParameter(typeParameter: TypeParameter): Type | undefined { - if (!typeParameter.constraint) { - if (typeParameter.target) { - const targetConstraint = getConstraintOfTypeParameter(typeParameter.target); - typeParameter.constraint = targetConstraint ? instantiateType(targetConstraint, typeParameter.mapper) : noConstraintType; - } - else { - const constraintDeclaration = getConstraintDeclaration(typeParameter); - typeParameter.constraint = constraintDeclaration ? getTypeFromTypeNode(constraintDeclaration) : - getInferredTypeParameterConstraint(typeParameter) || noConstraintType; - } - } - return typeParameter.constraint === noConstraintType ? undefined : typeParameter.constraint; - } - - function getParentSymbolOfTypeParameter(typeParameter: TypeParameter): Symbol | undefined { - const tp = getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter)!; - const host = isJSDocTemplateTag(tp.parent) ? getHostSignatureFromJSDoc(tp.parent) : tp.parent; - return host && getSymbolOfNode(host); - } - - function getTypeListId(types: readonly Type[] | undefined) { - let result = ""; - if (types) { - const length = types.length; - let i = 0; - while (i < length) { - const startId = types[i].id; - let count = 1; - while (i + count < length && types[i + count].id === startId + count) { - count++; - } - if (result.length) { - result += ","; - } - result += startId; - if (count > 1) { - result += ":" + count; - } - i += count; - } - } - return result; - } - - // This function is used to propagate certain flags when creating new object type references and union types. - // It is only necessary to do so if a constituent type might be the undefined type, the null type, the type - // of an object literal or the anyFunctionType. This is because there are operations in the type checker - // that care about the presence of such types at arbitrary depth in a containing type. - function getPropagatingFlagsOfTypes(types: readonly Type[], excludeKinds: TypeFlags): ObjectFlags { - let result: ObjectFlags = 0; - for (const type of types) { - if (!(type.flags & excludeKinds)) { - result |= getObjectFlags(type); - } - } - return result & ObjectFlags.PropagatingFlags; - } - - function createTypeReference(target: GenericType, typeArguments: readonly Type[] | undefined): TypeReference { - const id = getTypeListId(typeArguments); - let type = target.instantiations.get(id); - if (!type) { - type = createObjectType(ObjectFlags.Reference, target.symbol); - target.instantiations.set(id, type); - type.objectFlags |= typeArguments ? getPropagatingFlagsOfTypes(typeArguments, /*excludeKinds*/ 0) : 0; - type.target = target; - type.resolvedTypeArguments = typeArguments; - } - return type; - } - - function cloneTypeReference(source: TypeReference): TypeReference { - const type = createType(source.flags); - type.symbol = source.symbol; - type.objectFlags = source.objectFlags; - type.target = source.target; - type.resolvedTypeArguments = source.resolvedTypeArguments; - return type; - } - - function createDeferredTypeReference(target: GenericType, node: TypeReferenceNode | ArrayTypeNode | TupleTypeNode, mapper?: TypeMapper): DeferredTypeReference { - const aliasSymbol = getAliasSymbolForTypeNode(node); - const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); - const type = createObjectType(ObjectFlags.Reference, target.symbol); - type.target = target; - type.node = node; - type.mapper = mapper; - type.aliasSymbol = aliasSymbol; - type.aliasTypeArguments = mapper ? instantiateTypes(aliasTypeArguments, mapper) : aliasTypeArguments; - return type; - } - - function getTypeArguments(type: TypeReference): readonly Type[] { - if (!type.resolvedTypeArguments) { - if (!pushTypeResolution(type, TypeSystemPropertyName.ResolvedTypeArguments)) { - return type.target.localTypeParameters?.map(() => errorType) || emptyArray; - } - const node = type.node; - const typeArguments = !node ? emptyArray : - node.kind === SyntaxKind.TypeReference ? concatenate(type.target.outerTypeParameters, getEffectiveTypeArguments(node, type.target.localTypeParameters!)) : - node.kind === SyntaxKind.ArrayType ? [getTypeFromTypeNode(node.elementType)] : - map(node.elementTypes, getTypeFromTypeNode); - if (popTypeResolution()) { - type.resolvedTypeArguments = type.mapper ? instantiateTypes(typeArguments, type.mapper) : typeArguments; - } - else { - type.resolvedTypeArguments = type.target.localTypeParameters?.map(() => errorType) || emptyArray; - error( - type.node || currentNode, - type.target.symbol - ? Diagnostics.Type_arguments_for_0_circularly_reference_themselves - : Diagnostics.Tuple_type_arguments_circularly_reference_themselves - , - type.target.symbol && symbolToString(type.target.symbol) - ); - } - } - return type.resolvedTypeArguments; - } - - function getTypeReferenceArity(type: TypeReference): number { - return length(type.target.typeParameters); - } - - - /** - * Get type from type-reference that reference to class or interface - */ - function getTypeFromClassOrInterfaceReference(node: NodeWithTypeArguments, symbol: Symbol): Type { - const type = getDeclaredTypeOfSymbol(getMergedSymbol(symbol)); - const typeParameters = type.localTypeParameters; - if (typeParameters) { - const numTypeArguments = length(node.typeArguments); - const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters); - const isJs = isInJSFile(node); - const isJsImplicitAny = !noImplicitAny && isJs; - if (!isJsImplicitAny && (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length)) { - const missingAugmentsTag = isJs && isExpressionWithTypeArguments(node) && !isJSDocAugmentsTag(node.parent); - const diag = minTypeArgumentCount === typeParameters.length ? - missingAugmentsTag ? - Diagnostics.Expected_0_type_arguments_provide_these_with_an_extends_tag : - Diagnostics.Generic_type_0_requires_1_type_argument_s : - missingAugmentsTag ? - Diagnostics.Expected_0_1_type_arguments_provide_these_with_an_extends_tag : - Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments; - - const typeStr = typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType); - error(node, diag, typeStr, minTypeArgumentCount, typeParameters.length); - if (!isJs) { - // TODO: Adopt same permissive behavior in TS as in JS to reduce follow-on editing experience failures (requires editing fillMissingTypeArguments) - return errorType; - } - } - if (node.kind === SyntaxKind.TypeReference && isAliasedType(node)) { - return createDeferredTypeReference(type, node, /*mapper*/ undefined); - } - // In a type reference, the outer type parameters of the referenced class or interface are automatically - // supplied as type arguments and the type reference only specifies arguments for the local type parameters - // of the class or interface. - const typeArguments = concatenate(type.outerTypeParameters, fillMissingTypeArguments(typeArgumentsFromTypeReferenceNode(node), typeParameters, minTypeArgumentCount, isJs)); - return createTypeReference(type, typeArguments); - } - return checkNoTypeArguments(node, symbol) ? type : errorType; - } - - function getTypeAliasInstantiation(symbol: Symbol, typeArguments: readonly Type[] | undefined): Type { - const type = getDeclaredTypeOfSymbol(symbol); - const links = getSymbolLinks(symbol); - const typeParameters = links.typeParameters!; - const id = getTypeListId(typeArguments); - let instantiation = links.instantiations!.get(id); - if (!instantiation) { - links.instantiations!.set(id, instantiation = instantiateType(type, createTypeMapper(typeParameters, fillMissingTypeArguments(typeArguments, typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(symbol.valueDeclaration))))); - } - return instantiation; - } - - /** - * Get type from reference to type alias. When a type alias is generic, the declared type of the type alias may include - * references to the type parameters of the alias. We replace those with the actual type arguments by instantiating the - * declared type. Instantiations are cached using the type identities of the type arguments as the key. - */ - function getTypeFromTypeAliasReference(node: NodeWithTypeArguments, symbol: Symbol): Type { - const type = getDeclaredTypeOfSymbol(symbol); - const typeParameters = getSymbolLinks(symbol).typeParameters; - if (typeParameters) { - const numTypeArguments = length(node.typeArguments); - const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters); - if (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length) { - error(node, - minTypeArgumentCount === typeParameters.length ? - Diagnostics.Generic_type_0_requires_1_type_argument_s : - Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments, - symbolToString(symbol), - minTypeArgumentCount, - typeParameters.length); - return errorType; - } - return getTypeAliasInstantiation(symbol, typeArgumentsFromTypeReferenceNode(node)); - } - return checkNoTypeArguments(node, symbol) ? type : errorType; - } - - function getTypeReferenceName(node: TypeReferenceType): EntityNameOrEntityNameExpression | undefined { - switch (node.kind) { - case SyntaxKind.TypeReference: - return node.typeName; - case SyntaxKind.ExpressionWithTypeArguments: - // We only support expressions that are simple qualified names. For other - // expressions this produces undefined. - const expr = node.expression; - if (isEntityNameExpression(expr)) { - return expr; - } - // fall through; - } - - return undefined; - } - - function resolveTypeReferenceName(typeReferenceName: EntityNameExpression | EntityName | undefined, meaning: SymbolFlags, ignoreErrors?: boolean) { - if (!typeReferenceName) { - return unknownSymbol; - } - - return resolveEntityName(typeReferenceName, meaning, ignoreErrors) || unknownSymbol; - } - - function getTypeReferenceType(node: NodeWithTypeArguments, symbol: Symbol): Type { - if (symbol === unknownSymbol) { - return errorType; - } - symbol = getExpandoSymbol(symbol) || symbol; - if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - return getTypeFromClassOrInterfaceReference(node, symbol); - } - if (symbol.flags & SymbolFlags.TypeAlias) { - return getTypeFromTypeAliasReference(node, symbol); - } - // Get type from reference to named type that cannot be generic (enum or type parameter) - const res = tryGetDeclaredTypeOfSymbol(symbol); - if (res) { - return checkNoTypeArguments(node, symbol) ? - res.flags & TypeFlags.TypeParameter ? getConstrainedTypeVariable(res, node) : getRegularTypeOfLiteralType(res) : - errorType; - } - if (symbol.flags & SymbolFlags.Value && isJSDocTypeReference(node)) { - const jsdocType = getTypeFromJSDocValueReference(node, symbol); - if (jsdocType) { - return jsdocType; - } - else { - // Resolve the type reference as a Type for the purpose of reporting errors. - resolveTypeReferenceName(getTypeReferenceName(node), SymbolFlags.Type); - return getTypeOfSymbol(symbol); - } - } - return errorType; - } - - /** - * A JSdoc TypeReference may be to a value, but resolve it as a type anyway. - * Note: If the value is imported from commonjs, it should really be an alias, - * but this function's special-case code fakes alias resolution as well. - */ - function getTypeFromJSDocValueReference(node: NodeWithTypeArguments, symbol: Symbol): Type | undefined { - const valueType = getTypeOfSymbol(symbol); - let typeType = valueType; - if (symbol.valueDeclaration) { - const decl = getRootDeclaration(symbol.valueDeclaration); - let isRequireAlias = false; - if (isVariableDeclaration(decl) && decl.initializer) { - let expr = decl.initializer; - // skip past entity names, eg `require("x").a.b.c` - while (isPropertyAccessExpression(expr)) { - expr = expr.expression; - } - isRequireAlias = isCallExpression(expr) && isRequireCall(expr, /*requireStringLiteralLikeArgument*/ true) && !!valueType.symbol; - } - const isImportTypeWithQualifier = node.kind === SyntaxKind.ImportType && (node as ImportTypeNode).qualifier; - // valueType might not have a symbol, eg, {import('./b').STRING_LITERAL} - if (valueType.symbol && (isRequireAlias || isImportTypeWithQualifier)) { - typeType = getTypeReferenceType(node, valueType.symbol); - } - } - return getSymbolLinks(symbol).resolvedJSDocType = typeType; - } - - function getSubstitutionType(typeVariable: TypeVariable, substitute: Type) { - if (substitute.flags & TypeFlags.AnyOrUnknown || substitute === typeVariable) { - return typeVariable; - } - const id = `${getTypeId(typeVariable)}>${getTypeId(substitute)}`; - const cached = substitutionTypes.get(id); - if (cached) { - return cached; - } - const result = createType(TypeFlags.Substitution); - result.typeVariable = typeVariable; - result.substitute = substitute; - substitutionTypes.set(id, result); - return result; - } - - function isUnaryTupleTypeNode(node: TypeNode) { - return node.kind === SyntaxKind.TupleType && (node).elementTypes.length === 1; - } - - function getImpliedConstraint(typeVariable: TypeVariable, checkNode: TypeNode, extendsNode: TypeNode): Type | undefined { - return isUnaryTupleTypeNode(checkNode) && isUnaryTupleTypeNode(extendsNode) ? getImpliedConstraint(typeVariable, (checkNode).elementTypes[0], (extendsNode).elementTypes[0]) : - getActualTypeVariable(getTypeFromTypeNode(checkNode)) === typeVariable ? getTypeFromTypeNode(extendsNode) : - undefined; - } - - function getConstrainedTypeVariable(typeVariable: TypeVariable, node: Node) { - let constraints: Type[] | undefined; - while (node && !isStatement(node) && node.kind !== SyntaxKind.JSDocComment) { - const parent = node.parent; - if (parent.kind === SyntaxKind.ConditionalType && node === (parent).trueType) { - const constraint = getImpliedConstraint(typeVariable, (parent).checkType, (parent).extendsType); - if (constraint) { - constraints = append(constraints, constraint); - } - } - node = parent; - } - return constraints ? getSubstitutionType(typeVariable, getIntersectionType(append(constraints, typeVariable))) : typeVariable; - } - - function isJSDocTypeReference(node: Node): node is TypeReferenceNode { - return !!(node.flags & NodeFlags.JSDoc) && (node.kind === SyntaxKind.TypeReference || node.kind === SyntaxKind.ImportType); - } - - function checkNoTypeArguments(node: NodeWithTypeArguments, symbol?: Symbol) { - if (node.typeArguments) { - error(node, Diagnostics.Type_0_is_not_generic, symbol ? symbolToString(symbol) : (node).typeName ? declarationNameToString((node).typeName) : "(anonymous)"); - return false; - } - return true; - } - - function getIntendedTypeFromJSDocTypeReference(node: TypeReferenceNode): Type | undefined { - if (isIdentifier(node.typeName)) { - const typeArgs = node.typeArguments; - switch (node.typeName.escapedText) { - case "String": - checkNoTypeArguments(node); - return stringType; - case "Number": - checkNoTypeArguments(node); - return numberType; - case "Boolean": - checkNoTypeArguments(node); - return booleanType; - case "Void": - checkNoTypeArguments(node); - return voidType; - case "Undefined": - checkNoTypeArguments(node); - return undefinedType; - case "Null": - checkNoTypeArguments(node); - return nullType; - case "Function": - case "function": - checkNoTypeArguments(node); - return globalFunctionType; - case "array": - return (!typeArgs || !typeArgs.length) && !noImplicitAny ? anyArrayType : undefined; - case "promise": - return (!typeArgs || !typeArgs.length) && !noImplicitAny ? createPromiseType(anyType) : undefined; - case "Object": - if (typeArgs && typeArgs.length === 2) { - if (isJSDocIndexSignature(node)) { - const indexed = getTypeFromTypeNode(typeArgs[0]); - const target = getTypeFromTypeNode(typeArgs[1]); - const index = createIndexInfo(target, /*isReadonly*/ false); - return createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, indexed === stringType ? index : undefined, indexed === numberType ? index : undefined); - } - return anyType; - } - checkNoTypeArguments(node); - return !noImplicitAny ? anyType : undefined; - } - } - } - - function getTypeFromJSDocNullableTypeNode(node: JSDocNullableType) { - const type = getTypeFromTypeNode(node.type); - return strictNullChecks ? getNullableType(type, TypeFlags.Null) : type; - } - - function getTypeFromTypeReference(node: TypeReferenceType): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - let symbol: Symbol | undefined; - let type: Type | undefined; - const meaning = SymbolFlags.Type; - if (isJSDocTypeReference(node)) { - type = getIntendedTypeFromJSDocTypeReference(node); - if (!type) { - symbol = resolveTypeReferenceName(getTypeReferenceName(node), meaning, /*ignoreErrors*/ true); - if (symbol === unknownSymbol) { - symbol = resolveTypeReferenceName(getTypeReferenceName(node), meaning | SymbolFlags.Value); - } - else { - resolveTypeReferenceName(getTypeReferenceName(node), meaning); // Resolve again to mark errors, if any - } - type = getTypeReferenceType(node, symbol); - } - } - if (!type) { - symbol = resolveTypeReferenceName(getTypeReferenceName(node), meaning); - type = getTypeReferenceType(node, symbol); - } - // Cache both the resolved symbol and the resolved type. The resolved symbol is needed when we check the - // type reference in checkTypeReferenceNode. - links.resolvedSymbol = symbol; - links.resolvedType = type; - } - return links.resolvedType; - } - - function typeArgumentsFromTypeReferenceNode(node: NodeWithTypeArguments): Type[] | undefined { - return map(node.typeArguments, getTypeFromTypeNode); - } - - function getTypeFromTypeQueryNode(node: TypeQueryNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - // TypeScript 1.0 spec (April 2014): 3.6.3 - // The expression is processed as an identifier expression (section 4.3) - // or property access expression(section 4.10), - // the widened type(section 3.9) of which becomes the result. - links.resolvedType = getRegularTypeOfLiteralType(getWidenedType(checkExpression(node.exprName))); - } - return links.resolvedType; - } - - function getTypeOfGlobalSymbol(symbol: Symbol | undefined, arity: number): ObjectType { - - function getTypeDeclaration(symbol: Symbol): Declaration | undefined { - const declarations = symbol.declarations; - for (const declaration of declarations) { - switch (declaration.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - return declaration; - } - } - } - - if (!symbol) { - return arity ? emptyGenericType : emptyObjectType; - } - const type = getDeclaredTypeOfSymbol(symbol); - if (!(type.flags & TypeFlags.Object)) { - error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_be_a_class_or_interface_type, symbolName(symbol)); - return arity ? emptyGenericType : emptyObjectType; - } - if (length((type).typeParameters) !== arity) { - error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_have_1_type_parameter_s, symbolName(symbol), arity); - return arity ? emptyGenericType : emptyObjectType; - } - return type; - } - - function getGlobalValueSymbol(name: __String, reportErrors: boolean): Symbol | undefined { - return getGlobalSymbol(name, SymbolFlags.Value, reportErrors ? Diagnostics.Cannot_find_global_value_0 : undefined); - } - - function getGlobalTypeSymbol(name: __String, reportErrors: boolean): Symbol | undefined { - return getGlobalSymbol(name, SymbolFlags.Type, reportErrors ? Diagnostics.Cannot_find_global_type_0 : undefined); - } - - function getGlobalSymbol(name: __String, meaning: SymbolFlags, diagnostic: DiagnosticMessage | undefined): Symbol | undefined { - // Don't track references for global symbols anyway, so value if `isReference` is arbitrary - return resolveName(undefined, name, meaning, diagnostic, name, /*isUse*/ false); - } - - function getGlobalType(name: __String, arity: 0, reportErrors: boolean): ObjectType; - function getGlobalType(name: __String, arity: number, reportErrors: boolean): GenericType; - function getGlobalType(name: __String, arity: number, reportErrors: boolean): ObjectType | undefined { - const symbol = getGlobalTypeSymbol(name, reportErrors); - return symbol || reportErrors ? getTypeOfGlobalSymbol(symbol, arity) : undefined; - } - - function getGlobalTypedPropertyDescriptorType() { - return deferredGlobalTypedPropertyDescriptorType || (deferredGlobalTypedPropertyDescriptorType = getGlobalType("TypedPropertyDescriptor" as __String, /*arity*/ 1, /*reportErrors*/ true)) || emptyGenericType; - } - - function getGlobalTemplateStringsArrayType() { - return deferredGlobalTemplateStringsArrayType || (deferredGlobalTemplateStringsArrayType = getGlobalType("TemplateStringsArray" as __String, /*arity*/ 0, /*reportErrors*/ true)) || emptyObjectType; - } - - function getGlobalImportMetaType() { - return deferredGlobalImportMetaType || (deferredGlobalImportMetaType = getGlobalType("ImportMeta" as __String, /*arity*/ 0, /*reportErrors*/ true)) || emptyObjectType; - } - - function getGlobalESSymbolConstructorSymbol(reportErrors: boolean) { - return deferredGlobalESSymbolConstructorSymbol || (deferredGlobalESSymbolConstructorSymbol = getGlobalValueSymbol("Symbol" as __String, reportErrors)); - } - - function getGlobalESSymbolType(reportErrors: boolean) { - return deferredGlobalESSymbolType || (deferredGlobalESSymbolType = getGlobalType("Symbol" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; - } - - function getGlobalPromiseType(reportErrors: boolean) { - return deferredGlobalPromiseType || (deferredGlobalPromiseType = getGlobalType("Promise" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } - - function getGlobalPromiseLikeType(reportErrors: boolean) { - return deferredGlobalPromiseLikeType || (deferredGlobalPromiseLikeType = getGlobalType("PromiseLike" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } - - function getGlobalPromiseConstructorSymbol(reportErrors: boolean): Symbol | undefined { - return deferredGlobalPromiseConstructorSymbol || (deferredGlobalPromiseConstructorSymbol = getGlobalValueSymbol("Promise" as __String, reportErrors)); - } - - function getGlobalPromiseConstructorLikeType(reportErrors: boolean) { - return deferredGlobalPromiseConstructorLikeType || (deferredGlobalPromiseConstructorLikeType = getGlobalType("PromiseConstructorLike" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; - } - - function getGlobalAsyncIterableType(reportErrors: boolean) { - return deferredGlobalAsyncIterableType || (deferredGlobalAsyncIterableType = getGlobalType("AsyncIterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } - - function getGlobalAsyncIteratorType(reportErrors: boolean) { - return deferredGlobalAsyncIteratorType || (deferredGlobalAsyncIteratorType = getGlobalType("AsyncIterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; - } - - function getGlobalAsyncIterableIteratorType(reportErrors: boolean) { - return deferredGlobalAsyncIterableIteratorType || (deferredGlobalAsyncIterableIteratorType = getGlobalType("AsyncIterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } - - function getGlobalAsyncGeneratorType(reportErrors: boolean) { - return deferredGlobalAsyncGeneratorType || (deferredGlobalAsyncGeneratorType = getGlobalType("AsyncGenerator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; - } - - function getGlobalIterableType(reportErrors: boolean) { - return deferredGlobalIterableType || (deferredGlobalIterableType = getGlobalType("Iterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } - - function getGlobalIteratorType(reportErrors: boolean) { - return deferredGlobalIteratorType || (deferredGlobalIteratorType = getGlobalType("Iterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; - } - - function getGlobalIterableIteratorType(reportErrors: boolean) { - return deferredGlobalIterableIteratorType || (deferredGlobalIterableIteratorType = getGlobalType("IterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } - - function getGlobalGeneratorType(reportErrors: boolean) { - return deferredGlobalGeneratorType || (deferredGlobalGeneratorType = getGlobalType("Generator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; - } - - function getGlobalIteratorYieldResultType(reportErrors: boolean) { - return deferredGlobalIteratorYieldResultType || (deferredGlobalIteratorYieldResultType = getGlobalType("IteratorYieldResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } - - function getGlobalIteratorReturnResultType(reportErrors: boolean) { - return deferredGlobalIteratorReturnResultType || (deferredGlobalIteratorReturnResultType = getGlobalType("IteratorReturnResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } - - function getGlobalTypeOrUndefined(name: __String, arity = 0): ObjectType | undefined { - const symbol = getGlobalSymbol(name, SymbolFlags.Type, /*diagnostic*/ undefined); - return symbol && getTypeOfGlobalSymbol(symbol, arity); - } - - function getGlobalExtractSymbol(): Symbol { - return deferredGlobalExtractSymbol || (deferredGlobalExtractSymbol = getGlobalSymbol("Extract" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!); // TODO: GH#18217 - } - - function getGlobalOmitSymbol(): Symbol { - return deferredGlobalOmitSymbol || (deferredGlobalOmitSymbol = getGlobalSymbol("Omit" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!); // TODO: GH#18217 - } - - function getGlobalBigIntType(reportErrors: boolean) { - return deferredGlobalBigIntType || (deferredGlobalBigIntType = getGlobalType("BigInt" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; - } - - /** - * Instantiates a global type that is generic with some element type, and returns that instantiation. - */ - function createTypeFromGenericGlobalType(genericGlobalType: GenericType, typeArguments: readonly Type[]): ObjectType { - return genericGlobalType !== emptyGenericType ? createTypeReference(genericGlobalType, typeArguments) : emptyObjectType; - } - - function createTypedPropertyDescriptorType(propertyType: Type): Type { - return createTypeFromGenericGlobalType(getGlobalTypedPropertyDescriptorType(), [propertyType]); - } - - function createIterableType(iteratedType: Type): Type { - return createTypeFromGenericGlobalType(getGlobalIterableType(/*reportErrors*/ true), [iteratedType]); - } - - function createArrayType(elementType: Type, readonly?: boolean): ObjectType { - return createTypeFromGenericGlobalType(readonly ? globalReadonlyArrayType : globalArrayType, [elementType]); - } - - function getArrayOrTupleTargetType(node: ArrayTypeNode | TupleTypeNode): GenericType { - const readonly = isReadonlyTypeOperator(node.parent); - if (node.kind === SyntaxKind.ArrayType || node.elementTypes.length === 1 && node.elementTypes[0].kind === SyntaxKind.RestType) { - return readonly ? globalReadonlyArrayType : globalArrayType; - } - const lastElement = lastOrUndefined(node.elementTypes); - const restElement = lastElement && lastElement.kind === SyntaxKind.RestType ? lastElement : undefined; - const minLength = findLastIndex(node.elementTypes, n => n.kind !== SyntaxKind.OptionalType && n !== restElement) + 1; - return getTupleTypeOfArity(node.elementTypes.length, minLength, !!restElement, readonly, /*associatedNames*/ undefined); - } - - // Return true when the given node is transitively contained in type constructs that eagerly - // resolve their constituent types. We include SyntaxKind.TypeReference because type arguments - // of type aliases are eagerly resolved. - function isAliasedType(node: Node): boolean { - const parent = node.parent; - switch (parent.kind) { - case SyntaxKind.ParenthesizedType: - case SyntaxKind.TypeReference: - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: - case SyntaxKind.IndexedAccessType: - case SyntaxKind.ConditionalType: - case SyntaxKind.TypeOperator: - return isAliasedType(parent); - case SyntaxKind.TypeAliasDeclaration: - return true; - } - return false; - } - - function getTypeFromArrayOrTupleTypeNode(node: ArrayTypeNode | TupleTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - const target = getArrayOrTupleTargetType(node); - if (target === emptyGenericType) { - links.resolvedType = emptyObjectType; - } - else if (isAliasedType(node)) { - links.resolvedType = node.kind === SyntaxKind.TupleType && node.elementTypes.length === 0 ? target : - createDeferredTypeReference(target, node, /*mapper*/ undefined); - } - else { - const elementTypes = node.kind === SyntaxKind.ArrayType ? [getTypeFromTypeNode(node.elementType)] : map(node.elementTypes, getTypeFromTypeNode); - links.resolvedType = createTypeReference(target, elementTypes); - } - } - return links.resolvedType; - } - - function isReadonlyTypeOperator(node: Node) { - return isTypeOperatorNode(node) && node.operator === SyntaxKind.ReadonlyKeyword; - } - - // We represent tuple types as type references to synthesized generic interface types created by - // this function. The types are of the form: - // - // interface Tuple extends Array { 0: T0, 1: T1, 2: T2, ... } - // - // Note that the generic type created by this function has no symbol associated with it. The same - // is true for each of the synthesized type parameters. - function createTupleTypeOfArity(arity: number, minLength: number, hasRestElement: boolean, readonly: boolean, associatedNames: __String[] | undefined): TupleType { - let typeParameters: TypeParameter[] | undefined; - const properties: Symbol[] = []; - const maxLength = hasRestElement ? arity - 1 : arity; - if (arity) { - typeParameters = new Array(arity); - for (let i = 0; i < arity; i++) { - const typeParameter = typeParameters[i] = createTypeParameter(); - if (i < maxLength) { - const property = createSymbol(SymbolFlags.Property | (i >= minLength ? SymbolFlags.Optional : 0), - "" + i as __String, readonly ? CheckFlags.Readonly : 0); - property.type = typeParameter; - properties.push(property); - } - } - } - const literalTypes = []; - for (let i = minLength; i <= maxLength; i++) literalTypes.push(getLiteralType(i)); - const lengthSymbol = createSymbol(SymbolFlags.Property, "length" as __String); - lengthSymbol.type = hasRestElement ? numberType : getUnionType(literalTypes); - properties.push(lengthSymbol); - const type = createObjectType(ObjectFlags.Tuple | ObjectFlags.Reference); - type.typeParameters = typeParameters; - type.outerTypeParameters = undefined; - type.localTypeParameters = typeParameters; - type.instantiations = createMap(); - type.instantiations.set(getTypeListId(type.typeParameters), type); - type.target = type; - type.resolvedTypeArguments = type.typeParameters; - type.thisType = createTypeParameter(); - type.thisType.isThisType = true; - type.thisType.constraint = type; - type.declaredProperties = properties; - type.declaredCallSignatures = emptyArray; - type.declaredConstructSignatures = emptyArray; - type.declaredStringIndexInfo = undefined; - type.declaredNumberIndexInfo = undefined; - type.minLength = minLength; - type.hasRestElement = hasRestElement; - type.readonly = readonly; - type.associatedNames = associatedNames; - return type; - } - - function getTupleTypeOfArity(arity: number, minLength: number, hasRestElement: boolean, readonly: boolean, associatedNames?: __String[]): GenericType { - const key = arity + (hasRestElement ? "+" : ",") + minLength + (readonly ? "R" : "") + (associatedNames && associatedNames.length ? "," + associatedNames.join(",") : ""); - let type = tupleTypes.get(key); - if (!type) { - tupleTypes.set(key, type = createTupleTypeOfArity(arity, minLength, hasRestElement, readonly, associatedNames)); - } - return type; - } - - function createTupleType(elementTypes: readonly Type[], minLength = elementTypes.length, hasRestElement = false, readonly = false, associatedNames?: __String[]) { - const arity = elementTypes.length; - if (arity === 1 && hasRestElement) { - return createArrayType(elementTypes[0], readonly); - } - const tupleType = getTupleTypeOfArity(arity, minLength, arity > 0 && hasRestElement, readonly, associatedNames); - return elementTypes.length ? createTypeReference(tupleType, elementTypes) : tupleType; - } - - function sliceTupleType(type: TupleTypeReference, index: number) { - const tuple = type.target; - if (tuple.hasRestElement) { - // don't slice off rest element - index = Math.min(index, getTypeReferenceArity(type) - 1); - } - return createTupleType( - getTypeArguments(type).slice(index), - Math.max(0, tuple.minLength - index), - tuple.hasRestElement, - tuple.readonly, - tuple.associatedNames && tuple.associatedNames.slice(index), - ); - } - - function getTypeFromOptionalTypeNode(node: OptionalTypeNode): Type { - const type = getTypeFromTypeNode(node.type); - return strictNullChecks ? getOptionalType(type) : type; - } - - function getTypeId(type: Type) { - return type.id; - } - - function containsType(types: readonly Type[], type: Type): boolean { - return binarySearch(types, type, getTypeId, compareValues) >= 0; - } - - function insertType(types: Type[], type: Type): boolean { - const index = binarySearch(types, type, getTypeId, compareValues); - if (index < 0) { - types.splice(~index, 0, type); - return true; - } - return false; - } - - function addTypeToUnion(typeSet: Type[], includes: TypeFlags, type: Type) { - const flags = type.flags; - if (flags & TypeFlags.Union) { - return addTypesToUnion(typeSet, includes, (type).types); - } - // We ignore 'never' types in unions - if (!(flags & TypeFlags.Never)) { - includes |= flags & TypeFlags.IncludesMask; - if (flags & TypeFlags.StructuredOrInstantiable) includes |= TypeFlags.IncludesStructuredOrInstantiable; - if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; - if (!strictNullChecks && flags & TypeFlags.Nullable) { - if (!(getObjectFlags(type) & ObjectFlags.ContainsWideningType)) includes |= TypeFlags.IncludesNonWideningType; - } - else { - const len = typeSet.length; - const index = len && type.id > typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues); - if (index < 0) { - typeSet.splice(~index, 0, type); - } - } - } - return includes; - } - - // Add the given types to the given type set. Order is preserved, duplicates are removed, - // and nested types of the given kind are flattened into the set. - function addTypesToUnion(typeSet: Type[], includes: TypeFlags, types: readonly Type[]): TypeFlags { - for (const type of types) { - includes = addTypeToUnion(typeSet, includes, type); - } - return includes; - } - - function isSetOfLiteralsFromSameEnum(types: readonly Type[]): boolean { - const first = types[0]; - if (first.flags & TypeFlags.EnumLiteral) { - const firstEnum = getParentOfSymbol(first.symbol); - for (let i = 1; i < types.length; i++) { - const other = types[i]; - if (!(other.flags & TypeFlags.EnumLiteral) || (firstEnum !== getParentOfSymbol(other.symbol))) { - return false; - } - } - return true; - } - - return false; - } - - function removeSubtypes(types: Type[], primitivesOnly: boolean): boolean { - const len = types.length; - if (len === 0 || isSetOfLiteralsFromSameEnum(types)) { - return true; - } - let i = len; - let count = 0; - while (i > 0) { - i--; - const source = types[i]; - for (const target of types) { - if (source !== target) { - if (count === 100000) { - // After 100000 subtype checks we estimate the remaining amount of work by assuming the - // same ratio of checks per element. If the estimated number of remaining type checks is - // greater than an upper limit we deem the union type too complex to represent. The - // upper limit is 25M for unions of primitives only, and 1M otherwise. This for example - // caps union types at 5000 unique literal types and 1000 unique object types. - const estimatedCount = (count / (len - i)) * len; - if (estimatedCount > (primitivesOnly ? 25000000 : 1000000)) { - error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent); - return false; - } - } - count++; - if (isTypeSubtypeOf(source, target) && ( - !(getObjectFlags(getTargetType(source)) & ObjectFlags.Class) || - !(getObjectFlags(getTargetType(target)) & ObjectFlags.Class) || - isTypeDerivedFrom(source, target))) { - orderedRemoveItemAt(types, i); - break; - } - } - } - } - return true; - } - - function removeRedundantLiteralTypes(types: Type[], includes: TypeFlags) { - let i = types.length; - while (i > 0) { - i--; - const t = types[i]; - const remove = - t.flags & TypeFlags.StringLiteral && includes & TypeFlags.String || - t.flags & TypeFlags.NumberLiteral && includes & TypeFlags.Number || - t.flags & TypeFlags.BigIntLiteral && includes & TypeFlags.BigInt || - t.flags & TypeFlags.UniqueESSymbol && includes & TypeFlags.ESSymbol || - isFreshLiteralType(t) && containsType(types, (t).regularType); - if (remove) { - orderedRemoveItemAt(types, i); - } - } - } - - // We sort and deduplicate the constituent types based on object identity. If the subtypeReduction - // flag is specified we also reduce the constituent type set to only include types that aren't subtypes - // of other types. Subtype reduction is expensive for large union types and is possible only when union - // types are known not to circularly reference themselves (as is the case with union types created by - // expression constructs such as array literals and the || and ?: operators). Named types can - // circularly reference themselves and therefore cannot be subtype reduced during their declaration. - // For example, "type Item = string | (() => Item" is a named type that circularly references itself. - function getUnionType(types: readonly Type[], unionReduction: UnionReduction = UnionReduction.Literal, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { - if (types.length === 0) { - return neverType; - } - if (types.length === 1) { - return types[0]; - } - const typeSet: Type[] = []; - const includes = addTypesToUnion(typeSet, 0, types); - if (unionReduction !== UnionReduction.None) { - if (includes & TypeFlags.AnyOrUnknown) { - return includes & TypeFlags.Any ? includes & TypeFlags.IncludesWildcard ? wildcardType : anyType : unknownType; - } - switch (unionReduction) { - case UnionReduction.Literal: - if (includes & (TypeFlags.Literal | TypeFlags.UniqueESSymbol)) { - removeRedundantLiteralTypes(typeSet, includes); - } - break; - case UnionReduction.Subtype: - if (!removeSubtypes(typeSet, !(includes & TypeFlags.IncludesStructuredOrInstantiable))) { - return errorType; - } - break; - } - if (typeSet.length === 0) { - return includes & TypeFlags.Null ? includes & TypeFlags.IncludesNonWideningType ? nullType : nullWideningType : - includes & TypeFlags.Undefined ? includes & TypeFlags.IncludesNonWideningType ? undefinedType : undefinedWideningType : - neverType; - } - } - return getUnionTypeFromSortedList(typeSet, includes & TypeFlags.NotPrimitiveUnion ? 0 : ObjectFlags.PrimitiveUnion, aliasSymbol, aliasTypeArguments); - } - - function getUnionTypePredicate(signatures: readonly Signature[]): TypePredicate | undefined { - let first: TypePredicate | undefined; - const types: Type[] = []; - for (const sig of signatures) { - const pred = getTypePredicateOfSignature(sig); - if (!pred || pred.kind === TypePredicateKind.AssertsThis || pred.kind === TypePredicateKind.AssertsIdentifier) { - continue; - } - - if (first) { - if (!typePredicateKindsMatch(first, pred)) { - // No common type predicate. - return undefined; - } - } - else { - first = pred; - } - types.push(pred.type); - } - if (!first) { - // No union signatures had a type predicate. - return undefined; - } - const unionType = getUnionType(types); - return createTypePredicate(first.kind, first.parameterName, first.parameterIndex, unionType); - } - - function typePredicateKindsMatch(a: TypePredicate, b: TypePredicate): boolean { - return a.kind === b.kind && a.parameterIndex === b.parameterIndex; - } - - // This function assumes the constituent type list is sorted and deduplicated. - function getUnionTypeFromSortedList(types: Type[], objectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { - if (types.length === 0) { - return neverType; - } - if (types.length === 1) { - return types[0]; - } - const id = getTypeListId(types); - let type = unionTypes.get(id); - if (!type) { - type = createType(TypeFlags.Union); - unionTypes.set(id, type); - type.objectFlags = objectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); - type.types = types; - /* - Note: This is the alias symbol (or lack thereof) that we see when we first encounter this union type. - For aliases of identical unions, eg `type T = A | B; type U = A | B`, the symbol of the first alias encountered is the aliasSymbol. - (In the language service, the order may depend on the order in which a user takes actions, such as hovering over symbols.) - It's important that we create equivalent union types only once, so that's an unfortunate side effect. - */ - type.aliasSymbol = aliasSymbol; - type.aliasTypeArguments = aliasTypeArguments; - } - return type; - } - - function getTypeFromUnionTypeNode(node: UnionTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - const aliasSymbol = getAliasSymbolForTypeNode(node); - links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), UnionReduction.Literal, - aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)); - } - return links.resolvedType; - } - - function addTypeToIntersection(typeSet: Map, includes: TypeFlags, type: Type) { - const flags = type.flags; - if (flags & TypeFlags.Intersection) { - return addTypesToIntersection(typeSet, includes, (type).types); - } - if (isEmptyAnonymousObjectType(type)) { - if (!(includes & TypeFlags.IncludesEmptyObject)) { - includes |= TypeFlags.IncludesEmptyObject; - typeSet.set(type.id.toString(), type); - } - } - else { - if (flags & TypeFlags.AnyOrUnknown) { - if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; - } - else if ((strictNullChecks || !(flags & TypeFlags.Nullable)) && !typeSet.has(type.id.toString())) { - if (type.flags & TypeFlags.Unit && includes & TypeFlags.Unit) { - // We have seen two distinct unit types which means we should reduce to an - // empty intersection. Adding TypeFlags.NonPrimitive causes that to happen. - includes |= TypeFlags.NonPrimitive; - } - typeSet.set(type.id.toString(), type); - } - includes |= flags & TypeFlags.IncludesMask; - } - return includes; - } - - // Add the given types to the given type set. Order is preserved, freshness is removed from literal - // types, duplicates are removed, and nested types of the given kind are flattened into the set. - function addTypesToIntersection(typeSet: Map, includes: TypeFlags, types: readonly Type[]) { - for (const type of types) { - includes = addTypeToIntersection(typeSet, includes, getRegularTypeOfLiteralType(type)); - } - return includes; - } - - function removeRedundantPrimitiveTypes(types: Type[], includes: TypeFlags) { - let i = types.length; - while (i > 0) { - i--; - const t = types[i]; - const remove = - t.flags & TypeFlags.String && includes & TypeFlags.StringLiteral || - t.flags & TypeFlags.Number && includes & TypeFlags.NumberLiteral || - t.flags & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral || - t.flags & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol; - if (remove) { - orderedRemoveItemAt(types, i); - } - } - } - - // Check that the given type has a match in every union. A given type is matched by - // an identical type, and a literal type is additionally matched by its corresponding - // primitive type. - function eachUnionContains(unionTypes: UnionType[], type: Type) { - for (const u of unionTypes) { - if (!containsType(u.types, type)) { - const primitive = type.flags & TypeFlags.StringLiteral ? stringType : - type.flags & TypeFlags.NumberLiteral ? numberType : - type.flags & TypeFlags.BigIntLiteral ? bigintType : - type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : - undefined; - if (!primitive || !containsType(u.types, primitive)) { - return false; - } - } - } - return true; - } - - function extractIrreducible(types: Type[], flag: TypeFlags) { - if (every(types, t => !!(t.flags & TypeFlags.Union) && some((t as UnionType).types, tt => !!(tt.flags & flag)))) { - for (let i = 0; i < types.length; i++) { - types[i] = filterType(types[i], t => !(t.flags & flag)); - } - return true; - } - return false; - } - - // If the given list of types contains more than one union of primitive types, replace the - // first with a union containing an intersection of those primitive types, then remove the - // other unions and return true. Otherwise, do nothing and return false. - function intersectUnionsOfPrimitiveTypes(types: Type[]) { - let unionTypes: UnionType[] | undefined; - const index = findIndex(types, t => !!(getObjectFlags(t) & ObjectFlags.PrimitiveUnion)); - if (index < 0) { - return false; - } - let i = index + 1; - // Remove all but the first union of primitive types and collect them in - // the unionTypes array. - while (i < types.length) { - const t = types[i]; - if (getObjectFlags(t) & ObjectFlags.PrimitiveUnion) { - (unionTypes || (unionTypes = [types[index]])).push(t); - orderedRemoveItemAt(types, i); - } - else { - i++; - } - } - // Return false if there was only one union of primitive types - if (!unionTypes) { - return false; - } - // We have more than one union of primitive types, now intersect them. For each - // type in each union we check if the type is matched in every union and if so - // we include it in the result. - const checked: Type[] = []; - const result: Type[] = []; - for (const u of unionTypes) { - for (const t of u.types) { - if (insertType(checked, t)) { - if (eachUnionContains(unionTypes, t)) { - insertType(result, t); - } - } - } - } - // Finally replace the first union with the result - types[index] = getUnionTypeFromSortedList(result, ObjectFlags.PrimitiveUnion); - return true; - } - - function createIntersectionType(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) { - const result = createType(TypeFlags.Intersection); - result.objectFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); - result.types = types; - result.aliasSymbol = aliasSymbol; // See comment in `getUnionTypeFromSortedList`. - result.aliasTypeArguments = aliasTypeArguments; - return result; - } - - // We normalize combinations of intersection and union types based on the distributive property of the '&' - // operator. Specifically, because X & (A | B) is equivalent to X & A | X & B, we can transform intersection - // types with union type constituents into equivalent union types with intersection type constituents and - // effectively ensure that union types are always at the top level in type representations. - // - // We do not perform structural deduplication on intersection types. Intersection types are created only by the & - // type operator and we can't reduce those because we want to support recursive intersection types. For example, - // a type alias of the form "type List = T & { next: List }" cannot be reduced during its declaration. - // Also, unlike union types, the order of the constituent types is preserved in order that overload resolution - // for intersections of types with signatures can be deterministic. - function getIntersectionType(types: readonly Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { - const typeMembershipMap: Map = createMap(); - const includes = addTypesToIntersection(typeMembershipMap, 0, types); - const typeSet: Type[] = arrayFrom(typeMembershipMap.values()); - // An intersection type is considered empty if it contains - // the type never, or - // more than one unit type or, - // an object type and a nullable type (null or undefined), or - // a string-like type and a type known to be non-string-like, or - // a number-like type and a type known to be non-number-like, or - // a symbol-like type and a type known to be non-symbol-like, or - // a void-like type and a type known to be non-void-like, or - // a non-primitive type and a type known to be primitive. - if (includes & TypeFlags.Never || - strictNullChecks && includes & TypeFlags.Nullable && includes & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.IncludesEmptyObject) || - includes & TypeFlags.NonPrimitive && includes & (TypeFlags.DisjointDomains & ~TypeFlags.NonPrimitive) || - includes & TypeFlags.StringLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.StringLike) || - includes & TypeFlags.NumberLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.NumberLike) || - includes & TypeFlags.BigIntLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.BigIntLike) || - includes & TypeFlags.ESSymbolLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.ESSymbolLike) || - includes & TypeFlags.VoidLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.VoidLike)) { - return neverType; - } - if (includes & TypeFlags.Any) { - return includes & TypeFlags.IncludesWildcard ? wildcardType : anyType; - } - if (!strictNullChecks && includes & TypeFlags.Nullable) { - return includes & TypeFlags.Undefined ? undefinedType : nullType; - } - if (includes & TypeFlags.String && includes & TypeFlags.StringLiteral || - includes & TypeFlags.Number && includes & TypeFlags.NumberLiteral || - includes & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral || - includes & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol) { - removeRedundantPrimitiveTypes(typeSet, includes); - } - if (includes & TypeFlags.IncludesEmptyObject && includes & TypeFlags.Object) { - orderedRemoveItemAt(typeSet, findIndex(typeSet, isEmptyAnonymousObjectType)); - } - if (typeSet.length === 0) { - return unknownType; - } - if (typeSet.length === 1) { - return typeSet[0]; - } - const id = getTypeListId(typeSet); - let result = intersectionTypes.get(id); - if (!result) { - if (includes & TypeFlags.Union) { - if (intersectUnionsOfPrimitiveTypes(typeSet)) { - // When the intersection creates a reduced set (which might mean that *all* union types have - // disappeared), we restart the operation to get a new set of combined flags. Once we have - // reduced we'll never reduce again, so this occurs at most once. - result = getIntersectionType(typeSet, aliasSymbol, aliasTypeArguments); - } - else if (extractIrreducible(typeSet, TypeFlags.Undefined)) { - result = getUnionType([getIntersectionType(typeSet), undefinedType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); - } - else if (extractIrreducible(typeSet, TypeFlags.Null)) { - result = getUnionType([getIntersectionType(typeSet), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); - } - else { - // We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of - // the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain. - // If the estimated size of the resulting union type exceeds 100000 constituents, report an error. - const size = reduceLeft(typeSet, (n, t) => n * (t.flags & TypeFlags.Union ? (t).types.length : 1), 1); - if (size >= 100000) { - error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent); - return errorType; - } - const unionIndex = findIndex(typeSet, t => (t.flags & TypeFlags.Union) !== 0); - const unionType = typeSet[unionIndex]; - result = getUnionType(map(unionType.types, t => getIntersectionType(replaceElement(typeSet, unionIndex, t))), - UnionReduction.Literal, aliasSymbol, aliasTypeArguments); - } - } - else { - result = createIntersectionType(typeSet, aliasSymbol, aliasTypeArguments); - } - intersectionTypes.set(id, result); - } - return result; - } - - function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - const aliasSymbol = getAliasSymbolForTypeNode(node); - links.resolvedType = getIntersectionType(map(node.types, getTypeFromTypeNode), - aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)); - } - return links.resolvedType; - } - - function createIndexType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) { - const result = createType(TypeFlags.Index); - result.type = type; - result.stringsOnly = stringsOnly; - return result; - } - - function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) { - return stringsOnly ? - type.resolvedStringIndexType || (type.resolvedStringIndexType = createIndexType(type, /*stringsOnly*/ true)) : - type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, /*stringsOnly*/ false)); - } - - function getLiteralTypeFromPropertyName(name: PropertyName) { - return isIdentifier(name) ? getLiteralType(unescapeLeadingUnderscores(name.escapedText)) : - getRegularTypeOfLiteralType(isComputedPropertyName(name) ? checkComputedPropertyName(name) : checkExpression(name)); - } - - function getBigIntLiteralType(node: BigIntLiteral): LiteralType { - return getLiteralType({ - negative: false, - base10Value: parsePseudoBigInt(node.text) - }); - } - - function getLiteralTypeFromProperty(prop: Symbol, include: TypeFlags) { - if (!(getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier)) { - let type = getLateBoundSymbol(prop).nameType; - if (!type && !isKnownSymbol(prop)) { - if (prop.escapedName === InternalSymbolName.Default) { - type = getLiteralType("default"); - } - else { - const name = prop.valueDeclaration && getNameOfDeclaration(prop.valueDeclaration) as PropertyName; - type = name && getLiteralTypeFromPropertyName(name) || getLiteralType(symbolName(prop)); - } - } - if (type && type.flags & include) { - return type; - } - } - return neverType; - } - - function getLiteralTypeFromProperties(type: Type, include: TypeFlags) { - return getUnionType(map(getPropertiesOfType(type), p => getLiteralTypeFromProperty(p, include))); - } - - function getNonEnumNumberIndexInfo(type: Type) { - const numberIndexInfo = getIndexInfoOfType(type, IndexKind.Number); - return numberIndexInfo !== enumNumberIndexInfo ? numberIndexInfo : undefined; - } - - function getIndexType(type: Type, stringsOnly = keyofStringsOnly, noIndexSignatures?: boolean): Type { - return type.flags & TypeFlags.Union ? getIntersectionType(map((type).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : - type.flags & TypeFlags.Intersection ? getUnionType(map((type).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : - maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive) ? getIndexTypeForGenericType(type, stringsOnly) : - getObjectFlags(type) & ObjectFlags.Mapped ? filterType(getConstraintTypeFromMappedType(type), t => !(noIndexSignatures && t.flags & (TypeFlags.Any | TypeFlags.String))) : - type === wildcardType ? wildcardType : - type.flags & TypeFlags.Unknown ? neverType : - type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType : - stringsOnly ? !noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? stringType : getLiteralTypeFromProperties(type, TypeFlags.StringLiteral) : - !noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? getUnionType([stringType, numberType, getLiteralTypeFromProperties(type, TypeFlags.UniqueESSymbol)]) : - getNonEnumNumberIndexInfo(type) ? getUnionType([numberType, getLiteralTypeFromProperties(type, TypeFlags.StringLiteral | TypeFlags.UniqueESSymbol)]) : - getLiteralTypeFromProperties(type, TypeFlags.StringOrNumberLiteralOrUnique); - } - - function getExtractStringType(type: Type) { - if (keyofStringsOnly) { - return type; - } - const extractTypeAlias = getGlobalExtractSymbol(); - return extractTypeAlias ? getTypeAliasInstantiation(extractTypeAlias, [type, stringType]) : stringType; - } - - function getIndexTypeOrString(type: Type): Type { - const indexType = getExtractStringType(getIndexType(type)); - return indexType.flags & TypeFlags.Never ? stringType : indexType; - } - - function getTypeFromTypeOperatorNode(node: TypeOperatorNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - switch (node.operator) { - case SyntaxKind.KeyOfKeyword: - links.resolvedType = getIndexType(getTypeFromTypeNode(node.type)); - break; - case SyntaxKind.UniqueKeyword: - links.resolvedType = node.type.kind === SyntaxKind.SymbolKeyword - ? getESSymbolLikeTypeForNode(walkUpParenthesizedTypes(node.parent)) - : errorType; - break; - case SyntaxKind.ReadonlyKeyword: - links.resolvedType = getTypeFromTypeNode(node.type); - break; - default: - throw Debug.assertNever(node.operator); - } - } - return links.resolvedType; - } - - function createIndexedAccessType(objectType: Type, indexType: Type) { - const type = createType(TypeFlags.IndexedAccess); - type.objectType = objectType; - type.indexType = indexType; - return type; - } - - /** - * Returns if a type is or consists of a JSLiteral object type - * In addition to objects which are directly literals, - * * unions where every element is a jsliteral - * * intersections where at least one element is a jsliteral - * * and instantiable types constrained to a jsliteral - * Should all count as literals and not print errors on access or assignment of possibly existing properties. - * This mirrors the behavior of the index signature propagation, to which this behaves similarly (but doesn't affect assignability or inference). - */ - function isJSLiteralType(type: Type): boolean { - if (noImplicitAny) { - return false; // Flag is meaningless under `noImplicitAny` mode - } - if (getObjectFlags(type) & ObjectFlags.JSLiteral) { - return true; - } - if (type.flags & TypeFlags.Union) { - return every((type as UnionType).types, isJSLiteralType); - } - if (type.flags & TypeFlags.Intersection) { - return some((type as IntersectionType).types, isJSLiteralType); - } - if (type.flags & TypeFlags.Instantiable) { - return isJSLiteralType(getResolvedBaseConstraint(type)); - } - return false; - } - - function getPropertyNameFromIndex(indexType: Type, accessNode: StringLiteral | Identifier | ObjectBindingPattern | ArrayBindingPattern | ComputedPropertyName | NumericLiteral | IndexedAccessTypeNode | ElementAccessExpression | SyntheticExpression | undefined) { - const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; - return isTypeUsableAsPropertyName(indexType) ? - getPropertyNameFromType(indexType) : - accessExpression && checkThatExpressionIsProperSymbolReference(accessExpression.argumentExpression, indexType, /*reportError*/ false) ? - getPropertyNameForKnownSymbolName(idText((accessExpression.argumentExpression).name)) : - accessNode && isPropertyName(accessNode) ? - // late bound names are handled in the first branch, so here we only need to handle normal names - getPropertyNameForPropertyNameNode(accessNode) : - undefined; - } - - function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, fullIndexType: Type, suppressNoImplicitAnyError: boolean, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) { - const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; - const propName = getPropertyNameFromIndex(indexType, accessNode); - if (propName !== undefined) { - const prop = getPropertyOfType(objectType, propName); - if (prop) { - if (accessExpression) { - markPropertyAsReferenced(prop, accessExpression, /*isThisAccess*/ accessExpression.expression.kind === SyntaxKind.ThisKeyword); - if (isAssignmentTarget(accessExpression) && (isReferenceToReadonlyEntity(accessExpression, prop) || isReferenceThroughNamespaceImport(accessExpression))) { - error(accessExpression.argumentExpression, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(prop)); - return undefined; - } - if (accessFlags & AccessFlags.CacheSymbol) { - getNodeLinks(accessNode!).resolvedSymbol = prop; - } - } - const propType = getTypeOfSymbol(prop); - return accessExpression && getAssignmentTargetKind(accessExpression) !== AssignmentKind.Definite ? - getFlowTypeOfReference(accessExpression, propType) : - propType; - } - if (everyType(objectType, isTupleType) && isNumericLiteralName(propName) && +propName >= 0) { - if (accessNode && everyType(objectType, t => !(t).target.hasRestElement) && !(accessFlags & AccessFlags.NoTupleBoundsCheck)) { - const indexNode = getIndexNodeForAccessExpression(accessNode); - if (isTupleType(objectType)) { - error(indexNode, Diagnostics.Tuple_type_0_of_length_1_has_no_element_at_index_2, - typeToString(objectType), getTypeReferenceArity(objectType), unescapeLeadingUnderscores(propName)); - } - else { - error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); - } - } - errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, IndexKind.Number)); - return mapType(objectType, t => getRestTypeOfTupleType(t) || undefinedType); - } - } - if (!(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike)) { - if (objectType.flags & (TypeFlags.Any | TypeFlags.Never)) { - return objectType; - } - const stringIndexInfo = getIndexInfoOfType(objectType, IndexKind.String); - const indexInfo = isTypeAssignableToKind(indexType, TypeFlags.NumberLike) && getIndexInfoOfType(objectType, IndexKind.Number) || stringIndexInfo; - if (indexInfo) { - if (accessFlags & AccessFlags.NoIndexSignatures && indexInfo === stringIndexInfo) { - if (accessExpression) { - error(accessExpression, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(originalObjectType)); - } - return undefined; - } - if (accessNode && !isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { - const indexNode = getIndexNodeForAccessExpression(accessNode); - error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType)); - return indexInfo.type; - } - errorIfWritingToReadonlyIndex(indexInfo); - return indexInfo.type; - } - if (indexType.flags & TypeFlags.Never) { - return neverType; - } - if (isJSLiteralType(objectType)) { - return anyType; - } - if (accessExpression && !isConstEnumObjectType(objectType)) { - if (objectType.symbol === globalThisSymbol && propName !== undefined && globalThisSymbol.exports!.has(propName) && (globalThisSymbol.exports!.get(propName)!.flags & SymbolFlags.BlockScoped)) { - error(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); - } - else if (noImplicitAny && !compilerOptions.suppressImplicitAnyIndexErrors && !suppressNoImplicitAnyError) { - if (propName !== undefined && typeHasStaticProperty(propName, objectType)) { - error(accessExpression, Diagnostics.Property_0_is_a_static_member_of_type_1, propName as string, typeToString(objectType)); - } - else if (getIndexTypeOfType(objectType, IndexKind.Number)) { - error(accessExpression.argumentExpression, Diagnostics.Element_implicitly_has_an_any_type_because_index_expression_is_not_of_type_number); - } - else { - let suggestion: string | undefined; - if (propName !== undefined && (suggestion = getSuggestionForNonexistentProperty(propName as string, objectType))) { - if (suggestion !== undefined) { - error(accessExpression.argumentExpression, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, propName as string, typeToString(objectType), suggestion); - } - } - else { - const suggestion = getSuggestionForNonexistentIndexSignature(objectType, accessExpression, indexType); - if (suggestion !== undefined) { - error(accessExpression, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature_Did_you_mean_to_call_1, typeToString(objectType), suggestion); - } - else { - let errorInfo: DiagnosticMessageChain | undefined; - if (indexType.flags & TypeFlags.EnumLiteral) { - errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + typeToString(indexType) + "]", typeToString(objectType)); - } - else if (indexType.flags & TypeFlags.UniqueESSymbol) { - const symbolName = getFullyQualifiedName((indexType as UniqueESSymbolType).symbol, accessExpression); - errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + symbolName + "]", typeToString(objectType)); - } - else if (indexType.flags & TypeFlags.StringLiteral) { - errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as StringLiteralType).value, typeToString(objectType)); - } - else if (indexType.flags & TypeFlags.NumberLiteral) { - errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as NumberLiteralType).value, typeToString(objectType)); - } - else if (indexType.flags & (TypeFlags.Number | TypeFlags.String)) { - errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.No_index_signature_with_a_parameter_of_type_0_was_found_on_type_1, typeToString(indexType), typeToString(objectType)); - } - - errorInfo = chainDiagnosticMessages( - errorInfo, - Diagnostics.Element_implicitly_has_an_any_type_because_expression_of_type_0_can_t_be_used_to_index_type_1, typeToString(fullIndexType), typeToString(objectType) - ); - diagnostics.add(createDiagnosticForNodeFromMessageChain(accessExpression, errorInfo)); - } - } - } - } - return undefined; - } - } - if (isJSLiteralType(objectType)) { - return anyType; - } - if (accessNode) { - const indexNode = getIndexNodeForAccessExpression(accessNode); - if (indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { - error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, "" + (indexType).value, typeToString(objectType)); - } - else if (indexType.flags & (TypeFlags.String | TypeFlags.Number)) { - error(indexNode, Diagnostics.Type_0_has_no_matching_index_signature_for_type_1, typeToString(objectType), typeToString(indexType)); - } - else { - error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType)); - } - } - if (isTypeAny(indexType)) { - return indexType; - } - return undefined; - - function errorIfWritingToReadonlyIndex(indexInfo: IndexInfo | undefined): void { - if (indexInfo && indexInfo.isReadonly && accessExpression && (isAssignmentTarget(accessExpression) || isDeleteTarget(accessExpression))) { - error(accessExpression, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); - } - } - } - - function getIndexNodeForAccessExpression(accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression) { - return accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression : - accessNode.kind === SyntaxKind.IndexedAccessType ? accessNode.indexType : - accessNode.kind === SyntaxKind.ComputedPropertyName ? accessNode.expression : - accessNode; - } - - function isGenericObjectType(type: Type): boolean { - return maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive | TypeFlags.GenericMappedType); - } - - function isGenericIndexType(type: Type): boolean { - return maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive | TypeFlags.Index); - } - - function isThisTypeParameter(type: Type): boolean { - return !!(type.flags & TypeFlags.TypeParameter && (type).isThisType); - } - - function getSimplifiedType(type: Type, writing: boolean): Type { - return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(type, writing) : - type.flags & TypeFlags.Conditional ? getSimplifiedConditionalType(type, writing) : - type; - } - - function distributeIndexOverObjectType(objectType: Type, indexType: Type, writing: boolean) { - // (T | U)[K] -> T[K] | U[K] (reading) - // (T | U)[K] -> T[K] & U[K] (writing) - // (T & U)[K] -> T[K] & U[K] - if (objectType.flags & TypeFlags.UnionOrIntersection) { - const types = map((objectType as UnionOrIntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType), writing)); - return objectType.flags & TypeFlags.Intersection || writing ? getIntersectionType(types) : getUnionType(types); - } - } - - function distributeObjectOverIndexType(objectType: Type, indexType: Type, writing: boolean) { - // T[A | B] -> T[A] | T[B] (reading) - // T[A | B] -> T[A] & T[B] (writing) - if (indexType.flags & TypeFlags.Union) { - const types = map((indexType as UnionType).types, t => getSimplifiedType(getIndexedAccessType(objectType, t), writing)); - return writing ? getIntersectionType(types) : getUnionType(types); - } - } - - // Transform an indexed access to a simpler form, if possible. Return the simpler form, or return - // the type itself if no transformation is possible. The writing flag indicates that the type is - // the target of an assignment. - function getSimplifiedIndexedAccessType(type: IndexedAccessType, writing: boolean): Type { - const cache = writing ? "simplifiedForWriting" : "simplifiedForReading"; - if (type[cache]) { - return type[cache] === circularConstraintType ? type : type[cache]!; - } - type[cache] = circularConstraintType; - // We recursively simplify the object type as it may in turn be an indexed access type. For example, with - // '{ [P in T]: { [Q in U]: number } }[T][U]' we want to first simplify the inner indexed access type. - const objectType = getSimplifiedType(type.objectType, writing); - const indexType = getSimplifiedType(type.indexType, writing); - // T[A | B] -> T[A] | T[B] (reading) - // T[A | B] -> T[A] & T[B] (writing) - const distributedOverIndex = distributeObjectOverIndexType(objectType, indexType, writing); - if (distributedOverIndex) { - return type[cache] = distributedOverIndex; - } - // Only do the inner distributions if the index can no longer be instantiated to cause index distribution again - if (!(indexType.flags & TypeFlags.Instantiable)) { - // (T | U)[K] -> T[K] | U[K] (reading) - // (T | U)[K] -> T[K] & U[K] (writing) - // (T & U)[K] -> T[K] & U[K] - const distributedOverObject = distributeIndexOverObjectType(objectType, indexType, writing); - if (distributedOverObject) { - return type[cache] = distributedOverObject; - } - } - // So ultimately (reading): - // ((A & B) | C)[K1 | K2] -> ((A & B) | C)[K1] | ((A & B) | C)[K2] -> (A & B)[K1] | C[K1] | (A & B)[K2] | C[K2] -> (A[K1] & B[K1]) | C[K1] | (A[K2] & B[K2]) | C[K2] - - // If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper - // that substitutes the index type for P. For example, for an index access { [P in K]: Box }[X], we - // construct the type Box. - if (isGenericMappedType(objectType)) { - return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t, writing)); - } - return type[cache] = type; - } - - function getSimplifiedConditionalType(type: ConditionalType, writing: boolean) { - const checkType = type.checkType; - const extendsType = type.extendsType; - const trueType = getTrueTypeFromConditionalType(type); - const falseType = getFalseTypeFromConditionalType(type); - // Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`. - if (falseType.flags & TypeFlags.Never && getActualTypeVariable(trueType) === getActualTypeVariable(checkType)) { - if (checkType.flags & TypeFlags.Any || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true - return getSimplifiedType(trueType, writing); - } - else if (isIntersectionEmpty(checkType, extendsType)) { // Always false - return neverType; - } - } - else if (trueType.flags & TypeFlags.Never && getActualTypeVariable(falseType) === getActualTypeVariable(checkType)) { - if (!(checkType.flags & TypeFlags.Any) && isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true - return neverType; - } - else if (checkType.flags & TypeFlags.Any || isIntersectionEmpty(checkType, extendsType)) { // Always false - return getSimplifiedType(falseType, writing); - } - } - return type; - } - - /** - * Invokes union simplification logic to determine if an intersection is considered empty as a union constituent - */ - function isIntersectionEmpty(type1: Type, type2: Type) { - return !!(getUnionType([intersectTypes(type1, type2), neverType]).flags & TypeFlags.Never); - } - - function substituteIndexedMappedType(objectType: MappedType, index: Type) { - const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [index]); - const templateMapper = combineTypeMappers(objectType.mapper, mapper); - return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper); - } - - function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression): Type { - return getIndexedAccessTypeOrUndefined(objectType, indexType, accessNode, AccessFlags.None) || (accessNode ? errorType : unknownType); - } - - function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, accessFlags = AccessFlags.None): Type | undefined { - if (objectType === wildcardType || indexType === wildcardType) { - return wildcardType; - } - // If the object type has a string index signature and no other members we know that the result will - // always be the type of that index signature and we can simplify accordingly. - if (isStringIndexSignatureOnlyType(objectType) && !(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { - indexType = stringType; - } - // If the index type is generic, or if the object type is generic and doesn't originate in an expression, - // we are performing a higher-order index access where we cannot meaningfully access the properties of the - // object type. Note that for a generic T and a non-generic K, we eagerly resolve T[K] if it originates in - // an expression. This is to preserve backwards compatibility. For example, an element access 'this["foo"]' - // has always been resolved eagerly using the constraint type of 'this' at the given location. - if (isGenericIndexType(indexType) || !(accessNode && accessNode.kind !== SyntaxKind.IndexedAccessType) && isGenericObjectType(objectType)) { - if (objectType.flags & TypeFlags.AnyOrUnknown) { - return objectType; - } - // Defer the operation by creating an indexed access type. - const id = objectType.id + "," + indexType.id; - let type = indexedAccessTypes.get(id); - if (!type) { - indexedAccessTypes.set(id, type = createIndexedAccessType(objectType, indexType)); - } - return type; - } - // In the following we resolve T[K] to the type of the property in T selected by K. - // We treat boolean as different from other unions to improve errors; - // skipping straight to getPropertyTypeForIndexType gives errors with 'boolean' instead of 'true'. - const apparentObjectType = getApparentType(objectType); - if (indexType.flags & TypeFlags.Union && !(indexType.flags & TypeFlags.Boolean)) { - const propTypes: Type[] = []; - let wasMissingProp = false; - for (const t of (indexType).types) { - const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, indexType, wasMissingProp, accessNode, accessFlags); - if (propType) { - propTypes.push(propType); - } - else if (!accessNode) { - // If there's no error node, we can immeditely stop, since error reporting is off - return undefined; - } - else { - // Otherwise we set a flag and return at the end of the loop so we still mark all errors - wasMissingProp = true; - } - } - if (wasMissingProp) { - return undefined; - } - return accessFlags & AccessFlags.Writing ? getIntersectionType(propTypes) : getUnionType(propTypes); - } - return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, /* supressNoImplicitAnyError */ false, accessNode, accessFlags | AccessFlags.CacheSymbol); - } - - function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) { - const links = getNodeLinks(node); - if (!links.resolvedType) { - const objectType = getTypeFromTypeNode(node.objectType); - const indexType = getTypeFromTypeNode(node.indexType); - const resolved = getIndexedAccessType(objectType, indexType, node); - links.resolvedType = resolved.flags & TypeFlags.IndexedAccess && - (resolved).objectType === objectType && - (resolved).indexType === indexType ? - getConstrainedTypeVariable(resolved, node) : resolved; - } - return links.resolvedType; - } - - function getTypeFromMappedTypeNode(node: MappedTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - const type = createObjectType(ObjectFlags.Mapped, node.symbol); - type.declaration = node; - type.aliasSymbol = getAliasSymbolForTypeNode(node); - type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(type.aliasSymbol); - links.resolvedType = type; - // Eagerly resolve the constraint type which forces an error if the constraint type circularly - // references itself through one or more type aliases. - getConstraintTypeFromMappedType(type); - } - return links.resolvedType; - } - - function getActualTypeVariable(type: Type): Type { - if (type.flags & TypeFlags.Substitution) { - return (type).typeVariable; - } - if (type.flags & TypeFlags.IndexedAccess && ( - (type).objectType.flags & TypeFlags.Substitution || - (type).indexType.flags & TypeFlags.Substitution)) { - return getIndexedAccessType(getActualTypeVariable((type).objectType), getActualTypeVariable((type).indexType)); - } - return type; - } - - function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined): Type { - const checkType = instantiateType(root.checkType, mapper); - const extendsType = instantiateType(root.extendsType, mapper); - if (checkType === wildcardType || extendsType === wildcardType) { - return wildcardType; - } - const checkTypeInstantiable = maybeTypeOfKind(checkType, TypeFlags.Instantiable | TypeFlags.GenericMappedType); - let combinedMapper: TypeMapper | undefined; - if (root.inferTypeParameters) { - const context = createInferenceContext(root.inferTypeParameters, /*signature*/ undefined, InferenceFlags.None); - if (!checkTypeInstantiable) { - // We don't want inferences from constraints as they may cause us to eagerly resolve the - // conditional type instead of deferring resolution. Also, we always want strict function - // types rules (i.e. proper contravariance) for inferences. - inferTypes(context.inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); - } - combinedMapper = combineTypeMappers(mapper, context.mapper); - } - // Instantiate the extends type including inferences for 'infer T' type parameters - const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType; - // We attempt to resolve the conditional type only when the check and extends types are non-generic - if (!checkTypeInstantiable && !maybeTypeOfKind(inferredExtendsType, TypeFlags.Instantiable | TypeFlags.GenericMappedType)) { - if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown) { - return instantiateType(root.trueType, combinedMapper || mapper); - } - // Return union of trueType and falseType for 'any' since it matches anything - if (checkType.flags & TypeFlags.Any) { - return getUnionType([instantiateType(root.trueType, combinedMapper || mapper), instantiateType(root.falseType, mapper)]); - } - // Return falseType for a definitely false extends check. We check an instantiations of the two - // types with type parameters mapped to the wildcard type, the most permissive instantiations - // possible (the wildcard type is assignable to and from all types). If those are not related, - // then no instantiations will be and we can just return the false branch type. - if (!isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType))) { - return instantiateType(root.falseType, mapper); - } - // Return trueType for a definitely true extends check. We check instantiations of the two - // types with type parameters mapped to their restrictive form, i.e. a form of the type parameter - // that has no constraint. This ensures that, for example, the type - // type Foo = T extends { x: string } ? string : number - // doesn't immediately resolve to 'string' instead of being deferred. - if (isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) { - return instantiateType(root.trueType, combinedMapper || mapper); - } - } - // Return a deferred type for a check that is neither definitely true nor definitely false - const erasedCheckType = getActualTypeVariable(checkType); - const result = createType(TypeFlags.Conditional); - result.root = root; - result.checkType = erasedCheckType; - result.extendsType = extendsType; - result.mapper = mapper; - result.combinedMapper = combinedMapper; - result.aliasSymbol = root.aliasSymbol; - result.aliasTypeArguments = instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217 - return result; - } - - function getTrueTypeFromConditionalType(type: ConditionalType) { - return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(type.root.trueType, type.mapper)); - } - - function getFalseTypeFromConditionalType(type: ConditionalType) { - return type.resolvedFalseType || (type.resolvedFalseType = instantiateType(type.root.falseType, type.mapper)); - } - - function getInferredTrueTypeFromConditionalType(type: ConditionalType) { - return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = type.combinedMapper ? instantiateType(type.root.trueType, type.combinedMapper) : getTrueTypeFromConditionalType(type)); - } - - function getInferTypeParameters(node: ConditionalTypeNode): TypeParameter[] | undefined { - let result: TypeParameter[] | undefined; - if (node.locals) { - node.locals.forEach(symbol => { - if (symbol.flags & SymbolFlags.TypeParameter) { - result = append(result, getDeclaredTypeOfSymbol(symbol)); - } - }); - } - return result; - } - - function getTypeFromConditionalTypeNode(node: ConditionalTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - const checkType = getTypeFromTypeNode(node.checkType); - const aliasSymbol = getAliasSymbolForTypeNode(node); - const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); - const allOuterTypeParameters = getOuterTypeParameters(node, /*includeThisTypes*/ true); - const outerTypeParameters = aliasTypeArguments ? allOuterTypeParameters : filter(allOuterTypeParameters, tp => isTypeParameterPossiblyReferenced(tp, node)); - const root: ConditionalRoot = { - node, - checkType, - extendsType: getTypeFromTypeNode(node.extendsType), - trueType: getTypeFromTypeNode(node.trueType), - falseType: getTypeFromTypeNode(node.falseType), - isDistributive: !!(checkType.flags & TypeFlags.TypeParameter), - inferTypeParameters: getInferTypeParameters(node), - outerTypeParameters, - instantiations: undefined, - aliasSymbol, - aliasTypeArguments - }; - links.resolvedType = getConditionalType(root, /*mapper*/ undefined); - if (outerTypeParameters) { - root.instantiations = createMap(); - root.instantiations.set(getTypeListId(outerTypeParameters), links.resolvedType); - } - } - return links.resolvedType; - } - - function getTypeFromInferTypeNode(node: InferTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - links.resolvedType = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node.typeParameter)); - } - return links.resolvedType; - } - - function getIdentifierChain(node: EntityName): Identifier[] { - if (isIdentifier(node)) { - return [node]; - } - else { - return append(getIdentifierChain(node.left), node.right); - } - } - - function getTypeFromImportTypeNode(node: ImportTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - if (node.isTypeOf && node.typeArguments) { // Only the non-typeof form can make use of type arguments - error(node, Diagnostics.Type_arguments_cannot_be_used_here); - links.resolvedSymbol = unknownSymbol; - return links.resolvedType = errorType; - } - if (!isLiteralImportTypeNode(node)) { - error(node.argument, Diagnostics.String_literal_expected); - links.resolvedSymbol = unknownSymbol; - return links.resolvedType = errorType; - } - const targetMeaning = node.isTypeOf ? SymbolFlags.Value : node.flags & NodeFlags.JSDoc ? SymbolFlags.Value | SymbolFlags.Type : SymbolFlags.Type; - // TODO: Future work: support unions/generics/whatever via a deferred import-type - const innerModuleSymbol = resolveExternalModuleName(node, node.argument.literal); - if (!innerModuleSymbol) { - links.resolvedSymbol = unknownSymbol; - return links.resolvedType = errorType; - } - const moduleSymbol = resolveExternalModuleSymbol(innerModuleSymbol, /*dontResolveAlias*/ false); - if (!nodeIsMissing(node.qualifier)) { - const nameStack: Identifier[] = getIdentifierChain(node.qualifier!); - let currentNamespace = moduleSymbol; - let current: Identifier | undefined; - while (current = nameStack.shift()) { - const meaning = nameStack.length ? SymbolFlags.Namespace : targetMeaning; - const next = getSymbol(getExportsOfSymbol(getMergedSymbol(resolveSymbol(currentNamespace))), current.escapedText, meaning); - if (!next) { - error(current, Diagnostics.Namespace_0_has_no_exported_member_1, getFullyQualifiedName(currentNamespace), declarationNameToString(current)); - return links.resolvedType = errorType; - } - getNodeLinks(current).resolvedSymbol = next; - getNodeLinks(current.parent).resolvedSymbol = next; - currentNamespace = next; - } - links.resolvedType = resolveImportSymbolType(node, links, currentNamespace, targetMeaning); - } - else { - if (moduleSymbol.flags & targetMeaning) { - links.resolvedType = resolveImportSymbolType(node, links, moduleSymbol, targetMeaning); - } - else { - const errorMessage = targetMeaning === SymbolFlags.Value - ? Diagnostics.Module_0_does_not_refer_to_a_value_but_is_used_as_a_value_here - : Diagnostics.Module_0_does_not_refer_to_a_type_but_is_used_as_a_type_here_Did_you_mean_typeof_import_0; - - error(node, errorMessage, node.argument.literal.text); - - links.resolvedSymbol = unknownSymbol; - links.resolvedType = errorType; - } - } - } - return links.resolvedType; - } - - function resolveImportSymbolType(node: ImportTypeNode, links: NodeLinks, symbol: Symbol, meaning: SymbolFlags) { - const resolvedSymbol = resolveSymbol(symbol); - links.resolvedSymbol = resolvedSymbol; - if (meaning === SymbolFlags.Value) { - return getTypeOfSymbol(symbol); // intentionally doesn't use resolved symbol so type is cached as expected on the alias - } - else { - return getTypeReferenceType(node, resolvedSymbol); // getTypeReferenceType doesn't handle aliases - it must get the resolved symbol - } - } - - function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: TypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - // Deferred resolution of members is handled by resolveObjectTypeMembers - const aliasSymbol = getAliasSymbolForTypeNode(node); - if (getMembersOfSymbol(node.symbol).size === 0 && !aliasSymbol) { - links.resolvedType = emptyTypeLiteralType; - } - else { - let type = createObjectType(ObjectFlags.Anonymous, node.symbol); - type.aliasSymbol = aliasSymbol; - type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); - if (isJSDocTypeLiteral(node) && node.isArrayType) { - type = createArrayType(type); - } - links.resolvedType = type; - } - } - return links.resolvedType; - } - - function getAliasSymbolForTypeNode(node: TypeNode) { - let host = node.parent; - while (isParenthesizedTypeNode(host) || isTypeOperatorNode(host) && host.operator === SyntaxKind.ReadonlyKeyword) { - host = host.parent; - } - return isTypeAlias(host) ? getSymbolOfNode(host) : undefined; - } - - function getTypeArgumentsForAliasSymbol(symbol: Symbol | undefined) { - return symbol ? getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol) : undefined; - } - - function isNonGenericObjectType(type: Type) { - return !!(type.flags & TypeFlags.Object) && !isGenericMappedType(type); - } - - function isEmptyObjectTypeOrSpreadsIntoEmptyObject(type: Type) { - return isEmptyObjectType(type) || !!(type.flags & (TypeFlags.Null | TypeFlags.Undefined | TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)); - } - - function isSinglePropertyAnonymousObjectType(type: Type) { - return !!(type.flags & TypeFlags.Object) && - !!(getObjectFlags(type) & ObjectFlags.Anonymous) && - (length(getPropertiesOfType(type)) === 1 || every(getPropertiesOfType(type), p => !!(p.flags & SymbolFlags.Optional))); - } - - function tryMergeUnionOfObjectTypeAndEmptyObject(type: UnionType, readonly: boolean): Type | undefined { - if (type.types.length === 2) { - const firstType = type.types[0]; - const secondType = type.types[1]; - if (every(type.types, isEmptyObjectTypeOrSpreadsIntoEmptyObject)) { - return isEmptyObjectType(firstType) ? firstType : isEmptyObjectType(secondType) ? secondType : emptyObjectType; - } - if (isEmptyObjectTypeOrSpreadsIntoEmptyObject(firstType) && isSinglePropertyAnonymousObjectType(secondType)) { - return getAnonymousPartialType(secondType); - } - if (isEmptyObjectTypeOrSpreadsIntoEmptyObject(secondType) && isSinglePropertyAnonymousObjectType(firstType)) { - return getAnonymousPartialType(firstType); - } - } - - function getAnonymousPartialType(type: Type) { - // gets the type as if it had been spread, but where everything in the spread is made optional - const members = createSymbolTable(); - for (const prop of getPropertiesOfType(type)) { - if (getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) { - // do nothing, skip privates - } - else if (isSpreadableProperty(prop)) { - const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); - const flags = SymbolFlags.Property | SymbolFlags.Optional; - const result = createSymbol(flags, prop.escapedName, readonly ? CheckFlags.Readonly : 0); - result.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop); - result.declarations = prop.declarations; - result.nameType = prop.nameType; - result.syntheticOrigin = prop; - members.set(prop.escapedName, result); - } - } - const spread = createAnonymousType( - type.symbol, - members, - emptyArray, - emptyArray, - getIndexInfoOfType(type, IndexKind.String), - getIndexInfoOfType(type, IndexKind.Number)); - spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; - return spread; - } - } - - /** - * Since the source of spread types are object literals, which are not binary, - * this function should be called in a left folding style, with left = previous result of getSpreadType - * and right = the new element to be spread. - */ - function getSpreadType(left: Type, right: Type, symbol: Symbol | undefined, objectFlags: ObjectFlags, readonly: boolean): Type { - if (left.flags & TypeFlags.Any || right.flags & TypeFlags.Any) { - return anyType; - } - if (left.flags & TypeFlags.Unknown || right.flags & TypeFlags.Unknown) { - return unknownType; - } - if (left.flags & TypeFlags.Never) { - return right; - } - if (right.flags & TypeFlags.Never) { - return left; - } - if (left.flags & TypeFlags.Union) { - const merged = tryMergeUnionOfObjectTypeAndEmptyObject(left as UnionType, readonly); - if (merged) { - return getSpreadType(merged, right, symbol, objectFlags, readonly); - } - return mapType(left, t => getSpreadType(t, right, symbol, objectFlags, readonly)); - } - if (right.flags & TypeFlags.Union) { - const merged = tryMergeUnionOfObjectTypeAndEmptyObject(right as UnionType, readonly); - if (merged) { - return getSpreadType(left, merged, symbol, objectFlags, readonly); - } - return mapType(right, t => getSpreadType(left, t, symbol, objectFlags, readonly)); - } - if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)) { - return left; - } - - if (isGenericObjectType(left) || isGenericObjectType(right)) { - if (isEmptyObjectType(left)) { - return right; - } - // When the left type is an intersection, we may need to merge the last constituent of the - // intersection with the right type. For example when the left type is 'T & { a: string }' - // and the right type is '{ b: string }' we produce 'T & { a: string, b: string }'. - if (left.flags & TypeFlags.Intersection) { - const types = (left).types; - const lastLeft = types[types.length - 1]; - if (isNonGenericObjectType(lastLeft) && isNonGenericObjectType(right)) { - return getIntersectionType(concatenate(types.slice(0, types.length - 1), [getSpreadType(lastLeft, right, symbol, objectFlags, readonly)])); - } - } - return getIntersectionType([left, right]); - } - - const members = createSymbolTable(); - const skippedPrivateMembers = createUnderscoreEscapedMap(); - let stringIndexInfo: IndexInfo | undefined; - let numberIndexInfo: IndexInfo | undefined; - if (left === emptyObjectType) { - // for the first spread element, left === emptyObjectType, so take the right's string indexer - stringIndexInfo = getIndexInfoOfType(right, IndexKind.String); - numberIndexInfo = getIndexInfoOfType(right, IndexKind.Number); - } - else { - stringIndexInfo = unionSpreadIndexInfos(getIndexInfoOfType(left, IndexKind.String), getIndexInfoOfType(right, IndexKind.String)); - numberIndexInfo = unionSpreadIndexInfos(getIndexInfoOfType(left, IndexKind.Number), getIndexInfoOfType(right, IndexKind.Number)); - } - - for (const rightProp of getPropertiesOfType(right)) { - if (getDeclarationModifierFlagsFromSymbol(rightProp) & (ModifierFlags.Private | ModifierFlags.Protected)) { - skippedPrivateMembers.set(rightProp.escapedName, true); - } - else if (isSpreadableProperty(rightProp)) { - members.set(rightProp.escapedName, getSpreadSymbol(rightProp, readonly)); - } - } - - for (const leftProp of getPropertiesOfType(left)) { - if (skippedPrivateMembers.has(leftProp.escapedName) || !isSpreadableProperty(leftProp)) { - continue; - } - if (members.has(leftProp.escapedName)) { - const rightProp = members.get(leftProp.escapedName)!; - const rightType = getTypeOfSymbol(rightProp); - if (rightProp.flags & SymbolFlags.Optional) { - const declarations = concatenate(leftProp.declarations, rightProp.declarations); - const flags = SymbolFlags.Property | (leftProp.flags & SymbolFlags.Optional); - const result = createSymbol(flags, leftProp.escapedName); - result.type = getUnionType([getTypeOfSymbol(leftProp), getTypeWithFacts(rightType, TypeFacts.NEUndefined)]); - result.leftSpread = leftProp; - result.rightSpread = rightProp; - result.declarations = declarations; - result.nameType = leftProp.nameType; - members.set(leftProp.escapedName, result); - } - } - else { - members.set(leftProp.escapedName, getSpreadSymbol(leftProp, readonly)); - } - } - - const spread = createAnonymousType( - symbol, - members, - emptyArray, - emptyArray, - getIndexInfoWithReadonly(stringIndexInfo, readonly), - getIndexInfoWithReadonly(numberIndexInfo, readonly)); - spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral | ObjectFlags.ContainsSpread | objectFlags; - return spread; - } - - /** We approximate own properties as non-methods plus methods that are inside the object literal */ - function isSpreadableProperty(prop: Symbol): boolean { - return !(prop.flags & (SymbolFlags.Method | SymbolFlags.GetAccessor | SymbolFlags.SetAccessor)) || - !prop.declarations.some(decl => isClassLike(decl.parent)); - } - - function getSpreadSymbol(prop: Symbol, readonly: boolean) { - const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); - if (!isSetonlyAccessor && readonly === isReadonlySymbol(prop)) { - return prop; - } - const flags = SymbolFlags.Property | (prop.flags & SymbolFlags.Optional); - const result = createSymbol(flags, prop.escapedName, readonly ? CheckFlags.Readonly : 0); - result.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop); - result.declarations = prop.declarations; - result.nameType = prop.nameType; - result.syntheticOrigin = prop; - return result; - } - - function getIndexInfoWithReadonly(info: IndexInfo | undefined, readonly: boolean) { - return info && info.isReadonly !== readonly ? createIndexInfo(info.type, readonly, info.declaration) : info; - } - - function createLiteralType(flags: TypeFlags, value: string | number | PseudoBigInt, symbol: Symbol | undefined) { - const type = createType(flags); - type.symbol = symbol!; - type.value = value; - return type; - } - - function getFreshTypeOfLiteralType(type: Type): Type { - if (type.flags & TypeFlags.Literal) { - if (!(type).freshType) { - const freshType = createLiteralType(type.flags, (type).value, (type).symbol); - freshType.regularType = type; - freshType.freshType = freshType; - (type).freshType = freshType; - } - return (type).freshType; - } - return type; - } - - function getRegularTypeOfLiteralType(type: Type): Type { - return type.flags & TypeFlags.Literal ? (type).regularType : - type.flags & TypeFlags.Union ? getUnionType(sameMap((type).types, getRegularTypeOfLiteralType)) : - type; - } - - function isFreshLiteralType(type: Type) { - return !!(type.flags & TypeFlags.Literal) && (type).freshType === type; - } - - function getLiteralType(value: string | number | PseudoBigInt, enumId?: number, symbol?: Symbol) { - // We store all literal types in a single map with keys of the form '#NNN' and '@SSS', - // where NNN is the text representation of a numeric literal and SSS are the characters - // of a string literal. For literal enum members we use 'EEE#NNN' and 'EEE@SSS', where - // EEE is a unique id for the containing enum type. - const qualifier = typeof value === "number" ? "#" : typeof value === "string" ? "@" : "n"; - const key = (enumId ? enumId : "") + qualifier + (typeof value === "object" ? pseudoBigIntToString(value) : value); - let type = literalTypes.get(key); - if (!type) { - const flags = (typeof value === "number" ? TypeFlags.NumberLiteral : - typeof value === "string" ? TypeFlags.StringLiteral : TypeFlags.BigIntLiteral) | - (enumId ? TypeFlags.EnumLiteral : 0); - literalTypes.set(key, type = createLiteralType(flags, value, symbol)); - type.regularType = type; - } - return type; - } - - function getTypeFromLiteralTypeNode(node: LiteralTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - links.resolvedType = getRegularTypeOfLiteralType(checkExpression(node.literal)); - } - return links.resolvedType; - } - - function createUniqueESSymbolType(symbol: Symbol) { - const type = createType(TypeFlags.UniqueESSymbol); - type.symbol = symbol; - type.escapedName = `__@${type.symbol.escapedName}@${getSymbolId(type.symbol)}` as __String; - return type; - } - - function getESSymbolLikeTypeForNode(node: Node) { - if (isValidESSymbolDeclaration(node)) { - const symbol = getSymbolOfNode(node); - const links = getSymbolLinks(symbol); - return links.uniqueESSymbolType || (links.uniqueESSymbolType = createUniqueESSymbolType(symbol)); - } - return esSymbolType; - } - - function getThisType(node: Node): Type { - const container = getThisContainer(node, /*includeArrowFunctions*/ false); - const parent = container && container.parent; - if (parent && (isClassLike(parent) || parent.kind === SyntaxKind.InterfaceDeclaration)) { - if (!hasModifier(container, ModifierFlags.Static) && - (!isConstructorDeclaration(container) || isNodeDescendantOf(node, container.body))) { - return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent as ClassLikeDeclaration | InterfaceDeclaration)).thisType!; - } - } - - // inside x.prototype = { ... } - if (parent && isObjectLiteralExpression(parent) && isBinaryExpression(parent.parent) && getAssignmentDeclarationKind(parent.parent) === AssignmentDeclarationKind.Prototype) { - return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent.parent.left)!.parent!).thisType!; - } - // /** @return {this} */ - // x.prototype.m = function() { ... } - const host = node.flags & NodeFlags.JSDoc ? getHostSignatureFromJSDoc(node) : undefined; - if (host && isFunctionExpression(host) && isBinaryExpression(host.parent) && getAssignmentDeclarationKind(host.parent) === AssignmentDeclarationKind.PrototypeProperty) { - return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(host.parent.left)!.parent!).thisType!; - } - // inside constructor function C() { ... } - if (isJSConstructor(container) && isNodeDescendantOf(node, container.body)) { - return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(container)).thisType!; - } - error(node, Diagnostics.A_this_type_is_available_only_in_a_non_static_member_of_a_class_or_interface); - return errorType; - } - - function getTypeFromThisTypeNode(node: ThisExpression | ThisTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - links.resolvedType = getThisType(node); - } - return links.resolvedType; - } - - function getTypeFromTypeNode(node: TypeNode): Type { - switch (node.kind) { - case SyntaxKind.AnyKeyword: - case SyntaxKind.JSDocAllType: - case SyntaxKind.JSDocUnknownType: - return anyType; - case SyntaxKind.UnknownKeyword: - return unknownType; - case SyntaxKind.StringKeyword: - return stringType; - case SyntaxKind.NumberKeyword: - return numberType; - case SyntaxKind.BigIntKeyword: - return bigintType; - case SyntaxKind.BooleanKeyword: - return booleanType; - case SyntaxKind.SymbolKeyword: - return esSymbolType; - case SyntaxKind.VoidKeyword: - return voidType; - case SyntaxKind.UndefinedKeyword: - return undefinedType; - case SyntaxKind.NullKeyword: - return nullType; - case SyntaxKind.NeverKeyword: - return neverType; - case SyntaxKind.ObjectKeyword: - return node.flags & NodeFlags.JavaScriptFile ? anyType : nonPrimitiveType; - case SyntaxKind.ThisType: - case SyntaxKind.ThisKeyword: - return getTypeFromThisTypeNode(node as ThisExpression | ThisTypeNode); - case SyntaxKind.LiteralType: - return getTypeFromLiteralTypeNode(node); - case SyntaxKind.TypeReference: - return getTypeFromTypeReference(node); - case SyntaxKind.TypePredicate: - return (node).assertsModifier ? voidType : booleanType; - case SyntaxKind.ExpressionWithTypeArguments: - return getTypeFromTypeReference(node); - case SyntaxKind.TypeQuery: - return getTypeFromTypeQueryNode(node); - case SyntaxKind.ArrayType: - case SyntaxKind.TupleType: - return getTypeFromArrayOrTupleTypeNode(node); - case SyntaxKind.OptionalType: - return getTypeFromOptionalTypeNode(node); - case SyntaxKind.UnionType: - return getTypeFromUnionTypeNode(node); - case SyntaxKind.IntersectionType: - return getTypeFromIntersectionTypeNode(node); - case SyntaxKind.JSDocNullableType: - return getTypeFromJSDocNullableTypeNode(node); - case SyntaxKind.JSDocOptionalType: - return addOptionality(getTypeFromTypeNode((node as JSDocOptionalType).type)); - case SyntaxKind.ParenthesizedType: - case SyntaxKind.JSDocNonNullableType: - case SyntaxKind.JSDocTypeExpression: - return getTypeFromTypeNode((node).type); - case SyntaxKind.RestType: - return getElementTypeOfArrayType(getTypeFromTypeNode((node).type)) || errorType; - case SyntaxKind.JSDocVariadicType: - return getTypeFromJSDocVariadicType(node as JSDocVariadicType); - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.TypeLiteral: - case SyntaxKind.JSDocTypeLiteral: - case SyntaxKind.JSDocFunctionType: - case SyntaxKind.JSDocSignature: - return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); - case SyntaxKind.TypeOperator: - return getTypeFromTypeOperatorNode(node); - case SyntaxKind.IndexedAccessType: - return getTypeFromIndexedAccessTypeNode(node); - case SyntaxKind.MappedType: - return getTypeFromMappedTypeNode(node); - case SyntaxKind.ConditionalType: - return getTypeFromConditionalTypeNode(node); - case SyntaxKind.InferType: - return getTypeFromInferTypeNode(node); - case SyntaxKind.ImportType: - return getTypeFromImportTypeNode(node); - // This function assumes that an identifier or qualified name is a type expression - // Callers should first ensure this by calling isTypeNode - case SyntaxKind.Identifier: - case SyntaxKind.QualifiedName: - const symbol = getSymbolAtLocation(node); - return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType; - default: - return errorType; - } - } - - function instantiateList(items: readonly T[], mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[]; - function instantiateList(items: readonly T[] | undefined, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[] | undefined; - function instantiateList(items: readonly T[] | undefined, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[] | undefined { - if (items && items.length) { - for (let i = 0; i < items.length; i++) { - const item = items[i]; - const mapped = instantiator(item, mapper); - if (item !== mapped) { - const result = i === 0 ? [] : items.slice(0, i); - result.push(mapped); - for (i++; i < items.length; i++) { - result.push(instantiator(items[i], mapper)); - } - return result; - } - } - } - return items; - } - - function instantiateTypes(types: readonly Type[], mapper: TypeMapper): readonly Type[]; - function instantiateTypes(types: readonly Type[] | undefined, mapper: TypeMapper): readonly Type[] | undefined; - function instantiateTypes(types: readonly Type[] | undefined, mapper: TypeMapper): readonly Type[] | undefined { - return instantiateList(types, mapper, instantiateType); - } - - function instantiateSignatures(signatures: readonly Signature[], mapper: TypeMapper): readonly Signature[] { - return instantiateList(signatures, mapper, instantiateSignature); - } - - function makeUnaryTypeMapper(source: Type, target: Type) { - return (t: Type) => t === source ? target : t; - } - - function makeBinaryTypeMapper(source1: Type, target1: Type, source2: Type, target2: Type) { - return (t: Type) => t === source1 ? target1 : t === source2 ? target2 : t; - } - - function makeArrayTypeMapper(sources: readonly Type[], targets: readonly Type[] | undefined) { - return (t: Type) => { - for (let i = 0; i < sources.length; i++) { - if (t === sources[i]) { - return targets ? targets[i] : anyType; - } - } - return t; - }; - } - - function createTypeMapper(sources: readonly TypeParameter[], targets: readonly Type[] | undefined): TypeMapper { - Debug.assert(targets === undefined || sources.length === targets.length); - return sources.length === 1 ? makeUnaryTypeMapper(sources[0], targets ? targets[0] : anyType) : - sources.length === 2 ? makeBinaryTypeMapper(sources[0], targets ? targets[0] : anyType, sources[1], targets ? targets[1] : anyType) : - makeArrayTypeMapper(sources, targets); - } - - function createTypeEraser(sources: readonly TypeParameter[]): TypeMapper { - return createTypeMapper(sources, /*targets*/ undefined); - } - - /** - * Maps forward-references to later types parameters to the empty object type. - * This is used during inference when instantiating type parameter defaults. - */ - function createBackreferenceMapper(context: InferenceContext, index: number): TypeMapper { - return t => findIndex(context.inferences, info => info.typeParameter === t) >= index ? unknownType : t; - } - - function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper; - function combineTypeMappers(mapper1: TypeMapper, mapper2: TypeMapper | undefined): TypeMapper; - function combineTypeMappers(mapper1: TypeMapper, mapper2: TypeMapper): TypeMapper { - if (!mapper1) return mapper2; - if (!mapper2) return mapper1; - return t => instantiateType(mapper1(t), mapper2); - } - - function createReplacementMapper(source: Type, target: Type, baseMapper: TypeMapper): TypeMapper { - return t => t === source ? target : baseMapper(t); - } - - function permissiveMapper(type: Type) { - return type.flags & TypeFlags.TypeParameter ? wildcardType : type; - } - - function getRestrictiveTypeParameter(tp: TypeParameter) { - return tp.constraint === unknownType ? tp : tp.restrictiveInstantiation || ( - tp.restrictiveInstantiation = createTypeParameter(tp.symbol), - (tp.restrictiveInstantiation as TypeParameter).constraint = unknownType, - tp.restrictiveInstantiation - ); - } - - function restrictiveMapper(type: Type) { - return type.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(type) : type; - } - - function cloneTypeParameter(typeParameter: TypeParameter): TypeParameter { - const result = createTypeParameter(typeParameter.symbol); - result.target = typeParameter; - return result; - } - - function instantiateTypePredicate(predicate: TypePredicate, mapper: TypeMapper): TypePredicate { - return createTypePredicate(predicate.kind, predicate.parameterName, predicate.parameterIndex, instantiateType(predicate.type, mapper)); - } - - function instantiateSignature(signature: Signature, mapper: TypeMapper, eraseTypeParameters?: boolean): Signature { - let freshTypeParameters: TypeParameter[] | undefined; - if (signature.typeParameters && !eraseTypeParameters) { - // First create a fresh set of type parameters, then include a mapping from the old to the - // new type parameters in the mapper function. Finally store this mapper in the new type - // parameters such that we can use it when instantiating constraints. - freshTypeParameters = map(signature.typeParameters, cloneTypeParameter); - mapper = combineTypeMappers(createTypeMapper(signature.typeParameters, freshTypeParameters), mapper); - for (const tp of freshTypeParameters) { - tp.mapper = mapper; - } - } - // Don't compute resolvedReturnType and resolvedTypePredicate now, - // because using `mapper` now could trigger inferences to become fixed. (See `createInferenceContext`.) - // See GH#17600. - const result = createSignature(signature.declaration, freshTypeParameters, - signature.thisParameter && instantiateSymbol(signature.thisParameter, mapper), - instantiateList(signature.parameters, mapper, instantiateSymbol), - /*resolvedReturnType*/ undefined, - /*resolvedTypePredicate*/ undefined, - signature.minArgumentCount, - signature.flags & SignatureFlags.PropagatingFlags); - result.target = signature; - result.mapper = mapper; - return result; - } - - function instantiateSymbol(symbol: Symbol, mapper: TypeMapper): Symbol { - const links = getSymbolLinks(symbol); - if (links.type && !maybeTypeOfKind(links.type, TypeFlags.Object | TypeFlags.Instantiable)) { - // If the type of the symbol is already resolved, and if that type could not possibly - // be affected by instantiation, simply return the symbol itself. - return symbol; - } - if (getCheckFlags(symbol) & CheckFlags.Instantiated) { - // If symbol being instantiated is itself a instantiation, fetch the original target and combine the - // type mappers. This ensures that original type identities are properly preserved and that aliases - // always reference a non-aliases. - symbol = links.target!; - mapper = combineTypeMappers(links.mapper, mapper); - } - // Keep the flags from the symbol we're instantiating. Mark that is instantiated, and - // also transient so that we can just store data on it directly. - const result = createSymbol(symbol.flags, symbol.escapedName, CheckFlags.Instantiated | getCheckFlags(symbol) & (CheckFlags.Readonly | CheckFlags.Late | CheckFlags.OptionalParameter | CheckFlags.RestParameter)); - result.declarations = symbol.declarations; - result.parent = symbol.parent; - result.target = symbol; - result.mapper = mapper; - if (symbol.valueDeclaration) { - result.valueDeclaration = symbol.valueDeclaration; - } - if (symbol.nameType) { - result.nameType = symbol.nameType; - } - return result; - } - - function getObjectTypeInstantiation(type: AnonymousType | DeferredTypeReference, mapper: TypeMapper) { - const target = type.objectFlags & ObjectFlags.Instantiated ? type.target! : type; - const node = type.objectFlags & ObjectFlags.Reference ? (type).node! : type.symbol.declarations[0]; - const links = getNodeLinks(node); - let typeParameters = links.outerTypeParameters; - if (!typeParameters) { - // The first time an anonymous type is instantiated we compute and store a list of the type - // parameters that are in scope (and therefore potentially referenced). For type literals that - // aren't the right hand side of a generic type alias declaration we optimize by reducing the - // set of type parameters to those that are possibly referenced in the literal. - let declaration = node; - if (isInJSFile(declaration)) { - const paramTag = findAncestor(declaration, isJSDocParameterTag); - if (paramTag) { - const paramSymbol = getParameterSymbolFromJSDoc(paramTag); - if (paramSymbol) { - declaration = paramSymbol.valueDeclaration; - } - } - } - let outerTypeParameters = getOuterTypeParameters(declaration, /*includeThisTypes*/ true); - if (isJSConstructor(declaration)) { - const templateTagParameters = getTypeParametersFromDeclaration(declaration as DeclarationWithTypeParameters); - outerTypeParameters = addRange(outerTypeParameters, templateTagParameters); - } - typeParameters = outerTypeParameters || emptyArray; - typeParameters = (target.objectFlags & ObjectFlags.Reference || target.symbol.flags & SymbolFlags.TypeLiteral) && !target.aliasTypeArguments ? - filter(typeParameters, tp => isTypeParameterPossiblyReferenced(tp, declaration)) : - typeParameters; - links.outerTypeParameters = typeParameters; - if (typeParameters.length) { - links.instantiations = createMap(); - links.instantiations.set(getTypeListId(typeParameters), target); - } - } - if (typeParameters.length) { - // We are instantiating an anonymous type that has one or more type parameters in scope. Apply the - // mapper to the type parameters to produce the effective list of type arguments, and compute the - // instantiation cache key from the type IDs of the type arguments. - const typeArguments = map(typeParameters, combineTypeMappers(type.mapper, mapper)); - const id = getTypeListId(typeArguments); - let result = links.instantiations!.get(id); - if (!result) { - const newMapper = createTypeMapper(typeParameters, typeArguments); - result = target.objectFlags & ObjectFlags.Reference ? createDeferredTypeReference((type).target, (type).node, newMapper) : - target.objectFlags & ObjectFlags.Mapped ? instantiateMappedType(target, newMapper) : - instantiateAnonymousType(target, newMapper); - links.instantiations!.set(id, result); - } - return result; - } - return type; - } - - function maybeTypeParameterReference(node: Node) { - return !(node.kind === SyntaxKind.QualifiedName || - node.parent.kind === SyntaxKind.TypeReference && (node.parent).typeArguments && node === (node.parent).typeName || - node.parent.kind === SyntaxKind.ImportType && (node.parent as ImportTypeNode).typeArguments && node === (node.parent as ImportTypeNode).qualifier); - } - - function isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node) { - // If the type parameter doesn't have exactly one declaration, if there are invening statement blocks - // between the node and the type parameter declaration, if the node contains actual references to the - // type parameter, or if the node contains type queries, we consider the type parameter possibly referenced. - if (tp.symbol && tp.symbol.declarations && tp.symbol.declarations.length === 1) { - const container = tp.symbol.declarations[0].parent; - for (let n = node; n !== container; n = n.parent) { - if (!n || n.kind === SyntaxKind.Block || n.kind === SyntaxKind.ConditionalType && forEachChild((n).extendsType, containsReference)) { - return true; - } - } - return !!forEachChild(node, containsReference); - } - return true; - function containsReference(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.ThisType: - return !!tp.isThisType; - case SyntaxKind.Identifier: - return !tp.isThisType && isPartOfTypeNode(node) && maybeTypeParameterReference(node) && - getTypeFromTypeNode(node) === tp; - case SyntaxKind.TypeQuery: - return true; - } - return !!forEachChild(node, containsReference); - } - } - - function getHomomorphicTypeVariable(type: MappedType) { - const constraintType = getConstraintTypeFromMappedType(type); - if (constraintType.flags & TypeFlags.Index) { - const typeVariable = getActualTypeVariable((constraintType).type); - if (typeVariable.flags & TypeFlags.TypeParameter) { - return typeVariable; - } - } - return undefined; - } - - function instantiateMappedType(type: MappedType, mapper: TypeMapper): Type { - // For a homomorphic mapped type { [P in keyof T]: X }, where T is some type variable, the mapping - // operation depends on T as follows: - // * If T is a primitive type no mapping is performed and the result is simply T. - // * If T is a union type we distribute the mapped type over the union. - // * If T is an array we map to an array where the element type has been transformed. - // * If T is a tuple we map to a tuple where the element types have been transformed. - // * Otherwise we map to an object type where the type of each property has been transformed. - // For example, when T is instantiated to a union type A | B, we produce { [P in keyof A]: X } | - // { [P in keyof B]: X }, and when when T is instantiated to a union type A | undefined, we produce - // { [P in keyof A]: X } | undefined. - const typeVariable = getHomomorphicTypeVariable(type); - if (typeVariable) { - const mappedTypeVariable = instantiateType(typeVariable, mapper); - if (typeVariable !== mappedTypeVariable) { - return mapType(mappedTypeVariable, t => { - if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && t !== errorType) { - const replacementMapper = createReplacementMapper(typeVariable, t, mapper); - return isArrayType(t) ? instantiateMappedArrayType(t, type, replacementMapper) : - isTupleType(t) ? instantiateMappedTupleType(t, type, replacementMapper) : - instantiateAnonymousType(type, replacementMapper); - } - return t; - }); - } - } - return instantiateAnonymousType(type, mapper); - } - - function getModifiedReadonlyState(state: boolean, modifiers: MappedTypeModifiers) { - return modifiers & MappedTypeModifiers.IncludeReadonly ? true : modifiers & MappedTypeModifiers.ExcludeReadonly ? false : state; - } - - function instantiateMappedArrayType(arrayType: Type, mappedType: MappedType, mapper: TypeMapper) { - const elementType = instantiateMappedTypeTemplate(mappedType, numberType, /*isOptional*/ true, mapper); - return elementType === errorType ? errorType : - createArrayType(elementType, getModifiedReadonlyState(isReadonlyArrayType(arrayType), getMappedTypeModifiers(mappedType))); - } - - function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) { - const minLength = tupleType.target.minLength; - const elementTypes = map(getTypeArguments(tupleType), (_, i) => - instantiateMappedTypeTemplate(mappedType, getLiteralType("" + i), i >= minLength, mapper)); - const modifiers = getMappedTypeModifiers(mappedType); - const newMinLength = modifiers & MappedTypeModifiers.IncludeOptional ? 0 : - modifiers & MappedTypeModifiers.ExcludeOptional ? getTypeReferenceArity(tupleType) - (tupleType.target.hasRestElement ? 1 : 0) : - minLength; - const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, modifiers); - return contains(elementTypes, errorType) ? errorType : - createTupleType(elementTypes, newMinLength, tupleType.target.hasRestElement, newReadonly, tupleType.target.associatedNames); - } - - function instantiateMappedTypeTemplate(type: MappedType, key: Type, isOptional: boolean, mapper: TypeMapper) { - const templateMapper = combineTypeMappers(mapper, createTypeMapper([getTypeParameterFromMappedType(type)], [key])); - const propType = instantiateType(getTemplateTypeFromMappedType(type.target || type), templateMapper); - const modifiers = getMappedTypeModifiers(type); - return strictNullChecks && modifiers & MappedTypeModifiers.IncludeOptional && !isTypeAssignableTo(undefinedType, propType) ? getOptionalType(propType) : - strictNullChecks && modifiers & MappedTypeModifiers.ExcludeOptional && isOptional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) : - propType; - } - - function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper): AnonymousType { - const result = createObjectType(type.objectFlags | ObjectFlags.Instantiated, type.symbol); - if (type.objectFlags & ObjectFlags.Mapped) { - (result).declaration = (type).declaration; - // C.f. instantiateSignature - const origTypeParameter = getTypeParameterFromMappedType(type); - const freshTypeParameter = cloneTypeParameter(origTypeParameter); - (result).typeParameter = freshTypeParameter; - mapper = combineTypeMappers(makeUnaryTypeMapper(origTypeParameter, freshTypeParameter), mapper); - freshTypeParameter.mapper = mapper; - } - result.target = type; - result.mapper = mapper; - result.aliasSymbol = type.aliasSymbol; - result.aliasTypeArguments = instantiateTypes(type.aliasTypeArguments, mapper); - return result; - } - - function getConditionalTypeInstantiation(type: ConditionalType, mapper: TypeMapper): Type { - const root = type.root; - if (root.outerTypeParameters) { - // We are instantiating a conditional type that has one or more type parameters in scope. Apply the - // mapper to the type parameters to produce the effective list of type arguments, and compute the - // instantiation cache key from the type IDs of the type arguments. - const typeArguments = map(root.outerTypeParameters, mapper); - const id = getTypeListId(typeArguments); - let result = root.instantiations!.get(id); - if (!result) { - const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); - result = instantiateConditionalType(root, newMapper); - root.instantiations!.set(id, result); - } - return result; - } - return type; - } - - function instantiateConditionalType(root: ConditionalRoot, mapper: TypeMapper): Type { - // Check if we have a conditional type where the check type is a naked type parameter. If so, - // the conditional type is distributive over union types and when T is instantiated to a union - // type A | B, we produce (A extends U ? X : Y) | (B extends U ? X : Y). - if (root.isDistributive) { - const checkType = root.checkType; - const instantiatedType = mapper(checkType); - if (checkType !== instantiatedType && instantiatedType.flags & (TypeFlags.Union | TypeFlags.Never)) { - return mapType(instantiatedType, t => getConditionalType(root, createReplacementMapper(checkType, t, mapper))); - } - } - return getConditionalType(root, mapper); - } - - function instantiateType(type: Type, mapper: TypeMapper | undefined): Type; - function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined; - function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined { - if (!type || !mapper || mapper === identityMapper) { - return type; - } - if (instantiationDepth === 50 || instantiationCount >= 5000000) { - // We have reached 50 recursive type instantiations and there is a very high likelyhood we're dealing - // with a combination of infinite generic types that perpetually generate new type identities. We stop - // the recursion here by yielding the error type. - error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); - return errorType; - } - instantiationCount++; - instantiationDepth++; - const result = instantiateTypeWorker(type, mapper); - instantiationDepth--; - return result; - } - - function instantiateTypeWorker(type: Type, mapper: TypeMapper): Type { - const flags = type.flags; - if (flags & TypeFlags.TypeParameter) { - return mapper(type); - } - if (flags & TypeFlags.Object) { - const objectFlags = (type).objectFlags; - if (objectFlags & ObjectFlags.Anonymous) { - // If the anonymous type originates in a declaration of a function, method, class, or - // interface, in an object type literal, or in an object literal expression, we may need - // to instantiate the type because it might reference a type parameter. - return couldContainTypeVariables(type) ? - getObjectTypeInstantiation(type, mapper) : type; - } - if (objectFlags & ObjectFlags.Mapped) { - return getObjectTypeInstantiation(type, mapper); - } - if (objectFlags & ObjectFlags.Reference) { - if ((type).node) { - return getObjectTypeInstantiation(type, mapper); - } - const resolvedTypeArguments = (type).resolvedTypeArguments; - const newTypeArguments = instantiateTypes(resolvedTypeArguments, mapper); - return newTypeArguments !== resolvedTypeArguments ? createTypeReference((type).target, newTypeArguments) : type; - } - return type; - } - if (flags & TypeFlags.Union && !(flags & TypeFlags.Primitive)) { - const types = (type).types; - const newTypes = instantiateTypes(types, mapper); - return newTypes !== types ? getUnionType(newTypes, UnionReduction.Literal, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) : type; - } - if (flags & TypeFlags.Intersection) { - const types = (type).types; - const newTypes = instantiateTypes(types, mapper); - return newTypes !== types ? getIntersectionType(newTypes, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) : type; - } - if (flags & TypeFlags.Index) { - return getIndexType(instantiateType((type).type, mapper)); - } - if (flags & TypeFlags.IndexedAccess) { - return getIndexedAccessType(instantiateType((type).objectType, mapper), instantiateType((type).indexType, mapper)); - } - if (flags & TypeFlags.Conditional) { - return getConditionalTypeInstantiation(type, combineTypeMappers((type).mapper, mapper)); - } - if (flags & TypeFlags.Substitution) { - const maybeVariable = instantiateType((type).typeVariable, mapper); - if (maybeVariable.flags & TypeFlags.TypeVariable) { - return getSubstitutionType(maybeVariable as TypeVariable, instantiateType((type).substitute, mapper)); - } - else { - const sub = instantiateType((type).substitute, mapper); - if (sub.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(maybeVariable), getRestrictiveInstantiation(sub))) { - return maybeVariable; - } - return sub; - } - } - return type; - } - - function getPermissiveInstantiation(type: Type) { - return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type : - type.permissiveInstantiation || (type.permissiveInstantiation = instantiateType(type, permissiveMapper)); - } - - function getRestrictiveInstantiation(type: Type) { - if (type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never)) { - return type; - } - if (type.restrictiveInstantiation) { - return type.restrictiveInstantiation; - } - type.restrictiveInstantiation = instantiateType(type, restrictiveMapper); - // We set the following so we don't attempt to set the restrictive instance of a restrictive instance - // which is redundant - we'll produce new type identities, but all type params have already been mapped. - // This also gives us a way to detect restrictive instances upon comparisons and _disable_ the "distributeive constraint" - // assignability check for them, which is distinctly unsafe, as once you have a restrctive instance, all the type parameters - // are constrained to `unknown` and produce tons of false positives/negatives! - type.restrictiveInstantiation.restrictiveInstantiation = type.restrictiveInstantiation; - return type.restrictiveInstantiation; - } - - function instantiateIndexInfo(info: IndexInfo | undefined, mapper: TypeMapper): IndexInfo | undefined { - return info && createIndexInfo(instantiateType(info.type, mapper), info.isReadonly, info.declaration); - } - - // Returns true if the given expression contains (at any level of nesting) a function or arrow expression - // that is subject to contextual typing. - function isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean { - Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); - switch (node.kind) { - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.FunctionDeclaration: // Function declarations can have context when annotated with a jsdoc @type - return isContextSensitiveFunctionLikeDeclaration(node); - case SyntaxKind.ObjectLiteralExpression: - return some((node).properties, isContextSensitive); - case SyntaxKind.ArrayLiteralExpression: - return some((node).elements, isContextSensitive); - case SyntaxKind.ConditionalExpression: - return isContextSensitive((node).whenTrue) || - isContextSensitive((node).whenFalse); - case SyntaxKind.BinaryExpression: - return ((node).operatorToken.kind === SyntaxKind.BarBarToken || (node).operatorToken.kind === SyntaxKind.QuestionQuestionToken) && - (isContextSensitive((node).left) || isContextSensitive((node).right)); - case SyntaxKind.PropertyAssignment: - return isContextSensitive((node).initializer); - case SyntaxKind.ParenthesizedExpression: - return isContextSensitive((node).expression); - case SyntaxKind.JsxAttributes: - return some((node).properties, isContextSensitive) || isJsxOpeningElement(node.parent) && some(node.parent.parent.children, isContextSensitive); - case SyntaxKind.JsxAttribute: { - // If there is no initializer, JSX attribute has a boolean value of true which is not context sensitive. - const { initializer } = node as JsxAttribute; - return !!initializer && isContextSensitive(initializer); - } - case SyntaxKind.JsxExpression: { - // It is possible to that node.expression is undefined (e.g
) - const { expression } = node as JsxExpression; - return !!expression && isContextSensitive(expression); - } - } - - return false; - } - - function isContextSensitiveFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { - if (isFunctionDeclaration(node) && (!isInJSFile(node) || !getTypeForDeclarationFromJSDocComment(node))) { - return false; - } - // Functions with type parameters are not context sensitive. - if (node.typeParameters) { - return false; - } - // Functions with any parameters that lack type annotations are context sensitive. - if (some(node.parameters, p => !getEffectiveTypeAnnotationNode(p))) { - return true; - } - if (node.kind !== SyntaxKind.ArrowFunction) { - // If the first parameter is not an explicit 'this' parameter, then the function has - // an implicit 'this' parameter which is subject to contextual typing. - const parameter = firstOrUndefined(node.parameters); - if (!(parameter && parameterIsThisKeyword(parameter))) { - return true; - } - } - return hasContextSensitiveReturnExpression(node); - } - - function hasContextSensitiveReturnExpression(node: FunctionLikeDeclaration) { - // TODO(anhans): A block should be context-sensitive if it has a context-sensitive return value. - return !!node.body && node.body.kind !== SyntaxKind.Block && isContextSensitive(node.body); - } - - function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration { - return (isInJSFile(func) && isFunctionDeclaration(func) || isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) && - isContextSensitiveFunctionLikeDeclaration(func); - } - - function getTypeWithoutSignatures(type: Type): Type { - if (type.flags & TypeFlags.Object) { - const resolved = resolveStructuredTypeMembers(type); - if (resolved.constructSignatures.length || resolved.callSignatures.length) { - const result = createObjectType(ObjectFlags.Anonymous, type.symbol); - result.members = resolved.members; - result.properties = resolved.properties; - result.callSignatures = emptyArray; - result.constructSignatures = emptyArray; - return result; - } - } - else if (type.flags & TypeFlags.Intersection) { - return getIntersectionType(map((type).types, getTypeWithoutSignatures)); - } - return type; - } - - // TYPE CHECKING - - function isTypeIdenticalTo(source: Type, target: Type): boolean { - return isTypeRelatedTo(source, target, identityRelation); - } - - function compareTypesIdentical(source: Type, target: Type): Ternary { - return isTypeRelatedTo(source, target, identityRelation) ? Ternary.True : Ternary.False; - } - - function compareTypesAssignable(source: Type, target: Type): Ternary { - return isTypeRelatedTo(source, target, assignableRelation) ? Ternary.True : Ternary.False; - } - - function compareTypesSubtypeOf(source: Type, target: Type): Ternary { - return isTypeRelatedTo(source, target, subtypeRelation) ? Ternary.True : Ternary.False; - } - - function isTypeSubtypeOf(source: Type, target: Type): boolean { - return isTypeRelatedTo(source, target, subtypeRelation); - } - - function isTypeAssignableTo(source: Type, target: Type): boolean { - return isTypeRelatedTo(source, target, assignableRelation); - } - - // An object type S is considered to be derived from an object type T if - // S is a union type and every constituent of S is derived from T, - // T is a union type and S is derived from at least one constituent of T, or - // S is a type variable with a base constraint that is derived from T, - // T is one of the global types Object and Function and S is a subtype of T, or - // T occurs directly or indirectly in an 'extends' clause of S. - // Note that this check ignores type parameters and only considers the - // inheritance hierarchy. - function isTypeDerivedFrom(source: Type, target: Type): boolean { - return source.flags & TypeFlags.Union ? every((source).types, t => isTypeDerivedFrom(t, target)) : - target.flags & TypeFlags.Union ? some((target).types, t => isTypeDerivedFrom(source, t)) : - source.flags & TypeFlags.InstantiableNonPrimitive ? isTypeDerivedFrom(getBaseConstraintOfType(source) || unknownType, target) : - target === globalObjectType ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) : - target === globalFunctionType ? !!(source.flags & TypeFlags.Object) && isFunctionObjectType(source as ObjectType) : - hasBaseType(source, getTargetType(target)); - } - - /** - * This is *not* a bi-directional relationship. - * If one needs to check both directions for comparability, use a second call to this function or 'checkTypeComparableTo'. - * - * A type S is comparable to a type T if some (but not necessarily all) of the possible values of S are also possible values of T. - * It is used to check following cases: - * - the types of the left and right sides of equality/inequality operators (`===`, `!==`, `==`, `!=`). - * - the types of `case` clause expressions and their respective `switch` expressions. - * - the type of an expression in a type assertion with the type being asserted. - */ - function isTypeComparableTo(source: Type, target: Type): boolean { - return isTypeRelatedTo(source, target, comparableRelation); - } - - function areTypesComparable(type1: Type, type2: Type): boolean { - return isTypeComparableTo(type1, type2) || isTypeComparableTo(type2, type1); - } - - function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined, errorOutputObject?: { errors?: Diagnostic[] }): boolean { - return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain, errorOutputObject); - } - - /** - * Like `checkTypeAssignableTo`, but if it would issue an error, instead performs structural comparisons of the types using the given expression node to - * attempt to issue more specific errors on, for example, specific object literal properties or tuple members. - */ - function checkTypeAssignableToAndOptionallyElaborate(source: Type, target: Type, errorNode: Node | undefined, expr: Expression | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean { - return checkTypeRelatedToAndOptionallyElaborate(source, target, assignableRelation, errorNode, expr, headMessage, containingMessageChain, /*errorOutputContainer*/ undefined); - } - - function checkTypeRelatedToAndOptionallyElaborate( - source: Type, - target: Type, - relation: Map, - errorNode: Node | undefined, - expr: Expression | undefined, - headMessage: DiagnosticMessage | undefined, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ): boolean { - if (isTypeRelatedTo(source, target, relation)) return true; - if (!errorNode || !elaborateError(expr, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)) { - return checkTypeRelatedTo(source, target, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer); - } - return false; - } - - function isOrHasGenericConditional(type: Type): boolean { - return !!(type.flags & TypeFlags.Conditional || (type.flags & TypeFlags.Intersection && some((type as IntersectionType).types, isOrHasGenericConditional))); - } - - function elaborateError( - node: Expression | undefined, - source: Type, - target: Type, - relation: Map, - headMessage: DiagnosticMessage | undefined, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ): boolean { - if (!node || isOrHasGenericConditional(target)) return false; - if (!checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined) - && elaborateDidYouMeanToCallOrConstruct(node, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)) { - return true; - } - switch (node.kind) { - case SyntaxKind.JsxExpression: - case SyntaxKind.ParenthesizedExpression: - return elaborateError((node as ParenthesizedExpression | JsxExpression).expression, source, target, relation, headMessage, containingMessageChain, errorOutputContainer); - case SyntaxKind.BinaryExpression: - switch ((node as BinaryExpression).operatorToken.kind) { - case SyntaxKind.EqualsToken: - case SyntaxKind.CommaToken: - return elaborateError((node as BinaryExpression).right, source, target, relation, headMessage, containingMessageChain, errorOutputContainer); - } - break; - case SyntaxKind.ObjectLiteralExpression: - return elaborateObjectLiteral(node as ObjectLiteralExpression, source, target, relation, containingMessageChain, errorOutputContainer); - case SyntaxKind.ArrayLiteralExpression: - return elaborateArrayLiteral(node as ArrayLiteralExpression, source, target, relation, containingMessageChain, errorOutputContainer); - case SyntaxKind.JsxAttributes: - return elaborateJsxComponents(node as JsxAttributes, source, target, relation, containingMessageChain, errorOutputContainer); - case SyntaxKind.ArrowFunction: - return elaborateArrowFunction(node as ArrowFunction, source, target, relation, containingMessageChain, errorOutputContainer); - } - return false; - } - - function elaborateDidYouMeanToCallOrConstruct( - node: Expression, - source: Type, - target: Type, - relation: Map, - headMessage: DiagnosticMessage | undefined, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ): boolean { - const callSignatures = getSignaturesOfType(source, SignatureKind.Call); - const constructSignatures = getSignaturesOfType(source, SignatureKind.Construct); - for (const signatures of [constructSignatures, callSignatures]) { - if (some(signatures, s => { - const returnType = getReturnTypeOfSignature(s); - return !(returnType.flags & (TypeFlags.Any | TypeFlags.Never)) && checkTypeRelatedTo(returnType, target, relation, /*errorNode*/ undefined); - })) { - const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {}; - checkTypeAssignableTo(source, target, node, headMessage, containingMessageChain, resultObj); - const diagnostic = resultObj.errors![resultObj.errors!.length - 1]; - addRelatedInfo(diagnostic, createDiagnosticForNode( - node, - signatures === constructSignatures ? Diagnostics.Did_you_mean_to_use_new_with_this_expression : Diagnostics.Did_you_mean_to_call_this_expression - )); - return true; - } - } - return false; - } - - function elaborateArrowFunction( - node: ArrowFunction, - source: Type, - target: Type, - relation: Map, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ): boolean { - // Don't elaborate blocks - if (isBlock(node.body)) { - return false; - } - // Or functions with annotated parameter types - if (some(node.parameters, ts.hasType)) { - return false; - } - const sourceSig = getSingleCallSignature(source); - if (!sourceSig) { - return false; - } - const targetSignatures = getSignaturesOfType(target, SignatureKind.Call); - if (!length(targetSignatures)) { - return false; - } - const returnExpression = node.body; - const sourceReturn = getReturnTypeOfSignature(sourceSig); - const targetReturn = getUnionType(map(targetSignatures, getReturnTypeOfSignature)); - if (!checkTypeRelatedTo(sourceReturn, targetReturn, relation, /*errorNode*/ undefined)) { - const elaborated = returnExpression && elaborateError(returnExpression, sourceReturn, targetReturn, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer); - if (elaborated) { - return elaborated; - } - const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {}; - checkTypeRelatedTo(sourceReturn, targetReturn, relation, returnExpression, /*message*/ undefined, containingMessageChain, resultObj); - if (resultObj.errors) { - if (target.symbol && length(target.symbol.declarations)) { - addRelatedInfo(resultObj.errors[resultObj.errors.length - 1], createDiagnosticForNode( - target.symbol.declarations[0], - Diagnostics.The_expected_type_comes_from_the_return_type_of_this_signature, - )); - } - return true; - } - } - return false; - } - - type ElaborationIterator = IterableIterator<{ errorNode: Node, innerExpression: Expression | undefined, nameType: Type, errorMessage?: DiagnosticMessage | undefined }>; - /** - * For every element returned from the iterator, checks that element to issue an error on a property of that element's type - * If that element would issue an error, we first attempt to dive into that element's inner expression and issue a more specific error by recuring into `elaborateError` - * Otherwise, we issue an error on _every_ element which fail the assignability check - */ - function elaborateElementwise( - iterator: ElaborationIterator, - source: Type, - target: Type, - relation: Map, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ) { - // Assignability failure - check each prop individually, and if that fails, fall back on the bad error span - let reportedError = false; - for (let status = iterator.next(); !status.done; status = iterator.next()) { - const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.value; - const targetPropType = getIndexedAccessTypeOrUndefined(target, nameType); - if (!targetPropType || targetPropType.flags & TypeFlags.IndexedAccess) continue; // Don't elaborate on indexes on generic variables - const sourcePropType = getIndexedAccessTypeOrUndefined(source, nameType); - if (sourcePropType && !checkTypeRelatedTo(sourcePropType, targetPropType, relation, /*errorNode*/ undefined)) { - const elaborated = next && elaborateError(next, sourcePropType, targetPropType, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer); - if (elaborated) { - reportedError = true; - } - else { - // Issue error on the prop itself, since the prop couldn't elaborate the error - const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {}; - // Use the expression type, if available - const specificSource = next ? checkExpressionForMutableLocation(next, CheckMode.Normal, sourcePropType) : sourcePropType; - const result = checkTypeRelatedTo(specificSource, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj); - if (result && specificSource !== sourcePropType) { - // If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType - checkTypeRelatedTo(sourcePropType, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj); - } - if (resultObj.errors) { - const reportedDiag = resultObj.errors[resultObj.errors.length - 1]; - const propertyName = isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined; - const targetProp = propertyName !== undefined ? getPropertyOfType(target, propertyName) : undefined; - - let issuedElaboration = false; - if (!targetProp) { - const indexInfo = isTypeAssignableToKind(nameType, TypeFlags.NumberLike) && getIndexInfoOfType(target, IndexKind.Number) || - getIndexInfoOfType(target, IndexKind.String) || - undefined; - if (indexInfo && indexInfo.declaration && !getSourceFileOfNode(indexInfo.declaration).hasNoDefaultLib) { - issuedElaboration = true; - addRelatedInfo(reportedDiag, createDiagnosticForNode(indexInfo.declaration, Diagnostics.The_expected_type_comes_from_this_index_signature)); - } - } - - if (!issuedElaboration && (targetProp && length(targetProp.declarations) || target.symbol && length(target.symbol.declarations))) { - const targetNode = targetProp && length(targetProp.declarations) ? targetProp.declarations[0] : target.symbol.declarations[0]; - if (!getSourceFileOfNode(targetNode).hasNoDefaultLib) { - addRelatedInfo(reportedDiag, createDiagnosticForNode( - targetNode, - Diagnostics.The_expected_type_comes_from_property_0_which_is_declared_here_on_type_1, - propertyName && !(nameType.flags & TypeFlags.UniqueESSymbol) ? unescapeLeadingUnderscores(propertyName) : typeToString(nameType), - typeToString(target) - )); - } - } - } - reportedError = true; - } - } - } - return reportedError; - } - - function *generateJsxAttributes(node: JsxAttributes): ElaborationIterator { - if (!length(node.properties)) return; - for (const prop of node.properties) { - if (isJsxSpreadAttribute(prop)) continue; - yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getLiteralType(idText(prop.name)) }; - } - } - - function *generateJsxChildren(node: JsxElement, getInvalidTextDiagnostic: () => DiagnosticMessage): ElaborationIterator { - if (!length(node.children)) return; - let memberOffset = 0; - for (let i = 0; i < node.children.length; i++) { - const child = node.children[i]; - const nameType = getLiteralType(i - memberOffset); - const elem = getElaborationElementForJsxChild(child, nameType, getInvalidTextDiagnostic); - if (elem) { - yield elem; - } - else { - memberOffset++; - } - } - } - - function getElaborationElementForJsxChild(child: JsxChild, nameType: LiteralType, getInvalidTextDiagnostic: () => DiagnosticMessage) { - switch (child.kind) { - case SyntaxKind.JsxExpression: - // child is of the type of the expression - return { errorNode: child, innerExpression: child.expression, nameType }; - case SyntaxKind.JsxText: - if (child.containsOnlyTriviaWhiteSpaces) { - break; // Whitespace only jsx text isn't real jsx text - } - // child is a string - return { errorNode: child, innerExpression: undefined, nameType, errorMessage: getInvalidTextDiagnostic() }; - case SyntaxKind.JsxElement: - case SyntaxKind.JsxSelfClosingElement: - case SyntaxKind.JsxFragment: - // child is of type JSX.Element - return { errorNode: child, innerExpression: child, nameType }; - default: - return Debug.assertNever(child, "Found invalid jsx child"); - } - } - - function getSemanticJsxChildren(children: NodeArray) { - return filter(children, i => !isJsxText(i) || !i.containsOnlyTriviaWhiteSpaces); - } - - function elaborateJsxComponents( - node: JsxAttributes, - source: Type, - target: Type, - relation: Map, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ) { - let result = elaborateElementwise(generateJsxAttributes(node), source, target, relation, containingMessageChain, errorOutputContainer); - let invalidTextDiagnostic: DiagnosticMessage | undefined; - if (isJsxOpeningElement(node.parent) && isJsxElement(node.parent.parent)) { - const containingElement = node.parent.parent; - const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); - const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName); - const childrenNameType = getLiteralType(childrenPropName); - const childrenTargetType = getIndexedAccessType(target, childrenNameType); - const validChildren = getSemanticJsxChildren(containingElement.children); - if (!length(validChildren)) { - return result; - } - const moreThanOneRealChildren = length(validChildren) > 1; - const arrayLikeTargetParts = filterType(childrenTargetType, isArrayOrTupleLikeType); - const nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isArrayOrTupleLikeType(t)); - if (moreThanOneRealChildren) { - if (arrayLikeTargetParts !== neverType) { - const realSource = createTupleType(checkJsxChildren(containingElement, CheckMode.Normal)); - const children = generateJsxChildren(containingElement, getInvalidTextualChildDiagnostic); - result = elaborateElementwise(children, realSource, arrayLikeTargetParts, relation, containingMessageChain, errorOutputContainer) || result; - } - else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) { - // arity mismatch - result = true; - const diag = error( - containingElement.openingElement.tagName, - Diagnostics.This_JSX_tag_s_0_prop_expects_a_single_child_of_type_1_but_multiple_children_were_provided, - childrenPropName, - typeToString(childrenTargetType) - ); - if (errorOutputContainer && errorOutputContainer.skipLogging) { - (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); - } - } - } - else { - if (nonArrayLikeTargetParts !== neverType) { - const child = validChildren[0]; - const elem = getElaborationElementForJsxChild(child, childrenNameType, getInvalidTextualChildDiagnostic); - if (elem) { - result = elaborateElementwise( - (function*() { yield elem; })(), - source, - target, - relation, - containingMessageChain, - errorOutputContainer - ) || result; - } - } - else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) { - // arity mismatch - result = true; - const diag = error( - containingElement.openingElement.tagName, - Diagnostics.This_JSX_tag_s_0_prop_expects_type_1_which_requires_multiple_children_but_only_a_single_child_was_provided, - childrenPropName, - typeToString(childrenTargetType) - ); - if (errorOutputContainer && errorOutputContainer.skipLogging) { - (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); - } - } - } - } - return result; - - function getInvalidTextualChildDiagnostic() { - if (!invalidTextDiagnostic) { - const tagNameText = getTextOfNode(node.parent.tagName); - const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); - const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName); - const childrenTargetType = getIndexedAccessType(target, getLiteralType(childrenPropName)); - const diagnostic = Diagnostics._0_components_don_t_accept_text_as_child_elements_Text_in_JSX_has_the_type_string_but_the_expected_type_of_1_is_2; - invalidTextDiagnostic = { ...diagnostic, key: "!!ALREADY FORMATTED!!", message: formatMessage(/*_dummy*/ undefined, diagnostic, tagNameText, childrenPropName, typeToString(childrenTargetType)) }; - } - return invalidTextDiagnostic; - } - } - - function *generateLimitedTupleElements(node: ArrayLiteralExpression, target: Type): ElaborationIterator { - const len = length(node.elements); - if (!len) return; - for (let i = 0; i < len; i++) { - // Skip elements which do not exist in the target - a length error on the tuple overall is likely better than an error on a mismatched index signature - if (isTupleLikeType(target) && !getPropertyOfType(target, ("" + i) as __String)) continue; - const elem = node.elements[i]; - if (isOmittedExpression(elem)) continue; - const nameType = getLiteralType(i); - yield { errorNode: elem, innerExpression: elem, nameType }; - } - } - - function elaborateArrayLiteral( - node: ArrayLiteralExpression, - source: Type, - target: Type, - relation: Map, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ) { - if (target.flags & TypeFlags.Primitive) return false; - if (isTupleLikeType(source)) { - return elaborateElementwise(generateLimitedTupleElements(node, target), source, target, relation, containingMessageChain, errorOutputContainer); - } - // recreate a tuple from the elements, if possible - const tupleizedType = checkArrayLiteral(node, CheckMode.Contextual, /*forceTuple*/ true); - if (isTupleLikeType(tupleizedType)) { - return elaborateElementwise(generateLimitedTupleElements(node, target), tupleizedType, target, relation, containingMessageChain, errorOutputContainer); - } - return false; - } - - function *generateObjectLiteralElements(node: ObjectLiteralExpression): ElaborationIterator { - if (!length(node.properties)) return; - for (const prop of node.properties) { - if (isSpreadAssignment(prop)) continue; - const type = getLiteralTypeFromProperty(getSymbolOfNode(prop), TypeFlags.StringOrNumberLiteralOrUnique); - if (!type || (type.flags & TypeFlags.Never)) { - continue; - } - switch (prop.kind) { - case SyntaxKind.SetAccessor: - case SyntaxKind.GetAccessor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.ShorthandPropertyAssignment: - yield { errorNode: prop.name, innerExpression: undefined, nameType: type }; - break; - case SyntaxKind.PropertyAssignment: - yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: type, errorMessage: isComputedNonLiteralName(prop.name) ? Diagnostics.Type_of_computed_property_s_value_is_0_which_is_not_assignable_to_type_1 : undefined }; - break; - default: - Debug.assertNever(prop); - } - } - } - - function elaborateObjectLiteral( - node: ObjectLiteralExpression, - source: Type, - target: Type, - relation: Map, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ) { - if (target.flags & TypeFlags.Primitive) return false; - return elaborateElementwise(generateObjectLiteralElements(node), source, target, relation, containingMessageChain, errorOutputContainer); - } - - /** - * This is *not* a bi-directional relationship. - * If one needs to check both directions for comparability, use a second call to this function or 'isTypeComparableTo'. - */ - function checkTypeComparableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean { - return checkTypeRelatedTo(source, target, comparableRelation, errorNode, headMessage, containingMessageChain); - } - - function isSignatureAssignableTo(source: Signature, - target: Signature, - ignoreReturnTypes: boolean): boolean { - return compareSignaturesRelated(source, target, CallbackCheck.None, ignoreReturnTypes, /*reportErrors*/ false, - /*errorReporter*/ undefined, /*errorReporter*/ undefined, compareTypesAssignable, /*reportUnreliableMarkers*/ undefined) !== Ternary.False; - } - - type ErrorReporter = (message: DiagnosticMessage, arg0?: string, arg1?: string) => void; - - /** - * Returns true if `s` is `(...args: any[]) => any` or `(this: any, ...args: any[]) => any` - */ - function isAnySignature(s: Signature) { - return !s.typeParameters && (!s.thisParameter || isTypeAny(getTypeOfParameter(s.thisParameter))) && s.parameters.length === 1 && - signatureHasRestParameter(s) && (getTypeOfParameter(s.parameters[0]) === anyArrayType || isTypeAny(getTypeOfParameter(s.parameters[0]))) && - isTypeAny(getReturnTypeOfSignature(s)); - } - - /** - * See signatureRelatedTo, compareSignaturesIdentical - */ - function compareSignaturesRelated(source: Signature, - target: Signature, - callbackCheck: CallbackCheck, - ignoreReturnTypes: boolean, - reportErrors: boolean, - errorReporter: ErrorReporter | undefined, - incompatibleErrorReporter: ((source: Type, target: Type) => void) | undefined, - compareTypes: TypeComparer, - reportUnreliableMarkers: TypeMapper | undefined): Ternary { - // TODO (drosen): De-duplicate code between related functions. - if (source === target) { - return Ternary.True; - } - - if (isAnySignature(target)) { - return Ternary.True; - } - - const targetCount = getParameterCount(target); - if (!hasEffectiveRestParameter(target) && getMinArgumentCount(source) > targetCount) { - return Ternary.False; - } - - if (source.typeParameters && source.typeParameters !== target.typeParameters) { - target = getCanonicalSignature(target); - source = instantiateSignatureInContextOf(source, target, /*inferenceContext*/ undefined, compareTypes); - } - - const sourceCount = getParameterCount(source); - const sourceRestType = getNonArrayRestType(source); - const targetRestType = getNonArrayRestType(target); - if (sourceRestType || targetRestType) { - void instantiateType(sourceRestType || targetRestType, reportUnreliableMarkers); - } - if (sourceRestType && targetRestType && sourceCount !== targetCount) { - // We're not able to relate misaligned complex rest parameters - return Ternary.False; - } - - const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown; - const strictVariance = !callbackCheck && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration && - kind !== SyntaxKind.MethodSignature && kind !== SyntaxKind.Constructor; - let result = Ternary.True; - - const sourceThisType = getThisTypeOfSignature(source); - if (sourceThisType && sourceThisType !== voidType) { - const targetThisType = getThisTypeOfSignature(target); - if (targetThisType) { - // void sources are assignable to anything. - const related = !strictVariance && compareTypes(sourceThisType, targetThisType, /*reportErrors*/ false) - || compareTypes(targetThisType, sourceThisType, reportErrors); - if (!related) { - if (reportErrors) { - errorReporter!(Diagnostics.The_this_types_of_each_signature_are_incompatible); - } - return Ternary.False; - } - result &= related; - } - } - - const paramCount = sourceRestType || targetRestType ? Math.min(sourceCount, targetCount) : Math.max(sourceCount, targetCount); - const restIndex = sourceRestType || targetRestType ? paramCount - 1 : -1; - - for (let i = 0; i < paramCount; i++) { - const sourceType = i === restIndex ? getRestTypeAtPosition(source, i) : getTypeAtPosition(source, i); - const targetType = i === restIndex ? getRestTypeAtPosition(target, i) : getTypeAtPosition(target, i); - // In order to ensure that any generic type Foo is at least co-variant with respect to T no matter - // how Foo uses T, we need to relate parameters bi-variantly (given that parameters are input positions, - // they naturally relate only contra-variantly). However, if the source and target parameters both have - // function types with a single call signature, we know we are relating two callback parameters. In - // that case it is sufficient to only relate the parameters of the signatures co-variantly because, - // similar to return values, callback parameters are output positions. This means that a Promise, - // where T is used only in callback parameter positions, will be co-variant (as opposed to bi-variant) - // with respect to T. - const sourceSig = callbackCheck ? undefined : getSingleCallSignature(getNonNullableType(sourceType)); - const targetSig = callbackCheck ? undefined : getSingleCallSignature(getNonNullableType(targetType)); - const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) && - (getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable); - const related = callbacks ? - // TODO: GH#18217 It will work if they're both `undefined`, but not if only one is - compareSignaturesRelated(targetSig!, sourceSig!, strictVariance ? CallbackCheck.Strict : CallbackCheck.Bivariant, /*ignoreReturnTypes*/ false, reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) : - !callbackCheck && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors); - if (!related) { - if (reportErrors) { - errorReporter!(Diagnostics.Types_of_parameters_0_and_1_are_incompatible, - unescapeLeadingUnderscores(getParameterNameAtPosition(source, i)), - unescapeLeadingUnderscores(getParameterNameAtPosition(target, i))); - } - return Ternary.False; - } - result &= related; - } - - if (!ignoreReturnTypes) { - // If a signature resolution is already in-flight, skip issuing a circularity error - // here and just use the `any` type directly - const targetReturnType = isResolvingReturnTypeOfSignature(target) ? anyType - : target.declaration && isJSConstructor(target.declaration) ? getDeclaredTypeOfClassOrInterface(getMergedSymbol(target.declaration.symbol)) - : getReturnTypeOfSignature(target); - if (targetReturnType === voidType) { - return result; - } - const sourceReturnType = isResolvingReturnTypeOfSignature(source) ? anyType - : source.declaration && isJSConstructor(source.declaration) ? getDeclaredTypeOfClassOrInterface(getMergedSymbol(source.declaration.symbol)) - : getReturnTypeOfSignature(source); - - // The following block preserves behavior forbidding boolean returning functions from being assignable to type guard returning functions - const targetTypePredicate = getTypePredicateOfSignature(target); - if (targetTypePredicate) { - const sourceTypePredicate = getTypePredicateOfSignature(source); - if (sourceTypePredicate) { - result &= compareTypePredicateRelatedTo(sourceTypePredicate, targetTypePredicate, reportErrors, errorReporter, compareTypes); - } - else if (isIdentifierTypePredicate(targetTypePredicate)) { - if (reportErrors) { - errorReporter!(Diagnostics.Signature_0_must_be_a_type_predicate, signatureToString(source)); - } - return Ternary.False; - } - } - else { - // When relating callback signatures, we still need to relate return types bi-variantly as otherwise - // the containing type wouldn't be co-variant. For example, interface Foo { add(cb: () => T): void } - // wouldn't be co-variant for T without this rule. - result &= callbackCheck === CallbackCheck.Bivariant && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) || - compareTypes(sourceReturnType, targetReturnType, reportErrors); - if (!result && reportErrors && incompatibleErrorReporter) { - incompatibleErrorReporter(sourceReturnType, targetReturnType); - } - } - - } - - return result; - } - - function compareTypePredicateRelatedTo( - source: TypePredicate, - target: TypePredicate, - reportErrors: boolean, - errorReporter: ErrorReporter | undefined, - compareTypes: (s: Type, t: Type, reportErrors?: boolean) => Ternary): Ternary { - if (source.kind !== target.kind) { - if (reportErrors) { - errorReporter!(Diagnostics.A_this_based_type_guard_is_not_compatible_with_a_parameter_based_type_guard); - errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); - } - return Ternary.False; - } - - if (source.kind === TypePredicateKind.Identifier || source.kind === TypePredicateKind.AssertsIdentifier) { - if (source.parameterIndex !== (target as IdentifierTypePredicate).parameterIndex) { - if (reportErrors) { - errorReporter!(Diagnostics.Parameter_0_is_not_in_the_same_position_as_parameter_1, source.parameterName, (target as IdentifierTypePredicate).parameterName); - errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); - } - return Ternary.False; - } - } - - const related = source.type === target.type ? Ternary.True : - source.type && target.type ? compareTypes(source.type, target.type, reportErrors) : - Ternary.False; - if (related === Ternary.False && reportErrors) { - errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); - } - return related; - } - - function isImplementationCompatibleWithOverload(implementation: Signature, overload: Signature): boolean { - const erasedSource = getErasedSignature(implementation); - const erasedTarget = getErasedSignature(overload); - - // First see if the return types are compatible in either direction. - const sourceReturnType = getReturnTypeOfSignature(erasedSource); - const targetReturnType = getReturnTypeOfSignature(erasedTarget); - if (targetReturnType === voidType - || isTypeRelatedTo(targetReturnType, sourceReturnType, assignableRelation) - || isTypeRelatedTo(sourceReturnType, targetReturnType, assignableRelation)) { - - return isSignatureAssignableTo(erasedSource, erasedTarget, /*ignoreReturnTypes*/ true); - } - - return false; - } - - function isEmptyResolvedType(t: ResolvedType) { - return t.properties.length === 0 && - t.callSignatures.length === 0 && - t.constructSignatures.length === 0 && - !t.stringIndexInfo && - !t.numberIndexInfo; - } - - function isEmptyObjectType(type: Type): boolean { - return type.flags & TypeFlags.Object ? !isGenericMappedType(type) && isEmptyResolvedType(resolveStructuredTypeMembers(type)) : - type.flags & TypeFlags.NonPrimitive ? true : - type.flags & TypeFlags.Union ? some((type).types, isEmptyObjectType) : - type.flags & TypeFlags.Intersection ? every((type).types, isEmptyObjectType) : - false; - } - - function isEmptyAnonymousObjectType(type: Type) { - return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && isEmptyObjectType(type); - } - - function isStringIndexSignatureOnlyType(type: Type): boolean { - return type.flags & TypeFlags.Object && !isGenericMappedType(type) && getPropertiesOfType(type).length === 0 && getIndexInfoOfType(type, IndexKind.String) && !getIndexInfoOfType(type, IndexKind.Number) || - type.flags & TypeFlags.UnionOrIntersection && every((type).types, isStringIndexSignatureOnlyType) || - false; - } - - function isEnumTypeRelatedTo(sourceSymbol: Symbol, targetSymbol: Symbol, errorReporter?: ErrorReporter) { - if (sourceSymbol === targetSymbol) { - return true; - } - const id = getSymbolId(sourceSymbol) + "," + getSymbolId(targetSymbol); - const entry = enumRelation.get(id); - if (entry !== undefined && !(!(entry & RelationComparisonResult.Reported) && entry & RelationComparisonResult.Failed && errorReporter)) { - return !!(entry & RelationComparisonResult.Succeeded); - } - if (sourceSymbol.escapedName !== targetSymbol.escapedName || !(sourceSymbol.flags & SymbolFlags.RegularEnum) || !(targetSymbol.flags & SymbolFlags.RegularEnum)) { - enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); - return false; - } - const targetEnumType = getTypeOfSymbol(targetSymbol); - for (const property of getPropertiesOfType(getTypeOfSymbol(sourceSymbol))) { - if (property.flags & SymbolFlags.EnumMember) { - const targetProperty = getPropertyOfType(targetEnumType, property.escapedName); - if (!targetProperty || !(targetProperty.flags & SymbolFlags.EnumMember)) { - if (errorReporter) { - errorReporter(Diagnostics.Property_0_is_missing_in_type_1, symbolName(property), - typeToString(getDeclaredTypeOfSymbol(targetSymbol), /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType)); - enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); - } - else { - enumRelation.set(id, RelationComparisonResult.Failed); - } - return false; - } - } - } - enumRelation.set(id, RelationComparisonResult.Succeeded); - return true; - } - - function isSimpleTypeRelatedTo(source: Type, target: Type, relation: Map, errorReporter?: ErrorReporter) { - const s = source.flags; - const t = target.flags; - if (t & TypeFlags.AnyOrUnknown || s & TypeFlags.Never || source === wildcardType) return true; - if (t & TypeFlags.Never) return false; - if (s & TypeFlags.StringLike && t & TypeFlags.String) return true; - if (s & TypeFlags.StringLiteral && s & TypeFlags.EnumLiteral && - t & TypeFlags.StringLiteral && !(t & TypeFlags.EnumLiteral) && - (source).value === (target).value) return true; - if (s & TypeFlags.NumberLike && t & TypeFlags.Number) return true; - if (s & TypeFlags.NumberLiteral && s & TypeFlags.EnumLiteral && - t & TypeFlags.NumberLiteral && !(t & TypeFlags.EnumLiteral) && - (source).value === (target).value) return true; - if (s & TypeFlags.BigIntLike && t & TypeFlags.BigInt) return true; - if (s & TypeFlags.BooleanLike && t & TypeFlags.Boolean) return true; - if (s & TypeFlags.ESSymbolLike && t & TypeFlags.ESSymbol) return true; - if (s & TypeFlags.Enum && t & TypeFlags.Enum && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true; - if (s & TypeFlags.EnumLiteral && t & TypeFlags.EnumLiteral) { - if (s & TypeFlags.Union && t & TypeFlags.Union && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true; - if (s & TypeFlags.Literal && t & TypeFlags.Literal && - (source).value === (target).value && - isEnumTypeRelatedTo(getParentOfSymbol(source.symbol)!, getParentOfSymbol(target.symbol)!, errorReporter)) return true; - } - if (s & TypeFlags.Undefined && (!strictNullChecks || t & (TypeFlags.Undefined | TypeFlags.Void))) return true; - if (s & TypeFlags.Null && (!strictNullChecks || t & TypeFlags.Null)) return true; - if (s & TypeFlags.Object && t & TypeFlags.NonPrimitive) return true; - if (relation === assignableRelation || relation === comparableRelation) { - if (s & TypeFlags.Any) return true; - // Type number or any numeric literal type is assignable to any numeric enum type or any - // numeric enum literal type. This rule exists for backwards compatibility reasons because - // bit-flag enum types sometimes look like literal enum types with numeric literal values. - if (s & (TypeFlags.Number | TypeFlags.NumberLiteral) && !(s & TypeFlags.EnumLiteral) && ( - t & TypeFlags.Enum || t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral)) return true; - } - return false; - } - - function isTypeRelatedTo(source: Type, target: Type, relation: Map) { - if (isFreshLiteralType(source)) { - source = (source).regularType; - } - if (isFreshLiteralType(target)) { - target = (target).regularType; - } - if (source === target || - relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || - relation !== identityRelation && isSimpleTypeRelatedTo(source, target, relation)) { - return true; - } - if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { - const related = relation.get(getRelationKey(source, target, /*isIntersectionConstituent*/ false, relation)); - if (related !== undefined) { - return !!(related & RelationComparisonResult.Succeeded); - } - } - if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) { - return checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined); - } - return false; - } - - function isIgnoredJsxProperty(source: Type, sourceProp: Symbol) { - return getObjectFlags(source) & ObjectFlags.JsxAttributes && !isUnhyphenatedJsxName(sourceProp.escapedName); - } - - function getNormalizedType(type: Type, writing: boolean): Type { - return isFreshLiteralType(type) ? (type).regularType : - getObjectFlags(type) & ObjectFlags.Reference && (type).node ? createTypeReference((type).target, getTypeArguments(type)) : - type.flags & TypeFlags.Substitution ? writing ? (type).typeVariable : (type).substitute : - type.flags & TypeFlags.Simplifiable ? getSimplifiedType(type, writing) : - type; - } - - /** - * Checks if 'source' is related to 'target' (e.g.: is a assignable to). - * @param source The left-hand-side of the relation. - * @param target The right-hand-side of the relation. - * @param relation The relation considered. One of 'identityRelation', 'subtypeRelation', 'assignableRelation', or 'comparableRelation'. - * Used as both to determine which checks are performed and as a cache of previously computed results. - * @param errorNode The suggested node upon which all errors will be reported, if defined. This may or may not be the actual node used. - * @param headMessage If the error chain should be prepended by a head message, then headMessage will be used. - * @param containingMessageChain A chain of errors to prepend any new errors found. - * @param errorOutputContainer Return the diagnostic. Do not log if 'skipLogging' is truthy. - */ - function checkTypeRelatedTo( - source: Type, - target: Type, - relation: Map, - errorNode: Node | undefined, - headMessage?: DiagnosticMessage, - containingMessageChain?: () => DiagnosticMessageChain | undefined, - errorOutputContainer?: { errors?: Diagnostic[], skipLogging?: boolean }, - ): boolean { - let errorInfo: DiagnosticMessageChain | undefined; - let relatedInfo: [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined; - let maybeKeys: string[]; - let sourceStack: Type[]; - let targetStack: Type[]; - let maybeCount = 0; - let depth = 0; - let expandingFlags = ExpandingFlags.None; - let overflow = false; - let overrideNextErrorInfo = 0; // How many `reportRelationError` calls should be skipped in the elaboration pyramid - let lastSkippedInfo: [Type, Type] | undefined; - let incompatibleStack: [DiagnosticMessage, (string | number)?, (string | number)?, (string | number)?, (string | number)?][] = []; - - Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking"); - - const result = isRelatedTo(source, target, /*reportErrors*/ !!errorNode, headMessage); - if (incompatibleStack.length) { - reportIncompatibleStack(); - } - if (overflow) { - const diag = error(errorNode, Diagnostics.Excessive_stack_depth_comparing_types_0_and_1, typeToString(source), typeToString(target)); - if (errorOutputContainer) { - (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); - } - } - else if (errorInfo) { - if (containingMessageChain) { - const chain = containingMessageChain(); - if (chain) { - concatenateDiagnosticMessageChains(chain, errorInfo); - errorInfo = chain; - } - } - - let relatedInformation: DiagnosticRelatedInformation[] | undefined; - // Check if we should issue an extra diagnostic to produce a quickfix for a slightly incorrect import statement - if (headMessage && errorNode && !result && source.symbol) { - const links = getSymbolLinks(source.symbol); - if (links.originatingImport && !isImportCall(links.originatingImport)) { - const helpfulRetry = checkTypeRelatedTo(getTypeOfSymbol(links.target!), target, relation, /*errorNode*/ undefined); - if (helpfulRetry) { - // Likely an incorrect import. Issue a helpful diagnostic to produce a quickfix to change the import - const diag = createDiagnosticForNode(links.originatingImport, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead); - relatedInformation = append(relatedInformation, diag); // Cause the error to appear with the error that triggered it - } - } - } - const diag = createDiagnosticForNodeFromMessageChain(errorNode!, errorInfo, relatedInformation); - if (relatedInfo) { - addRelatedInfo(diag, ...relatedInfo); - } - if (errorOutputContainer) { - (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); - } - if (!errorOutputContainer || !errorOutputContainer.skipLogging) { - diagnostics.add(diag); - } - } - if (errorNode && errorOutputContainer && errorOutputContainer.skipLogging && result === Ternary.False) { - Debug.assert(!!errorOutputContainer.errors, "missed opportunity to interact with error."); - } - return result !== Ternary.False; - - function resetErrorInfo(saved: ReturnType) { - errorInfo = saved.errorInfo; - lastSkippedInfo = saved.lastSkippedInfo; - incompatibleStack = saved.incompatibleStack; - overrideNextErrorInfo = saved.overrideNextErrorInfo; - relatedInfo = saved.relatedInfo; - } - - function captureErrorCalculationState() { - return { - errorInfo, - lastSkippedInfo, - incompatibleStack: incompatibleStack.slice(), - overrideNextErrorInfo, - relatedInfo: !relatedInfo ? undefined : relatedInfo.slice() as ([DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined) - }; - } - - function reportIncompatibleError(message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number) { - overrideNextErrorInfo++; // Suppress the next relation error - lastSkippedInfo = undefined; // Reset skipped info cache - incompatibleStack.push([message, arg0, arg1, arg2, arg3]); - } - - function reportIncompatibleStack() { - const stack = incompatibleStack; - incompatibleStack = []; - const info = lastSkippedInfo; - lastSkippedInfo = undefined; - if (stack.length === 1) { - reportError(...stack[0]); - if (info) { - // Actually do the last relation error - reportRelationError(/*headMessage*/ undefined, ...info); - } - return; - } - // The first error will be the innermost, while the last will be the outermost - so by popping off the end, - // we can build from left to right - let path = ""; - const secondaryRootErrors: typeof incompatibleStack = []; - while (stack.length) { - const [msg, ...args] = stack.pop()!; - switch (msg.code) { - case Diagnostics.Types_of_property_0_are_incompatible.code: { - // Parenthesize a `new` if there is one - if (path.indexOf("new ") === 0) { - path = `(${path})`; - } - const str = "" + args[0]; - // If leading, just print back the arg (irrespective of if it's a valid identifier) - if (path.length === 0) { - path = `${str}`; - } - // Otherwise write a dotted name if possible - else if (isIdentifierText(str, compilerOptions.target)) { - path = `${path}.${str}`; - } - // Failing that, check if the name is already a computed name - else if (str[0] === "[" && str[str.length - 1] === "]") { - path = `${path}${str}`; - } - // And finally write out a computed name as a last resort - else { - path = `${path}[${str}]`; - } - break; - } - case Diagnostics.Call_signature_return_types_0_and_1_are_incompatible.code: - case Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible.code: - case Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code: - case Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code: { - if (path.length === 0) { - // Don't flatten signature compatability errors at the start of a chain - instead prefer - // to unify (the with no arguments bit is excessive for printback) and print them back - let mappedMsg = msg; - if (msg.code === Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) { - mappedMsg = Diagnostics.Call_signature_return_types_0_and_1_are_incompatible; - } - else if (msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) { - mappedMsg = Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible; - } - secondaryRootErrors.unshift([mappedMsg, args[0], args[1]]); - } - else { - const prefix = (msg.code === Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible.code || - msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) - ? "new " - : ""; - const params = (msg.code === Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code || - msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) - ? "" - : "..."; - path = `${prefix}${path}(${params})`; - } - break; - } - default: - return Debug.fail(`Unhandled Diagnostic: ${msg.code}`); - } - } - if (path) { - reportError(path[path.length - 1] === ")" - ? Diagnostics.The_types_returned_by_0_are_incompatible_between_these_types - : Diagnostics.The_types_of_0_are_incompatible_between_these_types, - path - ); - } - else { - // Remove the innermost secondary error as it will duplicate the error already reported by `reportRelationError` on entry - secondaryRootErrors.shift(); - } - for (const [msg, ...args] of secondaryRootErrors) { - const originalValue = msg.elidedInCompatabilityPyramid; - msg.elidedInCompatabilityPyramid = false; // Teporarily override elision to ensure error is reported - reportError(msg, ...args); - msg.elidedInCompatabilityPyramid = originalValue; - } - if (info) { - // Actually do the last relation error - reportRelationError(/*headMessage*/ undefined, ...info); - } - } - - function reportError(message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void { - Debug.assert(!!errorNode); - if (incompatibleStack.length) reportIncompatibleStack(); - if (message.elidedInCompatabilityPyramid) return; - errorInfo = chainDiagnosticMessages(errorInfo, message, arg0, arg1, arg2, arg3); - } - - function associateRelatedInfo(info: DiagnosticRelatedInformation) { - Debug.assert(!!errorInfo); - if (!relatedInfo) { - relatedInfo = [info]; - } - else { - relatedInfo.push(info); - } - } - - function reportRelationError(message: DiagnosticMessage | undefined, source: Type, target: Type) { - if (incompatibleStack.length) reportIncompatibleStack(); - const [sourceType, targetType] = getTypeNamesForErrorDisplay(source, target); - - if (target.flags & TypeFlags.TypeParameter && target.immediateBaseConstraint !== undefined && isTypeAssignableTo(source, target.immediateBaseConstraint)) { - reportError( - Diagnostics._0_is_assignable_to_the_constraint_of_type_1_but_1_could_be_instantiated_with_a_different_subtype_of_constraint_2, - sourceType, - targetType, - typeToString(target.immediateBaseConstraint), - ); - } - - if (!message) { - if (relation === comparableRelation) { - message = Diagnostics.Type_0_is_not_comparable_to_type_1; - } - else if (sourceType === targetType) { - message = Diagnostics.Type_0_is_not_assignable_to_type_1_Two_different_types_with_this_name_exist_but_they_are_unrelated; - } - else { - message = Diagnostics.Type_0_is_not_assignable_to_type_1; - } - } - - reportError(message, sourceType, targetType); - } - - function tryElaborateErrorsForPrimitivesAndObjects(source: Type, target: Type) { - const sourceType = symbolValueDeclarationIsContextSensitive(source.symbol) ? typeToString(source, source.symbol.valueDeclaration) : typeToString(source); - const targetType = symbolValueDeclarationIsContextSensitive(target.symbol) ? typeToString(target, target.symbol.valueDeclaration) : typeToString(target); - - if ((globalStringType === source && stringType === target) || - (globalNumberType === source && numberType === target) || - (globalBooleanType === source && booleanType === target) || - (getGlobalESSymbolType(/*reportErrors*/ false) === source && esSymbolType === target)) { - reportError(Diagnostics._0_is_a_primitive_but_1_is_a_wrapper_object_Prefer_using_0_when_possible, targetType, sourceType); - } - } - - /** - * Try and elaborate array and tuple errors. Returns false - * if we have found an elaboration, or we should ignore - * any other elaborations when relating the `source` and - * `target` types. - */ - function tryElaborateArrayLikeErrors(source: Type, target: Type, reportErrors: boolean): boolean { - /** - * The spec for elaboration is: - * - If the source is a readonly tuple and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations. - * - If the source is a tuple then skip property elaborations if the target is an array or tuple. - * - If the source is a readonly array and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations. - * - If the source an array then skip property elaborations if the target is a tuple. - */ - if (isTupleType(source)) { - if (source.target.readonly && isMutableArrayOrTuple(target)) { - if (reportErrors) { - reportError(Diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, typeToString(source), typeToString(target)); - } - return false; - } - return isTupleType(target) || isArrayType(target); - } - if (isReadonlyArrayType(source) && isMutableArrayOrTuple(target)) { - if (reportErrors) { - reportError(Diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, typeToString(source), typeToString(target)); - } - return false; - } - if (isTupleType(target)) { - return isArrayType(source); - } - return true; - } - - /** - * Compare two types and return - * * Ternary.True if they are related with no assumptions, - * * Ternary.Maybe if they are related with assumptions of other relationships, or - * * Ternary.False if they are not related. - */ - function isRelatedTo(originalSource: Type, originalTarget: Type, reportErrors = false, headMessage?: DiagnosticMessage, isApparentIntersectionConstituent?: boolean): Ternary { - // Normalize the source and target types: Turn fresh literal types into regular literal types, - // turn deferred type references into regular type references, simplify indexed access and - // conditional types, and resolve substitution types to either the substitution (on the source - // side) or the type variable (on the target side). - let source = getNormalizedType(originalSource, /*writing*/ false); - let target = getNormalizedType(originalTarget, /*writing*/ true); - - // Try to see if we're relating something like `Foo` -> `Bar | null | undefined`. - // If so, reporting the `null` and `undefined` in the type is hardly useful. - // First, see if we're even relating an object type to a union. - // Then see if the target is stripped down to a single non-union type. - // Note - // * We actually want to remove null and undefined naively here (rather than using getNonNullableType), - // since we don't want to end up with a worse error like "`Foo` is not assignable to `NonNullable`" - // when dealing with generics. - // * We also don't deal with primitive source types, since we already halt elaboration below. - if (target.flags & TypeFlags.Union && source.flags & TypeFlags.Object && - (target as UnionType).types.length <= 3 && maybeTypeOfKind(target, TypeFlags.Nullable)) { - const nullStrippedTarget = extractTypesOfKind(target, ~TypeFlags.Nullable); - if (!(nullStrippedTarget.flags & (TypeFlags.Union | TypeFlags.Never))) { - target = nullStrippedTarget; - } - } - - // both types are the same - covers 'they are the same primitive type or both are Any' or the same type parameter cases - if (source === target) return Ternary.True; - - if (relation === identityRelation) { - return isIdenticalTo(source, target); - } - - if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || - isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True; - - const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); - const isPerformingExcessPropertyChecks = !isApparentIntersectionConstituent && (isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral); - if (isPerformingExcessPropertyChecks) { - if (hasExcessProperties(source, target, reportErrors)) { - if (reportErrors) { - reportRelationError(headMessage, source, target); - } - return Ternary.False; - } - } - - const isPerformingCommonPropertyChecks = relation !== comparableRelation && !isApparentIntersectionConstituent && - source.flags & (TypeFlags.Primitive | TypeFlags.Object | TypeFlags.Intersection) && source !== globalObjectType && - target.flags & (TypeFlags.Object | TypeFlags.Intersection) && isWeakType(target) && - (getPropertiesOfType(source).length > 0 || typeHasCallOrConstructSignatures(source)); - if (isPerformingCommonPropertyChecks && !hasCommonProperties(source, target, isComparingJsxAttributes)) { - if (reportErrors) { - const calls = getSignaturesOfType(source, SignatureKind.Call); - const constructs = getSignaturesOfType(source, SignatureKind.Construct); - if (calls.length > 0 && isRelatedTo(getReturnTypeOfSignature(calls[0]), target, /*reportErrors*/ false) || - constructs.length > 0 && isRelatedTo(getReturnTypeOfSignature(constructs[0]), target, /*reportErrors*/ false)) { - reportError(Diagnostics.Value_of_type_0_has_no_properties_in_common_with_type_1_Did_you_mean_to_call_it, typeToString(source), typeToString(target)); - } - else { - reportError(Diagnostics.Type_0_has_no_properties_in_common_with_type_1, typeToString(source), typeToString(target)); - } - } - return Ternary.False; - } - - let result = Ternary.False; - const saveErrorInfo = captureErrorCalculationState(); - let isIntersectionConstituent = !!isApparentIntersectionConstituent; - - // Note that these checks are specifically ordered to produce correct results. In particular, - // we need to deconstruct unions before intersections (because unions are always at the top), - // and we need to handle "each" relations before "some" relations for the same kind of type. - if (source.flags & TypeFlags.Union) { - result = relation === comparableRelation ? - someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive)) : - eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive)); - } - else { - if (target.flags & TypeFlags.Union) { - result = typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive)); - } - else if (target.flags & TypeFlags.Intersection) { - isIntersectionConstituent = true; // set here to affect the following trio of checks - result = typeRelatedToEachType(getRegularTypeOfObjectLiteral(source), target as IntersectionType, reportErrors); - if (result && (isPerformingExcessPropertyChecks || isPerformingCommonPropertyChecks)) { - // Validate against excess props using the original `source` - if (!propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, /*isIntersectionConstituent*/ false)) { - return Ternary.False; - } - } - } - else if (source.flags & TypeFlags.Intersection) { - // Check to see if any constituents of the intersection are immediately related to the target. - // - // Don't report errors though. Checking whether a constituent is related to the source is not actually - // useful and leads to some confusing error messages. Instead it is better to let the below checks - // take care of this, or to not elaborate at all. For instance, - // - // - For an object type (such as 'C = A & B'), users are usually more interested in structural errors. - // - // - For a union type (such as '(A | B) = (C & D)'), it's better to hold onto the whole intersection - // than to report that 'D' is not assignable to 'A' or 'B'. - // - // - For a primitive type or type parameter (such as 'number = A & B') there is no point in - // breaking the intersection apart. - result = someTypeRelatedToType(source, target, /*reportErrors*/ false); - } - if (!result && (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable)) { - if (result = recursiveTypeRelatedTo(source, target, reportErrors, isIntersectionConstituent)) { - resetErrorInfo(saveErrorInfo); - } - } - } - if (!result && source.flags & (TypeFlags.Intersection | TypeFlags.TypeParameter)) { - // The combined constraint of an intersection type is the intersection of the constraints of - // the constituents. When an intersection type contains instantiable types with union type - // constraints, there are situations where we need to examine the combined constraint. One is - // when the target is a union type. Another is when the intersection contains types belonging - // to one of the disjoint domains. For example, given type variables T and U, each with the - // constraint 'string | number', the combined constraint of 'T & U' is 'string | number' and - // we need to check this constraint against a union on the target side. Also, given a type - // variable V constrained to 'string | number', 'V & number' has a combined constraint of - // 'string & number | number & number' which reduces to just 'number'. - // This also handles type parameters, as a type parameter with a union constraint compared against a union - // needs to have its constraint hoisted into an intersection with said type parameter, this way - // the type param can be compared with itself in the target (with the influence of its constraint to match other parts) - // For example, if `T extends 1 | 2` and `U extends 2 | 3` and we compare `T & U` to `T & U & (1 | 2 | 3)` - const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (source).types: [source], !!(target.flags & TypeFlags.Union)); - if (constraint && (source.flags & TypeFlags.Intersection || target.flags & TypeFlags.Union)) { - if (everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself - // TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this - if (result = isRelatedTo(constraint, target, /*reportErrors*/ false, /*headMessage*/ undefined, isIntersectionConstituent)) { - resetErrorInfo(saveErrorInfo); - } - } - } - } - - if (!result && reportErrors) { - source = originalSource.aliasSymbol ? originalSource : source; - target = originalTarget.aliasSymbol ? originalTarget : target; - let maybeSuppress = overrideNextErrorInfo > 0; - if (maybeSuppress) { - overrideNextErrorInfo--; - } - if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { - const currentError = errorInfo; - tryElaborateArrayLikeErrors(source, target, reportErrors); - if (errorInfo !== currentError) { - maybeSuppress = !!errorInfo; - } - } - if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Primitive) { - tryElaborateErrorsForPrimitivesAndObjects(source, target); - } - else if (source.symbol && source.flags & TypeFlags.Object && globalObjectType === source) { - reportError(Diagnostics.The_Object_type_is_assignable_to_very_few_other_types_Did_you_mean_to_use_the_any_type_instead); - } - else if (isComparingJsxAttributes && target.flags & TypeFlags.Intersection) { - const targetTypes = (target as IntersectionType).types; - const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes, errorNode); - const intrinsicClassAttributes = getJsxType(JsxNames.IntrinsicClassAttributes, errorNode); - if (intrinsicAttributes !== errorType && intrinsicClassAttributes !== errorType && - (contains(targetTypes, intrinsicAttributes) || contains(targetTypes, intrinsicClassAttributes))) { - // do not report top error - return result; - } - } - if (!headMessage && maybeSuppress) { - lastSkippedInfo = [source, target]; - // Used by, eg, missing property checking to replace the top-level message with a more informative one - return result; - } - reportRelationError(headMessage, source, target); - } - return result; - } - - function isIdenticalTo(source: Type, target: Type): Ternary { - let result: Ternary; - const flags = source.flags & target.flags; - if (flags & TypeFlags.Object || flags & TypeFlags.IndexedAccess || flags & TypeFlags.Conditional || flags & TypeFlags.Index || flags & TypeFlags.Substitution) { - return recursiveTypeRelatedTo(source, target, /*reportErrors*/ false, /*isIntersectionConstituent*/ false); - } - if (flags & (TypeFlags.Union | TypeFlags.Intersection)) { - if (result = eachTypeRelatedToSomeType(source, target)) { - if (result &= eachTypeRelatedToSomeType(target, source)) { - return result; - } - } - } - return Ternary.False; - } - - function getTypeOfPropertyInTypes(types: Type[], name: __String) { - const appendPropType = (propTypes: Type[] | undefined, type: Type) => { - type = getApparentType(type); - const prop = type.flags & TypeFlags.UnionOrIntersection ? getPropertyOfUnionOrIntersectionType(type, name) : getPropertyOfObjectType(type, name); - const propType = prop && getTypeOfSymbol(prop) || isNumericLiteralName(name) && getIndexTypeOfType(type, IndexKind.Number) || getIndexTypeOfType(type, IndexKind.String) || undefinedType; - return append(propTypes, propType); - }; - return getUnionType(reduceLeft(types, appendPropType, /*initial*/ undefined) || emptyArray); - } - - function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean { - if (!isExcessPropertyCheckTarget(target) || !noImplicitAny && getObjectFlags(target) & ObjectFlags.JSLiteral) { - return false; // Disable excess property checks on JS literals to simulate having an implicit "index signature" - but only outside of noImplicitAny - } - const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); - if ((relation === assignableRelation || relation === comparableRelation) && - (isTypeSubsetOf(globalObjectType, target) || (!isComparingJsxAttributes && isEmptyObjectType(target)))) { - return false; - } - let reducedTarget = target; - let checkTypes: Type[] | undefined; - if (target.flags & TypeFlags.Union) { - reducedTarget = findMatchingDiscriminantType(source, target) || filterPrimitivesIfContainsNonPrimitive(target); - checkTypes = reducedTarget.flags & TypeFlags.Union ? (reducedTarget).types : [reducedTarget]; - } - for (const prop of getPropertiesOfType(source)) { - if (shouldCheckAsExcessProperty(prop, source.symbol)) { - if (!isKnownProperty(reducedTarget, prop.escapedName, isComparingJsxAttributes)) { - if (reportErrors) { - // Report error in terms of object types in the target as those are the only ones - // we check in isKnownProperty. - const errorTarget = filterType(reducedTarget, isExcessPropertyCheckTarget); - // We know *exactly* where things went wrong when comparing the types. - // Use this property as the error node as this will be more helpful in - // reasoning about what went wrong. - if (!errorNode) return Debug.fail(); - if (isJsxAttributes(errorNode) || isJsxOpeningLikeElement(errorNode) || isJsxOpeningLikeElement(errorNode.parent)) { - // JsxAttributes has an object-literal flag and undergo same type-assignablity check as normal object-literal. - // However, using an object-literal error message will be very confusing to the users so we give different a message. - // TODO: Spelling suggestions for excess jsx attributes (needs new diagnostic messages) - if (prop.valueDeclaration && isJsxAttribute(prop.valueDeclaration) && getSourceFileOfNode(errorNode) === getSourceFileOfNode(prop.valueDeclaration.name)) { - // Note that extraneous children (as in `extra`) don't pass this check, - // since `children` is a SyntaxKind.PropertySignature instead of a SyntaxKind.JsxAttribute. - errorNode = prop.valueDeclaration.name; - } - reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(prop), typeToString(errorTarget)); - } - else { - // use the property's value declaration if the property is assigned inside the literal itself - const objectLiteralDeclaration = source.symbol && firstOrUndefined(source.symbol.declarations); - let suggestion; - if (prop.valueDeclaration && findAncestor(prop.valueDeclaration, d => d === objectLiteralDeclaration) && getSourceFileOfNode(objectLiteralDeclaration) === getSourceFileOfNode(errorNode)) { - const propDeclaration = prop.valueDeclaration as ObjectLiteralElementLike; - Debug.assertNode(propDeclaration, isObjectLiteralElementLike); - - errorNode = propDeclaration; - - const name = propDeclaration.name!; - if (isIdentifier(name)) { - suggestion = getSuggestionForNonexistentProperty(name, errorTarget); - } - } - if (suggestion !== undefined) { - reportError(Diagnostics.Object_literal_may_only_specify_known_properties_but_0_does_not_exist_in_type_1_Did_you_mean_to_write_2, - symbolToString(prop), typeToString(errorTarget), suggestion); - } - else { - reportError(Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, - symbolToString(prop), typeToString(errorTarget)); - } - } - } - return true; - } - if (checkTypes && !isRelatedTo(getTypeOfSymbol(prop), getTypeOfPropertyInTypes(checkTypes, prop.escapedName), reportErrors)) { - if (reportErrors) { - reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(prop)); - } - return true; - } - } - } - return false; - } - - function shouldCheckAsExcessProperty(prop: Symbol, container: Symbol) { - return prop.valueDeclaration && container.valueDeclaration && prop.valueDeclaration.parent === container.valueDeclaration; - } - - function eachTypeRelatedToSomeType(source: UnionOrIntersectionType, target: UnionOrIntersectionType): Ternary { - let result = Ternary.True; - const sourceTypes = source.types; - for (const sourceType of sourceTypes) { - const related = typeRelatedToSomeType(sourceType, target, /*reportErrors*/ false); - if (!related) { - return Ternary.False; - } - result &= related; - } - return result; - } - - function typeRelatedToSomeType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean): Ternary { - const targetTypes = target.types; - if (target.flags & TypeFlags.Union && containsType(targetTypes, source)) { - return Ternary.True; - } - for (const type of targetTypes) { - const related = isRelatedTo(source, type, /*reportErrors*/ false); - if (related) { - return related; - } - } - if (reportErrors) { - const bestMatchingType = - findMatchingDiscriminantType(source, target) || - findMatchingTypeReferenceOrTypeAliasReference(source, target) || - findBestTypeForObjectLiteral(source, target) || - findBestTypeForInvokable(source, target) || - findMostOverlappyType(source, target); - - isRelatedTo(source, bestMatchingType || targetTypes[targetTypes.length - 1], /*reportErrors*/ true); - } - return Ternary.False; - } - - function findMatchingTypeReferenceOrTypeAliasReference(source: Type, unionTarget: UnionOrIntersectionType) { - const sourceObjectFlags = getObjectFlags(source); - if (sourceObjectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous) && unionTarget.flags & TypeFlags.Union) { - return find(unionTarget.types, target => { - if (target.flags & TypeFlags.Object) { - const overlapObjFlags = sourceObjectFlags & getObjectFlags(target); - if (overlapObjFlags & ObjectFlags.Reference) { - return (source as TypeReference).target === (target as TypeReference).target; - } - if (overlapObjFlags & ObjectFlags.Anonymous) { - return !!(source as AnonymousType).aliasSymbol && (source as AnonymousType).aliasSymbol === (target as AnonymousType).aliasSymbol; - } - } - return false; - }); - } - } - - function findBestTypeForObjectLiteral(source: Type, unionTarget: UnionOrIntersectionType) { - if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && forEachType(unionTarget, isArrayLikeType)) { - return find(unionTarget.types, t => !isArrayLikeType(t)); - } - } - - function findBestTypeForInvokable(source: Type, unionTarget: UnionOrIntersectionType) { - let signatureKind = SignatureKind.Call; - const hasSignatures = getSignaturesOfType(source, signatureKind).length > 0 || - (signatureKind = SignatureKind.Construct, getSignaturesOfType(source, signatureKind).length > 0); - if (hasSignatures) { - return find(unionTarget.types, t => getSignaturesOfType(t, signatureKind).length > 0); - } - } - - function findMostOverlappyType(source: Type, unionTarget: UnionOrIntersectionType) { - let bestMatch: Type | undefined; - let matchingCount = 0; - for (const target of unionTarget.types) { - const overlap = getIntersectionType([getIndexType(source), getIndexType(target)]); - if (overlap.flags & TypeFlags.Index) { - // perfect overlap of keys - bestMatch = target; - matchingCount = Infinity; - } - else if (overlap.flags & TypeFlags.Union) { - // We only want to account for literal types otherwise. - // If we have a union of index types, it seems likely that we - // needed to elaborate between two generic mapped types anyway. - const len = length(filter((overlap as UnionType).types, isUnitType)); - if (len >= matchingCount) { - bestMatch = target; - matchingCount = len; - } - } - else if (isUnitType(overlap) && 1 >= matchingCount) { - bestMatch = target; - matchingCount = 1; - } - } - return bestMatch; - } - - function filterPrimitivesIfContainsNonPrimitive(type: UnionType) { - if (maybeTypeOfKind(type, TypeFlags.NonPrimitive)) { - const result = filterType(type, t => !(t.flags & TypeFlags.Primitive)); - if (!(result.flags & TypeFlags.Never)) { - return result; - } - } - return type; - } - - // Keep this up-to-date with the same logic within `getApparentTypeOfContextualType`, since they should behave similarly - function findMatchingDiscriminantType(source: Type, target: Type) { - if (target.flags & TypeFlags.Union && source.flags & (TypeFlags.Intersection | TypeFlags.Object)) { - const sourceProperties = getPropertiesOfType(source); - if (sourceProperties) { - const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target); - if (sourcePropertiesFiltered) { - return discriminateTypeByDiscriminableItems(target, map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => Type, __String])), isRelatedTo); - } - } - } - return undefined; - } - - function typeRelatedToEachType(source: Type, target: IntersectionType, reportErrors: boolean): Ternary { - let result = Ternary.True; - const targetTypes = target.types; - for (const targetType of targetTypes) { - const related = isRelatedTo(source, targetType, reportErrors, /*headMessage*/ undefined, /*isIntersectionConstituent*/ true); - if (!related) { - return Ternary.False; - } - result &= related; - } - return result; - } - - function someTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean): Ternary { - const sourceTypes = source.types; - if (source.flags & TypeFlags.Union && containsType(sourceTypes, target)) { - return Ternary.True; - } - const len = sourceTypes.length; - for (let i = 0; i < len; i++) { - const related = isRelatedTo(sourceTypes[i], target, reportErrors && i === len - 1); - if (related) { - return related; - } - } - return Ternary.False; - } - - function eachTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean): Ternary { - let result = Ternary.True; - const sourceTypes = source.types; - for (const sourceType of sourceTypes) { - const related = isRelatedTo(sourceType, target, reportErrors); - if (!related) { - return Ternary.False; - } - result &= related; - } - return result; - } - - function typeArgumentsRelatedTo(sources: readonly Type[] = emptyArray, targets: readonly Type[] = emptyArray, variances: readonly VarianceFlags[] = emptyArray, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary { - if (sources.length !== targets.length && relation === identityRelation) { - return Ternary.False; - } - const length = sources.length <= targets.length ? sources.length : targets.length; - let result = Ternary.True; - for (let i = 0; i < length; i++) { - // When variance information isn't available we default to covariance. This happens - // in the process of computing variance information for recursive types and when - // comparing 'this' type arguments. - const varianceFlags = i < variances.length ? variances[i] : VarianceFlags.Covariant; - const variance = varianceFlags & VarianceFlags.VarianceMask; - // We ignore arguments for independent type parameters (because they're never witnessed). - if (variance !== VarianceFlags.Independent) { - const s = sources[i]; - const t = targets[i]; - let related = Ternary.True; - if (varianceFlags & VarianceFlags.Unmeasurable) { - // Even an `Unmeasurable` variance works out without a structural check if the source and target are _identical_. - // We can't simply assume invariance, because `Unmeasurable` marks nonlinear relations, for example, a relation tained by - // the `-?` modifier in a mapped type (where, no matter how the inputs are related, the outputs still might not be) - related = relation === identityRelation ? isRelatedTo(s, t, /*reportErrors*/ false) : compareTypesIdentical(s, t); - } - else if (variance === VarianceFlags.Covariant) { - related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent); - } - else if (variance === VarianceFlags.Contravariant) { - related = isRelatedTo(t, s, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent); - } - else if (variance === VarianceFlags.Bivariant) { - // In the bivariant case we first compare contravariantly without reporting - // errors. Then, if that doesn't succeed, we compare covariantly with error - // reporting. Thus, error elaboration will be based on the the covariant check, - // which is generally easier to reason about. - related = isRelatedTo(t, s, /*reportErrors*/ false); - if (!related) { - related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent); - } - } - else { - // In the invariant case we first compare covariantly, and only when that - // succeeds do we proceed to compare contravariantly. Thus, error elaboration - // will typically be based on the covariant check. - related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent); - if (related) { - related &= isRelatedTo(t, s, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent); - } - } - if (!related) { - return Ternary.False; - } - result &= related; - } - } - return result; - } - - // Determine if possibly recursive types are related. First, check if the result is already available in the global cache. - // Second, check if we have already started a comparison of the given two types in which case we assume the result to be true. - // Third, check if both types are part of deeply nested chains of generic type instantiations and if so assume the types are - // equal and infinitely expanding. Fourth, if we have reached a depth of 100 nested comparisons, assume we have runaway recursion - // and issue an error. Otherwise, actually compare the structure of the two types. - function recursiveTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary { - if (overflow) { - return Ternary.False; - } - const id = getRelationKey(source, target, isIntersectionConstituent, relation); - const entry = relation.get(id); - if (entry !== undefined) { - if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Reported)) { - // We are elaborating errors and the cached result is an unreported failure. The result will be reported - // as a failure, and should be updated as a reported failure by the bottom of this function. - } - else { - if (outofbandVarianceMarkerHandler) { - // We're in the middle of variance checking - integrate any unmeasurable/unreliable flags from this cached component - const saved = entry & RelationComparisonResult.ReportsMask; - if (saved & RelationComparisonResult.ReportsUnmeasurable) { - instantiateType(source, reportUnmeasurableMarkers); - } - if (saved & RelationComparisonResult.ReportsUnreliable) { - instantiateType(source, reportUnreliableMarkers); - } - } - return entry & RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False; - } - } - if (!maybeKeys) { - maybeKeys = []; - sourceStack = []; - targetStack = []; - } - else { - for (let i = 0; i < maybeCount; i++) { - // If source and target are already being compared, consider them related with assumptions - if (id === maybeKeys[i]) { - return Ternary.Maybe; - } - } - if (depth === 100) { - overflow = true; - return Ternary.False; - } - } - const maybeStart = maybeCount; - maybeKeys[maybeCount] = id; - maybeCount++; - sourceStack[depth] = source; - targetStack[depth] = target; - depth++; - const saveExpandingFlags = expandingFlags; - if (!(expandingFlags & ExpandingFlags.Source) && isDeeplyNestedType(source, sourceStack, depth)) expandingFlags |= ExpandingFlags.Source; - if (!(expandingFlags & ExpandingFlags.Target) && isDeeplyNestedType(target, targetStack, depth)) expandingFlags |= ExpandingFlags.Target; - let originalHandler: typeof outofbandVarianceMarkerHandler; - let propagatingVarianceFlags: RelationComparisonResult = 0; - if (outofbandVarianceMarkerHandler) { - originalHandler = outofbandVarianceMarkerHandler; - outofbandVarianceMarkerHandler = onlyUnreliable => { - propagatingVarianceFlags |= onlyUnreliable ? RelationComparisonResult.ReportsUnreliable : RelationComparisonResult.ReportsUnmeasurable; - return originalHandler!(onlyUnreliable); - }; - } - const result = expandingFlags !== ExpandingFlags.Both ? structuredTypeRelatedTo(source, target, reportErrors, isIntersectionConstituent) : Ternary.Maybe; - if (outofbandVarianceMarkerHandler) { - outofbandVarianceMarkerHandler = originalHandler; - } - expandingFlags = saveExpandingFlags; - depth--; - if (result) { - if (result === Ternary.True || depth === 0) { - // If result is definitely true, record all maybe keys as having succeeded - for (let i = maybeStart; i < maybeCount; i++) { - relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags); - } - maybeCount = maybeStart; - } - } - else { - // A false result goes straight into global cache (when something is false under - // assumptions it will also be false without assumptions) - relation.set(id, (reportErrors ? RelationComparisonResult.Reported : 0) | RelationComparisonResult.Failed | propagatingVarianceFlags); - maybeCount = maybeStart; - } - return result; - } - - function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary { - const flags = source.flags & target.flags; - if (relation === identityRelation && !(flags & TypeFlags.Object)) { - if (flags & TypeFlags.Index) { - return isRelatedTo((source).type, (target).type, /*reportErrors*/ false); - } - let result = Ternary.False; - if (flags & TypeFlags.IndexedAccess) { - if (result = isRelatedTo((source).objectType, (target).objectType, /*reportErrors*/ false)) { - if (result &= isRelatedTo((source).indexType, (target).indexType, /*reportErrors*/ false)) { - return result; - } - } - } - if (flags & TypeFlags.Conditional) { - if ((source).root.isDistributive === (target).root.isDistributive) { - if (result = isRelatedTo((source).checkType, (target).checkType, /*reportErrors*/ false)) { - if (result &= isRelatedTo((source).extendsType, (target).extendsType, /*reportErrors*/ false)) { - if (result &= isRelatedTo(getTrueTypeFromConditionalType(source), getTrueTypeFromConditionalType(target), /*reportErrors*/ false)) { - if (result &= isRelatedTo(getFalseTypeFromConditionalType(source), getFalseTypeFromConditionalType(target), /*reportErrors*/ false)) { - return result; - } - } - } - } - } - } - if (flags & TypeFlags.Substitution) { - return isRelatedTo((source).substitute, (target).substitute, /*reportErrors*/ false); - } - return Ternary.False; - } - - let result: Ternary; - let originalErrorInfo: DiagnosticMessageChain | undefined; - let varianceCheckFailed = false; - const saveErrorInfo = captureErrorCalculationState(); - - // We limit alias variance probing to only object and conditional types since their alias behavior - // is more predictable than other, interned types, which may or may not have an alias depending on - // the order in which things were checked. - if (source.flags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol && - source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol && - !(source.aliasTypeArgumentsContainsMarker || target.aliasTypeArgumentsContainsMarker)) { - const variances = getAliasVariances(source.aliasSymbol); - const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, variances, isIntersectionConstituent); - if (varianceResult !== undefined) { - return varianceResult; - } - } - - if (target.flags & TypeFlags.TypeParameter) { - // A source type { [P in Q]: X } is related to a target type T if keyof T is related to Q and X is related to T[Q]. - if (getObjectFlags(source) & ObjectFlags.Mapped && isRelatedTo(getIndexType(target), getConstraintTypeFromMappedType(source))) { - if (!(getMappedTypeModifiers(source) & MappedTypeModifiers.IncludeOptional)) { - const templateType = getTemplateTypeFromMappedType(source); - const indexedAccessType = getIndexedAccessType(target, getTypeParameterFromMappedType(source)); - if (result = isRelatedTo(templateType, indexedAccessType, reportErrors)) { - return result; - } - } - } - } - else if (target.flags & TypeFlags.Index) { - // A keyof S is related to a keyof T if T is related to S. - if (source.flags & TypeFlags.Index) { - if (result = isRelatedTo((target).type, (source).type, /*reportErrors*/ false)) { - return result; - } - } - // A type S is assignable to keyof T if S is assignable to keyof C, where C is the - // simplified form of T or, if T doesn't simplify, the constraint of T. - const constraint = getSimplifiedTypeOrConstraint((target).type); - if (constraint) { - // We require Ternary.True here such that circular constraints don't cause - // false positives. For example, given 'T extends { [K in keyof T]: string }', - // 'keyof T' has itself as its constraint and produces a Ternary.Maybe when - // related to other types. - if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), reportErrors) === Ternary.True) { - return Ternary.True; - } - } - } - else if (target.flags & TypeFlags.IndexedAccess) { - // A type S is related to a type T[K] if S is related to C, where C is the base - // constraint of T[K] for writing. - if (relation !== identityRelation) { - const objectType = (target).objectType; - const indexType = (target).indexType; - const baseObjectType = getBaseConstraintOfType(objectType) || objectType; - const baseIndexType = getBaseConstraintOfType(indexType) || indexType; - if (!isGenericObjectType(baseObjectType) && !isGenericIndexType(baseIndexType)) { - const accessFlags = AccessFlags.Writing | (baseObjectType !== objectType ? AccessFlags.NoIndexSignatures : 0); - const constraint = getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, /*accessNode*/ undefined, accessFlags); - if (constraint && (result = isRelatedTo(source, constraint, reportErrors))) { - return result; - } - } - } - } - else if (isGenericMappedType(target)) { - // A source type T is related to a target type { [P in X]: T[P] } - const template = getTemplateTypeFromMappedType(target); - const modifiers = getMappedTypeModifiers(target); - if (!(modifiers & MappedTypeModifiers.ExcludeOptional)) { - if (template.flags & TypeFlags.IndexedAccess && (template).objectType === source && - (template).indexType === getTypeParameterFromMappedType(target)) { - return Ternary.True; - } - if (!isGenericMappedType(source)) { - const targetConstraint = getConstraintTypeFromMappedType(target); - const sourceKeys = getIndexType(source, /*stringsOnly*/ undefined, /*noIndexSignatures*/ true); - const includeOptional = modifiers & MappedTypeModifiers.IncludeOptional; - const filteredByApplicability = includeOptional ? intersectTypes(targetConstraint, sourceKeys) : undefined; - // A source type T is related to a target type { [P in Q]: X } if Q is related to keyof T and T[Q] is related to X. - // A source type T is related to a target type { [P in Q]?: X } if some constituent Q' of Q is related to keyof T and T[Q'] is related to X. - if (includeOptional - ? !(filteredByApplicability!.flags & TypeFlags.Never) - : isRelatedTo(targetConstraint, sourceKeys)) { - const typeParameter = getTypeParameterFromMappedType(target); - const indexingType = filteredByApplicability ? getIntersectionType([filteredByApplicability, typeParameter]) : typeParameter; - const indexedAccessType = getIndexedAccessType(source, indexingType); - const templateType = getTemplateTypeFromMappedType(target); - if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) { - return result; - } - } - originalErrorInfo = errorInfo; - resetErrorInfo(saveErrorInfo); - } - } - } - - if (source.flags & TypeFlags.TypeVariable) { - if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) { - // A type S[K] is related to a type T[J] if S is related to T and K is related to J. - if (result = isRelatedTo((source).objectType, (target).objectType, reportErrors)) { - result &= isRelatedTo((source).indexType, (target).indexType, reportErrors); - } - if (result) { - resetErrorInfo(saveErrorInfo); - return result; - } - } - else { - const constraint = getConstraintOfType(source); - if (!constraint || (source.flags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) { - // A type variable with no constraint is not related to the non-primitive object type. - if (result = isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive))) { - resetErrorInfo(saveErrorInfo); - return result; - } - } - // hi-speed no-this-instantiation check (less accurate, but avoids costly `this`-instantiation when the constraint will suffice), see #28231 for report on why this is needed - else if (result = isRelatedTo(constraint, target, /*reportErrors*/ false, /*headMessage*/ undefined, isIntersectionConstituent)) { - resetErrorInfo(saveErrorInfo); - return result; - } - // slower, fuller, this-instantiated check (necessary when comparing raw `this` types from base classes), see `subclassWithPolymorphicThisIsAssignable.ts` test for example - else if (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent)) { - resetErrorInfo(saveErrorInfo); - return result; - } - } - } - else if (source.flags & TypeFlags.Index) { - if (result = isRelatedTo(keyofConstraintType, target, reportErrors)) { - resetErrorInfo(saveErrorInfo); - return result; - } - } - else if (source.flags & TypeFlags.Conditional) { - if (target.flags & TypeFlags.Conditional) { - // Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if - // one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2, - // and Y1 is related to Y2. - const sourceParams = (source as ConditionalType).root.inferTypeParameters; - let sourceExtends = (source).extendsType; - let mapper: TypeMapper | undefined; - if (sourceParams) { - // If the source has infer type parameters, we instantiate them in the context of the target - const ctx = createInferenceContext(sourceParams, /*signature*/ undefined, InferenceFlags.None, isRelatedTo); - inferTypes(ctx.inferences, (target).extendsType, sourceExtends, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); - sourceExtends = instantiateType(sourceExtends, ctx.mapper); - mapper = ctx.mapper; - } - if (isTypeIdenticalTo(sourceExtends, (target).extendsType) && - (isRelatedTo((source).checkType, (target).checkType) || isRelatedTo((target).checkType, (source).checkType))) { - if (result = isRelatedTo(instantiateType(getTrueTypeFromConditionalType(source), mapper), getTrueTypeFromConditionalType(target), reportErrors)) { - result &= isRelatedTo(getFalseTypeFromConditionalType(source), getFalseTypeFromConditionalType(target), reportErrors); - } - if (result) { - resetErrorInfo(saveErrorInfo); - return result; - } - } - } - else { - const distributiveConstraint = getConstraintOfDistributiveConditionalType(source); - if (distributiveConstraint) { - if (result = isRelatedTo(distributiveConstraint, target, reportErrors)) { - resetErrorInfo(saveErrorInfo); - return result; - } - } - const defaultConstraint = getDefaultConstraintOfConditionalType(source); - if (defaultConstraint) { - if (result = isRelatedTo(defaultConstraint, target, reportErrors)) { - resetErrorInfo(saveErrorInfo); - return result; - } - } - } - } - else { - // An empty object type is related to any mapped type that includes a '?' modifier. - if (relation !== subtypeRelation && isPartialMappedType(target) && isEmptyObjectType(source)) { - return Ternary.True; - } - if (isGenericMappedType(target)) { - if (isGenericMappedType(source)) { - if (result = mappedTypeRelatedTo(source, target, reportErrors)) { - resetErrorInfo(saveErrorInfo); - return result; - } - } - return Ternary.False; - } - const sourceIsPrimitive = !!(source.flags & TypeFlags.Primitive); - if (relation !== identityRelation) { - source = getApparentType(source); - } - else if (isGenericMappedType(source)) { - return Ternary.False; - } - if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target && - !(getObjectFlags(source) & ObjectFlags.MarkerType || getObjectFlags(target) & ObjectFlags.MarkerType)) { - // We have type references to the same generic type, and the type references are not marker - // type references (which are intended by be compared structurally). Obtain the variance - // information for the type parameters and relate the type arguments accordingly. - const variances = getVariances((source).target); - const varianceResult = relateVariances(getTypeArguments(source), getTypeArguments(target), variances, isIntersectionConstituent); - if (varianceResult !== undefined) { - return varianceResult; - } - } - else if (isReadonlyArrayType(target) ? isArrayType(source) || isTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) { - if (relation !== identityRelation) { - return isRelatedTo(getIndexTypeOfType(source, IndexKind.Number) || anyType, getIndexTypeOfType(target, IndexKind.Number) || anyType, reportErrors); - } - else { - // By flags alone, we know that the `target` is a readonly array while the source is a normal array or tuple - // or `target` is an array and source is a tuple - in both cases the types cannot be identical, by construction - return Ternary.False; - } - } - // Consider a fresh empty object literal type "closed" under the subtype relationship - this way `{} <- {[idx: string]: any} <- fresh({})` - // and not `{} <- fresh({}) <- {[idx: string]: any}` - else if (relation === subtypeRelation && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source)) { - return Ternary.False; - } - // Even if relationship doesn't hold for unions, intersections, or generic type references, - // it may hold in a structural comparison. - // In a check of the form X = A & B, we will have previously checked if A relates to X or B relates - // to X. Failing both of those we want to check if the aggregation of A and B's members structurally - // relates to X. Thus, we include intersection types on the source side here. - if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) { - // Report structural errors only if we haven't reported any errors yet - const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo.errorInfo && !sourceIsPrimitive; - result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, isIntersectionConstituent); - if (result) { - result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors); - if (result) { - result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors); - if (result) { - result &= indexTypesRelatedTo(source, target, IndexKind.String, sourceIsPrimitive, reportStructuralErrors); - if (result) { - result &= indexTypesRelatedTo(source, target, IndexKind.Number, sourceIsPrimitive, reportStructuralErrors); - } - } - } - } - if (varianceCheckFailed && result) { - errorInfo = originalErrorInfo || errorInfo || saveErrorInfo.errorInfo; // Use variance error (there is no structural one) and return false - } - else if (result) { - return result; - } - } - // If S is an object type and T is a discriminated union, S may be related to T if - // there exists a constituent of T for every combination of the discriminants of S - // with respect to T. We do not report errors here, as we will use the existing - // error result from checking each constituent of the union. - if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Union) { - const objectOnlyTarget = extractTypesOfKind(target, TypeFlags.Object); - if (objectOnlyTarget.flags & TypeFlags.Union) { - const result = typeRelatedToDiscriminatedType(source, objectOnlyTarget as UnionType); - if (result) { - return result; - } - } - } - } - return Ternary.False; - - function relateVariances(sourceTypeArguments: readonly Type[] | undefined, targetTypeArguments: readonly Type[] | undefined, variances: VarianceFlags[], isIntersectionConstituent: boolean) { - if (result = typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors, isIntersectionConstituent)) { - return result; - } - if (some(variances, v => !!(v & VarianceFlags.AllowsStructuralFallback))) { - // If some type parameter was `Unmeasurable` or `Unreliable`, and we couldn't pass by assuming it was identical, then we - // have to allow a structural fallback check - // We elide the variance-based error elaborations, since those might not be too helpful, since we'll potentially - // be assuming identity of the type parameter. - originalErrorInfo = undefined; - resetErrorInfo(saveErrorInfo); - return undefined; - } - const allowStructuralFallback = targetTypeArguments && hasCovariantVoidArgument(targetTypeArguments, variances); - varianceCheckFailed = !allowStructuralFallback; - // The type arguments did not relate appropriately, but it may be because we have no variance - // information (in which case typeArgumentsRelatedTo defaulted to covariance for all type - // arguments). It might also be the case that the target type has a 'void' type argument for - // a covariant type parameter that is only used in return positions within the generic type - // (in which case any type argument is permitted on the source side). In those cases we proceed - // with a structural comparison. Otherwise, we know for certain the instantiations aren't - // related and we can return here. - if (variances !== emptyArray && !allowStructuralFallback) { - // In some cases generic types that are covariant in regular type checking mode become - // invariant in --strictFunctionTypes mode because one or more type parameters are used in - // both co- and contravariant positions. In order to make it easier to diagnose *why* such - // types are invariant, if any of the type parameters are invariant we reset the reported - // errors and instead force a structural comparison (which will include elaborations that - // reveal the reason). - // We can switch on `reportErrors` here, since varianceCheckFailed guarantees we return `False`, - // we can return `False` early here to skip calculating the structural error message we don't need. - if (varianceCheckFailed && !(reportErrors && some(variances, v => (v & VarianceFlags.VarianceMask) === VarianceFlags.Invariant))) { - return Ternary.False; - } - // We remember the original error information so we can restore it in case the structural - // comparison unexpectedly succeeds. This can happen when the structural comparison result - // is a Ternary.Maybe for example caused by the recursion depth limiter. - originalErrorInfo = errorInfo; - resetErrorInfo(saveErrorInfo); - } - } - } - - function reportUnmeasurableMarkers(p: TypeParameter) { - if (outofbandVarianceMarkerHandler && (p === markerSuperType || p === markerSubType || p === markerOtherType)) { - outofbandVarianceMarkerHandler(/*onlyUnreliable*/ false); - } - return p; - } - - function reportUnreliableMarkers(p: TypeParameter) { - if (outofbandVarianceMarkerHandler && (p === markerSuperType || p === markerSubType || p === markerOtherType)) { - outofbandVarianceMarkerHandler(/*onlyUnreliable*/ true); - } - return p; - } - - // A type [P in S]: X is related to a type [Q in T]: Y if T is related to S and X' is - // related to Y, where X' is an instantiation of X in which P is replaced with Q. Notice - // that S and T are contra-variant whereas X and Y are co-variant. - function mappedTypeRelatedTo(source: MappedType, target: MappedType, reportErrors: boolean): Ternary { - const modifiersRelated = relation === comparableRelation || (relation === identityRelation ? getMappedTypeModifiers(source) === getMappedTypeModifiers(target) : - getCombinedMappedTypeOptionality(source) <= getCombinedMappedTypeOptionality(target)); - if (modifiersRelated) { - let result: Ternary; - const targetConstraint = getConstraintTypeFromMappedType(target); - const sourceConstraint = instantiateType(getConstraintTypeFromMappedType(source), getCombinedMappedTypeOptionality(source) < 0 ? reportUnmeasurableMarkers : reportUnreliableMarkers); - if (result = isRelatedTo(targetConstraint, sourceConstraint, reportErrors)) { - const mapper = createTypeMapper([getTypeParameterFromMappedType(source)], [getTypeParameterFromMappedType(target)]); - return result & isRelatedTo(instantiateType(getTemplateTypeFromMappedType(source), mapper), getTemplateTypeFromMappedType(target), reportErrors); - } - } - return Ternary.False; - } - - function typeRelatedToDiscriminatedType(source: Type, target: UnionType) { - // 1. Generate the combinations of discriminant properties & types 'source' can satisfy. - // a. If the number of combinations is above a set limit, the comparison is too complex. - // 2. Filter 'target' to the subset of types whose discriminants exist in the matrix. - // a. If 'target' does not satisfy all discriminants in the matrix, 'source' is not related. - // 3. For each type in the filtered 'target', determine if all non-discriminant properties of - // 'target' are related to a property in 'source'. - // - // NOTE: See ~/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts - // for examples. - - const sourceProperties = getPropertiesOfObjectType(source); - const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target); - if (!sourcePropertiesFiltered) return Ternary.False; - - // Though we could compute the number of combinations as we generate - // the matrix, this would incur additional memory overhead due to - // array allocations. To reduce this overhead, we first compute - // the number of combinations to ensure we will not surpass our - // fixed limit before incurring the cost of any allocations: - let numCombinations = 1; - for (const sourceProperty of sourcePropertiesFiltered) { - numCombinations *= countTypes(getTypeOfSymbol(sourceProperty)); - if (numCombinations > 25) { - // We've reached the complexity limit. - return Ternary.False; - } - } - - // Compute the set of types for each discriminant property. - const sourceDiscriminantTypes: Type[][] = new Array(sourcePropertiesFiltered.length); - const excludedProperties = createUnderscoreEscapedMap(); - for (let i = 0; i < sourcePropertiesFiltered.length; i++) { - const sourceProperty = sourcePropertiesFiltered[i]; - const sourcePropertyType = getTypeOfSymbol(sourceProperty); - sourceDiscriminantTypes[i] = sourcePropertyType.flags & TypeFlags.Union - ? (sourcePropertyType as UnionType).types - : [sourcePropertyType]; - excludedProperties.set(sourceProperty.escapedName, true); - } - - // Match each combination of the cartesian product of discriminant properties to one or more - // constituents of 'target'. If any combination does not have a match then 'source' is not relatable. - const discriminantCombinations = cartesianProduct(sourceDiscriminantTypes); - const matchingTypes: Type[] = []; - for (const combination of discriminantCombinations) { - let hasMatch = false; - outer: for (const type of target.types) { - for (let i = 0; i < sourcePropertiesFiltered.length; i++) { - const sourceProperty = sourcePropertiesFiltered[i]; - const targetProperty = getPropertyOfObjectType(type, sourceProperty.escapedName); - if (!targetProperty) continue outer; - if (sourceProperty === targetProperty) continue; - // We compare the source property to the target in the context of a single discriminant type. - const related = propertyRelatedTo(source, target, sourceProperty, targetProperty, _ => combination[i], /*reportErrors*/ false, /*isIntersectionConstituent*/ false); - // If the target property could not be found, or if the properties were not related, - // then this constituent is not a match. - if (!related) { - continue outer; - } - } - pushIfUnique(matchingTypes, type, equateValues); - hasMatch = true; - } - if (!hasMatch) { - // We failed to match any type for this combination. - return Ternary.False; - } - } - - // Compare the remaining non-discriminant properties of each match. - let result = Ternary.True; - for (const type of matchingTypes) { - result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties, /*isIntersectionConstituent*/ false); - if (result) { - result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportStructuralErrors*/ false); - if (result) { - result &= signaturesRelatedTo(source, type, SignatureKind.Construct, /*reportStructuralErrors*/ false); - if (result) { - result &= indexTypesRelatedTo(source, type, IndexKind.String, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false); - if (result) { - result &= indexTypesRelatedTo(source, type, IndexKind.Number, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false); - } - } - } - } - if (!result) { - return result; - } - } - return result; - } - - function excludeProperties(properties: Symbol[], excludedProperties: UnderscoreEscapedMap | undefined) { - if (!excludedProperties || properties.length === 0) return properties; - let result: Symbol[] | undefined; - for (let i = 0; i < properties.length; i++) { - if (!excludedProperties.has(properties[i].escapedName)) { - if (result) { - result.push(properties[i]); - } - } - else if (!result) { - result = properties.slice(0, i); - } - } - return result || properties; - } - - function isPropertySymbolTypeRelated(sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary { - const targetIsOptional = strictNullChecks && !!(getCheckFlags(targetProp) & CheckFlags.Partial); - const source = getTypeOfSourceProperty(sourceProp); - if (getCheckFlags(targetProp) & CheckFlags.DeferredType && !getSymbolLinks(targetProp).type) { - // Rather than resolving (and normalizing) the type, relate constituent-by-constituent without performing normalization or seconadary passes - const links = getSymbolLinks(targetProp); - Debug.assertDefined(links.deferralParent); - Debug.assertDefined(links.deferralConstituents); - const unionParent = !!(links.deferralParent!.flags & TypeFlags.Union); - let result = unionParent ? Ternary.False : Ternary.True; - const targetTypes = links.deferralConstituents!; - for (const targetType of targetTypes) { - const related = isRelatedTo(source, targetType, /*reportErrors*/ false, /*headMessage*/ undefined, /*isIntersectionConstituent*/ !unionParent); - if (!unionParent) { - if (!related) { - // Can't assign to a target individually - have to fallback to assigning to the _whole_ intersection (which forces normalization) - return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors); - } - result &= related; - } - else { - if (related) { - return related; - } - } - } - if (unionParent && !result && targetIsOptional) { - result = isRelatedTo(source, undefinedType); - } - if (unionParent && !result && reportErrors) { - // The easiest way to get the right errors here is to un-defer (which may be costly) - // If it turns out this is too costly too often, we can replicate the error handling logic within - // typeRelatedToSomeType without the discriminatable type branch (as that requires a manifest union - // type on which to hand discriminable properties, which we are expressly trying to avoid here) - return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors); - } - return result; - } - else { - return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors, /*headMessage*/ undefined, isIntersectionConstituent); - } - } - - function propertyRelatedTo(source: Type, target: Type, sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary { - const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp); - const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp); - if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) { - const hasDifferingDeclarations = sourceProp.valueDeclaration !== targetProp.valueDeclaration; - if (getCheckFlags(sourceProp) & CheckFlags.ContainsPrivate && hasDifferingDeclarations) { - if (reportErrors) { - reportError(Diagnostics.Property_0_has_conflicting_declarations_and_is_inaccessible_in_type_1, symbolToString(sourceProp), typeToString(source)); - } - return Ternary.False; - } - if (hasDifferingDeclarations) { - if (reportErrors) { - if (sourcePropFlags & ModifierFlags.Private && targetPropFlags & ModifierFlags.Private) { - reportError(Diagnostics.Types_have_separate_declarations_of_a_private_property_0, symbolToString(targetProp)); - } - else { - reportError(Diagnostics.Property_0_is_private_in_type_1_but_not_in_type_2, symbolToString(targetProp), - typeToString(sourcePropFlags & ModifierFlags.Private ? source : target), - typeToString(sourcePropFlags & ModifierFlags.Private ? target : source)); - } - } - return Ternary.False; - } - } - else if (targetPropFlags & ModifierFlags.Protected) { - if (!isValidOverrideOf(sourceProp, targetProp)) { - if (reportErrors) { - reportError(Diagnostics.Property_0_is_protected_but_type_1_is_not_a_class_derived_from_2, symbolToString(targetProp), - typeToString(getDeclaringClass(sourceProp) || source), typeToString(getDeclaringClass(targetProp) || target)); - } - return Ternary.False; - } - } - else if (sourcePropFlags & ModifierFlags.Protected) { - if (reportErrors) { - reportError(Diagnostics.Property_0_is_protected_in_type_1_but_public_in_type_2, - symbolToString(targetProp), typeToString(source), typeToString(target)); - } - return Ternary.False; - } - // If the target comes from a partial union prop, allow `undefined` in the target type - const related = isPropertySymbolTypeRelated(sourceProp, targetProp, getTypeOfSourceProperty, reportErrors, isIntersectionConstituent); - if (!related) { - if (reportErrors) { - reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp)); - } - return Ternary.False; - } - // When checking for comparability, be more lenient with optional properties. - if (relation !== comparableRelation && sourceProp.flags & SymbolFlags.Optional && !(targetProp.flags & SymbolFlags.Optional)) { - // TypeScript 1.0 spec (April 2014): 3.8.3 - // S is a subtype of a type T, and T is a supertype of S if ... - // S' and T are object types and, for each member M in T.. - // M is a property and S' contains a property N where - // if M is a required property, N is also a required property - // (M - property in T) - // (N - property in S) - if (reportErrors) { - reportError(Diagnostics.Property_0_is_optional_in_type_1_but_required_in_type_2, - symbolToString(targetProp), typeToString(source), typeToString(target)); - } - return Ternary.False; - } - return related; - } - - function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: UnderscoreEscapedMap | undefined, isIntersectionConstituent: boolean): Ternary { - if (relation === identityRelation) { - return propertiesIdenticalTo(source, target, excludedProperties); - } - const requireOptionalProperties = relation === subtypeRelation && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source) && !isTupleType(source); - const unmatchedProperty = getUnmatchedProperty(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false); - if (unmatchedProperty) { - if (reportErrors) { - const props = arrayFrom(getUnmatchedProperties(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false)); - let shouldSkipElaboration = false; - if (!headMessage || (headMessage.code !== Diagnostics.Class_0_incorrectly_implements_interface_1.code && - headMessage.code !== Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass.code)) { - shouldSkipElaboration = true; // Retain top-level error for interface implementing issues, otherwise omit it - } - if (props.length === 1) { - const propName = symbolToString(unmatchedProperty); - reportError(Diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2, propName, ...getTypeNamesForErrorDisplay(source, target)); - if (length(unmatchedProperty.declarations)) { - associateRelatedInfo(createDiagnosticForNode(unmatchedProperty.declarations[0], Diagnostics._0_is_declared_here, propName)); - } - if (shouldSkipElaboration && errorInfo) { - overrideNextErrorInfo++; - } - } - else if (tryElaborateArrayLikeErrors(source, target, /*reportErrors*/ false)) { - if (props.length > 5) { // arbitrary cutoff for too-long list form - reportError(Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2_and_3_more, typeToString(source), typeToString(target), map(props.slice(0, 4), p => symbolToString(p)).join(", "), props.length - 4); - } - else { - reportError(Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2, typeToString(source), typeToString(target), map(props, p => symbolToString(p)).join(", ")); - } - if (shouldSkipElaboration && errorInfo) { - overrideNextErrorInfo++; - } - } - // ELSE: No array like or unmatched property error - just issue top level error (errorInfo = undefined) - } - return Ternary.False; - } - if (isObjectLiteralType(target)) { - for (const sourceProp of excludeProperties(getPropertiesOfType(source), excludedProperties)) { - if (!getPropertyOfObjectType(target, sourceProp.escapedName)) { - const sourceType = getTypeOfSymbol(sourceProp); - if (!(sourceType === undefinedType || sourceType === undefinedWideningType || sourceType === optionalType)) { - if (reportErrors) { - reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(sourceProp), typeToString(target)); - } - return Ternary.False; - } - } - } - } - let result = Ternary.True; - if (isTupleType(target)) { - const targetRestType = getRestTypeOfTupleType(target); - if (targetRestType) { - if (!isTupleType(source)) { - return Ternary.False; - } - const sourceRestType = getRestTypeOfTupleType(source); - if (sourceRestType && !isRelatedTo(sourceRestType, targetRestType, reportErrors)) { - if (reportErrors) { - reportError(Diagnostics.Rest_signatures_are_incompatible); - } - return Ternary.False; - } - const targetCount = getTypeReferenceArity(target) - 1; - const sourceCount = getTypeReferenceArity(source) - (sourceRestType ? 1 : 0); - const sourceTypeArguments = getTypeArguments(source); - for (let i = targetCount; i < sourceCount; i++) { - const related = isRelatedTo(sourceTypeArguments[i], targetRestType, reportErrors); - if (!related) { - if (reportErrors) { - reportError(Diagnostics.Property_0_is_incompatible_with_rest_element_type, "" + i); - } - return Ternary.False; - } - result &= related; - } - } - } - // We only call this for union target types when we're attempting to do excess property checking - in those cases, we want to get _all possible props_ - // from the target union, across all members - const properties = getPropertiesOfType(target); - const numericNamesOnly = isTupleType(source) && isTupleType(target); - for (const targetProp of excludeProperties(properties, excludedProperties)) { - const name = targetProp.escapedName; - if (!(targetProp.flags & SymbolFlags.Prototype) && (!numericNamesOnly || isNumericLiteralName(name) || name === "length")) { - const sourceProp = getPropertyOfType(source, name); - if (sourceProp && sourceProp !== targetProp) { - const related = propertyRelatedTo(source, target, sourceProp, targetProp, getTypeOfSymbol, reportErrors, isIntersectionConstituent); - if (!related) { - return Ternary.False; - } - result &= related; - } - } - } - return result; - } - - function propertiesIdenticalTo(source: Type, target: Type, excludedProperties: UnderscoreEscapedMap | undefined): Ternary { - if (!(source.flags & TypeFlags.Object && target.flags & TypeFlags.Object)) { - return Ternary.False; - } - const sourceProperties = excludeProperties(getPropertiesOfObjectType(source), excludedProperties); - const targetProperties = excludeProperties(getPropertiesOfObjectType(target), excludedProperties); - if (sourceProperties.length !== targetProperties.length) { - return Ternary.False; - } - let result = Ternary.True; - for (const sourceProp of sourceProperties) { - const targetProp = getPropertyOfObjectType(target, sourceProp.escapedName); - if (!targetProp) { - return Ternary.False; - } - const related = compareProperties(sourceProp, targetProp, isRelatedTo); - if (!related) { - return Ternary.False; - } - result &= related; - } - return result; - } - - function signaturesRelatedTo(source: Type, target: Type, kind: SignatureKind, reportErrors: boolean): Ternary { - if (relation === identityRelation) { - return signaturesIdenticalTo(source, target, kind); - } - if (target === anyFunctionType || source === anyFunctionType) { - return Ternary.True; - } - - const sourceIsJSConstructor = source.symbol && isJSConstructor(source.symbol.valueDeclaration); - const targetIsJSConstructor = target.symbol && isJSConstructor(target.symbol.valueDeclaration); - - const sourceSignatures = getSignaturesOfType(source, (sourceIsJSConstructor && kind === SignatureKind.Construct) ? - SignatureKind.Call : kind); - const targetSignatures = getSignaturesOfType(target, (targetIsJSConstructor && kind === SignatureKind.Construct) ? - SignatureKind.Call : kind); - - if (kind === SignatureKind.Construct && sourceSignatures.length && targetSignatures.length) { - if (isAbstractConstructorType(source) && !isAbstractConstructorType(target)) { - // An abstract constructor type is not assignable to a non-abstract constructor type - // as it would otherwise be possible to new an abstract class. Note that the assignability - // check we perform for an extends clause excludes construct signatures from the target, - // so this check never proceeds. - if (reportErrors) { - reportError(Diagnostics.Cannot_assign_an_abstract_constructor_type_to_a_non_abstract_constructor_type); - } - return Ternary.False; - } - if (!constructorVisibilitiesAreCompatible(sourceSignatures[0], targetSignatures[0], reportErrors)) { - return Ternary.False; - } - } - - let result = Ternary.True; - const saveErrorInfo = captureErrorCalculationState(); - const incompatibleReporter = kind === SignatureKind.Construct ? reportIncompatibleConstructSignatureReturn : reportIncompatibleCallSignatureReturn; - - if (getObjectFlags(source) & ObjectFlags.Instantiated && getObjectFlags(target) & ObjectFlags.Instantiated && source.symbol === target.symbol) { - // We have instantiations of the same anonymous type (which typically will be the type of a - // method). Simply do a pairwise comparison of the signatures in the two signature lists instead - // of the much more expensive N * M comparison matrix we explore below. We erase type parameters - // as they are known to always be the same. - for (let i = 0; i < targetSignatures.length; i++) { - const related = signatureRelatedTo(sourceSignatures[i], targetSignatures[i], /*erase*/ true, reportErrors, incompatibleReporter(sourceSignatures[i], targetSignatures[i])); - if (!related) { - return Ternary.False; - } - result &= related; - } - } - else if (sourceSignatures.length === 1 && targetSignatures.length === 1) { - // For simple functions (functions with a single signature) we only erase type parameters for - // the comparable relation. Otherwise, if the source signature is generic, we instantiate it - // in the context of the target signature before checking the relationship. Ideally we'd do - // this regardless of the number of signatures, but the potential costs are prohibitive due - // to the quadratic nature of the logic below. - const eraseGenerics = relation === comparableRelation || !!compilerOptions.noStrictGenericChecks; - result = signatureRelatedTo(sourceSignatures[0], targetSignatures[0], eraseGenerics, reportErrors, incompatibleReporter(sourceSignatures[0], targetSignatures[0])); - } - else { - outer: for (const t of targetSignatures) { - // Only elaborate errors from the first failure - let shouldElaborateErrors = reportErrors; - for (const s of sourceSignatures) { - const related = signatureRelatedTo(s, t, /*erase*/ true, shouldElaborateErrors, incompatibleReporter(s, t)); - if (related) { - result &= related; - resetErrorInfo(saveErrorInfo); - continue outer; - } - shouldElaborateErrors = false; - } - - if (shouldElaborateErrors) { - reportError(Diagnostics.Type_0_provides_no_match_for_the_signature_1, - typeToString(source), - signatureToString(t, /*enclosingDeclaration*/ undefined, /*flags*/ undefined, kind)); - } - return Ternary.False; - } - } - return result; - } - - function reportIncompatibleCallSignatureReturn(siga: Signature, sigb: Signature) { - if (siga.parameters.length === 0 && sigb.parameters.length === 0) { - return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, typeToString(source), typeToString(target)); - } - return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Call_signature_return_types_0_and_1_are_incompatible, typeToString(source), typeToString(target)); - } - - function reportIncompatibleConstructSignatureReturn(siga: Signature, sigb: Signature) { - if (siga.parameters.length === 0 && sigb.parameters.length === 0) { - return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, typeToString(source), typeToString(target)); - } - return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible, typeToString(source), typeToString(target)); - } - - /** - * See signatureAssignableTo, compareSignaturesIdentical - */ - function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, incompatibleReporter: (source: Type, target: Type) => void): Ternary { - return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target, - CallbackCheck.None, /*ignoreReturnTypes*/ false, reportErrors, reportError, incompatibleReporter, isRelatedTo, reportUnreliableMarkers); - } - - function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary { - const sourceSignatures = getSignaturesOfType(source, kind); - const targetSignatures = getSignaturesOfType(target, kind); - if (sourceSignatures.length !== targetSignatures.length) { - return Ternary.False; - } - let result = Ternary.True; - for (let i = 0; i < sourceSignatures.length; i++) { - const related = compareSignaturesIdentical(sourceSignatures[i], targetSignatures[i], /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false, isRelatedTo); - if (!related) { - return Ternary.False; - } - result &= related; - } - return result; - } - - function eachPropertyRelatedTo(source: Type, target: Type, kind: IndexKind, reportErrors: boolean): Ternary { - let result = Ternary.True; - for (const prop of getPropertiesOfObjectType(source)) { - if (isIgnoredJsxProperty(source, prop)) { - continue; - } - // Skip over symbol-named members - if (prop.nameType && prop.nameType.flags & TypeFlags.UniqueESSymbol) { - continue; - } - if (kind === IndexKind.String || isNumericLiteralName(prop.escapedName)) { - const related = isRelatedTo(getTypeOfSymbol(prop), target, reportErrors); - if (!related) { - if (reportErrors) { - reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, symbolToString(prop)); - } - return Ternary.False; - } - result &= related; - } - } - return result; - } - - function indexInfoRelatedTo(sourceInfo: IndexInfo, targetInfo: IndexInfo, reportErrors: boolean) { - const related = isRelatedTo(sourceInfo.type, targetInfo.type, reportErrors); - if (!related && reportErrors) { - reportError(Diagnostics.Index_signatures_are_incompatible); - } - return related; - } - - function indexTypesRelatedTo(source: Type, target: Type, kind: IndexKind, sourceIsPrimitive: boolean, reportErrors: boolean): Ternary { - if (relation === identityRelation) { - return indexTypesIdenticalTo(source, target, kind); - } - const targetInfo = getIndexInfoOfType(target, kind); - if (!targetInfo || targetInfo.type.flags & TypeFlags.Any && !sourceIsPrimitive) { - // Index signature of type any permits assignment from everything but primitives - return Ternary.True; - } - const sourceInfo = getIndexInfoOfType(source, kind) || - kind === IndexKind.Number && getIndexInfoOfType(source, IndexKind.String); - if (sourceInfo) { - return indexInfoRelatedTo(sourceInfo, targetInfo, reportErrors); - } - if (isGenericMappedType(source)) { - // A generic mapped type { [P in K]: T } is related to an index signature { [x: string]: U } - // if T is related to U. - return kind === IndexKind.String ? isRelatedTo(getTemplateTypeFromMappedType(source), targetInfo.type, reportErrors) : Ternary.False; - } - if (isObjectTypeWithInferableIndex(source)) { - let related = Ternary.True; - if (kind === IndexKind.String) { - const sourceNumberInfo = getIndexInfoOfType(source, IndexKind.Number); - if (sourceNumberInfo) { - related = indexInfoRelatedTo(sourceNumberInfo, targetInfo, reportErrors); - } - } - if (related) { - related &= eachPropertyRelatedTo(source, targetInfo.type, kind, reportErrors); - } - return related; - } - if (reportErrors) { - reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source)); - } - return Ternary.False; - } - - function indexTypesIdenticalTo(source: Type, target: Type, indexKind: IndexKind): Ternary { - const targetInfo = getIndexInfoOfType(target, indexKind); - const sourceInfo = getIndexInfoOfType(source, indexKind); - if (!sourceInfo && !targetInfo) { - return Ternary.True; - } - if (sourceInfo && targetInfo && sourceInfo.isReadonly === targetInfo.isReadonly) { - return isRelatedTo(sourceInfo.type, targetInfo.type); - } - return Ternary.False; - } - - function constructorVisibilitiesAreCompatible(sourceSignature: Signature, targetSignature: Signature, reportErrors: boolean) { - if (!sourceSignature.declaration || !targetSignature.declaration) { - return true; - } - - const sourceAccessibility = getSelectedModifierFlags(sourceSignature.declaration, ModifierFlags.NonPublicAccessibilityModifier); - const targetAccessibility = getSelectedModifierFlags(targetSignature.declaration, ModifierFlags.NonPublicAccessibilityModifier); - - // A public, protected and private signature is assignable to a private signature. - if (targetAccessibility === ModifierFlags.Private) { - return true; - } - - // A public and protected signature is assignable to a protected signature. - if (targetAccessibility === ModifierFlags.Protected && sourceAccessibility !== ModifierFlags.Private) { - return true; - } - - // Only a public signature is assignable to public signature. - if (targetAccessibility !== ModifierFlags.Protected && !sourceAccessibility) { - return true; - } - - if (reportErrors) { - reportError(Diagnostics.Cannot_assign_a_0_constructor_type_to_a_1_constructor_type, visibilityToString(sourceAccessibility), visibilityToString(targetAccessibility)); - } - - return false; - } - } - - function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary): Type | undefined; - function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue: Type): Type; - function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: Type) { - // undefined=unknown, true=discriminated, false=not discriminated - // The state of each type progresses from left to right. Discriminated types stop at 'true'. - const discriminable = target.types.map(_ => undefined) as (boolean | undefined)[]; - for (const [getDiscriminatingType, propertyName] of discriminators) { - let i = 0; - for (const type of target.types) { - const targetType = getTypeOfPropertyOfType(type, propertyName); - if (targetType && related(getDiscriminatingType(), targetType)) { - discriminable[i] = discriminable[i] === undefined ? true : discriminable[i]; - } - else { - discriminable[i] = false; - } - i++; - } - } - const match = discriminable.indexOf(/*searchElement*/ true); - // make sure exactly 1 matches before returning it - return match === -1 || discriminable.indexOf(/*searchElement*/ true, match + 1) !== -1 ? defaultValue : target.types[match]; - } - - /** - * A type is 'weak' if it is an object type with at least one optional property - * and no required properties, call/construct signatures or index signatures - */ - function isWeakType(type: Type): boolean { - if (type.flags & TypeFlags.Object) { - const resolved = resolveStructuredTypeMembers(type); - return resolved.callSignatures.length === 0 && resolved.constructSignatures.length === 0 && - !resolved.stringIndexInfo && !resolved.numberIndexInfo && - resolved.properties.length > 0 && - every(resolved.properties, p => !!(p.flags & SymbolFlags.Optional)); - } - if (type.flags & TypeFlags.Intersection) { - return every((type).types, isWeakType); - } - return false; - } - - function hasCommonProperties(source: Type, target: Type, isComparingJsxAttributes: boolean) { - for (const prop of getPropertiesOfType(source)) { - if (isKnownProperty(target, prop.escapedName, isComparingJsxAttributes)) { - return true; - } - } - return false; - } - - // Return a type reference where the source type parameter is replaced with the target marker - // type, and flag the result as a marker type reference. - function getMarkerTypeReference(type: GenericType, source: TypeParameter, target: Type) { - const result = createTypeReference(type, map(type.typeParameters, t => t === source ? target : t)); - result.objectFlags |= ObjectFlags.MarkerType; - return result; - } - - function getAliasVariances(symbol: Symbol) { - const links = getSymbolLinks(symbol); - return getVariancesWorker(links.typeParameters, links, (_links, param, marker) => { - const type = getTypeAliasInstantiation(symbol, instantiateTypes(links.typeParameters!, makeUnaryTypeMapper(param, marker))); - type.aliasTypeArgumentsContainsMarker = true; - return type; - }); - } - - // Return an array containing the variance of each type parameter. The variance is effectively - // a digest of the type comparisons that occur for each type argument when instantiations of the - // generic type are structurally compared. We infer the variance information by comparing - // instantiations of the generic type for type arguments with known relations. The function - // returns the emptyArray singleton if we're not in strictFunctionTypes mode or if the function - // has been invoked recursively for the given generic type. - function getVariancesWorker(typeParameters: readonly TypeParameter[] = emptyArray, cache: TCache, createMarkerType: (input: TCache, param: TypeParameter, marker: Type) => Type): VarianceFlags[] { - let variances = cache.variances; - if (!variances) { - // The emptyArray singleton is used to signal a recursive invocation. - cache.variances = emptyArray; - variances = []; - for (const tp of typeParameters) { - let unmeasurable = false; - let unreliable = false; - const oldHandler = outofbandVarianceMarkerHandler; - outofbandVarianceMarkerHandler = (onlyUnreliable) => onlyUnreliable ? unreliable = true : unmeasurable = true; - // We first compare instantiations where the type parameter is replaced with - // marker types that have a known subtype relationship. From this we can infer - // invariance, covariance, contravariance or bivariance. - const typeWithSuper = createMarkerType(cache, tp, markerSuperType); - const typeWithSub = createMarkerType(cache, tp, markerSubType); - let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? VarianceFlags.Covariant : 0) | - (isTypeAssignableTo(typeWithSuper, typeWithSub) ? VarianceFlags.Contravariant : 0); - // If the instantiations appear to be related bivariantly it may be because the - // type parameter is independent (i.e. it isn't witnessed anywhere in the generic - // type). To determine this we compare instantiations where the type parameter is - // replaced with marker types that are known to be unrelated. - if (variance === VarianceFlags.Bivariant && isTypeAssignableTo(createMarkerType(cache, tp, markerOtherType), typeWithSuper)) { - variance = VarianceFlags.Independent; - } - outofbandVarianceMarkerHandler = oldHandler; - if (unmeasurable || unreliable) { - if (unmeasurable) { - variance |= VarianceFlags.Unmeasurable; - } - if (unreliable) { - variance |= VarianceFlags.Unreliable; - } - } - variances.push(variance); - } - cache.variances = variances; - } - return variances; - } - - function getVariances(type: GenericType): VarianceFlags[] { - // Arrays and tuples are known to be covariant, no need to spend time computing this (emptyArray implies covariance for all parameters) - if (type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple) { - return emptyArray; - } - return getVariancesWorker(type.typeParameters, type, getMarkerTypeReference); - } - - // Return true if the given type reference has a 'void' type argument for a covariant type parameter. - // See comment at call in recursiveTypeRelatedTo for when this case matters. - function hasCovariantVoidArgument(typeArguments: readonly Type[], variances: VarianceFlags[]): boolean { - for (let i = 0; i < variances.length; i++) { - if ((variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Covariant && typeArguments[i].flags & TypeFlags.Void) { - return true; - } - } - return false; - } - - function isUnconstrainedTypeParameter(type: Type) { - return type.flags & TypeFlags.TypeParameter && !getConstraintOfTypeParameter(type); - } - - function isNonDeferredTypeReference(type: Type): type is TypeReference { - return !!(getObjectFlags(type) & ObjectFlags.Reference) && !(type).node; - } - - function isTypeReferenceWithGenericArguments(type: Type): boolean { - return isNonDeferredTypeReference(type) && some(getTypeArguments(type), t => isUnconstrainedTypeParameter(t) || isTypeReferenceWithGenericArguments(t)); - } - - /** - * getTypeReferenceId(A) returns "111=0-12=1" - * where A.id=111 and number.id=12 - */ - function getTypeReferenceId(type: TypeReference, typeParameters: Type[], depth = 0) { - let result = "" + type.target.id; - for (const t of getTypeArguments(type)) { - if (isUnconstrainedTypeParameter(t)) { - let index = typeParameters.indexOf(t); - if (index < 0) { - index = typeParameters.length; - typeParameters.push(t); - } - result += "=" + index; - } - else if (depth < 4 && isTypeReferenceWithGenericArguments(t)) { - result += "<" + getTypeReferenceId(t as TypeReference, typeParameters, depth + 1) + ">"; - } - else { - result += "-" + t.id; - } - } - return result; - } - - /** - * To improve caching, the relation key for two generic types uses the target's id plus ids of the type parameters. - * For other cases, the types ids are used. - */ - function getRelationKey(source: Type, target: Type, isIntersectionConstituent: boolean, relation: Map) { - if (relation === identityRelation && source.id > target.id) { - const temp = source; - source = target; - target = temp; - } - const intersection = isIntersectionConstituent ? "&" : ""; - if (isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target)) { - const typeParameters: Type[] = []; - return getTypeReferenceId(source, typeParameters) + "," + getTypeReferenceId(target, typeParameters) + intersection; - } - return source.id + "," + target.id + intersection; - } - - // Invoke the callback for each underlying property symbol of the given symbol and return the first - // value that isn't undefined. - function forEachProperty(prop: Symbol, callback: (p: Symbol) => T): T | undefined { - if (getCheckFlags(prop) & CheckFlags.Synthetic) { - for (const t of (prop).containingType!.types) { - const p = getPropertyOfType(t, prop.escapedName); - const result = p && forEachProperty(p, callback); - if (result) { - return result; - } - } - return undefined; - } - return callback(prop); - } - - // Return the declaring class type of a property or undefined if property not declared in class - function getDeclaringClass(prop: Symbol) { - return prop.parent && prop.parent.flags & SymbolFlags.Class ? getDeclaredTypeOfSymbol(getParentOfSymbol(prop)!) : undefined; - } - - // Return true if some underlying source property is declared in a class that derives - // from the given base class. - function isPropertyInClassDerivedFrom(prop: Symbol, baseClass: Type | undefined) { - return forEachProperty(prop, sp => { - const sourceClass = getDeclaringClass(sp); - return sourceClass ? hasBaseType(sourceClass, baseClass) : false; - }); - } - - // Return true if source property is a valid override of protected parts of target property. - function isValidOverrideOf(sourceProp: Symbol, targetProp: Symbol) { - return !forEachProperty(targetProp, tp => getDeclarationModifierFlagsFromSymbol(tp) & ModifierFlags.Protected ? - !isPropertyInClassDerivedFrom(sourceProp, getDeclaringClass(tp)) : false); - } - - // Return true if the given class derives from each of the declaring classes of the protected - // constituents of the given property. - function isClassDerivedFromDeclaringClasses(checkClass: Type, prop: Symbol) { - return forEachProperty(prop, p => getDeclarationModifierFlagsFromSymbol(p) & ModifierFlags.Protected ? - !hasBaseType(checkClass, getDeclaringClass(p)) : false) ? undefined : checkClass; - } - - // Return true if the given type is deeply nested. We consider this to be the case when structural type comparisons - // for 5 or more occurrences or instantiations of the type have been recorded on the given stack. It is possible, - // though highly unlikely, for this test to be true in a situation where a chain of instantiations is not infinitely - // expanding. Effectively, we will generate a false positive when two types are structurally equal to at least 5 - // levels, but unequal at some level beyond that. - // In addition, this will also detect when an indexed access has been chained off of 5 or more times (which is essentially - // the dual of the structural comparison), and likewise mark the type as deeply nested, potentially adding false positives - // for finite but deeply expanding indexed accesses (eg, for `Q[P1][P2][P3][P4][P5]`). - function isDeeplyNestedType(type: Type, stack: Type[], depth: number): boolean { - // We track all object types that have an associated symbol (representing the origin of the type) - if (depth >= 5 && type.flags & TypeFlags.Object && !isObjectOrArrayLiteralType(type)) { - const symbol = type.symbol; - if (symbol) { - let count = 0; - for (let i = 0; i < depth; i++) { - const t = stack[i]; - if (t.flags & TypeFlags.Object && t.symbol === symbol) { - count++; - if (count >= 5) return true; - } - } - } - } - if (depth >= 5 && type.flags & TypeFlags.IndexedAccess) { - const root = getRootObjectTypeFromIndexedAccessChain(type); - let count = 0; - for (let i = 0; i < depth; i++) { - const t = stack[i]; - if (getRootObjectTypeFromIndexedAccessChain(t) === root) { - count++; - if (count >= 5) return true; - } - } - } - return false; - } - - /** - * Gets the leftmost object type in a chain of indexed accesses, eg, in A[P][Q], returns A - */ - function getRootObjectTypeFromIndexedAccessChain(type: Type) { - let t = type; - while (t.flags & TypeFlags.IndexedAccess) { - t = (t as IndexedAccessType).objectType; - } - return t; - } - - function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean { - return compareProperties(sourceProp, targetProp, compareTypesIdentical) !== Ternary.False; - } - - function compareProperties(sourceProp: Symbol, targetProp: Symbol, compareTypes: (source: Type, target: Type) => Ternary): Ternary { - // Two members are considered identical when - // - they are public properties with identical names, optionality, and types, - // - they are private or protected properties originating in the same declaration and having identical types - if (sourceProp === targetProp) { - return Ternary.True; - } - const sourcePropAccessibility = getDeclarationModifierFlagsFromSymbol(sourceProp) & ModifierFlags.NonPublicAccessibilityModifier; - const targetPropAccessibility = getDeclarationModifierFlagsFromSymbol(targetProp) & ModifierFlags.NonPublicAccessibilityModifier; - if (sourcePropAccessibility !== targetPropAccessibility) { - return Ternary.False; - } - if (sourcePropAccessibility) { - if (getTargetSymbol(sourceProp) !== getTargetSymbol(targetProp)) { - return Ternary.False; - } - } - else { - if ((sourceProp.flags & SymbolFlags.Optional) !== (targetProp.flags & SymbolFlags.Optional)) { - return Ternary.False; - } - } - if (isReadonlySymbol(sourceProp) !== isReadonlySymbol(targetProp)) { - return Ternary.False; - } - return compareTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp)); - } - - function isMatchingSignature(source: Signature, target: Signature, partialMatch: boolean) { - const sourceParameterCount = getParameterCount(source); - const targetParameterCount = getParameterCount(target); - const sourceMinArgumentCount = getMinArgumentCount(source); - const targetMinArgumentCount = getMinArgumentCount(target); - const sourceHasRestParameter = hasEffectiveRestParameter(source); - const targetHasRestParameter = hasEffectiveRestParameter(target); - // A source signature matches a target signature if the two signatures have the same number of required, - // optional, and rest parameters. - if (sourceParameterCount === targetParameterCount && - sourceMinArgumentCount === targetMinArgumentCount && - sourceHasRestParameter === targetHasRestParameter) { - return true; - } - // A source signature partially matches a target signature if the target signature has no fewer required - // parameters - if (partialMatch && sourceMinArgumentCount <= targetMinArgumentCount) { - return true; - } - return false; - } - - /** - * See signatureRelatedTo, compareSignaturesIdentical - */ - function compareSignaturesIdentical(source: Signature, target: Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean, compareTypes: (s: Type, t: Type) => Ternary): Ternary { - // TODO (drosen): De-duplicate code between related functions. - if (source === target) { - return Ternary.True; - } - if (!(isMatchingSignature(source, target, partialMatch))) { - return Ternary.False; - } - // Check that the two signatures have the same number of type parameters. - if (length(source.typeParameters) !== length(target.typeParameters)) { - return Ternary.False; - } - // Check that type parameter constraints and defaults match. If they do, instantiate the source - // signature with the type parameters of the target signature and continue the comparison. - if (target.typeParameters) { - const mapper = createTypeMapper(source.typeParameters!, target.typeParameters); - for (let i = 0; i < target.typeParameters.length; i++) { - const s = source.typeParameters![i]; - const t = target.typeParameters[i]; - if (!(s === t || compareTypes(instantiateType(getConstraintFromTypeParameter(s), mapper) || unknownType, getConstraintFromTypeParameter(t) || unknownType) && - compareTypes(instantiateType(getDefaultFromTypeParameter(s), mapper) || unknownType, getDefaultFromTypeParameter(t) || unknownType))) { - return Ternary.False; - } - } - source = instantiateSignature(source, mapper, /*eraseTypeParameters*/ true); - } - let result = Ternary.True; - if (!ignoreThisTypes) { - const sourceThisType = getThisTypeOfSignature(source); - if (sourceThisType) { - const targetThisType = getThisTypeOfSignature(target); - if (targetThisType) { - const related = compareTypes(sourceThisType, targetThisType); - if (!related) { - return Ternary.False; - } - result &= related; - } - } - } - const targetLen = getParameterCount(target); - for (let i = 0; i < targetLen; i++) { - const s = getTypeAtPosition(source, i); - const t = getTypeAtPosition(target, i); - const related = compareTypes(t, s); - if (!related) { - return Ternary.False; - } - result &= related; - } - if (!ignoreReturnTypes) { - const sourceTypePredicate = getTypePredicateOfSignature(source); - const targetTypePredicate = getTypePredicateOfSignature(target); - result &= sourceTypePredicate || targetTypePredicate ? - compareTypePredicatesIdentical(sourceTypePredicate, targetTypePredicate, compareTypes) : - compareTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); - } - return result; - } - - function compareTypePredicatesIdentical(source: TypePredicate | undefined, target: TypePredicate | undefined, compareTypes: (s: Type, t: Type) => Ternary): Ternary { - return !(source && target && typePredicateKindsMatch(source, target)) ? Ternary.False : - source.type === target.type ? Ternary.True : - source.type && target.type ? compareTypes(source.type, target.type) : - Ternary.False; - } - - function literalTypesWithSameBaseType(types: Type[]): boolean { - let commonBaseType: Type | undefined; - for (const t of types) { - const baseType = getBaseTypeOfLiteralType(t); - if (!commonBaseType) { - commonBaseType = baseType; - } - if (baseType === t || baseType !== commonBaseType) { - return false; - } - } - return true; - } - - // When the candidate types are all literal types with the same base type, return a union - // of those literal types. Otherwise, return the leftmost type for which no type to the - // right is a supertype. - function getSupertypeOrUnion(types: Type[]): Type { - return literalTypesWithSameBaseType(types) ? - getUnionType(types) : - reduceLeft(types, (s, t) => isTypeSubtypeOf(s, t) ? t : s)!; - } - - function getCommonSupertype(types: Type[]): Type { - if (!strictNullChecks) { - return getSupertypeOrUnion(types); - } - const primaryTypes = filter(types, t => !(t.flags & TypeFlags.Nullable)); - return primaryTypes.length ? - getNullableType(getSupertypeOrUnion(primaryTypes), getFalsyFlagsOfTypes(types) & TypeFlags.Nullable) : - getUnionType(types, UnionReduction.Subtype); - } - - // Return the leftmost type for which no type to the right is a subtype. - function getCommonSubtype(types: Type[]) { - return reduceLeft(types, (s, t) => isTypeSubtypeOf(t, s) ? t : s)!; - } - - function isArrayType(type: Type): boolean { - return !!(getObjectFlags(type) & ObjectFlags.Reference) && ((type).target === globalArrayType || (type).target === globalReadonlyArrayType); - } - - function isReadonlyArrayType(type: Type): boolean { - return !!(getObjectFlags(type) & ObjectFlags.Reference) && (type).target === globalReadonlyArrayType; - } - - function isMutableArrayOrTuple(type: Type): boolean { - return isArrayType(type) && !isReadonlyArrayType(type) || isTupleType(type) && !type.target.readonly; - } - - function getElementTypeOfArrayType(type: Type): Type | undefined { - return isArrayType(type) ? getTypeArguments(type as TypeReference)[0] : undefined; - } - - function isArrayLikeType(type: Type): boolean { - // A type is array-like if it is a reference to the global Array or global ReadonlyArray type, - // or if it is not the undefined or null type and if it is assignable to ReadonlyArray - return isArrayType(type) || !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType); - } - - function isEmptyArrayLiteralType(type: Type): boolean { - const elementType = isArrayType(type) ? getTypeArguments(type)[0] : undefined; - return elementType === undefinedWideningType || elementType === implicitNeverType; - } - - function isTupleLikeType(type: Type): boolean { - return isTupleType(type) || !!getPropertyOfType(type, "0" as __String); - } - - function isArrayOrTupleLikeType(type: Type): boolean { - return isArrayLikeType(type) || isTupleLikeType(type); - } - - function getTupleElementType(type: Type, index: number) { - const propType = getTypeOfPropertyOfType(type, "" + index as __String); - if (propType) { - return propType; - } - if (everyType(type, isTupleType)) { - return mapType(type, t => getRestTypeOfTupleType(t) || undefinedType); - } - return undefined; - } - - function isNeitherUnitTypeNorNever(type: Type): boolean { - return !(type.flags & (TypeFlags.Unit | TypeFlags.Never)); - } - - function isUnitType(type: Type): boolean { - return !!(type.flags & TypeFlags.Unit); - } - - function isLiteralType(type: Type): boolean { - return type.flags & TypeFlags.Boolean ? true : - type.flags & TypeFlags.Union ? type.flags & TypeFlags.EnumLiteral ? true : every((type).types, isUnitType) : - isUnitType(type); - } - - function getBaseTypeOfLiteralType(type: Type): Type { - return type.flags & TypeFlags.EnumLiteral ? getBaseTypeOfEnumLiteralType(type) : - type.flags & TypeFlags.StringLiteral ? stringType : - type.flags & TypeFlags.NumberLiteral ? numberType : - type.flags & TypeFlags.BigIntLiteral ? bigintType : - type.flags & TypeFlags.BooleanLiteral ? booleanType : - type.flags & TypeFlags.Union ? getUnionType(sameMap((type).types, getBaseTypeOfLiteralType)) : - type; - } - - function getWidenedLiteralType(type: Type): Type { - return type.flags & TypeFlags.EnumLiteral && isFreshLiteralType(type) ? getBaseTypeOfEnumLiteralType(type) : - type.flags & TypeFlags.StringLiteral && isFreshLiteralType(type) ? stringType : - type.flags & TypeFlags.NumberLiteral && isFreshLiteralType(type) ? numberType : - type.flags & TypeFlags.BigIntLiteral && isFreshLiteralType(type) ? bigintType : - type.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(type) ? booleanType : - type.flags & TypeFlags.Union ? getUnionType(sameMap((type).types, getWidenedLiteralType)) : - type; - } - - function getWidenedUniqueESSymbolType(type: Type): Type { - return type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : - type.flags & TypeFlags.Union ? getUnionType(sameMap((type).types, getWidenedUniqueESSymbolType)) : - type; - } - - function getWidenedLiteralLikeTypeForContextualType(type: Type, contextualType: Type | undefined) { - if (!isLiteralOfContextualType(type, contextualType)) { - type = getWidenedUniqueESSymbolType(getWidenedLiteralType(type)); - } - return type; - } - - function getWidenedLiteralLikeTypeForContextualReturnTypeIfNeeded(type: Type | undefined, contextualSignatureReturnType: Type | undefined, isAsync: boolean) { - if (type && isUnitType(type)) { - const contextualType = !contextualSignatureReturnType ? undefined : - isAsync ? getPromisedTypeOfPromise(contextualSignatureReturnType) : - contextualSignatureReturnType; - type = getWidenedLiteralLikeTypeForContextualType(type, contextualType); - } - return type; - } - - function getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(type: Type | undefined, contextualSignatureReturnType: Type | undefined, kind: IterationTypeKind, isAsyncGenerator: boolean) { - if (type && isUnitType(type)) { - const contextualType = !contextualSignatureReturnType ? undefined : - getIterationTypeOfGeneratorFunctionReturnType(kind, contextualSignatureReturnType, isAsyncGenerator); - type = getWidenedLiteralLikeTypeForContextualType(type, contextualType); - } - return type; - } - - /** - * Check if a Type was written as a tuple type literal. - * Prefer using isTupleLikeType() unless the use of `elementTypes` is required. - */ - function isTupleType(type: Type): type is TupleTypeReference { - return !!(getObjectFlags(type) & ObjectFlags.Reference && (type).target.objectFlags & ObjectFlags.Tuple); - } - - function getRestTypeOfTupleType(type: TupleTypeReference) { - return type.target.hasRestElement ? getTypeArguments(type)[type.target.typeParameters!.length - 1] : undefined; - } - - function getRestArrayTypeOfTupleType(type: TupleTypeReference) { - const restType = getRestTypeOfTupleType(type); - return restType && createArrayType(restType); - } - - function getLengthOfTupleType(type: TupleTypeReference) { - return getTypeReferenceArity(type) - (type.target.hasRestElement ? 1 : 0); - } - - function isZeroBigInt({value}: BigIntLiteralType) { - return value.base10Value === "0"; - } - - function getFalsyFlagsOfTypes(types: Type[]): TypeFlags { - let result: TypeFlags = 0; - for (const t of types) { - result |= getFalsyFlags(t); - } - return result; - } - - // Returns the String, Number, Boolean, StringLiteral, NumberLiteral, BooleanLiteral, Void, Undefined, or Null - // flags for the string, number, boolean, "", 0, false, void, undefined, or null types respectively. Returns - // no flags for all other types (including non-falsy literal types). - function getFalsyFlags(type: Type): TypeFlags { - return type.flags & TypeFlags.Union ? getFalsyFlagsOfTypes((type).types) : - type.flags & TypeFlags.StringLiteral ? (type).value === "" ? TypeFlags.StringLiteral : 0 : - type.flags & TypeFlags.NumberLiteral ? (type).value === 0 ? TypeFlags.NumberLiteral : 0 : - type.flags & TypeFlags.BigIntLiteral ? isZeroBigInt(type) ? TypeFlags.BigIntLiteral : 0 : - type.flags & TypeFlags.BooleanLiteral ? (type === falseType || type === regularFalseType) ? TypeFlags.BooleanLiteral : 0 : - type.flags & TypeFlags.PossiblyFalsy; - } - - function removeDefinitelyFalsyTypes(type: Type): Type { - return getFalsyFlags(type) & TypeFlags.DefinitelyFalsy ? - filterType(type, t => !(getFalsyFlags(t) & TypeFlags.DefinitelyFalsy)) : - type; - } - - function extractDefinitelyFalsyTypes(type: Type): Type { - return mapType(type, getDefinitelyFalsyPartOfType); - } - - function getDefinitelyFalsyPartOfType(type: Type): Type { - return type.flags & TypeFlags.String ? emptyStringType : - type.flags & TypeFlags.Number ? zeroType : - type.flags & TypeFlags.BigInt ? zeroBigIntType : - type === regularFalseType || - type === falseType || - type.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null) || - type.flags & TypeFlags.StringLiteral && (type).value === "" || - type.flags & TypeFlags.NumberLiteral && (type).value === 0 || - type.flags & TypeFlags.BigIntLiteral && isZeroBigInt(type) ? type : - neverType; - } - - /** - * Add undefined or null or both to a type if they are missing. - * @param type - type to add undefined and/or null to if not present - * @param flags - Either TypeFlags.Undefined or TypeFlags.Null, or both - */ - function getNullableType(type: Type, flags: TypeFlags): Type { - const missing = (flags & ~type.flags) & (TypeFlags.Undefined | TypeFlags.Null); - return missing === 0 ? type : - missing === TypeFlags.Undefined ? getUnionType([type, undefinedType]) : - missing === TypeFlags.Null ? getUnionType([type, nullType]) : - getUnionType([type, undefinedType, nullType]); - } - - function getOptionalType(type: Type): Type { - Debug.assert(strictNullChecks); - return type.flags & TypeFlags.Undefined ? type : getUnionType([type, undefinedType]); - } - - function getGlobalNonNullableTypeInstantiation(type: Type) { - if (!deferredGlobalNonNullableTypeAlias) { - deferredGlobalNonNullableTypeAlias = getGlobalSymbol("NonNullable" as __String, SymbolFlags.TypeAlias, /*diagnostic*/ undefined) || unknownSymbol; - } - // Use NonNullable global type alias if available to improve quick info/declaration emit - if (deferredGlobalNonNullableTypeAlias !== unknownSymbol) { - return getTypeAliasInstantiation(deferredGlobalNonNullableTypeAlias, [type]); - } - return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); // Type alias unavailable, fall back to non-higher-order behavior - } - - function getNonNullableType(type: Type): Type { - return strictNullChecks ? getGlobalNonNullableTypeInstantiation(type) : type; - } - - function addOptionalTypeMarker(type: Type) { - return strictNullChecks ? getUnionType([type, optionalType]) : type; - } - - function isNotOptionalTypeMarker(type: Type) { - return type !== optionalType; - } - - function removeOptionalTypeMarker(type: Type): Type { - return strictNullChecks ? filterType(type, isNotOptionalTypeMarker) : type; - } - - function propagateOptionalTypeMarker(type: Type, node: OptionalChain, wasOptional: boolean) { - return wasOptional ? isOutermostOptionalChain(node) ? getOptionalType(type) : addOptionalTypeMarker(type) : type; - } - - function getOptionalExpressionType(exprType: Type, expression: Expression) { - return isExpressionOfOptionalChainRoot(expression) ? getNonNullableType(exprType) : - isOptionalChain(expression) ? removeOptionalTypeMarker(exprType) : - exprType; - } - - /** - * Is source potentially coercible to target type under `==`. - * Assumes that `source` is a constituent of a union, hence - * the boolean literal flag on the LHS, but not on the RHS. - * - * This does not fully replicate the semantics of `==`. The - * intention is to catch cases that are clearly not right. - * - * Comparing (string | number) to number should not remove the - * string element. - * - * Comparing (string | number) to 1 will remove the string - * element, though this is not sound. This is a pragmatic - * choice. - * - * @see narrowTypeByEquality - * - * @param source - * @param target - */ - function isCoercibleUnderDoubleEquals(source: Type, target: Type): boolean { - return ((source.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.BooleanLiteral)) !== 0) - && ((target.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.Boolean)) !== 0); - } - - /** - * Return true if type was inferred from an object literal, written as an object type literal, or is the shape of a module - * with no call or construct signatures. - */ - function isObjectTypeWithInferableIndex(type: Type): boolean { - return !!(type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum | SymbolFlags.ValueModule)) !== 0 && - !typeHasCallOrConstructSignatures(type)) || !!(getObjectFlags(type) & ObjectFlags.ReverseMapped && isObjectTypeWithInferableIndex((type as ReverseMappedType).source)); - } - - function createSymbolWithType(source: Symbol, type: Type | undefined) { - const symbol = createSymbol(source.flags, source.escapedName, getCheckFlags(source) & CheckFlags.Readonly); - symbol.declarations = source.declarations; - symbol.parent = source.parent; - symbol.type = type; - symbol.target = source; - if (source.valueDeclaration) { - symbol.valueDeclaration = source.valueDeclaration; - } - if (source.nameType) { - symbol.nameType = source.nameType; - } - return symbol; - } - - function transformTypeOfMembers(type: Type, f: (propertyType: Type) => Type) { - const members = createSymbolTable(); - for (const property of getPropertiesOfObjectType(type)) { - const original = getTypeOfSymbol(property); - const updated = f(original); - members.set(property.escapedName, updated === original ? property : createSymbolWithType(property, updated)); - } - return members; - } - - /** - * If the the provided object literal is subject to the excess properties check, - * create a new that is exempt. Recursively mark object literal members as exempt. - * Leave signatures alone since they are not subject to the check. - */ - function getRegularTypeOfObjectLiteral(type: Type): Type { - if (!(isObjectLiteralType(type) && getObjectFlags(type) & ObjectFlags.FreshLiteral)) { - return type; - } - const regularType = (type).regularType; - if (regularType) { - return regularType; - } - - const resolved = type; - const members = transformTypeOfMembers(type, getRegularTypeOfObjectLiteral); - const regularNew = createAnonymousType(resolved.symbol, - members, - resolved.callSignatures, - resolved.constructSignatures, - resolved.stringIndexInfo, - resolved.numberIndexInfo); - regularNew.flags = resolved.flags; - regularNew.objectFlags |= resolved.objectFlags & ~ObjectFlags.FreshLiteral; - (type).regularType = regularNew; - return regularNew; - } - - function createWideningContext(parent: WideningContext | undefined, propertyName: __String | undefined, siblings: Type[] | undefined): WideningContext { - return { parent, propertyName, siblings, resolvedProperties: undefined }; - } - - function getSiblingsOfContext(context: WideningContext): Type[] { - if (!context.siblings) { - const siblings: Type[] = []; - for (const type of getSiblingsOfContext(context.parent!)) { - if (isObjectLiteralType(type)) { - const prop = getPropertyOfObjectType(type, context.propertyName!); - if (prop) { - forEachType(getTypeOfSymbol(prop), t => { - siblings.push(t); - }); - } - } - } - context.siblings = siblings; - } - return context.siblings; - } - - function getPropertiesOfContext(context: WideningContext): Symbol[] { - if (!context.resolvedProperties) { - const names = createMap() as UnderscoreEscapedMap; - for (const t of getSiblingsOfContext(context)) { - if (isObjectLiteralType(t) && !(getObjectFlags(t) & ObjectFlags.ContainsSpread)) { - for (const prop of getPropertiesOfType(t)) { - names.set(prop.escapedName, prop); - } - } - } - context.resolvedProperties = arrayFrom(names.values()); - } - return context.resolvedProperties; - } - - function getWidenedProperty(prop: Symbol, context: WideningContext | undefined): Symbol { - if (!(prop.flags & SymbolFlags.Property)) { - // Since get accessors already widen their return value there is no need to - // widen accessor based properties here. - return prop; - } - const original = getTypeOfSymbol(prop); - const propContext = context && createWideningContext(context, prop.escapedName, /*siblings*/ undefined); - const widened = getWidenedTypeWithContext(original, propContext); - return widened === original ? prop : createSymbolWithType(prop, widened); - } - - function getUndefinedProperty(prop: Symbol) { - const cached = undefinedProperties.get(prop.escapedName); - if (cached) { - return cached; - } - const result = createSymbolWithType(prop, undefinedType); - result.flags |= SymbolFlags.Optional; - undefinedProperties.set(prop.escapedName, result); - return result; - } - - function getWidenedTypeOfObjectLiteral(type: Type, context: WideningContext | undefined): Type { - const members = createSymbolTable(); - for (const prop of getPropertiesOfObjectType(type)) { - members.set(prop.escapedName, getWidenedProperty(prop, context)); - } - if (context) { - for (const prop of getPropertiesOfContext(context)) { - if (!members.has(prop.escapedName)) { - members.set(prop.escapedName, getUndefinedProperty(prop)); - } - } - } - const stringIndexInfo = getIndexInfoOfType(type, IndexKind.String); - const numberIndexInfo = getIndexInfoOfType(type, IndexKind.Number); - const result = createAnonymousType(type.symbol, members, emptyArray, emptyArray, - stringIndexInfo && createIndexInfo(getWidenedType(stringIndexInfo.type), stringIndexInfo.isReadonly), - numberIndexInfo && createIndexInfo(getWidenedType(numberIndexInfo.type), numberIndexInfo.isReadonly)); - result.objectFlags |= (getObjectFlags(type) & (ObjectFlags.JSLiteral | ObjectFlags.NonInferrableType)); // Retain js literal flag through widening - return result; - } - - function getWidenedType(type: Type) { - return getWidenedTypeWithContext(type, /*context*/ undefined); - } - - function getWidenedTypeWithContext(type: Type, context: WideningContext | undefined): Type { - if (getObjectFlags(type) & ObjectFlags.RequiresWidening) { - if (context === undefined && type.widened) { - return type.widened; - } - let result: Type | undefined; - if (type.flags & (TypeFlags.Any | TypeFlags.Nullable)) { - result = anyType; - } - else if (isObjectLiteralType(type)) { - result = getWidenedTypeOfObjectLiteral(type, context); - } - else if (type.flags & TypeFlags.Union) { - const unionContext = context || createWideningContext(/*parent*/ undefined, /*propertyName*/ undefined, (type).types); - const widenedTypes = sameMap((type).types, t => t.flags & TypeFlags.Nullable ? t : getWidenedTypeWithContext(t, unionContext)); - // Widening an empty object literal transitions from a highly restrictive type to - // a highly inclusive one. For that reason we perform subtype reduction here if the - // union includes empty object types (e.g. reducing {} | string to just {}). - result = getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType) ? UnionReduction.Subtype : UnionReduction.Literal); - } - else if (type.flags & TypeFlags.Intersection) { - result = getIntersectionType(sameMap((type).types, getWidenedType)); - } - else if (isArrayType(type) || isTupleType(type)) { - result = createTypeReference((type).target, sameMap(getTypeArguments(type), getWidenedType)); - } - if (result && context === undefined) { - type.widened = result; - } - return result || type; - } - return type; - } - - /** - * Reports implicit any errors that occur as a result of widening 'null' and 'undefined' - * to 'any'. A call to reportWideningErrorsInType is normally accompanied by a call to - * getWidenedType. But in some cases getWidenedType is called without reporting errors - * (type argument inference is an example). - * - * The return value indicates whether an error was in fact reported. The particular circumstances - * are on a best effort basis. Currently, if the null or undefined that causes widening is inside - * an object literal property (arbitrarily deeply), this function reports an error. If no error is - * reported, reportImplicitAnyError is a suitable fallback to report a general error. - */ - function reportWideningErrorsInType(type: Type): boolean { - let errorReported = false; - if (getObjectFlags(type) & ObjectFlags.ContainsWideningType) { - if (type.flags & TypeFlags.Union) { - if (some((type).types, isEmptyObjectType)) { - errorReported = true; - } - else { - for (const t of (type).types) { - if (reportWideningErrorsInType(t)) { - errorReported = true; - } - } - } - } - if (isArrayType(type) || isTupleType(type)) { - for (const t of getTypeArguments(type)) { - if (reportWideningErrorsInType(t)) { - errorReported = true; - } - } - } - if (isObjectLiteralType(type)) { - for (const p of getPropertiesOfObjectType(type)) { - const t = getTypeOfSymbol(p); - if (getObjectFlags(t) & ObjectFlags.ContainsWideningType) { - if (!reportWideningErrorsInType(t)) { - error(p.valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, symbolToString(p), typeToString(getWidenedType(t))); - } - errorReported = true; - } - } - } - } - return errorReported; - } - - function reportImplicitAny(declaration: Declaration, type: Type, wideningKind?: WideningKind) { - const typeAsString = typeToString(getWidenedType(type)); - if (isInJSFile(declaration) && !isCheckJsEnabledForFile(getSourceFileOfNode(declaration), compilerOptions)) { - // Only report implicit any errors/suggestions in TS and ts-check JS files - return; - } - let diagnostic: DiagnosticMessage; - switch (declaration.kind) { - case SyntaxKind.BinaryExpression: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - diagnostic = noImplicitAny ? Diagnostics.Member_0_implicitly_has_an_1_type : Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; - break; - case SyntaxKind.Parameter: - const param = declaration as ParameterDeclaration; - if (isIdentifier(param.name) && - (isCallSignatureDeclaration(param.parent) || isMethodSignature(param.parent) || isFunctionTypeNode(param.parent)) && - param.parent.parameters.indexOf(param) > -1 && - (resolveName(param, param.name.escapedText, SymbolFlags.Type, undefined, param.name.escapedText, /*isUse*/ true) || - param.name.originalKeywordKind && isTypeNodeKind(param.name.originalKeywordKind))) { - const newName = "arg" + param.parent.parameters.indexOf(param); - errorOrSuggestion(noImplicitAny, declaration, Diagnostics.Parameter_has_a_name_but_no_type_Did_you_mean_0_Colon_1, newName, declarationNameToString(param.name)); - return; - } - diagnostic = (declaration).dotDotDotToken ? - noImplicitAny ? Diagnostics.Rest_parameter_0_implicitly_has_an_any_type : Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage : - noImplicitAny ? Diagnostics.Parameter_0_implicitly_has_an_1_type : Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; - break; - case SyntaxKind.BindingElement: - diagnostic = Diagnostics.Binding_element_0_implicitly_has_an_1_type; - if (!noImplicitAny) { - // Don't issue a suggestion for binding elements since the codefix doesn't yet support them. - return; - } - break; - case SyntaxKind.JSDocFunctionType: - error(declaration, Diagnostics.Function_type_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString); - return; - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - if (noImplicitAny && !(declaration as NamedDeclaration).name) { - if (wideningKind === WideningKind.GeneratorYield) { - error(declaration, Diagnostics.Generator_implicitly_has_yield_type_0_because_it_does_not_yield_any_values_Consider_supplying_a_return_type_annotation, typeAsString); - } - else { - error(declaration, Diagnostics.Function_expression_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString); - } - return; - } - diagnostic = !noImplicitAny ? Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage : - wideningKind === WideningKind.GeneratorYield ? Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_yield_type : - Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type; - break; - case SyntaxKind.MappedType: - if (noImplicitAny) { - error(declaration, Diagnostics.Mapped_object_type_implicitly_has_an_any_template_type); - } - return; - default: - diagnostic = noImplicitAny ? Diagnostics.Variable_0_implicitly_has_an_1_type : Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; - } - errorOrSuggestion(noImplicitAny, declaration, diagnostic, declarationNameToString(getNameOfDeclaration(declaration)), typeAsString); - } - - function reportErrorsFromWidening(declaration: Declaration, type: Type, wideningKind?: WideningKind) { - if (produceDiagnostics && noImplicitAny && getObjectFlags(type) & ObjectFlags.ContainsWideningType) { - // Report implicit any error within type if possible, otherwise report error on declaration - if (!reportWideningErrorsInType(type)) { - reportImplicitAny(declaration, type, wideningKind); - } - } - } - - function applyToParameterTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) { - const sourceCount = getParameterCount(source); - const targetCount = getParameterCount(target); - const sourceRestType = getEffectiveRestType(source); - const targetRestType = getEffectiveRestType(target); - const targetNonRestCount = targetRestType ? targetCount - 1 : targetCount; - const paramCount = sourceRestType ? targetNonRestCount : Math.min(sourceCount, targetNonRestCount); - const sourceThisType = getThisTypeOfSignature(source); - if (sourceThisType) { - const targetThisType = getThisTypeOfSignature(target); - if (targetThisType) { - callback(sourceThisType, targetThisType); - } - } - for (let i = 0; i < paramCount; i++) { - callback(getTypeAtPosition(source, i), getTypeAtPosition(target, i)); - } - if (targetRestType) { - callback(getRestTypeAtPosition(source, paramCount), targetRestType); - } - } - - function applyToReturnTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) { - const sourceTypePredicate = getTypePredicateOfSignature(source); - const targetTypePredicate = getTypePredicateOfSignature(target); - if (sourceTypePredicate && targetTypePredicate && typePredicateKindsMatch(sourceTypePredicate, targetTypePredicate) && sourceTypePredicate.type && targetTypePredicate.type) { - callback(sourceTypePredicate.type, targetTypePredicate.type); - } - else { - callback(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); - } - } - - function createInferenceContext(typeParameters: readonly TypeParameter[], signature: Signature | undefined, flags: InferenceFlags, compareTypes?: TypeComparer): InferenceContext { - return createInferenceContextWorker(typeParameters.map(createInferenceInfo), signature, flags, compareTypes || compareTypesAssignable); - } - - function cloneInferenceContext(context: T, extraFlags: InferenceFlags = 0): InferenceContext | T & undefined { - return context && createInferenceContextWorker(map(context.inferences, cloneInferenceInfo), context.signature, context.flags | extraFlags, context.compareTypes); - } - - function createInferenceContextWorker(inferences: InferenceInfo[], signature: Signature | undefined, flags: InferenceFlags, compareTypes: TypeComparer): InferenceContext { - const context: InferenceContext = { - inferences, - signature, - flags, - compareTypes, - mapper: t => mapToInferredType(context, t, /*fix*/ true), - nonFixingMapper: t => mapToInferredType(context, t, /*fix*/ false), - }; - return context; - } - - function mapToInferredType(context: InferenceContext, t: Type, fix: boolean): Type { - const inferences = context.inferences; - for (let i = 0; i < inferences.length; i++) { - const inference = inferences[i]; - if (t === inference.typeParameter) { - if (fix && !inference.isFixed) { - clearCachedInferences(inferences); - inference.isFixed = true; - } - return getInferredType(context, i); - } - } - return t; - } - - function clearCachedInferences(inferences: InferenceInfo[]) { - for (const inference of inferences) { - if (!inference.isFixed) { - inference.inferredType = undefined; - } - } - } - - function createInferenceInfo(typeParameter: TypeParameter): InferenceInfo { - return { - typeParameter, - candidates: undefined, - contraCandidates: undefined, - inferredType: undefined, - priority: undefined, - topLevel: true, - isFixed: false - }; - } - - function cloneInferenceInfo(inference: InferenceInfo): InferenceInfo { - return { - typeParameter: inference.typeParameter, - candidates: inference.candidates && inference.candidates.slice(), - contraCandidates: inference.contraCandidates && inference.contraCandidates.slice(), - inferredType: inference.inferredType, - priority: inference.priority, - topLevel: inference.topLevel, - isFixed: inference.isFixed - }; - } - - function cloneInferredPartOfContext(context: InferenceContext): InferenceContext | undefined { - const inferences = filter(context.inferences, hasInferenceCandidates); - return inferences.length ? - createInferenceContextWorker(map(inferences, cloneInferenceInfo), context.signature, context.flags, context.compareTypes) : - undefined; - } - - function getMapperFromContext(context: T): TypeMapper | T & undefined { - return context && context.mapper; - } - - // Return true if the given type could possibly reference a type parameter for which - // we perform type inference (i.e. a type parameter of a generic function). We cache - // results for union and intersection types for performance reasons. - function couldContainTypeVariables(type: Type): boolean { - const objectFlags = getObjectFlags(type); - return !!(type.flags & TypeFlags.Instantiable || - objectFlags & ObjectFlags.Reference && ((type).node || forEach(getTypeArguments(type), couldContainTypeVariables)) || - objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations || - objectFlags & (ObjectFlags.Mapped | ObjectFlags.ObjectRestType) || - type.flags & TypeFlags.UnionOrIntersection && !(type.flags & TypeFlags.EnumLiteral) && couldUnionOrIntersectionContainTypeVariables(type)); - } - - function couldUnionOrIntersectionContainTypeVariables(type: UnionOrIntersectionType): boolean { - if (type.couldContainTypeVariables === undefined) { - type.couldContainTypeVariables = some(type.types, couldContainTypeVariables); - } - return type.couldContainTypeVariables; - } - - function isTypeParameterAtTopLevel(type: Type, typeParameter: TypeParameter): boolean { - return !!(type === typeParameter || - type.flags & TypeFlags.UnionOrIntersection && some((type).types, t => isTypeParameterAtTopLevel(t, typeParameter)) || - type.flags & TypeFlags.Conditional && ( - isTypeParameterAtTopLevel(getTrueTypeFromConditionalType(type), typeParameter) || - isTypeParameterAtTopLevel(getFalseTypeFromConditionalType(type), typeParameter))); - } - - /** Create an object with properties named in the string literal type. Every property has type `any` */ - function createEmptyObjectTypeFromStringLiteral(type: Type) { - const members = createSymbolTable(); - forEachType(type, t => { - if (!(t.flags & TypeFlags.StringLiteral)) { - return; - } - const name = escapeLeadingUnderscores((t as StringLiteralType).value); - const literalProp = createSymbol(SymbolFlags.Property, name); - literalProp.type = anyType; - if (t.symbol) { - literalProp.declarations = t.symbol.declarations; - literalProp.valueDeclaration = t.symbol.valueDeclaration; - } - members.set(name, literalProp); - }); - const indexInfo = type.flags & TypeFlags.String ? createIndexInfo(emptyObjectType, /*isReadonly*/ false) : undefined; - return createAnonymousType(undefined, members, emptyArray, emptyArray, indexInfo, undefined); - } - - /** - * Infer a suitable input type for a homomorphic mapped type { [P in keyof T]: X }. We construct - * an object type with the same set of properties as the source type, where the type of each - * property is computed by inferring from the source property type to X for the type - * variable T[P] (i.e. we treat the type T[P] as the type variable we're inferring for). - */ - function inferTypeForHomomorphicMappedType(source: Type, target: MappedType, constraint: IndexType): Type | undefined { - const key = source.id + "," + target.id + "," + constraint.id; - if (reverseMappedCache.has(key)) { - return reverseMappedCache.get(key); - } - reverseMappedCache.set(key, undefined); - const type = createReverseMappedType(source, target, constraint); - reverseMappedCache.set(key, type); - return type; - } - - // We consider a type to be partially inferable if it isn't marked non-inferable or if it is - // an object literal type with at least one property of an inferable type. For example, an object - // literal { a: 123, b: x => true } is marked non-inferable because it contains a context sensitive - // arrow function, but is considered partially inferable because property 'a' has an inferable type. - function isPartiallyInferableType(type: Type): boolean { - return !(getObjectFlags(type) & ObjectFlags.NonInferrableType) || - isObjectLiteralType(type) && some(getPropertiesOfType(type), prop => isPartiallyInferableType(getTypeOfSymbol(prop))); - } - - function createReverseMappedType(source: Type, target: MappedType, constraint: IndexType) { - // We consider a source type reverse mappable if it has a string index signature or if - // it has one or more properties and is of a partially inferable type. - if (!(getIndexInfoOfType(source, IndexKind.String) || getPropertiesOfType(source).length !== 0 && isPartiallyInferableType(source))) { - return undefined; - } - // For arrays and tuples we infer new arrays and tuples where the reverse mapping has been - // applied to the element type(s). - if (isArrayType(source)) { - return createArrayType(inferReverseMappedType(getTypeArguments(source)[0], target, constraint), isReadonlyArrayType(source)); - } - if (isTupleType(source)) { - const elementTypes = map(getTypeArguments(source), t => inferReverseMappedType(t, target, constraint)); - const minLength = getMappedTypeModifiers(target) & MappedTypeModifiers.IncludeOptional ? - getTypeReferenceArity(source) - (source.target.hasRestElement ? 1 : 0) : source.target.minLength; - return createTupleType(elementTypes, minLength, source.target.hasRestElement, source.target.readonly, source.target.associatedNames); - } - // For all other object types we infer a new object type where the reverse mapping has been - // applied to the type of each property. - const reversed = createObjectType(ObjectFlags.ReverseMapped | ObjectFlags.Anonymous, /*symbol*/ undefined) as ReverseMappedType; - reversed.source = source; - reversed.mappedType = target; - reversed.constraintType = constraint; - return reversed; - } - - function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol) { - return inferReverseMappedType(symbol.propertyType, symbol.mappedType, symbol.constraintType); - } - - function inferReverseMappedType(sourceType: Type, target: MappedType, constraint: IndexType): Type { - const typeParameter = getIndexedAccessType(constraint.type, getTypeParameterFromMappedType(target)); - const templateType = getTemplateTypeFromMappedType(target); - const inference = createInferenceInfo(typeParameter); - inferTypes([inference], sourceType, templateType); - return getTypeFromInference(inference) || unknownType; - } - - function* getUnmatchedProperties(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): IterableIterator { - const properties = getPropertiesOfType(target); - for (const targetProp of properties) { - if (requireOptionalProperties || !(targetProp.flags & SymbolFlags.Optional || getCheckFlags(targetProp) & CheckFlags.Partial)) { - const sourceProp = getPropertyOfType(source, targetProp.escapedName); - if (!sourceProp) { - yield targetProp; - } - else if (matchDiscriminantProperties) { - const targetType = getTypeOfSymbol(targetProp); - if (targetType.flags & TypeFlags.Unit) { - const sourceType = getTypeOfSymbol(sourceProp); - if (!(sourceType.flags & TypeFlags.Any || getRegularTypeOfLiteralType(sourceType) === getRegularTypeOfLiteralType(targetType))) { - yield targetProp; - } - } - } - } - } - } - - function getUnmatchedProperty(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): Symbol | undefined { - const result = getUnmatchedProperties(source, target, requireOptionalProperties, matchDiscriminantProperties).next(); - if (!result.done) return result.value; - } - - function tupleTypesDefinitelyUnrelated(source: TupleTypeReference, target: TupleTypeReference) { - return target.target.minLength > source.target.minLength || - !getRestTypeOfTupleType(target) && (!!getRestTypeOfTupleType(source) || getLengthOfTupleType(target) < getLengthOfTupleType(source)); - } - - function typesDefinitelyUnrelated(source: Type, target: Type) { - // Two tuple types with incompatible arities are definitely unrelated. - // Two object types that each have a property that is unmatched in the other are definitely unrelated. - return isTupleType(source) && isTupleType(target) && tupleTypesDefinitelyUnrelated(source, target) || - !!getUnmatchedProperty(source, target, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ true) && - !!getUnmatchedProperty(target, source, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ true); - } - - function getTypeFromInference(inference: InferenceInfo) { - return inference.candidates ? getUnionType(inference.candidates, UnionReduction.Subtype) : - inference.contraCandidates ? getIntersectionType(inference.contraCandidates) : - undefined; - } - - function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0, contravariant = false) { - let symbolStack: Symbol[]; - let visited: Map; - let bivariant = false; - let propagationType: Type; - let inferencePriority = InferencePriority.MaxValue; - let allowComplexConstraintInference = true; - inferFromTypes(originalSource, originalTarget); - - function inferFromTypes(source: Type, target: Type): void { - if (!couldContainTypeVariables(target)) { - return; - } - if (source === wildcardType) { - // We are inferring from an 'any' type. We want to infer this type for every type parameter - // referenced in the target type, so we record it as the propagation type and infer from the - // target to itself. Then, as we find candidates we substitute the propagation type. - const savePropagationType = propagationType; - propagationType = source; - inferFromTypes(target, target); - propagationType = savePropagationType; - return; - } - if (source.aliasSymbol && source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol) { - // Source and target are types originating in the same generic type alias declaration. - // Simply infer from source type arguments to target type arguments. - inferFromTypeArguments(source.aliasTypeArguments, target.aliasTypeArguments!, getAliasVariances(source.aliasSymbol)); - return; - } - if (source === target && source.flags & TypeFlags.UnionOrIntersection) { - // When source and target are the same union or intersection type, just relate each constituent - // type to itself. - for (const t of (source).types) { - inferFromTypes(t, t); - } - return; - } - if (target.flags & TypeFlags.Union) { - // First, infer between identically matching source and target constituents and remove the - // matching types. - const [tempSources, tempTargets] = inferFromMatchingTypes(source.flags & TypeFlags.Union ? (source).types : [source], (target).types, isTypeOrBaseIdenticalTo); - // Next, infer between closely matching source and target constituents and remove - // the matching types. Types closely match when they are instantiations of the same - // object type or instantiations of the same type alias. - const [sources, targets] = inferFromMatchingTypes(tempSources, tempTargets, isTypeCloselyMatchedBy); - if (targets.length === 0) { - return; - } - target = getUnionType(targets); - if (sources.length === 0) { - // All source constituents have been matched and there is nothing further to infer from. - // However, simply making no inferences is undesirable because it could ultimately mean - // inferring a type parameter constraint. Instead, make a lower priority inference from - // the full source to whatever remains in the target. For example, when inferring from - // string to 'string | T', make a lower priority inference of string for T. - inferWithPriority(source, target, InferencePriority.NakedTypeVariable); - return; - } - source = getUnionType(sources); - } - else if (target.flags & TypeFlags.Intersection && some((target).types, - t => !!getInferenceInfoForType(t) || (isGenericMappedType(t) && !!getInferenceInfoForType(getHomomorphicTypeVariable(t) || neverType)))) { - // We reduce intersection types only when they contain naked type parameters. For example, when - // inferring from 'string[] & { extra: any }' to 'string[] & T' we want to remove string[] and - // infer { extra: any } for T. But when inferring to 'string[] & Iterable' we want to keep the - // string[] on the source side and infer string for T. - // Likewise, we consider a homomorphic mapped type constrainted to the target type parameter as similar to a "naked type variable" - // in such scenarios. - if (!(source.flags & TypeFlags.Union)) { - // Infer between identically matching source and target constituents and remove the matching types. - const [sources, targets] = inferFromMatchingTypes(source.flags & TypeFlags.Intersection ? (source).types : [source], (target).types, isTypeIdenticalTo); - if (sources.length === 0 || targets.length === 0) { - return; - } - source = getIntersectionType(sources); - target = getIntersectionType(targets); - } - } - else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) { - target = getActualTypeVariable(target); - } - if (target.flags & TypeFlags.TypeVariable) { - // If target is a type parameter, make an inference, unless the source type contains - // the anyFunctionType (the wildcard type that's used to avoid contextually typing functions). - // Because the anyFunctionType is internal, it should not be exposed to the user by adding - // it as an inference candidate. Hopefully, a better candidate will come along that does - // not contain anyFunctionType when we come back to this argument for its second round - // of inference. Also, we exclude inferences for silentNeverType (which is used as a wildcard - // when constructing types from type parameters that had no inference candidates). - if (getObjectFlags(source) & ObjectFlags.NonInferrableType || source === nonInferrableAnyType || source === silentNeverType || - (priority & InferencePriority.ReturnType && (source === autoType || source === autoArrayType))) { - return; - } - const inference = getInferenceInfoForType(target); - if (inference) { - if (!inference.isFixed) { - if (inference.priority === undefined || priority < inference.priority) { - inference.candidates = undefined; - inference.contraCandidates = undefined; - inference.topLevel = true; - inference.priority = priority; - } - if (priority === inference.priority) { - const candidate = propagationType || source; - // We make contravariant inferences only if we are in a pure contravariant position, - // i.e. only if we have not descended into a bivariant position. - if (contravariant && !bivariant) { - if (!contains(inference.contraCandidates, candidate)) { - inference.contraCandidates = append(inference.contraCandidates, candidate); - clearCachedInferences(inferences); - } - } - else if (!contains(inference.candidates, candidate)) { - inference.candidates = append(inference.candidates, candidate); - clearCachedInferences(inferences); - } - } - if (!(priority & InferencePriority.ReturnType) && target.flags & TypeFlags.TypeParameter && inference.topLevel && !isTypeParameterAtTopLevel(originalTarget, target)) { - inference.topLevel = false; - clearCachedInferences(inferences); - } - } - inferencePriority = Math.min(inferencePriority, priority); - return; - } - else { - // Infer to the simplified version of an indexed access, if possible, to (hopefully) expose more bare type parameters to the inference engine - const simplified = getSimplifiedType(target, /*writing*/ false); - if (simplified !== target) { - invokeOnce(source, simplified, inferFromTypes); - } - else if (target.flags & TypeFlags.IndexedAccess) { - const indexType = getSimplifiedType((target as IndexedAccessType).indexType, /*writing*/ false); - // Generally simplifications of instantiable indexes are avoided to keep relationship checking correct, however if our target is an access, we can consider - // that key of that access to be "instantiated", since we're looking to find the infernce goal in any way we can. - if (indexType.flags & TypeFlags.Instantiable) { - const simplified = distributeIndexOverObjectType(getSimplifiedType((target as IndexedAccessType).objectType, /*writing*/ false), indexType, /*writing*/ false); - if (simplified && simplified !== target) { - invokeOnce(source, simplified, inferFromTypes); - } - } - } - } - } - if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && ( - (source).target === (target).target || isArrayType(source) && isArrayType(target)) && - !((source).node && (target).node)) { - // If source and target are references to the same generic type, infer from type arguments - inferFromTypeArguments(getTypeArguments(source), getTypeArguments(target), getVariances((source).target)); - } - else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) { - contravariant = !contravariant; - inferFromTypes((source).type, (target).type); - contravariant = !contravariant; - } - else if ((isLiteralType(source) || source.flags & TypeFlags.String) && target.flags & TypeFlags.Index) { - const empty = createEmptyObjectTypeFromStringLiteral(source); - contravariant = !contravariant; - inferWithPriority(empty, (target as IndexType).type, InferencePriority.LiteralKeyof); - contravariant = !contravariant; - } - else if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) { - inferFromTypes((source).objectType, (target).objectType); - inferFromTypes((source).indexType, (target).indexType); - } - else if (source.flags & TypeFlags.Conditional && target.flags & TypeFlags.Conditional) { - inferFromTypes((source).checkType, (target).checkType); - inferFromTypes((source).extendsType, (target).extendsType); - inferFromTypes(getTrueTypeFromConditionalType(source), getTrueTypeFromConditionalType(target)); - inferFromTypes(getFalseTypeFromConditionalType(source), getFalseTypeFromConditionalType(target)); - } - else if (target.flags & TypeFlags.Conditional) { - const savePriority = priority; - priority |= contravariant ? InferencePriority.ContravariantConditional : 0; - const targetTypes = [getTrueTypeFromConditionalType(target), getFalseTypeFromConditionalType(target)]; - inferToMultipleTypes(source, targetTypes, target.flags); - priority = savePriority; - } - else if (target.flags & TypeFlags.UnionOrIntersection) { - inferToMultipleTypes(source, (target).types, target.flags); - } - else if (source.flags & TypeFlags.Union) { - // Source is a union or intersection type, infer from each constituent type - const sourceTypes = (source).types; - for (const sourceType of sourceTypes) { - inferFromTypes(sourceType, target); - } - } - else { - if (!(priority & InferencePriority.NoConstraints && source.flags & (TypeFlags.Intersection | TypeFlags.Instantiable))) { - const apparentSource = getApparentType(source); - // getApparentType can return _any_ type, since an indexed access or conditional may simplify to any other type. - // If that occurs and it doesn't simplify to an object or intersection, we'll need to restart `inferFromTypes` - // with the simplified source. - if (apparentSource !== source && allowComplexConstraintInference && !(apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection))) { - // TODO: The `allowComplexConstraintInference` flag is a hack! This forbids inference from complex constraints within constraints! - // This isn't required algorithmically, but rather is used to lower the memory burden caused by performing inference - // that is _too good_ in projects with complicated constraints (eg, fp-ts). In such cases, if we did not limit ourselves - // here, we might produce more valid inferences for types, causing us to do more checks and perform more instantiations - // (in addition to the extra stack depth here) which, in turn, can push the already close process over its limit. - // TL;DR: If we ever become generally more memory efficient (or our resource budget ever increases), we should just - // remove this `allowComplexConstraintInference` flag. - allowComplexConstraintInference = false; - return inferFromTypes(apparentSource, target); - } - source = apparentSource; - } - if (source.flags & (TypeFlags.Object | TypeFlags.Intersection)) { - invokeOnce(source, target, inferFromObjectTypes); - } - } - } - - function inferWithPriority(source: Type, target: Type, newPriority: InferencePriority) { - const savePriority = priority; - priority |= newPriority; - inferFromTypes(source, target); - priority = savePriority; - } - - function invokeOnce(source: Type, target: Type, action: (source: Type, target: Type) => void) { - const key = source.id + "," + target.id; - const status = visited && visited.get(key); - if (status !== undefined) { - inferencePriority = Math.min(inferencePriority, status); - return; - } - (visited || (visited = createMap())).set(key, InferencePriority.Circularity); - const saveInferencePriority = inferencePriority; - inferencePriority = InferencePriority.MaxValue; - action(source, target); - visited.set(key, inferencePriority); - inferencePriority = Math.min(inferencePriority, saveInferencePriority); - } - - function inferFromMatchingTypes(sources: Type[], targets: Type[], matches: (s: Type, t: Type) => boolean): [Type[], Type[]] { - let matchedSources: Type[] | undefined; - let matchedTargets: Type[] | undefined; - for (const t of targets) { - for (const s of sources) { - if (matches(s, t)) { - inferFromTypes(s, t); - matchedSources = appendIfUnique(matchedSources, s); - matchedTargets = appendIfUnique(matchedTargets, t); - } - } - } - return [ - matchedSources ? filter(sources, t => !contains(matchedSources, t)) : sources, - matchedTargets ? filter(targets, t => !contains(matchedTargets, t)) : targets, - ]; - } - - function inferFromTypeArguments(sourceTypes: readonly Type[], targetTypes: readonly Type[], variances: readonly VarianceFlags[]) { - const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length; - for (let i = 0; i < count; i++) { - if (i < variances.length && (variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Contravariant) { - inferFromContravariantTypes(sourceTypes[i], targetTypes[i]); - } - else { - inferFromTypes(sourceTypes[i], targetTypes[i]); - } - } - } - - function inferFromContravariantTypes(source: Type, target: Type) { - if (strictFunctionTypes || priority & InferencePriority.AlwaysStrict) { - contravariant = !contravariant; - inferFromTypes(source, target); - contravariant = !contravariant; - } - else { - inferFromTypes(source, target); - } - } - - function getInferenceInfoForType(type: Type) { - if (type.flags & TypeFlags.TypeVariable) { - for (const inference of inferences) { - if (type === inference.typeParameter) { - return inference; - } - } - } - return undefined; - } - - function getSingleTypeVariableFromIntersectionTypes(types: Type[]) { - let typeVariable: Type | undefined; - for (const type of types) { - const t = type.flags & TypeFlags.Intersection && find((type).types, t => !!getInferenceInfoForType(t)); - if (!t || typeVariable && t !== typeVariable) { - return undefined; - } - typeVariable = t; - } - return typeVariable; - } - - function inferToMultipleTypes(source: Type, targets: Type[], targetFlags: TypeFlags) { - let typeVariableCount = 0; - if (targetFlags & TypeFlags.Union) { - let nakedTypeVariable: Type | undefined; - const sources = source.flags & TypeFlags.Union ? (source).types : [source]; - const matched = new Array(sources.length); - let inferenceCircularity = false; - // First infer to types that are not naked type variables. For each source type we - // track whether inferences were made from that particular type to some target with - // equal priority (i.e. of equal quality) to what we would infer for a naked type - // parameter. - for (const t of targets) { - if (getInferenceInfoForType(t)) { - nakedTypeVariable = t; - typeVariableCount++; - } - else { - for (let i = 0; i < sources.length; i++) { - const saveInferencePriority = inferencePriority; - inferencePriority = InferencePriority.MaxValue; - inferFromTypes(sources[i], t); - if (inferencePriority === priority) matched[i] = true; - inferenceCircularity = inferenceCircularity || inferencePriority === InferencePriority.Circularity; - inferencePriority = Math.min(inferencePriority, saveInferencePriority); - } - } - } - if (typeVariableCount === 0) { - // If every target is an intersection of types containing a single naked type variable, - // make a lower priority inference to that type variable. This handles inferring from - // 'A | B' to 'T & (X | Y)' where we want to infer 'A | B' for T. - const intersectionTypeVariable = getSingleTypeVariableFromIntersectionTypes(targets); - if (intersectionTypeVariable) { - inferWithPriority(source, intersectionTypeVariable, InferencePriority.NakedTypeVariable); - } - return; - } - // If the target has a single naked type variable and no inference circularities were - // encountered above (meaning we explored the types fully), create a union of the source - // types from which no inferences have been made so far and infer from that union to the - // naked type variable. - if (typeVariableCount === 1 && !inferenceCircularity) { - const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s); - if (unmatched.length) { - inferFromTypes(getUnionType(unmatched), nakedTypeVariable!); - return; - } - } - } - else { - // We infer from types that are not naked type variables first so that inferences we - // make from nested naked type variables and given slightly higher priority by virtue - // of being first in the candidates array. - for (const t of targets) { - if (getInferenceInfoForType(t)) { - typeVariableCount++; - } - else { - inferFromTypes(source, t); - } - } - } - // Inferences directly to naked type variables are given lower priority as they are - // less specific. For example, when inferring from Promise to T | Promise, - // we want to infer string for T, not Promise | string. For intersection types - // we only infer to single naked type variables. - if (targetFlags & TypeFlags.Intersection ? typeVariableCount === 1 : typeVariableCount > 0) { - for (const t of targets) { - if (getInferenceInfoForType(t)) { - inferWithPriority(source, t, InferencePriority.NakedTypeVariable); - } - } - } - } - - function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean { - if (constraintType.flags & TypeFlags.Union) { - let result = false; - for (const type of (constraintType as UnionType).types) { - result = inferToMappedType(source, target, type) || result; - } - return result; - } - if (constraintType.flags & TypeFlags.Index) { - // We're inferring from some source type S to a homomorphic mapped type { [P in keyof T]: X }, - // where T is a type variable. Use inferTypeForHomomorphicMappedType to infer a suitable source - // type and then make a secondary inference from that type to T. We make a secondary inference - // such that direct inferences to T get priority over inferences to Partial, for example. - const inference = getInferenceInfoForType((constraintType).type); - if (inference && !inference.isFixed) { - const inferredType = inferTypeForHomomorphicMappedType(source, target, constraintType); - if (inferredType) { - // We assign a lower priority to inferences made from types containing non-inferrable - // types because we may only have a partial result (i.e. we may have failed to make - // reverse inferences for some properties). - inferWithPriority(inferredType, inference.typeParameter, - getObjectFlags(source) & ObjectFlags.NonInferrableType ? - InferencePriority.PartialHomomorphicMappedType : - InferencePriority.HomomorphicMappedType); - } - } - return true; - } - if (constraintType.flags & TypeFlags.TypeParameter) { - // We're inferring from some source type S to a mapped type { [P in K]: X }, where K is a type - // parameter. First infer from 'keyof S' to K. - inferWithPriority(getIndexType(source), constraintType, InferencePriority.MappedTypeConstraint); - // If K is constrained to a type C, also infer to C. Thus, for a mapped type { [P in K]: X }, - // where K extends keyof T, we make the same inferences as for a homomorphic mapped type - // { [P in keyof T]: X }. This enables us to make meaningful inferences when the target is a - // Pick. - const extendedConstraint = getConstraintOfType(constraintType); - if (extendedConstraint && inferToMappedType(source, target, extendedConstraint)) { - return true; - } - // If no inferences can be made to K's constraint, infer from a union of the property types - // in the source to the template type X. - const propTypes = map(getPropertiesOfType(source), getTypeOfSymbol); - const stringIndexType = getIndexTypeOfType(source, IndexKind.String); - const numberIndexInfo = getNonEnumNumberIndexInfo(source); - const numberIndexType = numberIndexInfo && numberIndexInfo.type; - inferFromTypes(getUnionType(append(append(propTypes, stringIndexType), numberIndexType)), getTemplateTypeFromMappedType(target)); - return true; - } - return false; - } - - function inferFromObjectTypes(source: Type, target: Type) { - // If we are already processing another target type with the same associated symbol (such as - // an instantiation of the same generic type), we do not explore this target as it would yield - // no further inferences. We exclude the static side of classes from this check since it shares - // its symbol with the instance side which would lead to false positives. - const isNonConstructorObject = target.flags & TypeFlags.Object && - !(getObjectFlags(target) & ObjectFlags.Anonymous && target.symbol && target.symbol.flags & SymbolFlags.Class); - const symbol = isNonConstructorObject ? target.symbol : undefined; - if (symbol) { - if (contains(symbolStack, symbol)) { - inferencePriority = InferencePriority.Circularity; - return; - } - (symbolStack || (symbolStack = [])).push(symbol); - inferFromObjectTypesWorker(source, target); - symbolStack.pop(); - } - else { - inferFromObjectTypesWorker(source, target); - } - } - - function inferFromObjectTypesWorker(source: Type, target: Type) { - if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && ( - (source).target === (target).target || isArrayType(source) && isArrayType(target))) { - // If source and target are references to the same generic type, infer from type arguments - inferFromTypeArguments(getTypeArguments(source), getTypeArguments(target), getVariances((source).target)); - return; - } - if (isGenericMappedType(source) && isGenericMappedType(target)) { - // The source and target types are generic types { [P in S]: X } and { [P in T]: Y }, so we infer - // from S to T and from X to Y. - inferFromTypes(getConstraintTypeFromMappedType(source), getConstraintTypeFromMappedType(target)); - inferFromTypes(getTemplateTypeFromMappedType(source), getTemplateTypeFromMappedType(target)); - } - if (getObjectFlags(target) & ObjectFlags.Mapped) { - const constraintType = getConstraintTypeFromMappedType(target); - if (inferToMappedType(source, target, constraintType)) { - return; - } - } - // Infer from the members of source and target only if the two types are possibly related - if (!typesDefinitelyUnrelated(source, target)) { - inferFromProperties(source, target); - inferFromSignatures(source, target, SignatureKind.Call); - inferFromSignatures(source, target, SignatureKind.Construct); - inferFromIndexTypes(source, target); - } - } - - function inferFromProperties(source: Type, target: Type) { - if (isArrayType(source) || isTupleType(source)) { - if (isTupleType(target)) { - const sourceLength = isTupleType(source) ? getLengthOfTupleType(source) : 0; - const targetLength = getLengthOfTupleType(target); - const sourceRestType = isTupleType(source) ? getRestTypeOfTupleType(source) : getElementTypeOfArrayType(source); - const targetRestType = getRestTypeOfTupleType(target); - const fixedLength = targetLength < sourceLength || sourceRestType ? targetLength : sourceLength; - for (let i = 0; i < fixedLength; i++) { - inferFromTypes(i < sourceLength ? getTypeArguments(source)[i] : sourceRestType!, getTypeArguments(target)[i]); - } - if (targetRestType) { - const types = fixedLength < sourceLength ? getTypeArguments(source).slice(fixedLength, sourceLength) : []; - if (sourceRestType) { - types.push(sourceRestType); - } - if (types.length) { - inferFromTypes(getUnionType(types), targetRestType); - } - } - return; - } - if (isArrayType(target)) { - inferFromIndexTypes(source, target); - return; - } - } - const properties = getPropertiesOfObjectType(target); - for (const targetProp of properties) { - const sourceProp = getPropertyOfType(source, targetProp.escapedName); - if (sourceProp) { - inferFromTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp)); - } - } - } - - function inferFromSignatures(source: Type, target: Type, kind: SignatureKind) { - const sourceSignatures = getSignaturesOfType(source, kind); - const targetSignatures = getSignaturesOfType(target, kind); - const sourceLen = sourceSignatures.length; - const targetLen = targetSignatures.length; - const len = sourceLen < targetLen ? sourceLen : targetLen; - const skipParameters = !!(getObjectFlags(source) & ObjectFlags.NonInferrableType); - for (let i = 0; i < len; i++) { - inferFromSignature(getBaseSignature(sourceSignatures[sourceLen - len + i]), getBaseSignature(targetSignatures[targetLen - len + i]), skipParameters); + else if (localName === InternalSymbolName.ExportEquals) { + localName = "_exports"; } + localName = isIdentifierText(localName, languageVersion) && !isStringANonContextualKeyword(localName) ? localName : "_" + localName.replace(/[^a-zA-Z0-9]/g, "_"); + return localName; } - - function inferFromSignature(source: Signature, target: Signature, skipParameters: boolean) { - if (!skipParameters) { - const saveBivariant = bivariant; - const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown; - // Once we descend into a bivariant signature we remain bivariant for all nested inferences - bivariant = bivariant || kind === SyntaxKind.MethodDeclaration || kind === SyntaxKind.MethodSignature || kind === SyntaxKind.Constructor; - applyToParameterTypes(source, target, inferFromContravariantTypes); - bivariant = saveBivariant; + function getInternalSymbolName(symbol: ts.Symbol, localName: string) { + if (context.remappedSymbolNames!.has("" + getSymbolId(symbol))) { + return context.remappedSymbolNames!.get("" + getSymbolId(symbol))!; } - applyToReturnTypes(source, target, inferFromTypes); + localName = getNameCandidateWorker(symbol, localName); + // The result of this is going to be used as the symbol's name - lock it in, so `getUnusedName` will also pick it up + context.remappedSymbolNames!.set("" + getSymbolId(symbol), localName); + return localName; } - - function inferFromIndexTypes(source: Type, target: Type) { - const targetStringIndexType = getIndexTypeOfType(target, IndexKind.String); - if (targetStringIndexType) { - const sourceIndexType = getIndexTypeOfType(source, IndexKind.String) || - getImplicitIndexTypeOfType(source, IndexKind.String); - if (sourceIndexType) { - inferFromTypes(sourceIndexType, targetStringIndexType); - } - } - const targetNumberIndexType = getIndexTypeOfType(target, IndexKind.Number); - if (targetNumberIndexType) { - const sourceIndexType = getIndexTypeOfType(source, IndexKind.Number) || - getIndexTypeOfType(source, IndexKind.String) || - getImplicitIndexTypeOfType(source, IndexKind.Number); - if (sourceIndexType) { - inferFromTypes(sourceIndexType, targetNumberIndexType); + } + } + function typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer?: EmitTextWriter): string { + return writer ? typePredicateToStringWorker(writer).getText() : usingSingleLineStringWriter(typePredicateToStringWorker); + function typePredicateToStringWorker(writer: EmitTextWriter) { + const predicate = createTypePredicateNodeWithModifier(typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? createToken(SyntaxKind.AssertsKeyword) : undefined, typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? createIdentifier(typePredicate.parameterName) : createThisTypeNode(), typePredicate.type && (nodeBuilder.typeToTypeNode(typePredicate.type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName)!) // TODO: GH#18217 + ); + const printer = createPrinter({ removeComments: true }); + const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); + printer.writeNode(EmitHint.Unspecified, predicate, /*sourceFile*/ sourceFile, writer); + return writer; + } + } + function formatUnionTypes(types: readonly ts.Type[]): ts.Type[] { + const result: ts.Type[] = []; + let flags: TypeFlags = 0; + for (let i = 0; i < types.length; i++) { + const t = types[i]; + flags |= t.flags; + if (!(t.flags & TypeFlags.Nullable)) { + if (t.flags & (TypeFlags.BooleanLiteral | TypeFlags.EnumLiteral)) { + const baseType = t.flags & TypeFlags.BooleanLiteral ? booleanType : getBaseTypeOfEnumLiteralType((t)); + if (baseType.flags & TypeFlags.Union) { + const count = (baseType).types.length; + if (i + count <= types.length && getRegularTypeOfLiteralType(types[i + count - 1]) === getRegularTypeOfLiteralType((baseType).types[count - 1])) { + result.push(baseType); + i += count - 1; + continue; + } } } + result.push(t); } } - - function isTypeOrBaseIdenticalTo(s: Type, t: Type) { - return isTypeIdenticalTo(s, t) || !!(s.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) && isTypeIdenticalTo(getBaseTypeOfLiteralType(s), t); - } - - function isTypeCloselyMatchedBy(s: Type, t: Type) { - return !!(s.flags & TypeFlags.Object && t.flags & TypeFlags.Object && s.symbol && s.symbol === t.symbol || - s.aliasSymbol && s.aliasTypeArguments && s.aliasSymbol === t.aliasSymbol); - } - - function hasPrimitiveConstraint(type: TypeParameter): boolean { - const constraint = getConstraintOfTypeParameter(type); - return !!constraint && maybeTypeOfKind(constraint.flags & TypeFlags.Conditional ? getDefaultConstraintOfConditionalType(constraint as ConditionalType) : constraint, TypeFlags.Primitive | TypeFlags.Index); + if (flags & TypeFlags.Null) + result.push(nullType); + if (flags & TypeFlags.Undefined) + result.push(undefinedType); + return result || types; + } + function visibilityToString(flags: ModifierFlags): string | undefined { + if (flags === ModifierFlags.Private) { + return "private"; } - - function isObjectLiteralType(type: Type) { - return !!(getObjectFlags(type) & ObjectFlags.ObjectLiteral); + if (flags === ModifierFlags.Protected) { + return "protected"; } - - function isObjectOrArrayLiteralType(type: Type) { - return !!(getObjectFlags(type) & (ObjectFlags.ObjectLiteral | ObjectFlags.ArrayLiteral)); + return "public"; + } + function getTypeAliasForTypeLiteral(type: ts.Type): ts.Symbol | undefined { + if (type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral) { + const node = (findAncestor(type.symbol.declarations[0].parent, n => n.kind !== SyntaxKind.ParenthesizedType)!); + if (node.kind === SyntaxKind.TypeAliasDeclaration) { + return getSymbolOfNode(node); + } } - - function unionObjectAndArrayLiteralCandidates(candidates: Type[]): Type[] { - if (candidates.length > 1) { - const objectLiterals = filter(candidates, isObjectOrArrayLiteralType); - if (objectLiterals.length) { - const literalsType = getUnionType(objectLiterals, UnionReduction.Subtype); - return concatenate(filter(candidates, t => !isObjectOrArrayLiteralType(t)), [literalsType]); + return undefined; + } + function isTopLevelInExternalModuleAugmentation(node: Node): boolean { + return node && node.parent && + node.parent.kind === SyntaxKind.ModuleBlock && + isExternalModuleAugmentation(node.parent.parent); + } + interface NodeBuilderContext { + enclosingDeclaration: Node | undefined; + flags: NodeBuilderFlags; + tracker: SymbolTracker; + // State + encounteredError: boolean; + visitedTypes: ts.Map | undefined; + symbolDepth: ts.Map | undefined; + inferTypeParameters: TypeParameter[] | undefined; + approximateLength: number; + truncating?: boolean; + typeParameterSymbolList?: ts.Map; + typeParameterNames?: ts.Map; + typeParameterNamesByText?: ts.Map; + usedSymbolNames?: ts.Map; + remappedSymbolNames?: ts.Map; + } + function isDefaultBindingContext(location: Node) { + return location.kind === SyntaxKind.SourceFile || isAmbientModule(location); + } + function getNameOfSymbolFromNameType(symbol: ts.Symbol, context?: NodeBuilderContext) { + const nameType = symbol.nameType; + if (nameType) { + if (nameType.flags & TypeFlags.StringOrNumberLiteral) { + const name = "" + (nameType).value; + if (!isIdentifierText(name, compilerOptions.target) && !isNumericLiteralName(name)) { + return `"${escapeString(name, CharacterCodes.doubleQuote)}"`; + } + if (isNumericLiteralName(name) && startsWith(name, "-")) { + return `[${name}]`; } + return name; + } + if (nameType.flags & TypeFlags.UniqueESSymbol) { + return `[${getNameOfSymbolAsWritten((nameType).symbol, context)}]`; } - return candidates; } - - function getContravariantInference(inference: InferenceInfo) { - return inference.priority! & InferencePriority.PriorityImpliesCombination ? getIntersectionType(inference.contraCandidates!) : getCommonSubtype(inference.contraCandidates!); + } + /** + * Gets a human-readable name for a symbol. + * Should *not* be used for the right-hand side of a `.` -- use `symbolName(symbol)` for that instead. + * + * Unlike `symbolName(symbol)`, this will include quotes if the name is from a string literal. + * It will also use a representation of a number as written instead of a decimal form, e.g. `0o11` instead of `9`. + */ + function getNameOfSymbolAsWritten(symbol: ts.Symbol, context?: NodeBuilderContext): string { + if (context && symbol.escapedName === InternalSymbolName.Default && !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope) && + // If it's not the first part of an entity name, it must print as `default` + (!(context.flags & NodeBuilderFlags.InInitialEntityName) || + // if the symbol is synthesized, it will only be referenced externally it must print as `default` + !symbol.declarations || + // if not in the same binding context (source file, module declaration), it must print as `default` + (context.enclosingDeclaration && findAncestor(symbol.declarations[0], isDefaultBindingContext) !== findAncestor(context.enclosingDeclaration, isDefaultBindingContext)))) { + return "default"; + } + if (symbol.declarations && symbol.declarations.length) { + let declaration = firstDefined(symbol.declarations, d => getNameOfDeclaration(d) ? d : undefined); // Try using a declaration with a name, first + const name = declaration && getNameOfDeclaration(declaration); + if (declaration && name) { + if (isCallExpression(declaration) && isBindableObjectDefinePropertyCall(declaration)) { + return symbolName(symbol); + } + if (isComputedPropertyName(name) && !(getCheckFlags(symbol) & CheckFlags.Late) && symbol.nameType && symbol.nameType.flags & TypeFlags.StringOrNumberLiteral) { + // Computed property name isn't late bound, but has a well-known name type - use name type to generate a symbol name + const result = getNameOfSymbolFromNameType(symbol, context); + if (result !== undefined) { + return result; + } + } + return declarationNameToString(name); + } + if (!declaration) { + declaration = symbol.declarations[0]; // Declaration may be nameless, but we'll try anyway + } + if (declaration.parent && declaration.parent.kind === SyntaxKind.VariableDeclaration) { + return declarationNameToString((declaration.parent).name); + } + switch (declaration.kind) { + case SyntaxKind.ClassExpression: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + if (context && !context.encounteredError && !(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier)) { + context.encounteredError = true; + } + return declaration.kind === SyntaxKind.ClassExpression ? "(Anonymous class)" : "(Anonymous function)"; + } } - - function getCovariantInference(inference: InferenceInfo, signature: Signature) { - // Extract all object and array literal types and replace them with a single widened and normalized type. - const candidates = unionObjectAndArrayLiteralCandidates(inference.candidates!); - // We widen inferred literal types if - // all inferences were made to top-level occurrences of the type parameter, and - // the type parameter has no constraint or its constraint includes no primitive or literal types, and - // the type parameter was fixed during inference or does not occur at top-level in the return type. - const primitiveConstraint = hasPrimitiveConstraint(inference.typeParameter); - const widenLiteralTypes = !primitiveConstraint && inference.topLevel && - (inference.isFixed || !isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), inference.typeParameter)); - const baseCandidates = primitiveConstraint ? sameMap(candidates, getRegularTypeOfLiteralType) : - widenLiteralTypes ? sameMap(candidates, getWidenedLiteralType) : - candidates; - // If all inferences were made from a position that implies a combined result, infer a union type. - // Otherwise, infer a common supertype. - const unwidenedType = inference.priority! & InferencePriority.PriorityImpliesCombination ? - getUnionType(baseCandidates, UnionReduction.Subtype) : - getCommonSupertype(baseCandidates); - return getWidenedType(unwidenedType); + const name = getNameOfSymbolFromNameType(symbol, context); + return name !== undefined ? name : symbolName(symbol); + } + function isDeclarationVisible(node: Node): boolean { + if (node) { + const links = getNodeLinks(node); + if (links.isVisible === undefined) { + links.isVisible = !!determineIfDeclarationIsVisible(); + } + return links.isVisible; } - - function getInferredType(context: InferenceContext, index: number): Type { - const inference = context.inferences[index]; - if (!inference.inferredType) { - let inferredType: Type | undefined; - const signature = context.signature; - if (signature) { - const inferredCovariantType = inference.candidates ? getCovariantInference(inference, signature) : undefined; - if (inference.contraCandidates) { - const inferredContravariantType = getContravariantInference(inference); - // If we have both co- and contra-variant inferences, we prefer the contra-variant inference - // unless the co-variant inference is a subtype and not 'never'. - inferredType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) && - isTypeSubtypeOf(inferredCovariantType, inferredContravariantType) ? - inferredCovariantType : inferredContravariantType; - } - else if (inferredCovariantType) { - inferredType = inferredCovariantType; - } - else if (context.flags & InferenceFlags.NoDefault) { - // We use silentNeverType as the wildcard that signals no inferences. - inferredType = silentNeverType; + return false; + function determineIfDeclarationIsVisible() { + switch (node.kind) { + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocEnumTag: + // Top-level jsdoc type aliases are considered exported + // First parent is comment node, second is hosting declaration or token; we only care about those tokens or declarations whose parent is a source file + return !!(node.parent && node.parent.parent && node.parent.parent.parent && isSourceFile(node.parent.parent.parent)); + case SyntaxKind.BindingElement: + return isDeclarationVisible(node.parent.parent); + case SyntaxKind.VariableDeclaration: + if (isBindingPattern((node as VariableDeclaration).name) && + !((node as VariableDeclaration).name as BindingPattern).elements.length) { + // If the binding pattern is empty, this variable declaration is not visible + return false; } - else { - // Infer either the default or the empty object type when no inferences were - // made. It is important to remember that in this case, inference still - // succeeds, meaning there is no error for not having inference candidates. An - // inference error only occurs when there are *conflicting* candidates, i.e. - // candidates with no common supertype. - const defaultType = getDefaultFromTypeParameter(inference.typeParameter); - if (defaultType) { - // Instantiate the default type. Any forward reference to a type - // parameter should be instantiated to the empty object type. - inferredType = instantiateType(defaultType, combineTypeMappers(createBackreferenceMapper(context, index), context.nonFixingMapper)); - } + // falls through + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + // external module augmentation is always visible + if (isExternalModuleAugmentation(node)) { + return true; + } + const parent = getDeclarationContainer(node); + // If the node is not exported or it is not ambient module element (except import declaration) + if (!(getCombinedModifierFlags((node as Declaration)) & ModifierFlags.Export) && + !(node.kind !== SyntaxKind.ImportEqualsDeclaration && parent.kind !== SyntaxKind.SourceFile && parent.flags & NodeFlags.Ambient)) { + return isGlobalSourceFile(parent); + } + // Exported members/ambient module elements (exception import declaration) are visible if parent is visible + return isDeclarationVisible(parent); + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + if (hasModifier(node, ModifierFlags.Private | ModifierFlags.Protected)) { + // Private/protected properties/methods are not visible + return false; } + // Public properties/methods are visible if its parents are visible, so: + // falls through + case SyntaxKind.Constructor: + case SyntaxKind.ConstructSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.Parameter: + case SyntaxKind.ModuleBlock: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.TypeLiteral: + case SyntaxKind.TypeReference: + case SyntaxKind.ArrayType: + case SyntaxKind.TupleType: + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + case SyntaxKind.ParenthesizedType: + return isDeclarationVisible(node.parent); + // Default binding, import specifier and namespace import is visible + // only on demand so by default it is not visible + case SyntaxKind.ImportClause: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportSpecifier: + return false; + // Type parameters are always visible + case SyntaxKind.TypeParameter: + // Source file and namespace export are always visible + // falls through + case SyntaxKind.SourceFile: + case SyntaxKind.NamespaceExportDeclaration: + return true; + // Export assignments do not create name bindings outside the module + case SyntaxKind.ExportAssignment: + return false; + default: + return false; + } + } + } + function collectLinkedAliases(node: Identifier, setVisibility?: boolean): Node[] | undefined { + let exportSymbol: ts.Symbol | undefined; + if (node.parent && node.parent.kind === SyntaxKind.ExportAssignment) { + exportSymbol = resolveName(node, node.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, node, /*isUse*/ false); + } + else if (node.parent.kind === SyntaxKind.ExportSpecifier) { + exportSymbol = getTargetOfExportSpecifier((node.parent), SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); + } + let result: Node[] | undefined; + let visited: ts.Map | undefined; + if (exportSymbol) { + visited = createMap(); + visited.set("" + getSymbolId(exportSymbol), true); + buildVisibleNodeList(exportSymbol.declarations); + } + return result; + function buildVisibleNodeList(declarations: Declaration[]) { + forEach(declarations, declaration => { + const resultNode = getAnyImportSyntax(declaration) || declaration; + if (setVisibility) { + getNodeLinks(declaration).isVisible = true; } else { - inferredType = getTypeFromInference(inference); + result = result || []; + pushIfUnique(result, resultNode); } - - inference.inferredType = inferredType || getDefaultTypeArgumentType(!!(context.flags & InferenceFlags.AnyDefault)); - - const constraint = getConstraintOfTypeParameter(inference.typeParameter); - if (constraint) { - const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper); - if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) { - inference.inferredType = inferredType = instantiatedConstraint; + if (isInternalModuleImportEqualsDeclaration(declaration)) { + // Add the referenced top container visible + const internalModuleReference = (declaration.moduleReference); + const firstIdentifier = getFirstIdentifier(internalModuleReference); + const importSymbol = resolveName(declaration, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, undefined, undefined, /*isUse*/ false); + const id = importSymbol && "" + getSymbolId(importSymbol); + if (importSymbol && !visited!.has(id!)) { + visited!.set(id!, true); + buildVisibleNodeList(importSymbol.declarations); } } - } - - return inference.inferredType; + }); } - - function getDefaultTypeArgumentType(isInJavaScriptFile: boolean): Type { - return isInJavaScriptFile ? anyType : unknownType; + } + /** + * Push an entry on the type resolution stack. If an entry with the given target and the given property name + * is already on the stack, and no entries in between already have a type, then a circularity has occurred. + * In this case, the result values of the existing entry and all entries pushed after it are changed to false, + * and the value false is returned. Otherwise, the new entry is just pushed onto the stack, and true is returned. + * In order to see if the same query has already been done before, the target object and the propertyName both + * must match the one passed in. + * + * @param target The symbol, type, or signature whose type is being queried + * @param propertyName The property name that should be used to query the target for its type + */ + function pushTypeResolution(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean { + const resolutionCycleStartIndex = findResolutionCycleStartIndex(target, propertyName); + if (resolutionCycleStartIndex >= 0) { + // A cycle was found + const { length } = resolutionTargets; + for (let i = resolutionCycleStartIndex; i < length; i++) { + resolutionResults[i] = false; + } + return false; } - - function getInferredTypes(context: InferenceContext): Type[] { - const result: Type[] = []; - for (let i = 0; i < context.inferences.length; i++) { - result.push(getInferredType(context, i)); + resolutionTargets.push(target); + resolutionResults.push(/*items*/ true); + resolutionPropertyNames.push(propertyName); + return true; + } + function findResolutionCycleStartIndex(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): number { + for (let i = resolutionTargets.length - 1; i >= 0; i--) { + if (hasType(resolutionTargets[i], resolutionPropertyNames[i])) { + return -1; + } + if (resolutionTargets[i] === target && resolutionPropertyNames[i] === propertyName) { + return i; } - return result; } - - // EXPRESSION TYPE CHECKING - - function getCannotFindNameDiagnosticForName(node: Identifier): DiagnosticMessage { - switch (node.escapedText) { - case "document": - case "console": - return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_include_dom; - case "$": - return compilerOptions.types - ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_types_Slashjquery_and_then_add_jquery_to_the_types_field_in_your_tsconfig - : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_types_Slashjquery; - case "describe": - case "suite": - case "it": - case "test": - return compilerOptions.types - ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_a_test_runner_Try_npm_i_types_Slashjest_or_npm_i_types_Slashmocha_and_then_add_jest_or_mocha_to_the_types_field_in_your_tsconfig - : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_a_test_runner_Try_npm_i_types_Slashjest_or_npm_i_types_Slashmocha; - case "process": - case "require": - case "Buffer": - case "module": - return compilerOptions.types - ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_types_Slashnode_and_then_add_node_to_the_types_field_in_your_tsconfig - : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_types_Slashnode; - case "Map": - case "Set": - case "Promise": - case "Symbol": - case "WeakMap": - case "WeakSet": - case "Iterator": - case "AsyncIterator": - return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_es2015_or_later; + return -1; + } + function hasType(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean { + switch (propertyName) { + case TypeSystemPropertyName.Type: + return !!getSymbolLinks((target)).type; + case TypeSystemPropertyName.EnumTagType: + return !!(getNodeLinks((target as JSDocEnumTag)).resolvedEnumType); + case TypeSystemPropertyName.DeclaredType: + return !!getSymbolLinks((target)).declaredType; + case TypeSystemPropertyName.ResolvedBaseConstructorType: + return !!(target).resolvedBaseConstructorType; + case TypeSystemPropertyName.ResolvedReturnType: + return !!(target).resolvedReturnType; + case TypeSystemPropertyName.ImmediateBaseConstraint: + return !!(target).immediateBaseConstraint; + case TypeSystemPropertyName.JSDocTypeReference: + return !!getSymbolLinks((target as ts.Symbol)).resolvedJSDocType; + case TypeSystemPropertyName.ResolvedTypeArguments: + return !!(target as TypeReference).resolvedTypeArguments; + } + return Debug.assertNever(propertyName); + } + /** + * Pop an entry from the type resolution stack and return its associated result value. The result value will + * be true if no circularities were detected, or false if a circularity was found. + */ + function popTypeResolution(): boolean { + resolutionTargets.pop(); + resolutionPropertyNames.pop(); + return resolutionResults.pop()!; + } + function getDeclarationContainer(node: Node): Node { + return findAncestor(getRootDeclaration(node), node => { + switch (node.kind) { + case SyntaxKind.VariableDeclaration: + case SyntaxKind.VariableDeclarationList: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.NamedImports: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportClause: + return false; default: - if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { - return Diagnostics.No_value_exists_in_scope_for_the_shorthand_property_0_Either_declare_one_or_provide_an_initializer; - } - else { - return Diagnostics.Cannot_find_name_0; - } + return true; } + })!.parent; + } + function getTypeOfPrototypeProperty(prototype: ts.Symbol): ts.Type { + // TypeScript 1.0 spec (April 2014): 8.4 + // Every class automatically contains a static property member named 'prototype', + // the type of which is an instantiation of the class type with type Any supplied as a type argument for each type parameter. + // It is an error to explicitly declare a static property member with the name 'prototype'. + const classType = (getDeclaredTypeOfSymbol(getParentOfSymbol(prototype)!)); + return classType.typeParameters ? createTypeReference((classType), map(classType.typeParameters, _ => anyType)) : classType; + } + // Return the type of the given property in the given type, or undefined if no such property exists + function getTypeOfPropertyOfType(type: ts.Type, name: __String): ts.Type | undefined { + const prop = getPropertyOfType(type, name); + return prop ? getTypeOfSymbol(prop) : undefined; + } + function getTypeOfPropertyOrIndexSignature(type: ts.Type, name: __String): ts.Type { + return getTypeOfPropertyOfType(type, name) || isNumericLiteralName(name) && getIndexTypeOfType(type, IndexKind.Number) || getIndexTypeOfType(type, IndexKind.String) || unknownType; + } + function isTypeAny(type: ts.Type | undefined) { + return type && (type.flags & TypeFlags.Any) !== 0; + } + // Return the type of a binding element parent. We check SymbolLinks first to see if a type has been + // assigned by contextual typing. + function getTypeForBindingElementParent(node: BindingElementGrandparent) { + const symbol = getSymbolOfNode(node); + return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false); + } + function isComputedNonLiteralName(name: PropertyName): boolean { + return name.kind === SyntaxKind.ComputedPropertyName && !isStringOrNumericLiteralLike(name.expression); + } + function getRestType(source: ts.Type, properties: PropertyName[], symbol: ts.Symbol | undefined): ts.Type { + source = filterType(source, t => !(t.flags & TypeFlags.Nullable)); + if (source.flags & TypeFlags.Never) { + return emptyObjectType; } - - function getResolvedSymbol(node: Identifier): Symbol { - const links = getNodeLinks(node); - if (!links.resolvedSymbol) { - links.resolvedSymbol = !nodeIsMissing(node) && - resolveName( - node, - node.escapedText, - SymbolFlags.Value | SymbolFlags.ExportValue, - getCannotFindNameDiagnosticForName(node), - node, - !isWriteOnlyAccess(node), - /*excludeGlobals*/ false, - Diagnostics.Cannot_find_name_0_Did_you_mean_1) || unknownSymbol; - } - return links.resolvedSymbol; + if (source.flags & TypeFlags.Union) { + return mapType(source, t => getRestType(t, properties, symbol)); } - - function isInTypeQuery(node: Node): boolean { - // TypeScript 1.0 spec (April 2014): 3.6.3 - // A type query consists of the keyword typeof followed by an expression. - // The expression is restricted to a single identifier or a sequence of identifiers separated by periods - return !!findAncestor( - node, - n => n.kind === SyntaxKind.TypeQuery ? true : n.kind === SyntaxKind.Identifier || n.kind === SyntaxKind.QualifiedName ? false : "quit"); + const omitKeyType = getUnionType(map(properties, getLiteralTypeFromPropertyName)); + if (isGenericObjectType(source) || isGenericIndexType(omitKeyType)) { + if (omitKeyType.flags & TypeFlags.Never) { + return source; + } + const omitTypeAlias = getGlobalOmitSymbol(); + if (!omitTypeAlias) { + return errorType; + } + return getTypeAliasInstantiation(omitTypeAlias, [source, omitKeyType]); } - - // Return the flow cache key for a "dotted name" (i.e. a sequence of identifiers - // separated by dots). The key consists of the id of the symbol referenced by the - // leftmost identifier followed by zero or more property names separated by dots. - // The result is undefined if the reference isn't a dotted name. We prefix nodes - // occurring in an apparent type position with '@' because the control flow type - // of such nodes may be based on the apparent type instead of the declared type. - function getFlowCacheKey(node: Node, declaredType: Type, initialType: Type, flowContainer: Node | undefined): string | undefined { - switch (node.kind) { - case SyntaxKind.Identifier: - const symbol = getResolvedSymbol(node); - return symbol !== unknownSymbol ? `${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}|${isConstraintPosition(node) ? "@" : ""}${getSymbolId(symbol)}` : undefined; - case SyntaxKind.ThisKeyword: - return "0"; - case SyntaxKind.NonNullExpression: - case SyntaxKind.ParenthesizedExpression: - return getFlowCacheKey((node).expression, declaredType, initialType, flowContainer); - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - const propName = getAccessedPropertyName(node); - if (propName !== undefined) { - const key = getFlowCacheKey((node).expression, declaredType, initialType, flowContainer); - return key && key + "." + propName; + const members = createSymbolTable(); + for (const prop of getPropertiesOfType(source)) { + if (!isTypeAssignableTo(getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique), omitKeyType) + && !(getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) + && isSpreadableProperty(prop)) { + members.set(prop.escapedName, getSpreadSymbol(prop, /*readonly*/ false)); + } + } + const stringIndexInfo = getIndexInfoOfType(source, IndexKind.String); + const numberIndexInfo = getIndexInfoOfType(source, IndexKind.Number); + const result = createAnonymousType(symbol, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); + result.objectFlags |= ObjectFlags.ObjectRestType; + return result; + } + // Determine the control flow type associated with a destructuring declaration or assignment. The following + // forms of destructuring are possible: + // let { x } = obj; // BindingElement + // let [ x ] = obj; // BindingElement + // { x } = obj; // ShorthandPropertyAssignment + // { x: v } = obj; // PropertyAssignment + // [ x ] = obj; // Expression + // We construct a synthetic element access expression corresponding to 'obj.x' such that the control + // flow analyzer doesn't have to handle all the different syntactic forms. + function getFlowTypeOfDestructuring(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression, declaredType: ts.Type) { + const reference = getSyntheticElementAccess(node); + return reference ? getFlowTypeOfReference(reference, declaredType) : declaredType; + } + function getSyntheticElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression): ElementAccessExpression | undefined { + const parentAccess = getParentElementAccess(node); + if (parentAccess && parentAccess.flowNode) { + const propName = getDestructuringPropertyName(node); + if (propName) { + const result = (createNode(SyntaxKind.ElementAccessExpression, node.pos, node.end)); + result.parent = node; + result.expression = (parentAccess); + const literal = (createNode(SyntaxKind.StringLiteral, node.pos, node.end)); + literal.parent = result; + literal.text = propName; + result.argumentExpression = literal; + result.flowNode = parentAccess.flowNode; + return result; + } + } + } + function getParentElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) { + const ancestor = node.parent.parent; + switch (ancestor.kind) { + case SyntaxKind.BindingElement: + case SyntaxKind.PropertyAssignment: + return getSyntheticElementAccess((ancestor)); + case SyntaxKind.ArrayLiteralExpression: + return getSyntheticElementAccess((node.parent)); + case SyntaxKind.VariableDeclaration: + return (ancestor).initializer; + case SyntaxKind.BinaryExpression: + return (ancestor).right; + } + } + function getDestructuringPropertyName(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) { + const parent = node.parent; + if (node.kind === SyntaxKind.BindingElement && parent.kind === SyntaxKind.ObjectBindingPattern) { + return getLiteralPropertyNameText((node).propertyName || ((node).name)); + } + if (node.kind === SyntaxKind.PropertyAssignment || node.kind === SyntaxKind.ShorthandPropertyAssignment) { + return getLiteralPropertyNameText((node).name); + } + return "" + (>(parent).elements).indexOf(node); + } + function getLiteralPropertyNameText(name: PropertyName) { + const type = getLiteralTypeFromPropertyName(name); + return type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral) ? "" + (type).value : undefined; + } + /** Return the inferred type for a binding element */ + function getTypeForBindingElement(declaration: BindingElement): ts.Type | undefined { + const pattern = declaration.parent; + let parentType = getTypeForBindingElementParent(pattern.parent); + // If no type or an any type was inferred for parent, infer that for the binding element + if (!parentType || isTypeAny(parentType)) { + return parentType; + } + // Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation + if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) { + parentType = getNonNullableType(parentType); + } + let type: ts.Type | undefined; + if (pattern.kind === SyntaxKind.ObjectBindingPattern) { + if (declaration.dotDotDotToken) { + if (parentType.flags & TypeFlags.Unknown || !isValidSpreadType(parentType)) { + error(declaration, Diagnostics.Rest_types_may_only_be_created_from_object_types); + return errorType; + } + const literalMembers: PropertyName[] = []; + for (const element of pattern.elements) { + if (!element.dotDotDotToken) { + literalMembers.push(element.propertyName || (element.name as Identifier)); } + } + type = getRestType(parentType, literalMembers, declaration.symbol); + } + else { + // Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form) + const name = declaration.propertyName || (declaration.name); + const indexType = getLiteralTypeFromPropertyName(name); + const declaredType = getConstraintForLocation(getIndexedAccessType(parentType, indexType, name), declaration.name); + type = getFlowTypeOfDestructuring(declaration, declaredType); } - return undefined; } - - function isMatchingReference(source: Node, target: Node): boolean { - switch (target.kind) { - case SyntaxKind.ParenthesizedExpression: - case SyntaxKind.NonNullExpression: - return isMatchingReference(source, (target as NonNullExpression | ParenthesizedExpression).expression); + else { + // This elementType will be used if the specific property corresponding to this index is not + // present (aka the tuple element property). This call also checks that the parentType is in + // fact an iterable or array (depending on target language). + const elementType = checkIteratedTypeOrElementType(IterationUse.Destructuring, parentType, undefinedType, pattern); + const index = pattern.elements.indexOf(declaration); + if (declaration.dotDotDotToken) { + // If the parent is a tuple type, the rest element has a tuple type of the + // remaining tuple element types. Otherwise, the rest element has an array type with same + // element type as the parent type. + type = everyType(parentType, isTupleType) ? + mapType(parentType, t => sliceTupleType((t), index)) : + createArrayType(elementType); + } + else if (isArrayLikeType(parentType)) { + const indexType = getLiteralType(index); + const accessFlags = hasDefaultValue(declaration) ? AccessFlags.NoTupleBoundsCheck : 0; + const declaredType = getConstraintForLocation(getIndexedAccessTypeOrUndefined(parentType, indexType, declaration.name, accessFlags) || errorType, declaration.name); + type = getFlowTypeOfDestructuring(declaration, declaredType); } - switch (source.kind) { - case SyntaxKind.Identifier: - return target.kind === SyntaxKind.Identifier && getResolvedSymbol(source) === getResolvedSymbol(target) || - (target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) && - getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source)) === getSymbolOfNode(target); - case SyntaxKind.ThisKeyword: - return target.kind === SyntaxKind.ThisKeyword; - case SyntaxKind.SuperKeyword: - return target.kind === SyntaxKind.SuperKeyword; - case SyntaxKind.NonNullExpression: - case SyntaxKind.ParenthesizedExpression: - return isMatchingReference((source as NonNullExpression | ParenthesizedExpression).expression, target); - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - return isAccessExpression(target) && - getAccessedPropertyName(source) === getAccessedPropertyName(target) && - isMatchingReference((source).expression, target.expression); + else { + type = elementType; } - return false; } - - function getAccessedPropertyName(access: AccessExpression): __String | undefined { - return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText : - isStringOrNumericLiteralLike(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) : - undefined; + if (!declaration.initializer) { + return type; } - - function containsMatchingReference(source: Node, target: Node) { - while (isAccessExpression(source)) { - source = source.expression; - if (isMatchingReference(source, target)) { - return true; + if (getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration))) { + // In strict null checking mode, if a default value of a non-undefined type is specified, remove + // undefined from the final type. + return strictNullChecks && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined) ? + getTypeWithFacts(type, TypeFacts.NEUndefined) : + type; + } + return getUnionType([getTypeWithFacts(type, TypeFacts.NEUndefined), checkDeclarationInitializer(declaration)], UnionReduction.Subtype); + } + function getTypeForDeclarationFromJSDocComment(declaration: Node) { + const jsdocType = getJSDocType(declaration); + if (jsdocType) { + return getTypeFromTypeNode(jsdocType); + } + return undefined; + } + function isNullOrUndefined(node: Expression) { + const expr = skipParentheses(node); + return expr.kind === SyntaxKind.NullKeyword || expr.kind === SyntaxKind.Identifier && getResolvedSymbol((expr)) === undefinedSymbol; + } + function isEmptyArrayLiteral(node: Expression) { + const expr = skipParentheses(node); + return expr.kind === SyntaxKind.ArrayLiteralExpression && (expr).elements.length === 0; + } + function addOptionality(type: ts.Type, optional = true): ts.Type { + return strictNullChecks && optional ? getOptionalType(type) : type; + } + function isParameterOfContextuallyTypedFunction(node: Declaration) { + return node.kind === SyntaxKind.Parameter && + (node.parent.kind === SyntaxKind.FunctionExpression || node.parent.kind === SyntaxKind.ArrowFunction) && + !!getContextualType((node.parent)); + } + // Return the inferred type for a variable, parameter, or property declaration + function getTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement, includeOptionality: boolean): ts.Type | undefined { + // A variable declared in a for..in statement is of type string, or of type keyof T when the + // right hand expression is of a type parameter type. + if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForInStatement) { + const indexType = getIndexType(getNonNullableTypeIfNeeded(checkExpression(declaration.parent.parent.expression))); + return indexType.flags & (TypeFlags.TypeParameter | TypeFlags.Index) ? getExtractStringType(indexType) : stringType; + } + if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForOfStatement) { + // checkRightHandSideOfForOf will return undefined if the for-of expression type was + // missing properties/signatures required to get its iteratedType (like + // [Symbol.iterator] or next). This may be because we accessed properties from anyType, + // or it may have led to an error inside getElementTypeOfIterable. + const forOfStatement = declaration.parent.parent; + return checkRightHandSideOfForOf(forOfStatement.expression, forOfStatement.awaitModifier) || anyType; + } + if (isBindingPattern(declaration.parent)) { + return getTypeForBindingElement((declaration)); + } + const isOptional = includeOptionality && (isParameter(declaration) && isJSDocOptionalParameter(declaration) + || !isBindingElement(declaration) && !isVariableDeclaration(declaration) && !!declaration.questionToken); + // Use type from type annotation if one is present + const declaredType = tryGetTypeFromEffectiveTypeNode(declaration); + if (declaredType) { + return addOptionality(declaredType, isOptional); + } + if ((noImplicitAny || isInJSFile(declaration)) && + declaration.kind === SyntaxKind.VariableDeclaration && !isBindingPattern(declaration.name) && + !(getCombinedModifierFlags(declaration) & ModifierFlags.Export) && !(declaration.flags & NodeFlags.Ambient)) { + // If --noImplicitAny is on or the declaration is in a Javascript file, + // use control flow tracked 'any' type for non-ambient, non-exported var or let variables with no + // initializer or a 'null' or 'undefined' initializer. + if (!(getCombinedNodeFlags(declaration) & NodeFlags.Const) && (!declaration.initializer || isNullOrUndefined(declaration.initializer))) { + return autoType; + } + // Use control flow tracked 'any[]' type for non-ambient, non-exported variables with an empty array + // literal initializer. + if (declaration.initializer && isEmptyArrayLiteral(declaration.initializer)) { + return autoArrayType; + } + } + if (declaration.kind === SyntaxKind.Parameter) { + const func = (declaration.parent); + // For a parameter of a set accessor, use the type of the get accessor if one is present + if (func.kind === SyntaxKind.SetAccessor && !hasNonBindableDynamicName(func)) { + const getter = getDeclarationOfKind(getSymbolOfNode(declaration.parent), SyntaxKind.GetAccessor); + if (getter) { + const getterSignature = getSignatureFromDeclaration(getter); + const thisParameter = getAccessorThisParameter((func as AccessorDeclaration)); + if (thisParameter && declaration === thisParameter) { + // Use the type from the *getter* + Debug.assert(!thisParameter.type); + return getTypeOfSymbol(getterSignature.thisParameter!); + } + return getReturnTypeOfSignature(getterSignature); } } - return false; - } - - function optionalChainContainsReference(source: Node, target: Node) { - while (isOptionalChain(source)) { - source = source.expression; - if (isMatchingReference(source, target)) { - return true; + if (isInJSFile(declaration)) { + const typeTag = getJSDocType(func); + if (typeTag && isFunctionTypeNode(typeTag)) { + return getTypeAtPosition(getSignatureFromDeclaration(typeTag), func.parameters.indexOf(declaration)); } } - return false; + // Use contextual parameter type if one is available + const type = declaration.symbol.escapedName === InternalSymbolName.This ? getContextualThisParameterType(func) : getContextuallyTypedParameterType(declaration, /*forCache*/ true); + if (type) { + return addOptionality(type, isOptional); + } } - - // Return true if target is a property access xxx.yyy, source is a property access xxx.zzz, the declared - // type of xxx is a union type, and yyy is a property that is possibly a discriminant. We consider a property - // a possible discriminant if its type differs in the constituents of containing union type, and if every - // choice is a unit type or a union of unit types. - function containsMatchingReferenceDiscriminant(source: Node, target: Node) { - let name; - return isAccessExpression(target) && - containsMatchingReference(source, target.expression) && - (name = getAccessedPropertyName(target)) !== undefined && - isDiscriminantProperty(getDeclaredTypeOfReference(target.expression), name); + else if (isInJSFile(declaration)) { + const containerObjectType = getJSContainerObjectType(declaration, getSymbolOfNode(declaration), getDeclaredExpandoInitializer(declaration)); + if (containerObjectType) { + return containerObjectType; + } } - - function getDeclaredTypeOfReference(expr: Node): Type | undefined { - if (expr.kind === SyntaxKind.Identifier) { - return getTypeOfSymbol(getResolvedSymbol(expr)); + // Use the type of the initializer expression if one is present and the declaration is + // not a parameter of a contextually typed function + if (declaration.initializer && !isParameterOfContextuallyTypedFunction(declaration)) { + const type = checkDeclarationInitializer(declaration); + return addOptionality(type, isOptional); + } + if (isJsxAttribute(declaration)) { + // if JSX attribute doesn't have initializer, by default the attribute will have boolean value of true. + // I.e is sugar for + return trueType; + } + // If the declaration specifies a binding pattern and is not a parameter of a contextually + // typed function, use the type implied by the binding pattern + if (isBindingPattern(declaration.name) && !isParameterOfContextuallyTypedFunction(declaration)) { + return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ false, /*reportErrors*/ true); + } + // No type specified and nothing can be inferred + return undefined; + } + function getWidenedTypeForAssignmentDeclaration(symbol: ts.Symbol, resolvedSymbol?: ts.Symbol) { + // function/class/{} initializers are themselves containers, so they won't merge in the same way as other initializers + const container = getAssignedExpandoInitializer(symbol.valueDeclaration); + if (container) { + const tag = getJSDocTypeTag(container); + if (tag && tag.typeExpression) { + return getTypeFromTypeNode(tag.typeExpression); + } + const containerObjectType = getJSContainerObjectType(symbol.valueDeclaration, symbol, container); + return containerObjectType || getWidenedLiteralType(checkExpressionCached(container)); + } + let definedInConstructor = false; + let definedInMethod = false; + let jsdocType: ts.Type | undefined; + let types: ts.Type[] | undefined; + for (const declaration of symbol.declarations) { + const expression = (isBinaryExpression(declaration) || isCallExpression(declaration)) ? declaration : + isAccessExpression(declaration) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration : + undefined; + if (!expression) { + continue; // Non-assignment declaration merged in (eg, an Identifier to mark the thing as a namespace) - skip over it and pull type info from elsewhere } - if (isAccessExpression(expr)) { - const type = getDeclaredTypeOfReference(expr.expression); - if (type) { - const propName = getAccessedPropertyName(expr); - return propName !== undefined ? getTypeOfPropertyOfType(type, propName) : undefined; + const kind = isAccessExpression(expression) + ? getAssignmentDeclarationPropertyAccessKind(expression) + : getAssignmentDeclarationKind(expression); + if (kind === AssignmentDeclarationKind.ThisProperty) { + if (isDeclarationInConstructor(expression)) { + definedInConstructor = true; + } + else { + definedInMethod = true; } } - return undefined; + if (!isCallExpression(expression)) { + jsdocType = getAnnotatedTypeForAssignmentDeclaration(jsdocType, expression, symbol, declaration); + } + if (!jsdocType) { + (types || (types = [])).push((isBinaryExpression(expression) || isCallExpression(expression)) ? getInitializerTypeFromAssignmentDeclaration(symbol, resolvedSymbol, expression, kind) : neverType); + } } - - function isDiscriminantProperty(type: Type | undefined, name: __String) { - if (type && type.flags & TypeFlags.Union) { - const prop = getUnionOrIntersectionProperty(type, name); - if (prop && getCheckFlags(prop) & CheckFlags.SyntheticProperty) { - if ((prop).isDiscriminantProperty === undefined) { - (prop).isDiscriminantProperty = - ((prop).checkFlags & CheckFlags.Discriminant) === CheckFlags.Discriminant && - !maybeTypeOfKind(getTypeOfSymbol(prop), TypeFlags.Instantiable); - } - return !!(prop).isDiscriminantProperty; + let type = jsdocType; + if (!type) { + if (!length(types)) { + return errorType; // No types from any declarations :( + } + let constructorTypes = definedInConstructor ? getConstructorDefinedThisAssignmentTypes(types!, symbol.declarations) : undefined; + // use only the constructor types unless they were only assigned null | undefined (including widening variants) + if (definedInMethod) { + const propType = getTypeOfAssignmentDeclarationPropertyOfBaseType(symbol); + if (propType) { + (constructorTypes || (constructorTypes = [])).push(propType); + definedInConstructor = true; } } - return false; + const sourceTypes = some(constructorTypes, t => !!(t.flags & ~TypeFlags.Nullable)) ? constructorTypes : types; // TODO: GH#18217 + type = getUnionType((sourceTypes!), UnionReduction.Subtype); } - - function isSyntheticThisPropertyAccess(expr: Node) { - return isAccessExpression(expr) && expr.expression.kind === SyntaxKind.ThisKeyword && !!(expr.expression.flags & NodeFlags.Synthesized); + const widened = getWidenedType(addOptionality(type, definedInMethod && !definedInConstructor)); + if (filterType(widened, t => !!(t.flags & ~TypeFlags.Nullable)) === neverType) { + reportImplicitAny(symbol.valueDeclaration, anyType); + return anyType; } - - function findDiscriminantProperties(sourceProperties: Symbol[], target: Type): Symbol[] | undefined { - let result: Symbol[] | undefined; - for (const sourceProperty of sourceProperties) { - if (isDiscriminantProperty(target, sourceProperty.escapedName)) { - if (result) { - result.push(sourceProperty); - continue; - } - result = [sourceProperty]; - } + return widened; + } + function getJSContainerObjectType(decl: Node, symbol: ts.Symbol, init: Expression | undefined): ts.Type | undefined { + if (!isInJSFile(decl) || !init || !isObjectLiteralExpression(init) || init.properties.length) { + return undefined; + } + const exports = createSymbolTable(); + while (isBinaryExpression(decl) || isPropertyAccessExpression(decl)) { + const s = getSymbolOfNode(decl); + if (s && hasEntries(s.exports)) { + mergeSymbolTable(exports, s.exports); } - return result; + decl = isBinaryExpression(decl) ? decl.parent : decl.parent.parent; } - - function isOrContainsMatchingReference(source: Node, target: Node) { - return isMatchingReference(source, target) || containsMatchingReference(source, target); + const s = getSymbolOfNode(decl); + if (s && hasEntries(s.exports)) { + mergeSymbolTable(exports, s.exports); } - - function hasMatchingArgument(callExpression: CallExpression, reference: Node) { - if (callExpression.arguments) { - for (const argument of callExpression.arguments) { - if (isOrContainsMatchingReference(reference, argument)) { - return true; - } - } + const type = createAnonymousType(symbol, exports, emptyArray, emptyArray, undefined, undefined); + type.objectFlags |= ObjectFlags.JSLiteral; + return type; + } + function getAnnotatedTypeForAssignmentDeclaration(declaredType: ts.Type | undefined, expression: Expression, symbol: ts.Symbol, declaration: Declaration) { + const typeNode = getEffectiveTypeAnnotationNode(expression.parent); + if (typeNode) { + const type = getWidenedType(getTypeFromTypeNode(typeNode)); + if (!declaredType) { + return type; } - if (callExpression.expression.kind === SyntaxKind.PropertyAccessExpression && - isOrContainsMatchingReference(reference, (callExpression.expression).expression)) { - return true; + else if (declaredType !== errorType && type !== errorType && !isTypeIdenticalTo(declaredType, type)) { + errorNextVariableOrPropertyDeclarationMustHaveSameType(/*firstDeclaration*/ undefined, declaredType, declaration, type); } - return false; } - - function getFlowNodeId(flow: FlowNode): number { - if (!flow.id || flow.id < 0) { - flow.id = nextFlowId; - nextFlowId++; + if (symbol.parent) { + const typeNode = getEffectiveTypeAnnotationNode(symbol.parent.valueDeclaration); + if (typeNode) { + return getTypeOfPropertyOfType(getTypeFromTypeNode(typeNode), symbol.escapedName); } - return flow.id; } - - function typeMaybeAssignableTo(source: Type, target: Type) { - if (!(source.flags & TypeFlags.Union)) { - return isTypeAssignableTo(source, target); + return declaredType; + } + /** If we don't have an explicit JSDoc type, get the type from the initializer. */ + function getInitializerTypeFromAssignmentDeclaration(symbol: ts.Symbol, resolvedSymbol: ts.Symbol | undefined, expression: BinaryExpression | CallExpression, kind: AssignmentDeclarationKind) { + if (isCallExpression(expression)) { + if (resolvedSymbol) { + return getTypeOfSymbol(resolvedSymbol); // This shouldn't happen except under some hopefully forbidden merges of export assignments and object define assignments } - for (const t of (source).types) { - if (isTypeAssignableTo(t, target)) { - return true; + const objectLitType = checkExpressionCached((expression as BindableObjectDefinePropertyCall).arguments[2]); + const valueType = getTypeOfPropertyOfType(objectLitType, ("value" as __String)); + if (valueType) { + return valueType; + } + const getFunc = getTypeOfPropertyOfType(objectLitType, ("get" as __String)); + if (getFunc) { + const getSig = getSingleCallSignature(getFunc); + if (getSig) { + return getReturnTypeOfSignature(getSig); } } - return false; - } - - // Remove those constituent types of declaredType to which no constituent type of assignedType is assignable. - // For example, when a variable of type number | string | boolean is assigned a value of type number | boolean, - // we remove type string. - function getAssignmentReducedType(declaredType: UnionType, assignedType: Type) { - if (declaredType !== assignedType) { - if (assignedType.flags & TypeFlags.Never) { - return assignedType; - } - let reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t)); - if (assignedType.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(assignedType)) { - reducedType = mapType(reducedType, getFreshTypeOfLiteralType); // Ensure that if the assignment is a fresh type, that we narrow to fresh types - } - // Our crude heuristic produces an invalid result in some cases: see GH#26130. - // For now, when that happens, we give up and don't narrow at all. (This also - // means we'll never narrow for erroneous assignments where the assigned type - // is not assignable to the declared type.) - if (isTypeAssignableTo(assignedType, reducedType)) { - return reducedType; + const setFunc = getTypeOfPropertyOfType(objectLitType, ("set" as __String)); + if (setFunc) { + const setSig = getSingleCallSignature(setFunc); + if (setSig) { + return getTypeOfFirstParameterOfSignature(setSig); } } - return declaredType; + return anyType; } - - function getTypeFactsOfTypes(types: Type[]): TypeFacts { - let result: TypeFacts = TypeFacts.None; - for (const t of types) { - result |= getTypeFacts(t); + const type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol) : getWidenedLiteralType(checkExpressionCached(expression.right)); + if (type.flags & TypeFlags.Object && + kind === AssignmentDeclarationKind.ModuleExports && + symbol.escapedName === InternalSymbolName.ExportEquals) { + const exportedType = resolveStructuredTypeMembers((type as ObjectType)); + const members = createSymbolTable(); + copyEntries(exportedType.members, members); + if (resolvedSymbol && !resolvedSymbol.exports) { + resolvedSymbol.exports = createSymbolTable(); } + (resolvedSymbol || symbol).exports!.forEach((s, name) => { + if (members.has(name)) { + const exportedMember = exportedType.members.get(name)!; + const union = createSymbol(s.flags | exportedMember.flags, name); + union.type = getUnionType([getTypeOfSymbol(s), getTypeOfSymbol(exportedMember)]); + members.set(name, union); + } + else { + members.set(name, s); + } + }); + const result = createAnonymousType(exportedType.symbol, members, exportedType.callSignatures, exportedType.constructSignatures, exportedType.stringIndexInfo, exportedType.numberIndexInfo); + result.objectFlags |= (getObjectFlags(type) & ObjectFlags.JSLiteral); // Propagate JSLiteral flag return result; } - - function isFunctionObjectType(type: ObjectType): boolean { - // We do a quick check for a "bind" property before performing the more expensive subtype - // check. This gives us a quicker out in the common case where an object type is not a function. - const resolved = resolveStructuredTypeMembers(type); - return !!(resolved.callSignatures.length || resolved.constructSignatures.length || - resolved.members.get("bind" as __String) && isTypeSubtypeOf(type, globalFunctionType)); + if (isEmptyArrayLiteralType(type)) { + reportImplicitAny(expression, anyArrayType); + return anyArrayType; } - - function getTypeFacts(type: Type): TypeFacts { - const flags = type.flags; - if (flags & TypeFlags.String) { - return strictNullChecks ? TypeFacts.StringStrictFacts : TypeFacts.StringFacts; + return type; + } + function isDeclarationInConstructor(expression: Expression) { + const thisContainer = getThisContainer(expression, /*includeArrowFunctions*/ false); + // Properties defined in a constructor (or base constructor, or javascript constructor function) don't get undefined added. + // Function expressions that are assigned to the prototype count as methods. + return thisContainer.kind === SyntaxKind.Constructor || + thisContainer.kind === SyntaxKind.FunctionDeclaration || + (thisContainer.kind === SyntaxKind.FunctionExpression && !isPrototypePropertyAssignment(thisContainer.parent)); + } + function getConstructorDefinedThisAssignmentTypes(types: ts.Type[], declarations: Declaration[]): ts.Type[] | undefined { + Debug.assert(types.length === declarations.length); + return types.filter((_, i) => { + const declaration = declarations[i]; + const expression = isBinaryExpression(declaration) ? declaration : + isBinaryExpression(declaration.parent) ? declaration.parent : undefined; + return expression && isDeclarationInConstructor(expression); + }); + } + /** check for definition in base class if any declaration is in a class */ + function getTypeOfAssignmentDeclarationPropertyOfBaseType(property: ts.Symbol) { + const parentDeclaration = forEach(property.declarations, d => { + const parent = getThisContainer(d, /*includeArrowFunctions*/ false).parent; + return isClassLike(parent) && parent; + }); + if (parentDeclaration) { + const classType = (getDeclaredTypeOfSymbol(getSymbolOfNode(parentDeclaration)) as InterfaceType); + const baseClassType = classType && getBaseTypes(classType)[0]; + if (baseClassType) { + return getTypeOfPropertyOfType(baseClassType, property.escapedName); + } + } + } + // Return the type implied by a binding pattern element. This is the type of the initializer of the element if + // one is present. Otherwise, if the element is itself a binding pattern, it is the type implied by the binding + // pattern. Otherwise, it is the type any. + function getTypeFromBindingElement(element: BindingElement, includePatternInType?: boolean, reportErrors?: boolean): ts.Type { + if (element.initializer) { + return addOptionality(checkDeclarationInitializer(element)); + } + if (isBindingPattern(element.name)) { + return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors); + } + if (reportErrors && !declarationBelongsToPrivateAmbientMember(element)) { + reportImplicitAny(element, anyType); + } + // When we're including the pattern in the type (an indication we're obtaining a contextual type), we + // use the non-inferrable any type. Inference will never directly infer this type, but it is possible + // to infer a type that contains it, e.g. for a binding pattern like [foo] or { foo }. In such cases, + // widening of the binding pattern type substitutes a regular any for the non-inferrable any. + return includePatternInType ? nonInferrableAnyType : anyType; + } + // Return the type implied by an object binding pattern + function getTypeFromObjectBindingPattern(pattern: ObjectBindingPattern, includePatternInType: boolean, reportErrors: boolean): ts.Type { + const members = createSymbolTable(); + let stringIndexInfo: IndexInfo | undefined; + let objectFlags = ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + forEach(pattern.elements, e => { + const name = e.propertyName || (e.name); + if (e.dotDotDotToken) { + stringIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false); + return; + } + const exprType = getLiteralTypeFromPropertyName(name); + if (!isTypeUsableAsPropertyName(exprType)) { + // do not include computed properties in the implied type + objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties; + return; } - if (flags & TypeFlags.StringLiteral) { - const isEmpty = (type).value === ""; - return strictNullChecks ? - isEmpty ? TypeFacts.EmptyStringStrictFacts : TypeFacts.NonEmptyStringStrictFacts : - isEmpty ? TypeFacts.EmptyStringFacts : TypeFacts.NonEmptyStringFacts; + const text = getPropertyNameFromType(exprType); + const flags = SymbolFlags.Property | (e.initializer ? SymbolFlags.Optional : 0); + const symbol = createSymbol(flags, text); + symbol.type = getTypeFromBindingElement(e, includePatternInType, reportErrors); + symbol.bindingElement = e; + members.set(symbol.escapedName, symbol); + }); + const result = createAnonymousType(undefined, members, emptyArray, emptyArray, stringIndexInfo, undefined); + result.objectFlags |= objectFlags; + if (includePatternInType) { + result.pattern = pattern; + result.objectFlags |= ObjectFlags.ContainsObjectOrArrayLiteral; + } + return result; + } + // Return the type implied by an array binding pattern + function getTypeFromArrayBindingPattern(pattern: BindingPattern, includePatternInType: boolean, reportErrors: boolean): ts.Type { + const elements = pattern.elements; + const lastElement = lastOrUndefined(elements); + const hasRestElement = !!(lastElement && lastElement.kind === SyntaxKind.BindingElement && lastElement.dotDotDotToken); + if (elements.length === 0 || elements.length === 1 && hasRestElement) { + return languageVersion >= ScriptTarget.ES2015 ? createIterableType(anyType) : anyArrayType; + } + const elementTypes = map(elements, e => isOmittedExpression(e) ? anyType : getTypeFromBindingElement(e, includePatternInType, reportErrors)); + const minLength = findLastIndex(elements, e => !isOmittedExpression(e) && !hasDefaultValue(e), elements.length - (hasRestElement ? 2 : 1)) + 1; + let result = (createTupleType(elementTypes, minLength, hasRestElement)); + if (includePatternInType) { + result = cloneTypeReference(result); + result.pattern = pattern; + result.objectFlags |= ObjectFlags.ContainsObjectOrArrayLiteral; + } + return result; + } + // Return the type implied by a binding pattern. This is the type implied purely by the binding pattern itself + // and without regard to its context (i.e. without regard any type annotation or initializer associated with the + // declaration in which the binding pattern is contained). For example, the implied type of [x, y] is [any, any] + // and the implied type of { x, y: z = 1 } is { x: any; y: number; }. The type implied by a binding pattern is + // used as the contextual type of an initializer associated with the binding pattern. Also, for a destructuring + // parameter with no type annotation or initializer, the type implied by the binding pattern becomes the type of + // the parameter. + function getTypeFromBindingPattern(pattern: BindingPattern, includePatternInType = false, reportErrors = false): ts.Type { + return pattern.kind === SyntaxKind.ObjectBindingPattern + ? getTypeFromObjectBindingPattern(pattern, includePatternInType, reportErrors) + : getTypeFromArrayBindingPattern(pattern, includePatternInType, reportErrors); + } + // Return the type associated with a variable, parameter, or property declaration. In the simple case this is the type + // specified in a type annotation or inferred from an initializer. However, in the case of a destructuring declaration it + // is a bit more involved. For example: + // + // var [x, s = ""] = [1, "one"]; + // + // Here, the array literal [1, "one"] is contextually typed by the type [any, string], which is the implied type of the + // binding pattern [x, s = ""]. Because the contextual type is a tuple type, the resulting type of [1, "one"] is the + // tuple type [number, string]. Thus, the type inferred for 'x' is number and the type inferred for 's' is string. + function getWidenedTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement, reportErrors?: boolean): ts.Type { + return widenTypeForVariableLikeDeclaration(getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true), declaration, reportErrors); + } + function widenTypeForVariableLikeDeclaration(type: ts.Type | undefined, declaration: any, reportErrors?: boolean) { + if (type) { + if (reportErrors) { + reportErrorsFromWidening(declaration, type); } - if (flags & (TypeFlags.Number | TypeFlags.Enum)) { - return strictNullChecks ? TypeFacts.NumberStrictFacts : TypeFacts.NumberFacts; + // always widen a 'unique symbol' type if the type was created for a different declaration. + if (type.flags & TypeFlags.UniqueESSymbol && (isBindingElement(declaration) || !declaration.type) && type.symbol !== getSymbolOfNode(declaration)) { + type = esSymbolType; } - if (flags & TypeFlags.NumberLiteral) { - const isZero = (type).value === 0; - return strictNullChecks ? - isZero ? TypeFacts.ZeroNumberStrictFacts : TypeFacts.NonZeroNumberStrictFacts : - isZero ? TypeFacts.ZeroNumberFacts : TypeFacts.NonZeroNumberFacts; + return getWidenedType(type); + } + // Rest parameters default to type any[], other parameters default to type any + type = isParameter(declaration) && declaration.dotDotDotToken ? anyArrayType : anyType; + // Report implicit any errors unless this is a private property within an ambient declaration + if (reportErrors) { + if (!declarationBelongsToPrivateAmbientMember(declaration)) { + reportImplicitAny(declaration, type); } - if (flags & TypeFlags.BigInt) { - return strictNullChecks ? TypeFacts.BigIntStrictFacts : TypeFacts.BigIntFacts; + } + return type; + } + function declarationBelongsToPrivateAmbientMember(declaration: VariableLikeDeclaration) { + const root = getRootDeclaration(declaration); + const memberDeclaration = root.kind === SyntaxKind.Parameter ? root.parent : root; + return isPrivateWithinAmbient(memberDeclaration); + } + function tryGetTypeFromEffectiveTypeNode(declaration: Declaration) { + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + return getTypeFromTypeNode(typeNode); + } + } + function getTypeOfVariableOrParameterOrProperty(symbol: ts.Symbol): ts.Type { + const links = getSymbolLinks(symbol); + if (!links.type) { + const type = getTypeOfVariableOrParameterOrPropertyWorker(symbol); + // For a contextually typed parameter it is possible that a type has already + // been assigned (in assignTypeToParameterAndFixTypeParameters), and we want + // to preserve this type. + if (!links.type) { + links.type = type; } - if (flags & TypeFlags.BigIntLiteral) { - const isZero = isZeroBigInt(type); - return strictNullChecks ? - isZero ? TypeFacts.ZeroBigIntStrictFacts : TypeFacts.NonZeroBigIntStrictFacts : - isZero ? TypeFacts.ZeroBigIntFacts : TypeFacts.NonZeroBigIntFacts; + } + return links.type; + } + function getTypeOfVariableOrParameterOrPropertyWorker(symbol: ts.Symbol) { + // Handle prototype property + if (symbol.flags & SymbolFlags.Prototype) { + return getTypeOfPrototypeProperty(symbol); + } + // CommonsJS require and module both have type any. + if (symbol === requireSymbol) { + return anyType; + } + if (symbol.flags & SymbolFlags.ModuleExports) { + const fileSymbol = getSymbolOfNode(getSourceFileOfNode(symbol.valueDeclaration)); + const members = createSymbolTable(); + members.set(("exports" as __String), fileSymbol); + return createAnonymousType(symbol, members, emptyArray, emptyArray, undefined, undefined); + } + // Handle catch clause variables + const declaration = symbol.valueDeclaration; + if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) { + return anyType; + } + // Handle export default expressions + if (isSourceFile(declaration) && isJsonSourceFile(declaration)) { + if (!declaration.statements.length) { + return emptyObjectType; } - if (flags & TypeFlags.Boolean) { - return strictNullChecks ? TypeFacts.BooleanStrictFacts : TypeFacts.BooleanFacts; + return getWidenedType(getWidenedLiteralType(checkExpression(declaration.statements[0].expression))); + } + // Handle variable, parameter or property + if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { + // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` + if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) { + return getTypeOfFuncClassEnumModule(symbol); } - if (flags & TypeFlags.BooleanLike) { - return strictNullChecks ? - (type === falseType || type === regularFalseType) ? TypeFacts.FalseStrictFacts : TypeFacts.TrueStrictFacts : - (type === falseType || type === regularFalseType) ? TypeFacts.FalseFacts : TypeFacts.TrueFacts; + return reportCircularityError(symbol); + } + let type: ts.Type | undefined; + if (declaration.kind === SyntaxKind.ExportAssignment) { + type = widenTypeForVariableLikeDeclaration(checkExpressionCached((declaration).expression), declaration); + } + else if (isBinaryExpression(declaration) || + (isInJSFile(declaration) && + (isCallExpression(declaration) || (isPropertyAccessExpression(declaration) || isBindableStaticElementAccessExpression(declaration)) && isBinaryExpression(declaration.parent)))) { + type = getWidenedTypeForAssignmentDeclaration(symbol); + } + else if (isJSDocPropertyLikeTag(declaration) + || isPropertyAccessExpression(declaration) + || isElementAccessExpression(declaration) + || isIdentifier(declaration) + || isStringLiteralLike(declaration) + || isNumericLiteral(declaration) + || isClassDeclaration(declaration) + || isFunctionDeclaration(declaration) + || (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration)) + || isMethodSignature(declaration) + || isSourceFile(declaration)) { + // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { + return getTypeOfFuncClassEnumModule(symbol); } - if (flags & TypeFlags.Object) { - return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type) ? - strictNullChecks ? TypeFacts.EmptyObjectStrictFacts : TypeFacts.EmptyObjectFacts : - isFunctionObjectType(type) ? - strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts : - strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; + type = isBinaryExpression(declaration.parent) ? + getWidenedTypeForAssignmentDeclaration(symbol) : + tryGetTypeFromEffectiveTypeNode(declaration) || anyType; + } + else if (isPropertyAssignment(declaration)) { + type = tryGetTypeFromEffectiveTypeNode(declaration) || checkPropertyAssignment(declaration); + } + else if (isJsxAttribute(declaration)) { + type = tryGetTypeFromEffectiveTypeNode(declaration) || checkJsxAttribute(declaration); + } + else if (isShorthandPropertyAssignment(declaration)) { + type = tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionForMutableLocation(declaration.name, CheckMode.Normal); + } + else if (isObjectLiteralMethod(declaration)) { + type = tryGetTypeFromEffectiveTypeNode(declaration) || checkObjectLiteralMethod(declaration, CheckMode.Normal); + } + else if (isParameter(declaration) + || isPropertyDeclaration(declaration) + || isPropertySignature(declaration) + || isVariableDeclaration(declaration) + || isBindingElement(declaration)) { + type = getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true); + } + // getTypeOfSymbol dispatches some JS merges incorrectly because their symbol flags are not mutually exclusive. + // Re-dispatch based on valueDeclaration.kind instead. + else if (isEnumDeclaration(declaration)) { + type = getTypeOfFuncClassEnumModule(symbol); + } + else if (isEnumMember(declaration)) { + type = getTypeOfEnumMember(symbol); + } + else if (isAccessor(declaration)) { + type = resolveTypeOfAccessors(symbol); + } + else { + return Debug.fail("Unhandled declaration kind! " + Debug.formatSyntaxKind(declaration.kind) + " for " + Debug.formatSymbol(symbol)); + } + if (!popTypeResolution()) { + // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` + if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) { + return getTypeOfFuncClassEnumModule(symbol); } - if (flags & (TypeFlags.Void | TypeFlags.Undefined)) { - return TypeFacts.UndefinedFacts; + return reportCircularityError(symbol); + } + return type; + } + function getAnnotatedAccessorTypeNode(accessor: AccessorDeclaration | undefined): TypeNode | undefined { + if (accessor) { + if (accessor.kind === SyntaxKind.GetAccessor) { + const getterTypeAnnotation = getEffectiveReturnTypeNode(accessor); + return getterTypeAnnotation; } - if (flags & TypeFlags.Null) { - return TypeFacts.NullFacts; + else { + const setterTypeAnnotation = getEffectiveSetAccessorTypeAnnotationNode(accessor); + return setterTypeAnnotation; } - if (flags & TypeFlags.ESSymbolLike) { - return strictNullChecks ? TypeFacts.SymbolStrictFacts : TypeFacts.SymbolFacts; + } + return undefined; + } + function getAnnotatedAccessorType(accessor: AccessorDeclaration | undefined): ts.Type | undefined { + const node = getAnnotatedAccessorTypeNode(accessor); + return node && getTypeFromTypeNode(node); + } + function getAnnotatedAccessorThisParameter(accessor: AccessorDeclaration): ts.Symbol | undefined { + const parameter = getAccessorThisParameter(accessor); + return parameter && parameter.symbol; + } + function getThisTypeOfDeclaration(declaration: SignatureDeclaration): ts.Type | undefined { + return getThisTypeOfSignature(getSignatureFromDeclaration(declaration)); + } + function getTypeOfAccessors(symbol: ts.Symbol): ts.Type { + const links = getSymbolLinks(symbol); + return links.type || (links.type = getTypeOfAccessorsWorker(symbol)); + } + function getTypeOfAccessorsWorker(symbol: ts.Symbol): ts.Type { + if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { + return errorType; + } + let type = resolveTypeOfAccessors(symbol); + if (!popTypeResolution()) { + type = anyType; + if (noImplicitAny) { + const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); + error(getter, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, symbolToString(symbol)); } - if (flags & TypeFlags.NonPrimitive) { - return strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; + } + return type; + } + function resolveTypeOfAccessors(symbol: ts.Symbol) { + const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); + const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor); + if (getter && isInJSFile(getter)) { + const jsDocType = getTypeForDeclarationFromJSDocComment(getter); + if (jsDocType) { + return jsDocType; + } + } + // First try to see if the user specified a return type on the get-accessor. + const getterReturnType = getAnnotatedAccessorType(getter); + if (getterReturnType) { + return getterReturnType; + } + else { + // If the user didn't specify a return type, try to use the set-accessor's parameter type. + const setterParameterType = getAnnotatedAccessorType(setter); + if (setterParameterType) { + return setterParameterType; } - if (flags & TypeFlags.Instantiable) { - return getTypeFacts(getBaseConstraintOfType(type) || unknownType); + else { + // If there are no specified types, try to infer it from the body of the get accessor if it exists. + if (getter && getter.body) { + return getReturnTypeFromBody(getter); + } + // Otherwise, fall back to 'any'. + else { + if (setter) { + if (!isPrivateWithinAmbient(setter)) { + errorOrSuggestion(noImplicitAny, setter, Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation, symbolToString(symbol)); + } + } + else { + Debug.assert(!!getter, "there must exist a getter as we are current checking either setter or getter in this function"); + if (!isPrivateWithinAmbient(getter!)) { + errorOrSuggestion(noImplicitAny, (getter!), Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation, symbolToString(symbol)); + } + } + return anyType; + } } - if (flags & TypeFlags.UnionOrIntersection) { - return getTypeFactsOfTypes((type).types); + } + } + function getBaseTypeVariableOfClass(symbol: ts.Symbol) { + const baseConstructorType = getBaseConstructorTypeOfClass(getDeclaredTypeOfClassOrInterface(symbol)); + return baseConstructorType.flags & TypeFlags.TypeVariable ? baseConstructorType : + baseConstructorType.flags & TypeFlags.Intersection ? find((baseConstructorType as IntersectionType).types, t => !!(t.flags & TypeFlags.TypeVariable)) : + undefined; + } + function getTypeOfFuncClassEnumModule(symbol: ts.Symbol): ts.Type { + let links = getSymbolLinks(symbol); + const originalLinks = links; + if (!links.type) { + const jsDeclaration = getDeclarationOfExpando(symbol.valueDeclaration); + if (jsDeclaration) { + const merged = mergeJSSymbols(symbol, getSymbolOfNode(jsDeclaration)); + if (merged) { + // note:we overwrite links because we just cloned the symbol + symbol = links = merged; + } } - return TypeFacts.All; + originalLinks.type = links.type = getTypeOfFuncClassEnumModuleWorker(symbol); } - - function getTypeWithFacts(type: Type, include: TypeFacts) { - return filterType(type, t => (getTypeFacts(t) & include) !== 0); + return links.type; + } + function getTypeOfFuncClassEnumModuleWorker(symbol: ts.Symbol): ts.Type { + const declaration = symbol.valueDeclaration; + if (symbol.flags & SymbolFlags.Module && isShorthandAmbientModuleSymbol(symbol)) { + return anyType; } - - function getTypeWithDefault(type: Type, defaultExpression: Expression) { - if (defaultExpression) { - const defaultType = getTypeOfExpression(defaultExpression); - return getUnionType([getTypeWithFacts(type, TypeFacts.NEUndefined), defaultType]); + else if (declaration.kind === SyntaxKind.BinaryExpression || + (declaration.kind === SyntaxKind.PropertyAccessExpression || declaration.kind === SyntaxKind.ElementAccessExpression) && + declaration.parent.kind === SyntaxKind.BinaryExpression) { + return getWidenedTypeForAssignmentDeclaration(symbol); + } + else if (symbol.flags & SymbolFlags.ValueModule && declaration && isSourceFile(declaration) && declaration.commonJsModuleIndicator) { + const resolvedModule = resolveExternalModuleSymbol(symbol); + if (resolvedModule !== symbol) { + if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { + return errorType; + } + const exportEquals = getMergedSymbol((symbol.exports!.get(InternalSymbolName.ExportEquals)!)); + const type = getWidenedTypeForAssignmentDeclaration(exportEquals, exportEquals === resolvedModule ? undefined : resolvedModule); + if (!popTypeResolution()) { + return reportCircularityError(symbol); + } + return type; } - return type; } - - function getTypeOfDestructuredProperty(type: Type, name: PropertyName) { - const nameType = getLiteralTypeFromPropertyName(name); - if (!isTypeUsableAsPropertyName(nameType)) return errorType; - const text = getPropertyNameFromType(nameType); - return getConstraintForLocation(getTypeOfPropertyOfType(type, text), name) || - isNumericLiteralName(text) && getIndexTypeOfType(type, IndexKind.Number) || - getIndexTypeOfType(type, IndexKind.String) || - errorType; + const type = createObjectType(ObjectFlags.Anonymous, symbol); + if (symbol.flags & SymbolFlags.Class) { + const baseTypeVariable = getBaseTypeVariableOfClass(symbol); + return baseTypeVariable ? getIntersectionType([type, baseTypeVariable]) : type; } - - function getTypeOfDestructuredArrayElement(type: Type, index: number) { - return everyType(type, isTupleLikeType) && getTupleElementType(type, index) || - checkIteratedTypeOrElementType(IterationUse.Destructuring, type, undefinedType, /*errorNode*/ undefined) || - errorType; + else { + return strictNullChecks && symbol.flags & SymbolFlags.Optional ? getOptionalType(type) : type; } - - function getTypeOfDestructuredSpreadExpression(type: Type) { - return createArrayType(checkIteratedTypeOrElementType(IterationUse.Destructuring, type, undefinedType, /*errorNode*/ undefined) || errorType); + } + function getTypeOfEnumMember(symbol: ts.Symbol): ts.Type { + const links = getSymbolLinks(symbol); + return links.type || (links.type = getDeclaredTypeOfEnumMember(symbol)); + } + function getTypeOfAlias(symbol: ts.Symbol): ts.Type { + const links = getSymbolLinks(symbol); + if (!links.type) { + const targetSymbol = resolveAlias(symbol); + // It only makes sense to get the type of a value symbol. If the result of resolving + // the alias is not a value, then it has no type. To get the type associated with a + // type symbol, call getDeclaredTypeOfSymbol. + // This check is important because without it, a call to getTypeOfSymbol could end + // up recursively calling getTypeOfAlias, causing a stack overflow. + links.type = targetSymbol.flags & SymbolFlags.Value + ? getTypeOfSymbol(targetSymbol) + : errorType; } - - function getAssignedTypeOfBinaryExpression(node: BinaryExpression): Type { - const isDestructuringDefaultAssignment = - node.parent.kind === SyntaxKind.ArrayLiteralExpression && isDestructuringAssignmentTarget(node.parent) || - node.parent.kind === SyntaxKind.PropertyAssignment && isDestructuringAssignmentTarget(node.parent.parent); - return isDestructuringDefaultAssignment ? - getTypeWithDefault(getAssignedType(node), node.right) : - getTypeOfExpression(node.right); + return links.type; + } + function getTypeOfInstantiatedSymbol(symbol: ts.Symbol): ts.Type { + const links = getSymbolLinks(symbol); + if (!links.type) { + if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { + return links.type = errorType; + } + let type = instantiateType(getTypeOfSymbol(links.target!), links.mapper); + if (!popTypeResolution()) { + type = reportCircularityError(symbol); + } + links.type = type; } - - function isDestructuringAssignmentTarget(parent: Node) { - return parent.parent.kind === SyntaxKind.BinaryExpression && (parent.parent as BinaryExpression).left === parent || - parent.parent.kind === SyntaxKind.ForOfStatement && (parent.parent as ForOfStatement).initializer === parent; + return links.type; + } + function reportCircularityError(symbol: ts.Symbol) { + const declaration = (symbol.valueDeclaration); + // Check if variable has type annotation that circularly references the variable itself + if (getEffectiveTypeAnnotationNode(declaration)) { + error(symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, symbolToString(symbol)); + return errorType; } - - function getAssignedTypeOfArrayLiteralElement(node: ArrayLiteralExpression, element: Expression): Type { - return getTypeOfDestructuredArrayElement(getAssignedType(node), node.elements.indexOf(element)); + // Check if variable has initializer that circularly references the variable itself + if (noImplicitAny && (declaration.kind !== SyntaxKind.Parameter || (declaration).initializer)) { + error(symbol.valueDeclaration, Diagnostics._0_implicitly_has_type_any_because_it_does_not_have_a_type_annotation_and_is_referenced_directly_or_indirectly_in_its_own_initializer, symbolToString(symbol)); } - - function getAssignedTypeOfSpreadExpression(node: SpreadElement): Type { - return getTypeOfDestructuredSpreadExpression(getAssignedType(node.parent)); + // Circularities could also result from parameters in function expressions that end up + // having themselves as contextual types following type argument inference. In those cases + // we have already reported an implicit any error so we don't report anything here. + return anyType; + } + function getTypeOfSymbolWithDeferredType(symbol: ts.Symbol) { + const links = getSymbolLinks(symbol); + if (!links.type) { + Debug.assertDefined(links.deferralParent); + Debug.assertDefined(links.deferralConstituents); + links.type = links.deferralParent!.flags & TypeFlags.Union ? getUnionType(links.deferralConstituents!) : getIntersectionType(links.deferralConstituents!); + } + return links.type; + } + function getTypeOfSymbol(symbol: ts.Symbol): ts.Type { + if (getCheckFlags(symbol) & CheckFlags.DeferredType) { + return getTypeOfSymbolWithDeferredType(symbol); } - - function getAssignedTypeOfPropertyAssignment(node: PropertyAssignment | ShorthandPropertyAssignment): Type { - return getTypeOfDestructuredProperty(getAssignedType(node.parent), node.name); + if (getCheckFlags(symbol) & CheckFlags.Instantiated) { + return getTypeOfInstantiatedSymbol(symbol); } - - function getAssignedTypeOfShorthandPropertyAssignment(node: ShorthandPropertyAssignment): Type { - return getTypeWithDefault(getAssignedTypeOfPropertyAssignment(node), node.objectAssignmentInitializer!); + if (getCheckFlags(symbol) & CheckFlags.ReverseMapped) { + return getTypeOfReverseMappedSymbol((symbol as ReverseMappedSymbol)); } - - function getAssignedType(node: Expression): Type { - const { parent } = node; - switch (parent.kind) { - case SyntaxKind.ForInStatement: - return stringType; - case SyntaxKind.ForOfStatement: - return checkRightHandSideOfForOf((parent).expression, (parent).awaitModifier) || errorType; - case SyntaxKind.BinaryExpression: - return getAssignedTypeOfBinaryExpression(parent); - case SyntaxKind.DeleteExpression: - return undefinedType; - case SyntaxKind.ArrayLiteralExpression: - return getAssignedTypeOfArrayLiteralElement(parent, node); - case SyntaxKind.SpreadElement: - return getAssignedTypeOfSpreadExpression(parent); - case SyntaxKind.PropertyAssignment: - return getAssignedTypeOfPropertyAssignment(parent); - case SyntaxKind.ShorthandPropertyAssignment: - return getAssignedTypeOfShorthandPropertyAssignment(parent); - } - return errorType; + if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { + return getTypeOfVariableOrParameterOrProperty(symbol); } - - function getInitialTypeOfBindingElement(node: BindingElement): Type { - const pattern = node.parent; - const parentType = getInitialType(pattern.parent); - const type = pattern.kind === SyntaxKind.ObjectBindingPattern ? - getTypeOfDestructuredProperty(parentType, node.propertyName || node.name) : - !node.dotDotDotToken ? - getTypeOfDestructuredArrayElement(parentType, pattern.elements.indexOf(node)) : - getTypeOfDestructuredSpreadExpression(parentType); - return getTypeWithDefault(type, node.initializer!); + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { + return getTypeOfFuncClassEnumModule(symbol); } - - function getTypeOfInitializer(node: Expression) { - // Return the cached type if one is available. If the type of the variable was inferred - // from its initializer, we'll already have cached the type. Otherwise we compute it now - // without caching such that transient types are reflected. - const links = getNodeLinks(node); - return links.resolvedType || getTypeOfExpression(node); + if (symbol.flags & SymbolFlags.EnumMember) { + return getTypeOfEnumMember(symbol); } - - function getInitialTypeOfVariableDeclaration(node: VariableDeclaration) { - if (node.initializer) { - return getTypeOfInitializer(node.initializer); + if (symbol.flags & SymbolFlags.Accessor) { + return getTypeOfAccessors(symbol); + } + if (symbol.flags & SymbolFlags.Alias) { + return getTypeOfAlias(symbol); + } + return errorType; + } + function isReferenceToType(type: ts.Type, target: ts.Type) { + return type !== undefined + && target !== undefined + && (getObjectFlags(type) & ObjectFlags.Reference) !== 0 + && (type).target === target; + } + function getTargetType(type: ts.Type): ts.Type { + return getObjectFlags(type) & ObjectFlags.Reference ? (type).target : type; + } + // TODO: GH#18217 If `checkBase` is undefined, we should not call this because this will always return false. + function hasBaseType(type: ts.Type, checkBase: ts.Type | undefined) { + return check(type); + function check(type: ts.Type): boolean { + if (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference)) { + const target = (getTargetType(type)); + return target === checkBase || some(getBaseTypes(target), check); } - if (node.parent.parent.kind === SyntaxKind.ForInStatement) { - return stringType; + else if (type.flags & TypeFlags.Intersection) { + return some((type).types, check); + } + return false; + } + } + // Appends the type parameters given by a list of declarations to a set of type parameters and returns the resulting set. + // The function allocates a new array if the input type parameter set is undefined, but otherwise it modifies the set + // in-place and returns the same array. + function appendTypeParameters(typeParameters: TypeParameter[] | undefined, declarations: readonly TypeParameterDeclaration[]): TypeParameter[] | undefined { + for (const declaration of declarations) { + typeParameters = appendIfUnique(typeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode(declaration))); + } + return typeParameters; + } + // Return the outer type parameters of a node or undefined if the node has no outer type parameters. + function getOuterTypeParameters(node: Node, includeThisTypes?: boolean): TypeParameter[] | undefined { + while (true) { + node = node.parent; // TODO: GH#18217 Use SourceFile kind check instead + if (node && isBinaryExpression(node)) { + // prototype assignments get the outer type parameters of their constructor function + const assignmentKind = getAssignmentDeclarationKind(node); + if (assignmentKind === AssignmentDeclarationKind.Prototype || assignmentKind === AssignmentDeclarationKind.PrototypeProperty) { + const symbol = getSymbolOfNode(node.left); + if (symbol && symbol.parent && !findAncestor(symbol.parent.valueDeclaration, d => node === d)) { + node = symbol.parent.valueDeclaration; + } + } } - if (node.parent.parent.kind === SyntaxKind.ForOfStatement) { - return checkRightHandSideOfForOf(node.parent.parent.expression, node.parent.parent.awaitModifier) || errorType; + if (!node) { + return undefined; + } + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.MethodSignature: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.JSDocFunctionType: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.JSDocTemplateTag: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocEnumTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.MappedType: + case SyntaxKind.ConditionalType: + const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes); + if (node.kind === SyntaxKind.MappedType) { + return append(outerTypeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode((node).typeParameter))); + } + else if (node.kind === SyntaxKind.ConditionalType) { + return concatenate(outerTypeParameters, getInferTypeParameters((node))); + } + const outerAndOwnTypeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations((node))); + const thisType = includeThisTypes && + (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.InterfaceDeclaration || isJSConstructor(node)) && + getDeclaredTypeOfClassOrInterface(getSymbolOfNode((node as ClassLikeDeclaration | InterfaceDeclaration))).thisType; + return thisType ? append(outerAndOwnTypeParameters, thisType) : outerAndOwnTypeParameters; } - return errorType; } - - function getInitialType(node: VariableDeclaration | BindingElement) { - return node.kind === SyntaxKind.VariableDeclaration ? - getInitialTypeOfVariableDeclaration(node) : - getInitialTypeOfBindingElement(node); + } + // The outer type parameters are those defined by enclosing generic classes, methods, or functions. + function getOuterTypeParametersOfClassOrInterface(symbol: ts.Symbol): TypeParameter[] | undefined { + const declaration = symbol.flags & SymbolFlags.Class ? symbol.valueDeclaration : getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration)!; + Debug.assert(!!declaration, "Class was missing valueDeclaration -OR- non-class had no interface declarations"); + return getOuterTypeParameters(declaration); + } + // The local type parameters are the combined set of type parameters from all declarations of the class, + // interface, or type alias. + function getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: ts.Symbol): TypeParameter[] | undefined { + let result: TypeParameter[] | undefined; + for (const node of symbol.declarations) { + if (node.kind === SyntaxKind.InterfaceDeclaration || + node.kind === SyntaxKind.ClassDeclaration || + node.kind === SyntaxKind.ClassExpression || + isJSConstructor(node) || + isTypeAlias(node)) { + const declaration = (node); + result = appendTypeParameters(result, getEffectiveTypeParameterDeclarations(declaration)); + } } - - function isEmptyArrayAssignment(node: VariableDeclaration | BindingElement | Expression) { - return node.kind === SyntaxKind.VariableDeclaration && (node).initializer && - isEmptyArrayLiteral((node).initializer!) || - node.kind !== SyntaxKind.BindingElement && node.parent.kind === SyntaxKind.BinaryExpression && - isEmptyArrayLiteral((node.parent).right); + return result; + } + // The full set of type parameters for a generic class or interface type consists of its outer type parameters plus + // its locally declared type parameters. + function getTypeParametersOfClassOrInterface(symbol: ts.Symbol): TypeParameter[] | undefined { + return concatenate(getOuterTypeParametersOfClassOrInterface(symbol), getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol)); + } + // A type is a mixin constructor if it has a single construct signature taking no type parameters and a single + // rest parameter of type any[]. + function isMixinConstructorType(type: ts.Type) { + const signatures = getSignaturesOfType(type, SignatureKind.Construct); + if (signatures.length === 1) { + const s = signatures[0]; + return !s.typeParameters && s.parameters.length === 1 && signatureHasRestParameter(s) && getElementTypeOfArrayType(getTypeOfParameter(s.parameters[0])) === anyType; + } + return false; + } + function isConstructorType(type: ts.Type): boolean { + if (getSignaturesOfType(type, SignatureKind.Construct).length > 0) { + return true; } - - function getReferenceCandidate(node: Expression): Expression { - switch (node.kind) { - case SyntaxKind.ParenthesizedExpression: - return getReferenceCandidate((node).expression); - case SyntaxKind.BinaryExpression: - switch ((node).operatorToken.kind) { - case SyntaxKind.EqualsToken: - return getReferenceCandidate((node).left); - case SyntaxKind.CommaToken: - return getReferenceCandidate((node).right); + if (type.flags & TypeFlags.TypeVariable) { + const constraint = getBaseConstraintOfType(type); + return !!constraint && isMixinConstructorType(constraint); + } + return false; + } + function getBaseTypeNodeOfClass(type: InterfaceType): ExpressionWithTypeArguments | undefined { + return getEffectiveBaseTypeNode((type.symbol.valueDeclaration as ClassLikeDeclaration)); + } + function getConstructorsForTypeArguments(type: ts.Type, typeArgumentNodes: readonly TypeNode[] | undefined, location: Node): readonly ts.Signature[] { + const typeArgCount = length(typeArgumentNodes); + const isJavascript = isInJSFile(location); + return filter(getSignaturesOfType(type, SignatureKind.Construct), sig => (isJavascript || typeArgCount >= getMinTypeArgumentCount(sig.typeParameters)) && typeArgCount <= length(sig.typeParameters)); + } + function getInstantiatedConstructorsForTypeArguments(type: ts.Type, typeArgumentNodes: readonly TypeNode[] | undefined, location: Node): readonly ts.Signature[] { + const signatures = getConstructorsForTypeArguments(type, typeArgumentNodes, location); + const typeArguments = map(typeArgumentNodes, getTypeFromTypeNode); + return sameMap(signatures, sig => some(sig.typeParameters) ? getSignatureInstantiation(sig, typeArguments, isInJSFile(location)) : sig); + } + /** + * The base constructor of a class can resolve to + * * undefinedType if the class has no extends clause, + * * unknownType if an error occurred during resolution of the extends expression, + * * nullType if the extends expression is the null value, + * * anyType if the extends expression has type any, or + * * an object type with at least one construct signature. + */ + function getBaseConstructorTypeOfClass(type: InterfaceType): ts.Type { + if (!type.resolvedBaseConstructorType) { + const decl = (type.symbol.valueDeclaration); + const extended = getEffectiveBaseTypeNode(decl); + const baseTypeNode = getBaseTypeNodeOfClass(type); + if (!baseTypeNode) { + return type.resolvedBaseConstructorType = undefinedType; + } + if (!pushTypeResolution(type, TypeSystemPropertyName.ResolvedBaseConstructorType)) { + return errorType; + } + const baseConstructorType = checkExpression(baseTypeNode.expression); + if (extended && baseTypeNode !== extended) { + Debug.assert(!extended.typeArguments); // Because this is in a JS file, and baseTypeNode is in an @extends tag + checkExpression(extended.expression); + } + if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection)) { + // Resolving the members of a class requires us to resolve the base class of that class. + // We force resolution here such that we catch circularities now. + resolveStructuredTypeMembers((baseConstructorType)); + } + if (!popTypeResolution()) { + error(type.symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_base_expression, symbolToString(type.symbol)); + return type.resolvedBaseConstructorType = errorType; + } + if (!(baseConstructorType.flags & TypeFlags.Any) && baseConstructorType !== nullWideningType && !isConstructorType(baseConstructorType)) { + const err = error(baseTypeNode.expression, Diagnostics.Type_0_is_not_a_constructor_function_type, typeToString(baseConstructorType)); + if (baseConstructorType.flags & TypeFlags.TypeParameter) { + const constraint = getConstraintFromTypeParameter(baseConstructorType); + let ctorReturn: ts.Type = unknownType; + if (constraint) { + const ctorSig = getSignaturesOfType(constraint, SignatureKind.Construct); + if (ctorSig[0]) { + ctorReturn = getReturnTypeOfSignature(ctorSig[0]); + } } + addRelatedInfo(err, createDiagnosticForNode(baseConstructorType.symbol.declarations[0], Diagnostics.Did_you_mean_for_0_to_be_constrained_to_type_new_args_Colon_any_1, symbolToString(baseConstructorType.symbol), typeToString(ctorReturn))); + } + return type.resolvedBaseConstructorType = errorType; + } + type.resolvedBaseConstructorType = baseConstructorType; + } + return type.resolvedBaseConstructorType; + } + function getBaseTypes(type: InterfaceType): BaseType[] { + if (!type.resolvedBaseTypes) { + if (type.objectFlags & ObjectFlags.Tuple) { + type.resolvedBaseTypes = [createArrayType(getUnionType(type.typeParameters || emptyArray), (type).readonly)]; + } + else if (type.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + if (type.symbol.flags & SymbolFlags.Class) { + resolveBaseTypesOfClass(type); + } + if (type.symbol.flags & SymbolFlags.Interface) { + resolveBaseTypesOfInterface(type); + } + } + else { + Debug.fail("type must be class or interface"); + } + } + return type.resolvedBaseTypes; + } + function resolveBaseTypesOfClass(type: InterfaceType) { + type.resolvedBaseTypes = resolvingEmptyArray; + const baseConstructorType = getApparentType(getBaseConstructorTypeOfClass(type)); + if (!(baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Any))) { + return type.resolvedBaseTypes = emptyArray; + } + const baseTypeNode = getBaseTypeNodeOfClass(type)!; + let baseType: ts.Type; + const originalBaseType = baseConstructorType.symbol ? getDeclaredTypeOfSymbol(baseConstructorType.symbol) : undefined; + if (baseConstructorType.symbol && baseConstructorType.symbol.flags & SymbolFlags.Class && + areAllOuterTypeParametersApplied(originalBaseType!)) { + // When base constructor type is a class with no captured type arguments we know that the constructors all have the same type parameters as the + // class and all return the instance type of the class. There is no need for further checks and we can apply the + // type arguments in the same manner as a type reference to get the same error reporting experience. + baseType = getTypeFromClassOrInterfaceReference(baseTypeNode, baseConstructorType.symbol); + } + else if (baseConstructorType.flags & TypeFlags.Any) { + baseType = baseConstructorType; + } + else { + // The class derives from a "class-like" constructor function, check that we have at least one construct signature + // with a matching number of type parameters and use the return type of the first instantiated signature. Elsewhere + // we check that all instantiated signatures return the same type. + const constructors = getInstantiatedConstructorsForTypeArguments(baseConstructorType, baseTypeNode.typeArguments, baseTypeNode); + if (!constructors.length) { + error(baseTypeNode.expression, Diagnostics.No_base_constructor_has_the_specified_number_of_type_arguments); + return type.resolvedBaseTypes = emptyArray; } - return node; + baseType = getReturnTypeOfSignature(constructors[0]); } - - function getReferenceRoot(node: Node): Node { - const { parent } = node; - return parent.kind === SyntaxKind.ParenthesizedExpression || - parent.kind === SyntaxKind.BinaryExpression && (parent).operatorToken.kind === SyntaxKind.EqualsToken && (parent).left === node || - parent.kind === SyntaxKind.BinaryExpression && (parent).operatorToken.kind === SyntaxKind.CommaToken && (parent).right === node ? - getReferenceRoot(parent) : node; + if (baseType === errorType) { + return type.resolvedBaseTypes = emptyArray; } - - function getTypeOfSwitchClause(clause: CaseClause | DefaultClause) { - if (clause.kind === SyntaxKind.CaseClause) { - return getRegularTypeOfLiteralType(getTypeOfExpression(clause.expression)); - } - return neverType; + if (!isValidBaseType(baseType)) { + error(baseTypeNode.expression, Diagnostics.Base_constructor_return_type_0_is_not_an_object_type_or_intersection_of_object_types_with_statically_known_members, typeToString(baseType)); + return type.resolvedBaseTypes = emptyArray; } - - function getSwitchClauseTypes(switchStatement: SwitchStatement): Type[] { - const links = getNodeLinks(switchStatement); - if (!links.switchTypes) { - links.switchTypes = []; - for (const clause of switchStatement.caseBlock.clauses) { - links.switchTypes.push(getTypeOfSwitchClause(clause)); + if (type === baseType || hasBaseType(baseType, type)) { + error(type.symbol.valueDeclaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); + return type.resolvedBaseTypes = emptyArray; + } + if (type.resolvedBaseTypes === resolvingEmptyArray) { + // Circular reference, likely through instantiation of default parameters + // (otherwise there'd be an error from hasBaseType) - this is fine, but `.members` should be reset + // as `getIndexedAccessType` via `instantiateType` via `getTypeFromClassOrInterfaceReference` forces a + // partial instantiation of the members without the base types fully resolved + type.members = undefined; + } + return type.resolvedBaseTypes = [baseType]; + } + function areAllOuterTypeParametersApplied(type: ts.Type): boolean { + // An unapplied type parameter has its symbol still the same as the matching argument symbol. + // Since parameters are applied outer-to-inner, only the last outer parameter needs to be checked. + const outerTypeParameters = (type).outerTypeParameters; + if (outerTypeParameters) { + const last = outerTypeParameters.length - 1; + const typeArguments = getTypeArguments((type)); + return outerTypeParameters[last].symbol !== typeArguments[last].symbol; + } + return true; + } + // A valid base type is `any`, an object type or intersection of object types. + function isValidBaseType(type: ts.Type): type is BaseType { + if (type.flags & TypeFlags.TypeParameter) { + const constraint = getBaseConstraintOfType(type); + if (constraint) { + return isValidBaseType(constraint); + } + } + // TODO: Given that we allow type parmeters here now, is this `!isGenericMappedType(type)` check really needed? + // There's no reason a `T` should be allowed while a `Readonly` should not. + return !!(type.flags & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.Any)) && !isGenericMappedType(type) || + !!(type.flags & TypeFlags.Intersection) && every((type).types, isValidBaseType); + } + function resolveBaseTypesOfInterface(type: InterfaceType): void { + type.resolvedBaseTypes = type.resolvedBaseTypes || emptyArray; + for (const declaration of type.symbol.declarations) { + if (declaration.kind === SyntaxKind.InterfaceDeclaration && getInterfaceBaseTypeNodes((declaration))) { + for (const node of getInterfaceBaseTypeNodes((declaration))!) { + const baseType = getTypeFromTypeNode(node); + if (baseType !== errorType) { + if (isValidBaseType(baseType)) { + if (type !== baseType && !hasBaseType(baseType, type)) { + if (type.resolvedBaseTypes === emptyArray) { + type.resolvedBaseTypes = [(baseType)]; + } + else { + type.resolvedBaseTypes.push(baseType); + } + } + else { + error(declaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); + } + } + else { + error(node, Diagnostics.An_interface_can_only_extend_an_object_type_or_intersection_of_object_types_with_statically_known_members); + } + } } } - return links.switchTypes; } - - // Get the types from all cases in a switch on `typeof`. An - // `undefined` element denotes an explicit `default` clause. - function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement): (string | undefined)[] { - const witnesses: (string | undefined)[] = []; - for (const clause of switchStatement.caseBlock.clauses) { - if (clause.kind === SyntaxKind.CaseClause) { - if (isStringLiteralLike(clause.expression)) { - witnesses.push(clause.expression.text); - continue; + } + /** + * Returns true if the interface given by the symbol is free of "this" references. + * + * Specifically, the result is true if the interface itself contains no references + * to "this" in its body, if all base types are interfaces, + * and if none of the base interfaces have a "this" type. + */ + function isThislessInterface(symbol: ts.Symbol): boolean { + for (const declaration of symbol.declarations) { + if (declaration.kind === SyntaxKind.InterfaceDeclaration) { + if (declaration.flags & NodeFlags.ContainsThis) { + return false; + } + const baseTypeNodes = getInterfaceBaseTypeNodes((declaration)); + if (baseTypeNodes) { + for (const node of baseTypeNodes) { + if (isEntityNameExpression(node.expression)) { + const baseSymbol = resolveEntityName(node.expression, SymbolFlags.Type, /*ignoreErrors*/ true); + if (!baseSymbol || !(baseSymbol.flags & SymbolFlags.Interface) || getDeclaredTypeOfClassOrInterface(baseSymbol).thisType) { + return false; + } + } } - return emptyArray; } - witnesses.push(/*explicitDefaultStatement*/ undefined); } - return witnesses; - } - - function eachTypeContainedIn(source: Type, types: Type[]) { - return source.flags & TypeFlags.Union ? !forEach((source).types, t => !contains(types, t)) : contains(types, source); - } - - function isTypeSubsetOf(source: Type, target: Type) { - return source === target || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, target); } - - function isTypeSubsetOfUnion(source: Type, target: UnionType) { - if (source.flags & TypeFlags.Union) { - for (const t of (source).types) { - if (!containsType(target.types, t)) { - return false; - } + return true; + } + function getDeclaredTypeOfClassOrInterface(symbol: ts.Symbol): InterfaceType { + let links = getSymbolLinks(symbol); + const originalLinks = links; + if (!links.declaredType) { + const kind = symbol.flags & SymbolFlags.Class ? ObjectFlags.Class : ObjectFlags.Interface; + const merged = mergeJSSymbols(symbol, getAssignedClassSymbol(symbol.valueDeclaration)); + if (merged) { + // note:we overwrite links because we just cloned the symbol + symbol = links = merged; + } + const type = originalLinks.declaredType = links.declaredType = (createObjectType(kind, symbol)); + const outerTypeParameters = getOuterTypeParametersOfClassOrInterface(symbol); + const localTypeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); + // A class or interface is generic if it has type parameters or a "this" type. We always give classes a "this" type + // because it is not feasible to analyze all members to determine if the "this" type escapes the class (in particular, + // property types inferred from initializers and method return types inferred from return statements are very hard + // to exhaustively analyze). We give interfaces a "this" type if we can't definitely determine that they are free of + // "this" references. + if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || !isThislessInterface(symbol)) { + type.objectFlags |= ObjectFlags.Reference; + type.typeParameters = concatenate(outerTypeParameters, localTypeParameters); + type.outerTypeParameters = outerTypeParameters; + type.localTypeParameters = localTypeParameters; + (type).instantiations = createMap(); + (type).instantiations.set(getTypeListId(type.typeParameters), (type)); + (type).target = (type); + (type).resolvedTypeArguments = type.typeParameters; + type.thisType = createTypeParameter(symbol); + type.thisType.isThisType = true; + type.thisType.constraint = type; + } + } + return links.declaredType; + } + function getDeclaredTypeOfTypeAlias(symbol: ts.Symbol): ts.Type { + const links = getSymbolLinks(symbol); + if (!links.declaredType) { + // Note that we use the links object as the target here because the symbol object is used as the unique + // identity for resolution of the 'type' property in SymbolLinks. + if (!pushTypeResolution(symbol, TypeSystemPropertyName.DeclaredType)) { + return errorType; + } + const declaration = find(symbol.declarations, isTypeAlias); + if (!declaration) { + return Debug.fail("Type alias symbol with no valid declaration found"); + } + const typeNode = isJSDocTypeAlias(declaration) ? declaration.typeExpression : declaration.type; + // If typeNode is missing, we will error in checkJSDocTypedefTag. + let type = typeNode ? getTypeFromTypeNode(typeNode) : errorType; + if (popTypeResolution()) { + const typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); + if (typeParameters) { + // Initialize the instantiation cache for generic type aliases. The declared type corresponds to + // an instantiation of the type alias with the type parameters supplied as type arguments. + links.typeParameters = typeParameters; + links.instantiations = createMap(); + links.instantiations.set(getTypeListId(typeParameters), type); } - return true; } - if (source.flags & TypeFlags.EnumLiteral && getBaseTypeOfEnumLiteralType(source) === target) { - return true; + else { + type = errorType; + error(isJSDocEnumTag(declaration) ? declaration : declaration.name || declaration, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol)); } - return containsType(target.types, source); + links.declaredType = type; } - - function forEachType(type: Type, f: (t: Type) => T | undefined): T | undefined { - return type.flags & TypeFlags.Union ? forEach((type).types, f) : f(type); + return links.declaredType; + } + function isStringConcatExpression(expr: Node): boolean { + if (isStringLiteralLike(expr)) { + return true; } - - function everyType(type: Type, f: (t: Type) => boolean): boolean { - return type.flags & TypeFlags.Union ? every((type).types, f) : f(type); + else if (expr.kind === SyntaxKind.BinaryExpression) { + return isStringConcatExpression((expr).left) && isStringConcatExpression((expr).right); } - - function filterType(type: Type, f: (t: Type) => boolean): Type { - if (type.flags & TypeFlags.Union) { - const types = (type).types; - const filtered = filter(types, f); - return filtered === types ? type : getUnionTypeFromSortedList(filtered, (type).objectFlags); - } - return f(type) ? type : neverType; + return false; + } + function isLiteralEnumMember(member: EnumMember) { + const expr = member.initializer; + if (!expr) { + return !(member.flags & NodeFlags.Ambient); + } + switch (expr.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + return true; + case SyntaxKind.PrefixUnaryExpression: + return (expr).operator === SyntaxKind.MinusToken && + (expr).operand.kind === SyntaxKind.NumericLiteral; + case SyntaxKind.Identifier: + return nodeIsMissing(expr) || !!getSymbolOfNode(member.parent).exports!.get((expr).escapedText); + case SyntaxKind.BinaryExpression: + return isStringConcatExpression(expr); + default: + return false; } - - function countTypes(type: Type) { - return type.flags & TypeFlags.Union ? (type as UnionType).types.length : 1; + } + function getEnumKind(symbol: ts.Symbol): EnumKind { + const links = getSymbolLinks(symbol); + if (links.enumKind !== undefined) { + return links.enumKind; } - - // Apply a mapping function to a type and return the resulting type. If the source type - // is a union type, the mapping function is applied to each constituent type and a union - // of the resulting types is returned. - function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean): Type; - function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined; - function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined { - if (type.flags & TypeFlags.Never) { - return type; - } - if (!(type.flags & TypeFlags.Union)) { - return mapper(type); - } - let mappedTypes: Type[] | undefined; - for (const t of (type).types) { - const mapped = mapper(t); - if (mapped) { - if (!mappedTypes) { - mappedTypes = [mapped]; + let hasNonLiteralMember = false; + for (const declaration of symbol.declarations) { + if (declaration.kind === SyntaxKind.EnumDeclaration) { + for (const member of (declaration).members) { + if (member.initializer && isStringLiteralLike(member.initializer)) { + return links.enumKind = EnumKind.Literal; } - else { - mappedTypes.push(mapped); + if (!isLiteralEnumMember(member)) { + hasNonLiteralMember = true; } } } - return mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal); } - - function extractTypesOfKind(type: Type, kind: TypeFlags) { - return filterType(type, t => (t.flags & kind) !== 0); + return links.enumKind = hasNonLiteralMember ? EnumKind.Numeric : EnumKind.Literal; + } + function getBaseTypeOfEnumLiteralType(type: ts.Type) { + return type.flags & TypeFlags.EnumLiteral && !(type.flags & TypeFlags.Union) ? getDeclaredTypeOfSymbol(getParentOfSymbol(type.symbol)!) : type; + } + function getDeclaredTypeOfEnum(symbol: ts.Symbol): ts.Type { + const links = getSymbolLinks(symbol); + if (links.declaredType) { + return links.declaredType; } - - // Return a new type in which occurrences of the string and number primitive types in - // typeWithPrimitives have been replaced with occurrences of string literals and numeric - // literals in typeWithLiterals, respectively. - function replacePrimitivesWithLiterals(typeWithPrimitives: Type, typeWithLiterals: Type) { - if (isTypeSubsetOf(stringType, typeWithPrimitives) && maybeTypeOfKind(typeWithLiterals, TypeFlags.StringLiteral) || - isTypeSubsetOf(numberType, typeWithPrimitives) && maybeTypeOfKind(typeWithLiterals, TypeFlags.NumberLiteral) || - isTypeSubsetOf(bigintType, typeWithPrimitives) && maybeTypeOfKind(typeWithLiterals, TypeFlags.BigIntLiteral)) { - return mapType(typeWithPrimitives, t => - t.flags & TypeFlags.String ? extractTypesOfKind(typeWithLiterals, TypeFlags.String | TypeFlags.StringLiteral) : - t.flags & TypeFlags.Number ? extractTypesOfKind(typeWithLiterals, TypeFlags.Number | TypeFlags.NumberLiteral) : - t.flags & TypeFlags.BigInt ? extractTypesOfKind(typeWithLiterals, TypeFlags.BigInt | TypeFlags.BigIntLiteral) : t); + if (getEnumKind(symbol) === EnumKind.Literal) { + enumCount++; + const memberTypeList: ts.Type[] = []; + for (const declaration of symbol.declarations) { + if (declaration.kind === SyntaxKind.EnumDeclaration) { + for (const member of (declaration).members) { + const value = getEnumMemberValue(member); + const memberType = getFreshTypeOfLiteralType(getLiteralType(value !== undefined ? value : 0, enumCount, getSymbolOfNode(member))); + getSymbolLinks(getSymbolOfNode(member)).declaredType = memberType; + memberTypeList.push(getRegularTypeOfLiteralType(memberType)); + } + } + } + if (memberTypeList.length) { + const enumType = getUnionType(memberTypeList, UnionReduction.Literal, symbol, /*aliasTypeArguments*/ undefined); + if (enumType.flags & TypeFlags.Union) { + enumType.flags |= TypeFlags.EnumLiteral; + enumType.symbol = symbol; + } + return links.declaredType = enumType; } - return typeWithPrimitives; - } - - function isIncomplete(flowType: FlowType) { - return flowType.flags === 0; - } - - function getTypeFromFlowType(flowType: FlowType) { - return flowType.flags === 0 ? (flowType).type : flowType; } - - function createFlowType(type: Type, incomplete: boolean): FlowType { - return incomplete ? { flags: 0, type } : type; + const enumType = createType(TypeFlags.Enum); + enumType.symbol = symbol; + return links.declaredType = enumType; + } + function getDeclaredTypeOfEnumMember(symbol: ts.Symbol): ts.Type { + const links = getSymbolLinks(symbol); + if (!links.declaredType) { + const enumType = getDeclaredTypeOfEnum(getParentOfSymbol(symbol)!); + if (!links.declaredType) { + links.declaredType = enumType; + } } - - // An evolving array type tracks the element types that have so far been seen in an - // 'x.push(value)' or 'x[n] = value' operation along the control flow graph. Evolving - // array types are ultimately converted into manifest array types (using getFinalArrayType) - // and never escape the getFlowTypeOfReference function. - function createEvolvingArrayType(elementType: Type): EvolvingArrayType { - const result = createObjectType(ObjectFlags.EvolvingArray); - result.elementType = elementType; - return result; + return links.declaredType; + } + function getDeclaredTypeOfTypeParameter(symbol: ts.Symbol): TypeParameter { + const links = getSymbolLinks(symbol); + return links.declaredType || (links.declaredType = createTypeParameter(symbol)); + } + function getDeclaredTypeOfAlias(symbol: ts.Symbol): ts.Type { + const links = getSymbolLinks(symbol); + return links.declaredType || (links.declaredType = getDeclaredTypeOfSymbol(resolveAlias(symbol))); + } + function getDeclaredTypeOfSymbol(symbol: ts.Symbol): ts.Type { + return tryGetDeclaredTypeOfSymbol(symbol) || errorType; + } + function tryGetDeclaredTypeOfSymbol(symbol: ts.Symbol): ts.Type | undefined { + if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + return getDeclaredTypeOfClassOrInterface(symbol); } - - function getEvolvingArrayType(elementType: Type): EvolvingArrayType { - return evolvingArrayTypes[elementType.id] || (evolvingArrayTypes[elementType.id] = createEvolvingArrayType(elementType)); + if (symbol.flags & SymbolFlags.TypeAlias) { + return getDeclaredTypeOfTypeAlias(symbol); } - - // When adding evolving array element types we do not perform subtype reduction. Instead, - // we defer subtype reduction until the evolving array type is finalized into a manifest - // array type. - function addEvolvingArrayElementType(evolvingArrayType: EvolvingArrayType, node: Expression): EvolvingArrayType { - const elementType = getBaseTypeOfLiteralType(getContextFreeTypeOfExpression(node)); - return isTypeSubsetOf(elementType, evolvingArrayType.elementType) ? evolvingArrayType : getEvolvingArrayType(getUnionType([evolvingArrayType.elementType, elementType])); + if (symbol.flags & SymbolFlags.TypeParameter) { + return getDeclaredTypeOfTypeParameter(symbol); } - - function createFinalArrayType(elementType: Type) { - return elementType.flags & TypeFlags.Never ? - autoArrayType : - createArrayType(elementType.flags & TypeFlags.Union ? - getUnionType((elementType).types, UnionReduction.Subtype) : - elementType); + if (symbol.flags & SymbolFlags.Enum) { + return getDeclaredTypeOfEnum(symbol); } - - // We perform subtype reduction upon obtaining the final array type from an evolving array type. - function getFinalArrayType(evolvingArrayType: EvolvingArrayType): Type { - return evolvingArrayType.finalArrayType || (evolvingArrayType.finalArrayType = createFinalArrayType(evolvingArrayType.elementType)); + if (symbol.flags & SymbolFlags.EnumMember) { + return getDeclaredTypeOfEnumMember(symbol); } - - function finalizeEvolvingArrayType(type: Type): Type { - return getObjectFlags(type) & ObjectFlags.EvolvingArray ? getFinalArrayType(type) : type; + if (symbol.flags & SymbolFlags.Alias) { + return getDeclaredTypeOfAlias(symbol); } - - function getElementTypeOfEvolvingArrayType(type: Type) { - return getObjectFlags(type) & ObjectFlags.EvolvingArray ? (type).elementType : neverType; + return undefined; + } + /** + * A type is free of this references if it's the any, string, number, boolean, symbol, or void keyword, a string + * literal type, an array with an element type that is free of this references, or a type reference that is + * free of this references. + */ + function isThislessType(node: TypeNode): boolean { + switch (node.kind) { + case SyntaxKind.AnyKeyword: + case SyntaxKind.UnknownKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.BigIntKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.SymbolKeyword: + case SyntaxKind.ObjectKeyword: + case SyntaxKind.VoidKeyword: + case SyntaxKind.UndefinedKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.NeverKeyword: + case SyntaxKind.LiteralType: + return true; + case SyntaxKind.ArrayType: + return isThislessType((node).elementType); + case SyntaxKind.TypeReference: + return !(node as TypeReferenceNode).typeArguments || (node as TypeReferenceNode).typeArguments!.every(isThislessType); } - - function isEvolvingArrayTypeList(types: Type[]) { - let hasEvolvingArrayType = false; - for (const t of types) { - if (!(t.flags & TypeFlags.Never)) { - if (!(getObjectFlags(t) & ObjectFlags.EvolvingArray)) { - return false; - } - hasEvolvingArrayType = true; + return false; + } + /** A type parameter is thisless if its constraint is thisless, or if it has no constraint. */ + function isThislessTypeParameter(node: TypeParameterDeclaration) { + const constraint = getEffectiveConstraintOfTypeParameter(node); + return !constraint || isThislessType(constraint); + } + /** + * A variable-like declaration is free of this references if it has a type annotation + * that is thisless, or if it has no type annotation and no initializer (and is thus of type any). + */ + function isThislessVariableLikeDeclaration(node: VariableLikeDeclaration): boolean { + const typeNode = getEffectiveTypeAnnotationNode(node); + return typeNode ? isThislessType(typeNode) : !hasInitializer(node); + } + /** + * A function-like declaration is considered free of `this` references if it has a return type + * annotation that is free of this references and if each parameter is thisless and if + * each type parameter (if present) is thisless. + */ + function isThislessFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { + const returnType = getEffectiveReturnTypeNode(node); + const typeParameters = getEffectiveTypeParameterDeclarations(node); + return (node.kind === SyntaxKind.Constructor || (!!returnType && isThislessType(returnType))) && + node.parameters.every(isThislessVariableLikeDeclaration) && + typeParameters.every(isThislessTypeParameter); + } + /** + * Returns true if the class or interface member given by the symbol is free of "this" references. The + * function may return false for symbols that are actually free of "this" references because it is not + * feasible to perform a complete analysis in all cases. In particular, property members with types + * inferred from their initializers and function members with inferred return types are conservatively + * assumed not to be free of "this" references. + */ + function isThisless(symbol: ts.Symbol): boolean { + if (symbol.declarations && symbol.declarations.length === 1) { + const declaration = symbol.declarations[0]; + if (declaration) { + switch (declaration.kind) { + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + return isThislessVariableLikeDeclaration((declaration)); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return isThislessFunctionLikeDeclaration((declaration)); } } - return hasEvolvingArrayType; } - - // At flow control branch or loop junctions, if the type along every antecedent code path - // is an evolving array type, we construct a combined evolving array type. Otherwise we - // finalize all evolving array types. - function getUnionOrEvolvingArrayType(types: Type[], subtypeReduction: UnionReduction) { - return isEvolvingArrayTypeList(types) ? - getEvolvingArrayType(getUnionType(map(types, getElementTypeOfEvolvingArrayType))) : - getUnionType(sameMap(types, finalizeEvolvingArrayType), subtypeReduction); - } - - // Return true if the given node is 'x' in an 'x.length', x.push(value)', 'x.unshift(value)' or - // 'x[n] = value' operation, where 'n' is an expression of type any, undefined, or a number-like type. - function isEvolvingArrayOperationTarget(node: Node) { - const root = getReferenceRoot(node); - const parent = root.parent; - const isLengthPushOrUnshift = parent.kind === SyntaxKind.PropertyAccessExpression && ( - (parent).name.escapedText === "length" || - parent.parent.kind === SyntaxKind.CallExpression && isPushOrUnshiftIdentifier((parent).name)); - const isElementAssignment = parent.kind === SyntaxKind.ElementAccessExpression && - (parent).expression === root && - parent.parent.kind === SyntaxKind.BinaryExpression && - (parent.parent).operatorToken.kind === SyntaxKind.EqualsToken && - (parent.parent).left === parent && - !isAssignmentTarget(parent.parent) && - isTypeAssignableToKind(getTypeOfExpression((parent).argumentExpression), TypeFlags.NumberLike); - return isLengthPushOrUnshift || isElementAssignment; - } - - function isDeclarationWithExplicitTypeAnnotation(declaration: Declaration | undefined) { - return !!(declaration && ( - declaration.kind === SyntaxKind.VariableDeclaration || declaration.kind === SyntaxKind.Parameter || - declaration.kind === SyntaxKind.PropertyDeclaration || declaration.kind === SyntaxKind.PropertySignature) && - getEffectiveTypeAnnotationNode(declaration as VariableDeclaration | ParameterDeclaration | PropertyDeclaration | PropertySignature)); - } - - function getExplicitTypeOfSymbol(symbol: Symbol, diagnostic?: Diagnostic) { - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.ValueModule)) { - return getTypeOfSymbol(symbol); - } - if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { - if (isDeclarationWithExplicitTypeAnnotation(symbol.valueDeclaration)) { - return getTypeOfSymbol(symbol); - } - if (diagnostic && symbol.valueDeclaration) { - addRelatedInfo(diagnostic, createDiagnosticForNode(symbol.valueDeclaration, Diagnostics._0_is_declared_here, symbolToString(symbol))); - } + return false; + } + // The mappingThisOnly flag indicates that the only type parameter being mapped is "this". When the flag is true, + // we check symbols to see if we can quickly conclude they are free of "this" references, thus needing no instantiation. + function createInstantiatedSymbolTable(symbols: ts.Symbol[], mapper: TypeMapper, mappingThisOnly: boolean): SymbolTable { + const result = createSymbolTable(); + for (const symbol of symbols) { + result.set(symbol.escapedName, mappingThisOnly && isThisless(symbol) ? symbol : instantiateSymbol(symbol, mapper)); + } + return result; + } + function addInheritedMembers(symbols: SymbolTable, baseSymbols: ts.Symbol[]) { + for (const s of baseSymbols) { + if (!symbols.has(s.escapedName)) { + symbols.set(s.escapedName, s); } } - - // We require the dotted function name in an assertion expression to be comprised of identifiers - // that reference function, method, class or value module symbols; or variable, property or - // parameter symbols with declarations that have explicit type annotations. Such references are - // resolvable with no possibility of triggering circularities in control flow analysis. - function getTypeOfDottedName(node: Expression, diagnostic: Diagnostic | undefined): Type | undefined { - if (!(node.flags & NodeFlags.InWithStatement)) { - switch (node.kind) { - case SyntaxKind.Identifier: - const symbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(node)); - return getExplicitTypeOfSymbol(symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol, diagnostic); - case SyntaxKind.ThisKeyword: - return getExplicitThisType(node); - case SyntaxKind.PropertyAccessExpression: - const type = getTypeOfDottedName((node).expression, diagnostic); - const prop = type && getPropertyOfType(type, (node).name.escapedText); - return prop && getExplicitTypeOfSymbol(prop, diagnostic); - case SyntaxKind.ParenthesizedExpression: - return getTypeOfDottedName((node).expression, diagnostic); - } - } + } + function resolveDeclaredMembers(type: InterfaceType): InterfaceTypeWithDeclaredMembers { + if (!(type).declaredProperties) { + const symbol = type.symbol; + const members = getMembersOfSymbol(symbol); + (type).declaredProperties = getNamedMembers(members); + // Start with signatures at empty array in case of recursive types + (type).declaredCallSignatures = emptyArray; + (type).declaredConstructSignatures = emptyArray; + (type).declaredCallSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.Call)); + (type).declaredConstructSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.New)); + (type).declaredStringIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.String); + (type).declaredNumberIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.Number); + } + return type; + } + /** + * Indicates whether a type can be used as a property name. + */ + function isTypeUsableAsPropertyName(type: ts.Type): type is StringLiteralType | NumberLiteralType | UniqueESSymbolType { + return !!(type.flags & TypeFlags.StringOrNumberLiteralOrUnique); + } + /** + * Indicates whether a declaration name is definitely late-bindable. + * A declaration name is only late-bindable if: + * - It is a `ComputedPropertyName`. + * - Its expression is an `Identifier` or either a `PropertyAccessExpression` an + * `ElementAccessExpression` consisting only of these same three types of nodes. + * - The type of its expression is a string or numeric literal type, or is a `unique symbol` type. + */ + function isLateBindableName(node: DeclarationName): node is LateBoundName { + if (!isComputedPropertyName(node) && !isElementAccessExpression(node)) { + return false; } - - function getEffectsSignature(node: CallExpression) { - const links = getNodeLinks(node); - let signature = links.effectsSignature; - if (signature === undefined) { - // A call expression parented by an expression statement is a potential assertion. Other call - // expressions are potential type predicate function calls. In order to avoid triggering - // circularities in control flow analysis, we use getTypeOfDottedName when resolving the call - // target expression of an assertion. - let funcType: Type | undefined; - if (node.parent.kind === SyntaxKind.ExpressionStatement) { - funcType = getTypeOfDottedName(node.expression, /*diagnostic*/ undefined); - } - else if (node.expression.kind !== SyntaxKind.SuperKeyword) { - if (isOptionalChain(node)) { - funcType = checkNonNullType( - getOptionalExpressionType(checkExpression(node.expression), node.expression), - node.expression - ); - } - else { - funcType = checkNonNullExpression(node.expression); - } - } - const signatures = getSignaturesOfType(funcType && getApparentType(funcType) || unknownType, SignatureKind.Call); - const candidate = signatures.length === 1 && !signatures[0].typeParameters ? signatures[0] : - some(signatures, hasTypePredicateOrNeverReturnType) ? getResolvedSignature(node) : - undefined; - signature = links.effectsSignature = candidate && hasTypePredicateOrNeverReturnType(candidate) ? candidate : unknownSignature; - } - return signature === unknownSignature ? undefined : signature; + const expr = isComputedPropertyName(node) ? node.expression : node.argumentExpression; + return isEntityNameExpression(expr) + && isTypeUsableAsPropertyName(isComputedPropertyName(node) ? checkComputedPropertyName(node) : checkExpressionCached(expr)); + } + function isLateBoundName(name: __String): boolean { + return (name as string).charCodeAt(0) === CharacterCodes._ && + (name as string).charCodeAt(1) === CharacterCodes._ && + (name as string).charCodeAt(2) === CharacterCodes.at; + } + /** + * Indicates whether a declaration has a late-bindable dynamic name. + */ + function hasLateBindableName(node: Declaration): node is LateBoundDeclaration | LateBoundBinaryExpressionDeclaration { + const name = getNameOfDeclaration(node); + return !!name && isLateBindableName(name); + } + /** + * Indicates whether a declaration has a dynamic name that cannot be late-bound. + */ + function hasNonBindableDynamicName(node: Declaration) { + return hasDynamicName(node) && !hasLateBindableName(node); + } + /** + * Indicates whether a declaration name is a dynamic name that cannot be late-bound. + */ + function isNonBindableDynamicName(node: DeclarationName) { + return isDynamicName(node) && !isLateBindableName(node); + } + /** + * Gets the symbolic name for a member from its type. + */ + function getPropertyNameFromType(type: StringLiteralType | NumberLiteralType | UniqueESSymbolType): __String { + if (type.flags & TypeFlags.UniqueESSymbol) { + return (type).escapedName; } - - function hasTypePredicateOrNeverReturnType(signature: Signature) { - return !!(getTypePredicateOfSignature(signature) || - signature.declaration && (getReturnTypeFromAnnotation(signature.declaration) || unknownType).flags & TypeFlags.Never); + if (type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { + return escapeLeadingUnderscores("" + (type).value); } - - function getTypePredicateArgument(predicate: TypePredicate, callExpression: CallExpression) { - if (predicate.kind === TypePredicateKind.Identifier || predicate.kind === TypePredicateKind.AssertsIdentifier) { - return callExpression.arguments[predicate.parameterIndex]; + return Debug.fail(); + } + /** + * Adds a declaration to a late-bound dynamic member. This performs the same function for + * late-bound members that `addDeclarationToSymbol` in binder.ts performs for early-bound + * members. + */ + function addDeclarationToLateBoundSymbol(symbol: ts.Symbol, member: LateBoundDeclaration | BinaryExpression, symbolFlags: SymbolFlags) { + Debug.assert(!!(getCheckFlags(symbol) & CheckFlags.Late), "Expected a late-bound symbol."); + symbol.flags |= symbolFlags; + getSymbolLinks(member.symbol).lateSymbol = symbol; + if (!symbol.declarations) { + symbol.declarations = [member]; + } + else { + symbol.declarations.push(member); + } + if (symbolFlags & SymbolFlags.Value) { + if (!symbol.valueDeclaration || symbol.valueDeclaration.kind !== member.kind) { + symbol.valueDeclaration = member; } - const invokedExpression = skipParentheses(callExpression.expression); - return isAccessExpression(invokedExpression) ? skipParentheses(invokedExpression.expression) : undefined; - } - - function reportFlowControlError(node: Node) { - const block = findAncestor(node, isFunctionOrModuleBlock); - const sourceFile = getSourceFileOfNode(node); - const span = getSpanOfTokenAtPosition(sourceFile, block.statements.pos); - diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.The_containing_function_or_module_body_is_too_large_for_control_flow_analysis)); - } - - function isReachableFlowNode(flow: FlowNode) { - const result = isReachableFlowNodeWorker(flow, /*skipCacheCheck*/ false); - lastFlowNode = flow; - lastFlowNodeReachable = result; - return result; - } - - function isUnlockedReachableFlowNode(flow: FlowNode) { - return !(flow.flags & FlowFlags.PreFinally && (flow).lock.locked) && isReachableFlowNodeWorker(flow, /*skipCacheCheck*/ false); - } - - function isFalseExpression(expr: Expression): boolean { - const node = skipParentheses(expr); - return node.kind === SyntaxKind.FalseKeyword || node.kind === SyntaxKind.BinaryExpression && ( - (node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken && (isFalseExpression((node).left) || isFalseExpression((node).right)) || - (node).operatorToken.kind === SyntaxKind.BarBarToken && isFalseExpression((node).left) && isFalseExpression((node).right)); } - - function isReachableFlowNodeWorker(flow: FlowNode, noCacheCheck: boolean): boolean { - while (true) { - if (flow === lastFlowNode) { - return lastFlowNodeReachable; - } - const flags = flow.flags; - if (flags & FlowFlags.Shared) { - if (!noCacheCheck) { - const id = getFlowNodeId(flow); - const reachable = flowNodeReachable[id]; - return reachable !== undefined ? reachable : (flowNodeReachable[id] = isReachableFlowNodeWorker(flow, /*skipCacheCheck*/ true)); - } - noCacheCheck = false; + } + /** + * Performs late-binding of a dynamic member. This performs the same function for + * late-bound members that `declareSymbol` in binder.ts performs for early-bound + * members. + * + * If a symbol is a dynamic name from a computed property, we perform an additional "late" + * binding phase to attempt to resolve the name for the symbol from the type of the computed + * property's expression. If the type of the expression is a string-literal, numeric-literal, + * or unique symbol type, we can use that type as the name of the symbol. + * + * For example, given: + * + * const x = Symbol(); + * + * interface I { + * [x]: number; + * } + * + * The binder gives the property `[x]: number` a special symbol with the name "__computed". + * In the late-binding phase we can type-check the expression `x` and see that it has a + * unique symbol type which we can then use as the name of the member. This allows users + * to define custom symbols that can be used in the members of an object type. + * + * @param parent The containing symbol for the member. + * @param earlySymbols The early-bound symbols of the parent. + * @param lateSymbols The late-bound symbols of the parent. + * @param decl The member to bind. + */ + function lateBindMember(parent: ts.Symbol, earlySymbols: SymbolTable | undefined, lateSymbols: SymbolTable, decl: LateBoundDeclaration | LateBoundBinaryExpressionDeclaration) { + Debug.assert(!!decl.symbol, "The member is expected to have a symbol."); + const links = getNodeLinks(decl); + if (!links.resolvedSymbol) { + // In the event we attempt to resolve the late-bound name of this member recursively, + // fall back to the early-bound name of this member. + links.resolvedSymbol = decl.symbol; + const declName = isBinaryExpression(decl) ? decl.left : decl.name; + const type = isElementAccessExpression(declName) ? checkExpressionCached(declName.argumentExpression) : checkComputedPropertyName(declName); + if (isTypeUsableAsPropertyName(type)) { + const memberName = getPropertyNameFromType(type); + const symbolFlags = decl.symbol.flags; + // Get or add a late-bound symbol for the member. This allows us to merge late-bound accessor declarations. + let lateSymbol = lateSymbols.get(memberName); + if (!lateSymbol) + lateSymbols.set(memberName, lateSymbol = createSymbol(SymbolFlags.None, memberName, CheckFlags.Late)); + // Report an error if a late-bound member has the same name as an early-bound member, + // or if we have another early-bound symbol declaration with the same name and + // conflicting flags. + const earlySymbol = earlySymbols && earlySymbols.get(memberName); + if (lateSymbol.flags & getExcludedSymbolFlags(symbolFlags) || earlySymbol) { + // If we have an existing early-bound member, combine its declarations so that we can + // report an error at each declaration. + const declarations = earlySymbol ? concatenate(earlySymbol.declarations, lateSymbol.declarations) : lateSymbol.declarations; + const name = !(type.flags & TypeFlags.UniqueESSymbol) && unescapeLeadingUnderscores(memberName) || declarationNameToString(declName); + forEach(declarations, declaration => error(getNameOfDeclaration(declaration) || declaration, Diagnostics.Property_0_was_also_declared_here, name)); + error(declName || decl, Diagnostics.Duplicate_property_0, name); + lateSymbol = createSymbol(SymbolFlags.None, memberName, CheckFlags.Late); + } + lateSymbol.nameType = type; + addDeclarationToLateBoundSymbol(lateSymbol, decl, symbolFlags); + if (lateSymbol.parent) { + Debug.assert(lateSymbol.parent === parent, "Existing symbol parent should match new one"); } - if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation | FlowFlags.PreFinally)) { - flow = (flow).antecedent; + else { + lateSymbol.parent = parent; } - else if (flags & FlowFlags.Call) { - const signature = getEffectsSignature((flow).node); - if (signature) { - const predicate = getTypePredicateOfSignature(signature); - if (predicate && predicate.kind === TypePredicateKind.AssertsIdentifier) { - const predicateArgument = (flow).node.arguments[predicate.parameterIndex]; - if (predicateArgument && isFalseExpression(predicateArgument)) { - return false; - } - } - if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) { - return false; + return links.resolvedSymbol = lateSymbol; + } + } + return links.resolvedSymbol; + } + function getResolvedMembersOrExportsOfSymbol(symbol: ts.Symbol, resolutionKind: MembersOrExportsResolutionKind): UnderscoreEscapedMap { + const links = getSymbolLinks(symbol); + if (!links[resolutionKind]) { + const isStatic = resolutionKind === MembersOrExportsResolutionKind.resolvedExports; + const earlySymbols = !isStatic ? symbol.members : + symbol.flags & SymbolFlags.Module ? getExportsOfModuleWorker(symbol) : + symbol.exports; + // In the event we recursively resolve the members/exports of the symbol, we + // set the initial value of resolvedMembers/resolvedExports to the early-bound + // members/exports of the symbol. + links[resolutionKind] = earlySymbols || emptySymbols; + // fill in any as-yet-unresolved late-bound members. + const lateSymbols = createSymbolTable(); + for (const decl of symbol.declarations) { + const members = getMembersOfDeclaration(decl); + if (members) { + for (const member of members) { + if (isStatic === hasStaticModifier(member) && hasLateBindableName(member)) { + lateBindMember(symbol, earlySymbols, lateSymbols, member); } } - flow = (flow).antecedent; - } - else if (flags & FlowFlags.BranchLabel) { - // A branching point is reachable if any branch is reachable. - return some((flow).antecedents, isUnlockedReachableFlowNode); } - else if (flags & FlowFlags.LoopLabel) { - // A loop is reachable if the control flow path that leads to the top is reachable. - flow = (flow).antecedents![0]; - } - else if (flags & FlowFlags.SwitchClause) { - // The control flow path representing an unmatched value in a switch statement with - // no default clause is unreachable if the switch statement is exhaustive. - if ((flow).clauseStart === (flow).clauseEnd && isExhaustiveSwitchStatement((flow).switchStatement)) { - return false; + } + const assignments = symbol.assignmentDeclarationMembers; + if (assignments) { + const decls = arrayFrom(assignments.values()); + for (const member of decls) { + const assignmentKind = getAssignmentDeclarationKind((member as BinaryExpression | CallExpression)); + const isInstanceMember = assignmentKind === AssignmentDeclarationKind.PrototypeProperty + || assignmentKind === AssignmentDeclarationKind.ThisProperty + || assignmentKind === AssignmentDeclarationKind.ObjectDefinePrototypeProperty + || assignmentKind === AssignmentDeclarationKind.Prototype; // A straight `Prototype` assignment probably can never have a computed name + if (isStatic === !isInstanceMember && hasLateBindableName(member)) { + lateBindMember(symbol, earlySymbols, lateSymbols, member); } - flow = (flow).antecedent; } - else if (flags & FlowFlags.AfterFinally) { - // Cache is unreliable once we start locking nodes - lastFlowNode = undefined; - (flow).locked = true; - const result = isReachableFlowNodeWorker((flow).antecedent, /*skipCacheCheck*/ false); - (flow).locked = false; - return result; + } + links[resolutionKind] = combineSymbolTables(earlySymbols, lateSymbols) || emptySymbols; + } + return links[resolutionKind]!; + } + /** + * Gets a SymbolTable containing both the early- and late-bound members of a symbol. + * + * For a description of late-binding, see `lateBindMember`. + */ + function getMembersOfSymbol(symbol: ts.Symbol) { + return symbol.flags & SymbolFlags.LateBindingContainer + ? getResolvedMembersOrExportsOfSymbol(symbol, MembersOrExportsResolutionKind.resolvedMembers) + : symbol.members || emptySymbols; + } + /** + * If a symbol is the dynamic name of the member of an object type, get the late-bound + * symbol of the member. + * + * For a description of late-binding, see `lateBindMember`. + */ + function getLateBoundSymbol(symbol: ts.Symbol): ts.Symbol { + if (symbol.flags & SymbolFlags.ClassMember && symbol.escapedName === InternalSymbolName.Computed) { + const links = getSymbolLinks(symbol); + if (!links.lateSymbol && some(symbol.declarations, hasLateBindableName)) { + // force late binding of members/exports. This will set the late-bound symbol + const parent = getMergedSymbol(symbol.parent)!; + if (some(symbol.declarations, hasStaticModifier)) { + getExportsOfSymbol(parent); } else { - return !(flags & FlowFlags.Unreachable); + getMembersOfSymbol(parent); } } + return links.lateSymbol || (links.lateSymbol = symbol); } - - function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, couldBeUninitialized?: boolean) { - let key: string | undefined; - let keySet = false; - let flowDepth = 0; - if (flowAnalysisDisabled) { - return errorType; - } - if (!reference.flowNode || !couldBeUninitialized && !(declaredType.flags & TypeFlags.Narrowable)) { - return declaredType; + return symbol; + } + function getTypeWithThisArgument(type: ts.Type, thisArgument?: ts.Type, needApparentType?: boolean): ts.Type { + if (getObjectFlags(type) & ObjectFlags.Reference) { + const target = (type).target; + const typeArguments = getTypeArguments((type)); + if (length(target.typeParameters) === length(typeArguments)) { + const ref = createTypeReference(target, concatenate(typeArguments, [thisArgument || target.thisType!])); + return needApparentType ? getApparentType(ref) : ref; } - flowInvocationCount++; - const sharedFlowStart = sharedFlowCount; - const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode)); - sharedFlowCount = sharedFlowStart; - // When the reference is 'x' in an 'x.length', 'x.push(value)', 'x.unshift(value)' or x[n] = value' operation, - // we give type 'any[]' to 'x' instead of using the type determined by control flow analysis such that operations - // on empty arrays are possible without implicit any errors and new element types can be inferred without - // type mismatch errors. - const resultType = getObjectFlags(evolvedType) & ObjectFlags.EvolvingArray && isEvolvingArrayOperationTarget(reference) ? autoArrayType : finalizeEvolvingArrayType(evolvedType); - if (resultType === unreachableNeverType|| reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) { - return declaredType; + } + else if (type.flags & TypeFlags.Intersection) { + return getIntersectionType(map((type).types, t => getTypeWithThisArgument(t, thisArgument, needApparentType))); + } + return needApparentType ? getApparentType(type) : type; + } + function resolveObjectTypeMembers(type: ObjectType, source: InterfaceTypeWithDeclaredMembers, typeParameters: readonly TypeParameter[], typeArguments: readonly ts.Type[]) { + let mapper: TypeMapper; + let members: SymbolTable; + let callSignatures: readonly ts.Signature[]; + let constructSignatures: readonly ts.Signature[] | undefined; + let stringIndexInfo: IndexInfo | undefined; + let numberIndexInfo: IndexInfo | undefined; + if (rangeEquals(typeParameters, typeArguments, 0, typeParameters.length)) { + mapper = identityMapper; + members = source.symbol ? getMembersOfSymbol(source.symbol) : createSymbolTable(source.declaredProperties); + callSignatures = source.declaredCallSignatures; + constructSignatures = source.declaredConstructSignatures; + stringIndexInfo = source.declaredStringIndexInfo; + numberIndexInfo = source.declaredNumberIndexInfo; + } + else { + mapper = createTypeMapper(typeParameters, typeArguments); + members = createInstantiatedSymbolTable(source.declaredProperties, mapper, /*mappingThisOnly*/ typeParameters.length === 1); + callSignatures = instantiateSignatures(source.declaredCallSignatures, mapper); + constructSignatures = instantiateSignatures(source.declaredConstructSignatures, mapper); + stringIndexInfo = instantiateIndexInfo(source.declaredStringIndexInfo, mapper); + numberIndexInfo = instantiateIndexInfo(source.declaredNumberIndexInfo, mapper); + } + const baseTypes = getBaseTypes(source); + if (baseTypes.length) { + if (source.symbol && members === getMembersOfSymbol(source.symbol)) { + members = createSymbolTable(source.declaredProperties); } - return resultType; - - function getOrSetCacheKey() { - if (keySet) { - return key; + setStructuredTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); + const thisArgument = lastOrUndefined(typeArguments); + for (const baseType of baseTypes) { + const instantiatedBaseType = thisArgument ? getTypeWithThisArgument(instantiateType(baseType, mapper), thisArgument) : baseType; + addInheritedMembers(members, getPropertiesOfType(instantiatedBaseType)); + callSignatures = concatenate(callSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Call)); + constructSignatures = concatenate(constructSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Construct)); + if (!stringIndexInfo) { + stringIndexInfo = instantiatedBaseType === anyType ? + createIndexInfo(anyType, /*isReadonly*/ false) : + getIndexInfoOfType(instantiatedBaseType, IndexKind.String); } - keySet = true; - return key = getFlowCacheKey(reference, declaredType, initialType, flowContainer); + numberIndexInfo = numberIndexInfo || getIndexInfoOfType(instantiatedBaseType, IndexKind.Number); } - - function getTypeAtFlowNode(flow: FlowNode): FlowType { - if (flowDepth === 2000) { - // We have made 2000 recursive invocations. To avoid overflowing the call stack we report an error - // and disable further control flow analysis in the containing function or module body. - flowAnalysisDisabled = true; - reportFlowControlError(reference); - return errorType; - } - flowDepth++; - while (true) { - const flags = flow.flags; - if (flags & FlowFlags.Shared) { - // We cache results of flow type resolution for shared nodes that were previously visited in - // the same getFlowTypeOfReference invocation. A node is considered shared when it is the - // antecedent of more than one node. - for (let i = sharedFlowStart; i < sharedFlowCount; i++) { - if (sharedFlowNodes[i] === flow) { - flowDepth--; - return sharedFlowTypes[i]; - } - } - } - let type: FlowType | undefined; - if (flags & FlowFlags.AfterFinally) { - // block flow edge: finally -> pre-try (for larger explanation check comment in binder.ts - bindTryStatement - (flow).locked = true; - type = getTypeAtFlowNode((flow).antecedent); - (flow).locked = false; - } - else if (flags & FlowFlags.PreFinally) { - // locked pre-finally flows are filtered out in getTypeAtFlowBranchLabel - // so here just redirect to antecedent - flow = (flow).antecedent; - continue; - } - else if (flags & FlowFlags.Assignment) { - type = getTypeAtFlowAssignment(flow); - if (!type) { - flow = (flow).antecedent; - continue; - } - } - else if (flags & FlowFlags.Call) { - type = getTypeAtFlowCall(flow); - if (!type) { - flow = (flow).antecedent; - continue; - } - } - else if (flags & FlowFlags.Condition) { - type = getTypeAtFlowCondition(flow); - } - else if (flags & FlowFlags.SwitchClause) { - type = getTypeAtSwitchClause(flow); - } - else if (flags & FlowFlags.Label) { - if ((flow).antecedents!.length === 1) { - flow = (flow).antecedents![0]; - continue; - } - type = flags & FlowFlags.BranchLabel ? - getTypeAtFlowBranchLabel(flow) : - getTypeAtFlowLoopLabel(flow); - } - else if (flags & FlowFlags.ArrayMutation) { - type = getTypeAtFlowArrayMutation(flow); - if (!type) { - flow = (flow).antecedent; - continue; - } - } - else if (flags & FlowFlags.Start) { - // Check if we should continue with the control flow of the containing function. - const container = (flow).node; - if (container && container !== flowContainer && - reference.kind !== SyntaxKind.PropertyAccessExpression && - reference.kind !== SyntaxKind.ElementAccessExpression && - reference.kind !== SyntaxKind.ThisKeyword) { - flow = container.flowNode!; - continue; - } - // At the top of the flow we have the initial type. - type = initialType; - } - else { - // Unreachable code errors are reported in the binding phase. Here we - // simply return the non-auto declared type to reduce follow-on errors. - type = convertAutoToAny(declaredType); - } - if (flags & FlowFlags.Shared) { - // Record visited node and the associated type in the cache. - sharedFlowNodes[sharedFlowCount] = flow; - sharedFlowTypes[sharedFlowCount] = type; - sharedFlowCount++; - } - flowDepth--; - return type; - } + } + setStructuredTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); + } + function resolveClassOrInterfaceMembers(type: InterfaceType): void { + resolveObjectTypeMembers(type, resolveDeclaredMembers(type), emptyArray, emptyArray); + } + function resolveTypeReferenceMembers(type: TypeReference): void { + const source = resolveDeclaredMembers(type.target); + const typeParameters = concatenate((source.typeParameters!), [source.thisType!]); + const typeArguments = getTypeArguments(type); + const paddedTypeArguments = typeArguments.length === typeParameters.length ? typeArguments : concatenate(typeArguments, [type]); + resolveObjectTypeMembers(type, source, typeParameters, paddedTypeArguments); + } + function createSignature(declaration: SignatureDeclaration | JSDocSignature | undefined, typeParameters: readonly TypeParameter[] | undefined, thisParameter: ts.Symbol | undefined, parameters: readonly ts.Symbol[], resolvedReturnType: ts.Type | undefined, resolvedTypePredicate: TypePredicate | undefined, minArgumentCount: number, flags: SignatureFlags): ts.Signature { + const sig = new Signature(checker, flags); + sig.declaration = declaration; + sig.typeParameters = typeParameters; + sig.parameters = parameters; + sig.thisParameter = thisParameter; + sig.resolvedReturnType = resolvedReturnType; + sig.resolvedTypePredicate = resolvedTypePredicate; + sig.minArgumentCount = minArgumentCount; + sig.target = undefined; + sig.mapper = undefined; + return sig; + } + function cloneSignature(sig: ts.Signature): ts.Signature { + const result = createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, /*resolvedReturnType*/ undefined, + /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.flags & SignatureFlags.PropagatingFlags); + result.target = sig.target; + result.mapper = sig.mapper; + return result; + } + function createUnionSignature(signature: ts.Signature, unionSignatures: ts.Signature[]) { + const result = cloneSignature(signature); + result.unionSignatures = unionSignatures; + result.target = undefined; + result.mapper = undefined; + return result; + } + function getOptionalCallSignature(signature: ts.Signature, callChainFlags: SignatureFlags): ts.Signature { + if ((signature.flags & SignatureFlags.CallChainFlags) === callChainFlags) { + return signature; + } + if (!signature.optionalCallSignatureCache) { + signature.optionalCallSignatureCache = {}; + } + const key = callChainFlags === SignatureFlags.IsInnerCallChain ? "inner" : "outer"; + return signature.optionalCallSignatureCache[key] + || (signature.optionalCallSignatureCache[key] = createOptionalCallSignature(signature, callChainFlags)); + } + function createOptionalCallSignature(signature: ts.Signature, callChainFlags: SignatureFlags) { + Debug.assert(callChainFlags === SignatureFlags.IsInnerCallChain || callChainFlags === SignatureFlags.IsOuterCallChain, "An optional call signature can either be for an inner call chain or an outer call chain, but not both."); + const result = cloneSignature(signature); + result.flags |= callChainFlags; + return result; + } + function getExpandedParameters(sig: ts.Signature): readonly ts.Symbol[] { + if (signatureHasRestParameter(sig)) { + const restIndex = sig.parameters.length - 1; + const restParameter = sig.parameters[restIndex]; + const restType = getTypeOfSymbol(restParameter); + if (isTupleType(restType)) { + const elementTypes = getTypeArguments(restType); + const minLength = restType.target.minLength; + const tupleRestIndex = restType.target.hasRestElement ? elementTypes.length - 1 : -1; + const restParams = map(elementTypes, (t, i) => { + const name = getParameterNameAtPosition(sig, restIndex + i); + const checkFlags = i === tupleRestIndex ? CheckFlags.RestParameter : + i >= minLength ? CheckFlags.OptionalParameter : 0; + const symbol = createSymbol(SymbolFlags.FunctionScopedVariable, name, checkFlags); + symbol.type = i === tupleRestIndex ? createArrayType(t) : t; + return symbol; + }); + return concatenate(sig.parameters.slice(0, restIndex), restParams); } - - function getInitialOrAssignedType(flow: FlowAssignment) { - const node = flow.node; - return getConstraintForLocation(node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ? - getInitialType(node) : - getAssignedType(node), reference); + } + return sig.parameters; + } + function getDefaultConstructSignatures(classType: InterfaceType): ts.Signature[] { + const baseConstructorType = getBaseConstructorTypeOfClass(classType); + const baseSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct); + if (baseSignatures.length === 0) { + return [createSignature(undefined, classType.localTypeParameters, undefined, emptyArray, classType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None)]; + } + const baseTypeNode = getBaseTypeNodeOfClass(classType)!; + const isJavaScript = isInJSFile(baseTypeNode); + const typeArguments = typeArgumentsFromTypeReferenceNode(baseTypeNode); + const typeArgCount = length(typeArguments); + const result: ts.Signature[] = []; + for (const baseSig of baseSignatures) { + const minTypeArgumentCount = getMinTypeArgumentCount(baseSig.typeParameters); + const typeParamCount = length(baseSig.typeParameters); + if (isJavaScript || typeArgCount >= minTypeArgumentCount && typeArgCount <= typeParamCount) { + const sig = typeParamCount ? createSignatureInstantiation(baseSig, fillMissingTypeArguments(typeArguments, baseSig.typeParameters, minTypeArgumentCount, isJavaScript)) : cloneSignature(baseSig); + sig.typeParameters = classType.localTypeParameters; + sig.resolvedReturnType = classType; + result.push(sig); + } + } + return result; + } + function findMatchingSignature(signatureList: readonly ts.Signature[], signature: ts.Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean): ts.Signature | undefined { + for (const s of signatureList) { + if (compareSignaturesIdentical(s, signature, partialMatch, ignoreThisTypes, ignoreReturnTypes, partialMatch ? compareTypesSubtypeOf : compareTypesIdentical)) { + return s; } - - function getTypeAtFlowAssignment(flow: FlowAssignment) { - const node = flow.node; - // Assignments only narrow the computed type if the declared type is a union type. Thus, we - // only need to evaluate the assigned type if the declared type is a union type. - if (isMatchingReference(reference, node)) { - if (!isReachableFlowNode(flow)) { - return unreachableNeverType; - } - if (getAssignmentTargetKind(node) === AssignmentKind.Compound) { - const flowType = getTypeAtFlowNode(flow.antecedent); - return createFlowType(getBaseTypeOfLiteralType(getTypeFromFlowType(flowType)), isIncomplete(flowType)); - } - if (declaredType === autoType || declaredType === autoArrayType) { - if (isEmptyArrayAssignment(node)) { - return getEvolvingArrayType(neverType); - } - const assignedType = getBaseTypeOfLiteralType(getInitialOrAssignedType(flow)); - return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType; - } - if (declaredType.flags & TypeFlags.Union) { - return getAssignmentReducedType(declaredType, getInitialOrAssignedType(flow)); - } - return declaredType; - } - // We didn't have a direct match. However, if the reference is a dotted name, this - // may be an assignment to a left hand part of the reference. For example, for a - // reference 'x.y.z', we may be at an assignment to 'x.y' or 'x'. In that case, - // return the declared type. - if (containsMatchingReference(reference, node)) { - if (!isReachableFlowNode(flow)) { - return unreachableNeverType; - } - // A matching dotted name might also be an expando property on a function *expression*, - // in which case we continue control flow analysis back to the function's declaration - if (isVariableDeclaration(node) && (isInJSFile(node) || isVarConst(node))) { - const init = getDeclaredExpandoInitializer(node); - if (init && (init.kind === SyntaxKind.FunctionExpression || init.kind === SyntaxKind.ArrowFunction)) { - return getTypeAtFlowNode(flow.antecedent); - } - } - return declaredType; - } - // for (const _ in ref) acts as a nonnull on ref - if (isVariableDeclaration(node) && node.parent.parent.kind === SyntaxKind.ForInStatement && isMatchingReference(reference, node.parent.parent.expression)) { - return getNonNullableTypeIfNeeded(getTypeFromFlowType(getTypeAtFlowNode(flow.antecedent))); - } - // Assignment doesn't affect reference + } + } + function findMatchingSignatures(signatureLists: readonly (readonly ts.Signature[])[], signature: ts.Signature, listIndex: number): ts.Signature[] | undefined { + if (signature.typeParameters) { + // We require an exact match for generic signatures, so we only return signatures from the first + // signature list and only if they have exact matches in the other signature lists. + if (listIndex > 0) { return undefined; } - - function narrowTypeByAssertion(type: Type, expr: Expression): Type { - const node = skipParentheses(expr); - if (node.kind === SyntaxKind.FalseKeyword) { - return unreachableNeverType; - } - if (node.kind === SyntaxKind.BinaryExpression) { - if ((node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { - return narrowTypeByAssertion(narrowTypeByAssertion(type, (node).left), (node).right); - } - if ((node).operatorToken.kind === SyntaxKind.BarBarToken) { - return getUnionType([narrowTypeByAssertion(type, (node).left), narrowTypeByAssertion(type, (node).right)]); - } + for (let i = 1; i < signatureLists.length; i++) { + if (!findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false)) { + return undefined; } - return narrowType(type, node, /*assumeTrue*/ true); } - - function getTypeAtFlowCall(flow: FlowCall): FlowType | undefined { - const signature = getEffectsSignature(flow.node); - if (signature) { - const predicate = getTypePredicateOfSignature(signature); - if (predicate && (predicate.kind === TypePredicateKind.AssertsThis || predicate.kind === TypePredicateKind.AssertsIdentifier)) { - const flowType = getTypeAtFlowNode(flow.antecedent); - const type = getTypeFromFlowType(flowType); - const narrowedType = predicate.type ? narrowTypeByTypePredicate(type, predicate, flow.node, /*assumeTrue*/ true) : - predicate.kind === TypePredicateKind.AssertsIdentifier && predicate.parameterIndex >= 0 && predicate.parameterIndex < flow.node.arguments.length ? narrowTypeByAssertion(type, flow.node.arguments[predicate.parameterIndex]) : - type; - return narrowedType === type ? flowType : createFlowType(narrowedType, isIncomplete(flowType)); - } - if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) { - return unreachableNeverType; - } - } + return [signature]; + } + let result: ts.Signature[] | undefined; + for (let i = 0; i < signatureLists.length; i++) { + // Allow matching non-generic signatures to have excess parameters and different return types. + // Prefer matching this types if possible. + const match = i === listIndex ? signature : findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ true, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true); + if (!match) { return undefined; } - - function getTypeAtFlowArrayMutation(flow: FlowArrayMutation): FlowType | undefined { - if (declaredType === autoType || declaredType === autoArrayType) { - const node = flow.node; - const expr = node.kind === SyntaxKind.CallExpression ? - (node.expression).expression : - (node.left).expression; - if (isMatchingReference(reference, getReferenceCandidate(expr))) { - const flowType = getTypeAtFlowNode(flow.antecedent); - const type = getTypeFromFlowType(flowType); - if (getObjectFlags(type) & ObjectFlags.EvolvingArray) { - let evolvedType = type; - if (node.kind === SyntaxKind.CallExpression) { - for (const arg of node.arguments) { - evolvedType = addEvolvingArrayElementType(evolvedType, arg); - } - } - else { - // We must get the context free expression type so as to not recur in an uncached fashion on the LHS (which causes exponential blowup in compile time) - const indexType = getContextFreeTypeOfExpression((node.left).argumentExpression); - if (isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) { - evolvedType = addEvolvingArrayElementType(evolvedType, node.right); - } + result = appendIfUnique(result, match); + } + return result; + } + // The signatures of a union type are those signatures that are present in each of the constituent types. + // Generic signatures must match exactly, but non-generic signatures are allowed to have extra optional + // parameters and may differ in return types. When signatures differ in return types, the resulting return + // type is the union of the constituent return types. + function getUnionSignatures(signatureLists: readonly (readonly ts.Signature[])[]): ts.Signature[] { + let result: ts.Signature[] | undefined; + let indexWithLengthOverOne: number | undefined; + for (let i = 0; i < signatureLists.length; i++) { + if (signatureLists[i].length === 0) + return emptyArray; + if (signatureLists[i].length > 1) { + indexWithLengthOverOne = indexWithLengthOverOne === undefined ? i : -1; // -1 is a signal there are multiple overload sets + } + for (const signature of signatureLists[i]) { + // Only process signatures with parameter lists that aren't already in the result list + if (!result || !findMatchingSignature(result, signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true)) { + const unionSignatures = findMatchingSignatures(signatureLists, signature, i); + if (unionSignatures) { + let s = signature; + // Union the result types when more than one signature matches + if (unionSignatures.length > 1) { + let thisParameter = signature.thisParameter; + const firstThisParameterOfUnionSignatures = forEach(unionSignatures, sig => sig.thisParameter); + if (firstThisParameterOfUnionSignatures) { + const thisType = getIntersectionType(mapDefined(unionSignatures, sig => sig.thisParameter && getTypeOfSymbol(sig.thisParameter))); + thisParameter = createSymbolWithType(firstThisParameterOfUnionSignatures, thisType); } - return evolvedType === type ? flowType : createFlowType(evolvedType, isIncomplete(flowType)); + s = createUnionSignature(signature, unionSignatures); + s.thisParameter = thisParameter; } - return flowType; + (result || (result = [])).push(s); } } - return undefined; - } - - function getTypeAtFlowCondition(flow: FlowCondition): FlowType { - const flowType = getTypeAtFlowNode(flow.antecedent); - const type = getTypeFromFlowType(flowType); - if (type.flags & TypeFlags.Never) { - return flowType; - } - // If we have an antecedent type (meaning we're reachable in some way), we first - // attempt to narrow the antecedent type. If that produces the never type, and if - // the antecedent type is incomplete (i.e. a transient type in a loop), then we - // take the type guard as an indication that control *could* reach here once we - // have the complete type. We proceed by switching to the silent never type which - // doesn't report errors when operators are applied to it. Note that this is the - // *only* place a silent never type is ever generated. - const assumeTrue = (flow.flags & FlowFlags.TrueCondition) !== 0; - const nonEvolvingType = finalizeEvolvingArrayType(type); - const narrowedType = narrowType(nonEvolvingType, flow.node, assumeTrue); - if (narrowedType === nonEvolvingType) { - return flowType; - } - const incomplete = isIncomplete(flowType); - const resultType = incomplete && narrowedType.flags & TypeFlags.Never ? silentNeverType : narrowedType; - return createFlowType(resultType, incomplete); } - - function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType { - const expr = flow.switchStatement.expression; - const flowType = getTypeAtFlowNode(flow.antecedent); - let type = getTypeFromFlowType(flowType); - if (isMatchingReference(reference, expr)) { - type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); - } - else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) { - type = narrowBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); - } - else { - if (strictNullChecks) { - if (optionalChainContainsReference(expr, reference)) { - type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd, - t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never))); - } - else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) { - type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd, - t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (t).value === "undefined")); - } - } - if (isMatchingReferenceDiscriminant(expr, type)) { - type = narrowTypeByDiscriminant(type, expr as AccessExpression, - t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd)); - } - else if (containsMatchingReferenceDiscriminant(reference, expr)) { - type = declaredType; + } + if (!length(result) && indexWithLengthOverOne !== -1) { + // No sufficiently similar signature existed to subsume all the other signatures in the union - time to see if we can make a single + // signature that handles all over them. We only do this when there are overloads in only one constituent. + // (Overloads are conditional in nature and having overloads in multiple constituents would necessitate making a power set of + // signatures from the type, whose ordering would be non-obvious) + const masterList = signatureLists[indexWithLengthOverOne !== undefined ? indexWithLengthOverOne : 0]; + let results: ts.Signature[] | undefined = masterList.slice(); + for (const signatures of signatureLists) { + if (signatures !== masterList) { + const signature = signatures[0]; + Debug.assert(!!signature, "getUnionSignatures bails early on empty signature lists and should not have empty lists on second pass"); + results = signature.typeParameters && some(results, s => !!s.typeParameters) ? undefined : map(results, sig => combineSignaturesOfUnionMembers(sig, signature)); + if (!results) { + break; } } - return createFlowType(type, isIncomplete(flowType)); } - - function getTypeAtFlowBranchLabel(flow: FlowLabel): FlowType { - const antecedentTypes: Type[] = []; - let subtypeReduction = false; - let seenIncomplete = false; - let bypassFlow: FlowSwitchClause | undefined; - for (const antecedent of flow.antecedents!) { - if (antecedent.flags & FlowFlags.PreFinally && (antecedent).lock.locked) { - // if flow correspond to branch from pre-try to finally and this branch is locked - this means that - // we initially have started following the flow outside the finally block. - // in this case we should ignore this branch. - continue; - } - if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (antecedent).clauseStart === (antecedent).clauseEnd) { - // The antecedent is the bypass branch of a potentially exhaustive switch statement. - bypassFlow = antecedent; - continue; - } - const flowType = getTypeAtFlowNode(antecedent); - const type = getTypeFromFlowType(flowType); - // If the type at a particular antecedent path is the declared type and the - // reference is known to always be assigned (i.e. when declared and initial types - // are the same), there is no reason to process more antecedents since the only - // possible outcome is subtypes that will be removed in the final union type anyway. - if (type === declaredType && declaredType === initialType) { - return type; - } - pushIfUnique(antecedentTypes, type); - // If an antecedent type is not a subset of the declared type, we need to perform - // subtype reduction. This happens when a "foreign" type is injected into the control - // flow using the instanceof operator or a user defined type predicate. - if (!isTypeSubsetOf(type, declaredType)) { - subtypeReduction = true; - } - if (isIncomplete(flowType)) { - seenIncomplete = true; - } + result = results; + } + return result || emptyArray; + } + function combineUnionThisParam(left: ts.Symbol | undefined, right: ts.Symbol | undefined): ts.Symbol | undefined { + if (!left || !right) { + return left || right; + } + // A signature `this` type might be a read or a write position... It's very possible that it should be invariant + // and we should refuse to merge signatures if there are `this` types and they do not match. However, so as to be + // permissive when calling, for now, we'll intersect the `this` types just like we do for param types in union signatures. + const thisType = getIntersectionType([getTypeOfSymbol(left), getTypeOfSymbol(right)]); + return createSymbolWithType(left, thisType); + } + function combineUnionParameters(left: ts.Signature, right: ts.Signature) { + const leftCount = getParameterCount(left); + const rightCount = getParameterCount(right); + const longest = leftCount >= rightCount ? left : right; + const shorter = longest === left ? right : left; + const longestCount = longest === left ? leftCount : rightCount; + const eitherHasEffectiveRest = (hasEffectiveRestParameter(left) || hasEffectiveRestParameter(right)); + const needsExtraRestElement = eitherHasEffectiveRest && !hasEffectiveRestParameter(longest); + const params = new Array(longestCount + (needsExtraRestElement ? 1 : 0)); + for (let i = 0; i < longestCount; i++) { + const longestParamType = tryGetTypeAtPosition(longest, i)!; + const shorterParamType = tryGetTypeAtPosition(shorter, i) || unknownType; + const unionParamType = getIntersectionType([longestParamType, shorterParamType]); + const isRestParam = eitherHasEffectiveRest && !needsExtraRestElement && i === (longestCount - 1); + const isOptional = i >= getMinArgumentCount(longest) && i >= getMinArgumentCount(shorter); + const leftName = i >= leftCount ? undefined : getParameterNameAtPosition(left, i); + const rightName = i >= rightCount ? undefined : getParameterNameAtPosition(right, i); + const paramName = leftName === rightName ? leftName : + !leftName ? rightName : + !rightName ? leftName : + undefined; + const paramSymbol = createSymbol(SymbolFlags.FunctionScopedVariable | (isOptional && !isRestParam ? SymbolFlags.Optional : 0), paramName || (`arg${i}` as __String)); + paramSymbol.type = isRestParam ? createArrayType(unionParamType) : unionParamType; + params[i] = paramSymbol; + } + if (needsExtraRestElement) { + const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, ("args" as __String)); + restParamSymbol.type = createArrayType(getTypeAtPosition(shorter, longestCount)); + params[longestCount] = restParamSymbol; + } + return params; + } + function combineSignaturesOfUnionMembers(left: ts.Signature, right: ts.Signature): ts.Signature { + const declaration = left.declaration; + const params = combineUnionParameters(left, right); + const thisParam = combineUnionThisParam(left.thisParameter, right.thisParameter); + const minArgCount = Math.max(left.minArgumentCount, right.minArgumentCount); + const result = createSignature(declaration, left.typeParameters || right.typeParameters, thisParam, params, + /*resolvedReturnType*/ undefined, + /*resolvedTypePredicate*/ undefined, minArgCount, (left.flags | right.flags) & SignatureFlags.PropagatingFlags); + result.unionSignatures = concatenate(left.unionSignatures || [left], [right]); + return result; + } + function getUnionIndexInfo(types: readonly ts.Type[], kind: IndexKind): IndexInfo | undefined { + const indexTypes: ts.Type[] = []; + let isAnyReadonly = false; + for (const type of types) { + const indexInfo = getIndexInfoOfType(type, kind); + if (!indexInfo) { + return undefined; + } + indexTypes.push(indexInfo.type); + isAnyReadonly = isAnyReadonly || indexInfo.isReadonly; + } + return createIndexInfo(getUnionType(indexTypes, UnionReduction.Subtype), isAnyReadonly); + } + function resolveUnionTypeMembers(type: UnionType) { + // The members and properties collections are empty for union types. To get all properties of a union + // type use getPropertiesOfType (only the language service uses this). + const callSignatures = getUnionSignatures(map(type.types, t => t === globalFunctionType ? [unknownSignature] : getSignaturesOfType(t, SignatureKind.Call))); + const constructSignatures = getUnionSignatures(map(type.types, t => getSignaturesOfType(t, SignatureKind.Construct))); + const stringIndexInfo = getUnionIndexInfo(type.types, IndexKind.String); + const numberIndexInfo = getUnionIndexInfo(type.types, IndexKind.Number); + setStructuredTypeMembers(type, emptySymbols, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); + } + function intersectTypes(type1: ts.Type, type2: ts.Type): ts.Type; + function intersectTypes(type1: ts.Type | undefined, type2: ts.Type | undefined): ts.Type | undefined; + function intersectTypes(type1: ts.Type | undefined, type2: ts.Type | undefined): ts.Type | undefined { + return !type1 ? type2 : !type2 ? type1 : getIntersectionType([type1, type2]); + } + function intersectIndexInfos(info1: IndexInfo | undefined, info2: IndexInfo | undefined): IndexInfo | undefined { + return !info1 ? info2 : !info2 ? info1 : createIndexInfo(getIntersectionType([info1.type, info2.type]), info1.isReadonly && info2.isReadonly); + } + function unionSpreadIndexInfos(info1: IndexInfo | undefined, info2: IndexInfo | undefined): IndexInfo | undefined { + return info1 && info2 && createIndexInfo(getUnionType([info1.type, info2.type]), info1.isReadonly || info2.isReadonly); + } + function findMixins(types: readonly ts.Type[]): readonly boolean[] { + const constructorTypeCount = countWhere(types, (t) => getSignaturesOfType(t, SignatureKind.Construct).length > 0); + const mixinFlags = map(types, isMixinConstructorType); + if (constructorTypeCount > 0 && constructorTypeCount === countWhere(mixinFlags, (b) => b)) { + const firstMixinIndex = mixinFlags.indexOf(/*searchElement*/ true); + mixinFlags[firstMixinIndex] = false; + } + return mixinFlags; + } + function includeMixinType(type: ts.Type, types: readonly ts.Type[], mixinFlags: readonly boolean[], index: number): ts.Type { + const mixedTypes: ts.Type[] = []; + for (let i = 0; i < types.length; i++) { + if (i === index) { + mixedTypes.push(type); + } + else if (mixinFlags[i]) { + mixedTypes.push(getReturnTypeOfSignature(getSignaturesOfType(types[i], SignatureKind.Construct)[0])); + } + } + return getIntersectionType(mixedTypes); + } + function resolveIntersectionTypeMembers(type: IntersectionType) { + // The members and properties collections are empty for intersection types. To get all properties of an + // intersection type use getPropertiesOfType (only the language service uses this). + let callSignatures: ts.Signature[] | undefined; + let constructSignatures: ts.Signature[] | undefined; + let stringIndexInfo: IndexInfo | undefined; + let numberIndexInfo: IndexInfo | undefined; + const types = type.types; + const mixinFlags = findMixins(types); + const mixinCount = countWhere(mixinFlags, (b) => b); + for (let i = 0; i < types.length; i++) { + const t = type.types[i]; + // When an intersection type contains mixin constructor types, the construct signatures from + // those types are discarded and their return types are mixed into the return types of all + // other construct signatures in the intersection type. For example, the intersection type + // '{ new(...args: any[]) => A } & { new(s: string) => B }' has a single construct signature + // 'new(s: string) => A & B'. + if (!mixinFlags[i]) { + let signatures = getSignaturesOfType(t, SignatureKind.Construct); + if (signatures.length && mixinCount > 0) { + signatures = map(signatures, s => { + const clone = cloneSignature(s); + clone.resolvedReturnType = includeMixinType(getReturnTypeOfSignature(s), types, mixinFlags, i); + return clone; + }); } - if (bypassFlow) { - const flowType = getTypeAtFlowNode(bypassFlow); - const type = getTypeFromFlowType(flowType); - // If the bypass flow contributes a type we haven't seen yet and the switch statement - // isn't exhaustive, process the bypass flow type. Since exhaustiveness checks increase - // the risk of circularities, we only want to perform them when they make a difference. - if (!contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.switchStatement)) { - if (type === declaredType && declaredType === initialType) { - return type; - } - antecedentTypes.push(type); - if (!isTypeSubsetOf(type, declaredType)) { - subtypeReduction = true; - } - if (isIncomplete(flowType)) { - seenIncomplete = true; + constructSignatures = appendSignatures(constructSignatures, signatures); + } + callSignatures = appendSignatures(callSignatures, getSignaturesOfType(t, SignatureKind.Call)); + stringIndexInfo = intersectIndexInfos(stringIndexInfo, getIndexInfoOfType(t, IndexKind.String)); + numberIndexInfo = intersectIndexInfos(numberIndexInfo, getIndexInfoOfType(t, IndexKind.Number)); + } + setStructuredTypeMembers(type, emptySymbols, callSignatures || emptyArray, constructSignatures || emptyArray, stringIndexInfo, numberIndexInfo); + } + function appendSignatures(signatures: ts.Signature[] | undefined, newSignatures: readonly ts.Signature[]) { + for (const sig of newSignatures) { + if (!signatures || every(signatures, s => !compareSignaturesIdentical(s, sig, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false, compareTypesIdentical))) { + signatures = append(signatures, sig); + } + } + return signatures; + } + /** + * Converts an AnonymousType to a ResolvedType. + */ + function resolveAnonymousTypeMembers(type: AnonymousType) { + const symbol = getMergedSymbol(type.symbol); + if (type.target) { + setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, undefined, undefined); + const members = createInstantiatedSymbolTable(getPropertiesOfObjectType(type.target), type.mapper!, /*mappingThisOnly*/ false); + const callSignatures = instantiateSignatures(getSignaturesOfType(type.target, SignatureKind.Call), (type.mapper!)); + const constructSignatures = instantiateSignatures(getSignaturesOfType(type.target, SignatureKind.Construct), (type.mapper!)); + const stringIndexInfo = instantiateIndexInfo(getIndexInfoOfType(type.target, IndexKind.String), (type.mapper!)); + const numberIndexInfo = instantiateIndexInfo(getIndexInfoOfType(type.target, IndexKind.Number), (type.mapper!)); + setStructuredTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); + } + else if (symbol.flags & SymbolFlags.TypeLiteral) { + setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, undefined, undefined); + const members = getMembersOfSymbol(symbol); + const callSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.Call)); + const constructSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.New)); + const stringIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.String); + const numberIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.Number); + setStructuredTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); + } + else { + // Combinations of function, class, enum and module + let members = emptySymbols; + let stringIndexInfo: IndexInfo | undefined; + if (symbol.exports) { + members = getExportsOfSymbol(symbol); + if (symbol === globalThisSymbol) { + const varsOnly = (createMap() as SymbolTable); + members.forEach(p => { + if (!(p.flags & SymbolFlags.BlockScoped)) { + varsOnly.set(p.escapedName, p); } - } + }); + members = varsOnly; } - return createFlowType(getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal), seenIncomplete); } - - function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType { - // If we have previously computed the control flow type for the reference at - // this flow loop junction, return the cached type. - const id = getFlowNodeId(flow); - const cache = flowLoopCaches[id] || (flowLoopCaches[id] = createMap()); - const key = getOrSetCacheKey(); - if (!key) { - // No cache key is generated when binding patterns are in unnarrowable situations - return declaredType; - } - const cached = cache.get(key); - if (cached) { - return cached; - } - // If this flow loop junction and reference are already being processed, return - // the union of the types computed for each branch so far, marked as incomplete. - // It is possible to see an empty array in cases where loops are nested and the - // back edge of the outer loop reaches an inner loop that is already being analyzed. - // In such cases we restart the analysis of the inner loop, which will then see - // a non-empty in-process array for the outer loop and eventually terminate because - // the first antecedent of a loop junction is always the non-looping control flow - // path that leads to the top. - for (let i = flowLoopStart; i < flowLoopCount; i++) { - if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key && flowLoopTypes[i].length) { - return createFlowType(getUnionOrEvolvingArrayType(flowLoopTypes[i], UnionReduction.Literal), /*incomplete*/ true); - } - } - // Add the flow loop junction and reference to the in-process stack and analyze - // each antecedent code path. - const antecedentTypes: Type[] = []; - let subtypeReduction = false; - let firstAntecedentType: FlowType | undefined; - for (const antecedent of flow.antecedents!) { - let flowType; - if (!firstAntecedentType) { - // The first antecedent of a loop junction is always the non-looping control - // flow path that leads to the top. - flowType = firstAntecedentType = getTypeAtFlowNode(antecedent); - } - else { - // All but the first antecedent are the looping control flow paths that lead - // back to the loop junction. We track these on the flow loop stack. - flowLoopNodes[flowLoopCount] = flow; - flowLoopKeys[flowLoopCount] = key; - flowLoopTypes[flowLoopCount] = antecedentTypes; - flowLoopCount++; - const saveFlowTypeCache = flowTypeCache; - flowTypeCache = undefined; - flowType = getTypeAtFlowNode(antecedent); - flowTypeCache = saveFlowTypeCache; - flowLoopCount--; - // If we see a value appear in the cache it is a sign that control flow analysis - // was restarted and completed by checkExpressionCached. We can simply pick up - // the resulting type and bail out. - const cached = cache.get(key); - if (cached) { - return cached; - } - } - const type = getTypeFromFlowType(flowType); - pushIfUnique(antecedentTypes, type); - // If an antecedent type is not a subset of the declared type, we need to perform - // subtype reduction. This happens when a "foreign" type is injected into the control - // flow using the instanceof operator or a user defined type predicate. - if (!isTypeSubsetOf(type, declaredType)) { - subtypeReduction = true; - } - // If the type at a particular antecedent path is the declared type there is no - // reason to process more antecedents since the only possible outcome is subtypes - // that will be removed in the final union type anyway. - if (type === declaredType) { - break; - } + setStructuredTypeMembers(type, members, emptyArray, emptyArray, undefined, undefined); + if (symbol.flags & SymbolFlags.Class) { + const classType = getDeclaredTypeOfClassOrInterface(symbol); + const baseConstructorType = getBaseConstructorTypeOfClass(classType); + if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.TypeVariable)) { + members = createSymbolTable(getNamedMembers(members)); + addInheritedMembers(members, getPropertiesOfType(baseConstructorType)); } - // The result is incomplete if the first antecedent (the non-looping control flow path) - // is incomplete. - const result = getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal); - if (isIncomplete(firstAntecedentType!)) { - return createFlowType(result, /*incomplete*/ true); + else if (baseConstructorType === anyType) { + stringIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false); } - cache.set(key, result); - return result; } - - function isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) { - if (!(computedType.flags & TypeFlags.Union) || !isAccessExpression(expr)) { - return false; + const numberIndexInfo = symbol.flags & SymbolFlags.Enum && (getDeclaredTypeOfSymbol(symbol).flags & TypeFlags.Enum || + some(type.properties, prop => !!(getTypeOfSymbol(prop).flags & TypeFlags.NumberLike))) ? enumNumberIndexInfo : undefined; + setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); + // We resolve the members before computing the signatures because a signature may use + // typeof with a qualified name expression that circularly references the type we are + // in the process of resolving (see issue #6072). The temporarily empty signature list + // will never be observed because a qualified name can't reference signatures. + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) { + type.callSignatures = getSignaturesOfSymbol(symbol); + } + // And likewise for construct signatures for classes + if (symbol.flags & SymbolFlags.Class) { + const classType = getDeclaredTypeOfClassOrInterface(symbol); + let constructSignatures = symbol.members ? getSignaturesOfSymbol(symbol.members.get(InternalSymbolName.Constructor)) : emptyArray; + if (symbol.flags & SymbolFlags.Function) { + constructSignatures = addRange(constructSignatures.slice(), mapDefined(type.callSignatures, sig => isJSConstructor(sig.declaration) ? + createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, classType, /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.flags & SignatureFlags.PropagatingFlags) : + undefined)); } - const name = getAccessedPropertyName(expr); - if (name === undefined) { - return false; + if (!constructSignatures.length) { + constructSignatures = getDefaultConstructSignatures(classType); } - return isMatchingReference(reference, expr.expression) && isDiscriminantProperty(computedType, name); + type.constructSignatures = constructSignatures; } - - function narrowTypeByDiscriminant(type: Type, access: AccessExpression, narrowType: (t: Type) => Type): Type { - const propName = getAccessedPropertyName(access); - if (propName === undefined) { - return type; - } - const propType = getTypeOfPropertyOfType(type, propName); - if (!propType) { - return type; + } + } + function resolveReverseMappedTypeMembers(type: ReverseMappedType) { + const indexInfo = getIndexInfoOfType(type.source, IndexKind.String); + const modifiers = getMappedTypeModifiers(type.mappedType); + const readonlyMask = modifiers & MappedTypeModifiers.IncludeReadonly ? false : true; + const optionalMask = modifiers & MappedTypeModifiers.IncludeOptional ? 0 : SymbolFlags.Optional; + const stringIndexInfo = indexInfo && createIndexInfo(inferReverseMappedType(indexInfo.type, type.mappedType, type.constraintType), readonlyMask && indexInfo.isReadonly); + const members = createSymbolTable(); + for (const prop of getPropertiesOfType(type.source)) { + const checkFlags = CheckFlags.ReverseMapped | (readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0); + const inferredProp = (createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags) as ReverseMappedSymbol); + inferredProp.declarations = prop.declarations; + inferredProp.nameType = prop.nameType; + inferredProp.propertyType = getTypeOfSymbol(prop); + inferredProp.mappedType = type.mappedType; + inferredProp.constraintType = type.constraintType; + members.set(prop.escapedName, inferredProp); + } + setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined); + } + // Return the lower bound of the key type in a mapped type. Intuitively, the lower + // bound includes those keys that are known to always be present, for example because + // because of constraints on type parameters (e.g. 'keyof T' for a constrained T). + function getLowerBoundOfKeyType(type: ts.Type): ts.Type { + if (type.flags & (TypeFlags.Any | TypeFlags.Primitive)) { + return type; + } + if (type.flags & TypeFlags.Index) { + return getIndexType(getApparentType((type).type)); + } + if (type.flags & TypeFlags.Conditional) { + if ((type).root.isDistributive) { + const checkType = (type).checkType; + const constraint = getLowerBoundOfKeyType(checkType); + if (constraint !== checkType) { + const mapper = makeUnaryTypeMapper((type).root.checkType, constraint); + return getConditionalTypeInstantiation((type), combineTypeMappers(mapper, (type).mapper)); } - const narrowedPropType = narrowType(propType); - return filterType(type, t => { - const discriminantType = getTypeOfPropertyOrIndexSignature(t, propName); - return !(discriminantType.flags & TypeFlags.Never) && isTypeComparableTo(discriminantType, narrowedPropType); - }); } - - function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type { - if (isMatchingReference(reference, expr)) { - return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy); + return type; + } + if (type.flags & TypeFlags.Union) { + return getUnionType(sameMap((type).types, getLowerBoundOfKeyType)); + } + if (type.flags & TypeFlags.Intersection) { + return getIntersectionType(sameMap((type).types, getLowerBoundOfKeyType)); + } + return neverType; + } + /** Resolve the members of a mapped type { [P in K]: T } */ + function resolveMappedTypeMembers(type: MappedType) { + const members: SymbolTable = createSymbolTable(); + let stringIndexInfo: IndexInfo | undefined; + let numberIndexInfo: IndexInfo | undefined; + // Resolve upfront such that recursive references see an empty object type. + setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, undefined, undefined); + // In { [P in K]: T }, we refer to P as the type parameter type, K as the constraint type, + // and T as the template type. + const typeParameter = getTypeParameterFromMappedType(type); + const constraintType = getConstraintTypeFromMappedType(type); + const templateType = getTemplateTypeFromMappedType((type.target) || type); + const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' + const templateModifiers = getMappedTypeModifiers(type); + const include = keyofStringsOnly ? TypeFlags.StringLiteral : TypeFlags.StringOrNumberLiteralOrUnique; + if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + // We have a { [P in keyof T]: X } + for (const prop of getPropertiesOfType(modifiersType)) { + addMemberForKeyType(getLiteralTypeFromProperty(prop, include)); + } + if (modifiersType.flags & TypeFlags.Any || getIndexInfoOfType(modifiersType, IndexKind.String)) { + addMemberForKeyType(stringType); + } + if (!keyofStringsOnly && getIndexInfoOfType(modifiersType, IndexKind.Number)) { + addMemberForKeyType(numberType); + } + } + else { + forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType); + } + setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); + function addMemberForKeyType(t: ts.Type) { + // Create a mapper from T to the current iteration type constituent. Then, if the + // mapped type is itself an instantiated type, combine the iteration mapper with the + // instantiation mapper. + const templateMapper = combineTypeMappers(type.mapper, createTypeMapper([typeParameter], [t])); + const propType = instantiateType(templateType, templateMapper); + // If the current iteration type constituent is a string literal type, create a property. + // Otherwise, for type string create a string index signature. + if (isTypeUsableAsPropertyName(t)) { + const propName = getPropertyNameFromType(t); + const modifiersProp = getPropertyOfType(modifiersType, propName); + const isOptional = !!(templateModifiers & MappedTypeModifiers.IncludeOptional || + !(templateModifiers & MappedTypeModifiers.ExcludeOptional) && modifiersProp && modifiersProp.flags & SymbolFlags.Optional); + const isReadonly = !!(templateModifiers & MappedTypeModifiers.IncludeReadonly || + !(templateModifiers & MappedTypeModifiers.ExcludeReadonly) && modifiersProp && isReadonlySymbol(modifiersProp)); + const prop = createSymbol(SymbolFlags.Property | (isOptional ? SymbolFlags.Optional : 0), propName, isReadonly ? CheckFlags.Readonly : 0); + // When creating an optional property in strictNullChecks mode, if 'undefined' isn't assignable to the + // type, we include 'undefined' in the type. Similarly, when creating a non-optional property in strictNullChecks + // mode, if the underlying property is optional we remove 'undefined' from the type. + prop.type = strictNullChecks && isOptional && !isTypeAssignableTo(undefinedType, propType) ? getOptionalType(propType) : + strictNullChecks && !isOptional && modifiersProp && modifiersProp.flags & SymbolFlags.Optional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) : + propType; + if (modifiersProp) { + prop.syntheticOrigin = modifiersProp; + prop.declarations = modifiersProp.declarations; } - if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) { - type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); + prop.nameType = t; + members.set(propName, prop); + } + else if (t.flags & (TypeFlags.Any | TypeFlags.String)) { + stringIndexInfo = createIndexInfo(propType, !!(templateModifiers & MappedTypeModifiers.IncludeReadonly)); + } + else if (t.flags & (TypeFlags.Number | TypeFlags.Enum)) { + numberIndexInfo = createIndexInfo(numberIndexInfo ? getUnionType([numberIndexInfo.type, propType]) : propType, !!(templateModifiers & MappedTypeModifiers.IncludeReadonly)); + } + } + } + function getTypeParameterFromMappedType(type: MappedType) { + return type.typeParameter || + (type.typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(type.declaration.typeParameter))); + } + function getConstraintTypeFromMappedType(type: MappedType) { + return type.constraintType || + (type.constraintType = getConstraintOfTypeParameter(getTypeParameterFromMappedType(type)) || errorType); + } + function getTemplateTypeFromMappedType(type: MappedType) { + return type.templateType || + (type.templateType = type.declaration.type ? + instantiateType(addOptionality(getTypeFromTypeNode(type.declaration.type), !!(getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional)), type.mapper || identityMapper) : + errorType); + } + function getConstraintDeclarationForMappedType(type: MappedType) { + return getEffectiveConstraintOfTypeParameter(type.declaration.typeParameter); + } + function isMappedTypeWithKeyofConstraintDeclaration(type: MappedType) { + const constraintDeclaration = getConstraintDeclarationForMappedType(type)!; // TODO: GH#18217 + return constraintDeclaration.kind === SyntaxKind.TypeOperator && + (constraintDeclaration).operator === SyntaxKind.KeyOfKeyword; + } + function getModifiersTypeFromMappedType(type: MappedType) { + if (!type.modifiersType) { + if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + // If the constraint declaration is a 'keyof T' node, the modifiers type is T. We check + // AST nodes here because, when T is a non-generic type, the logic below eagerly resolves + // 'keyof T' to a literal union type and we can't recover T from that type. + type.modifiersType = instantiateType(getTypeFromTypeNode((getConstraintDeclarationForMappedType(type)).type), type.mapper || identityMapper); + } + else { + // Otherwise, get the declared constraint type, and if the constraint type is a type parameter, + // get the constraint of that type parameter. If the resulting type is an indexed type 'keyof T', + // the modifiers type is T. Otherwise, the modifiers type is unknown. + const declaredType = (getTypeFromMappedTypeNode(type.declaration)); + const constraint = getConstraintTypeFromMappedType(declaredType); + const extendedConstraint = constraint && constraint.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter((constraint)) : constraint; + type.modifiersType = extendedConstraint && extendedConstraint.flags & TypeFlags.Index ? instantiateType((extendedConstraint).type, type.mapper || identityMapper) : unknownType; + } + } + return type.modifiersType; + } + function getMappedTypeModifiers(type: MappedType): MappedTypeModifiers { + const declaration = type.declaration; + return (declaration.readonlyToken ? declaration.readonlyToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeReadonly : MappedTypeModifiers.IncludeReadonly : 0) | + (declaration.questionToken ? declaration.questionToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeOptional : MappedTypeModifiers.IncludeOptional : 0); + } + function getMappedTypeOptionality(type: MappedType): number { + const modifiers = getMappedTypeModifiers(type); + return modifiers & MappedTypeModifiers.ExcludeOptional ? -1 : modifiers & MappedTypeModifiers.IncludeOptional ? 1 : 0; + } + function getCombinedMappedTypeOptionality(type: MappedType): number { + const optionality = getMappedTypeOptionality(type); + const modifiersType = getModifiersTypeFromMappedType(type); + return optionality || (isGenericMappedType(modifiersType) ? getMappedTypeOptionality(modifiersType) : 0); + } + function isPartialMappedType(type: ts.Type) { + return !!(getObjectFlags(type) & ObjectFlags.Mapped && getMappedTypeModifiers((type)) & MappedTypeModifiers.IncludeOptional); + } + function isGenericMappedType(type: ts.Type): type is MappedType { + return !!(getObjectFlags(type) & ObjectFlags.Mapped) && isGenericIndexType(getConstraintTypeFromMappedType((type))); + } + function resolveStructuredTypeMembers(type: StructuredType): ResolvedType { + if (!(type).members) { + if (type.flags & TypeFlags.Object) { + if ((type).objectFlags & ObjectFlags.Reference) { + resolveTypeReferenceMembers((type)); } - if (isMatchingReferenceDiscriminant(expr, declaredType)) { - return narrowTypeByDiscriminant(type, expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy)); + else if ((type).objectFlags & ObjectFlags.ClassOrInterface) { + resolveClassOrInterfaceMembers((type)); } - if (containsMatchingReferenceDiscriminant(reference, expr)) { - return declaredType; + else if ((type).objectFlags & ObjectFlags.ReverseMapped) { + resolveReverseMappedTypeMembers((type as ReverseMappedType)); } - return type; - } - - function isTypePresencePossible(type: Type, propName: __String, assumeTrue: boolean) { - if (getIndexInfoOfType(type, IndexKind.String)) { - return true; + else if ((type).objectFlags & ObjectFlags.Anonymous) { + resolveAnonymousTypeMembers((type)); } - const prop = getPropertyOfType(type, propName); - if (prop) { - return prop.flags & SymbolFlags.Optional ? true : assumeTrue; + else if ((type).objectFlags & ObjectFlags.Mapped) { + resolveMappedTypeMembers((type)); } - return !assumeTrue; } - - function narrowByInKeyword(type: Type, literal: LiteralExpression, assumeTrue: boolean) { - if (type.flags & (TypeFlags.Union | TypeFlags.Object) || isThisTypeParameter(type)) { - const propName = escapeLeadingUnderscores(literal.text); - return filterType(type, t => isTypePresencePossible(t, propName, assumeTrue)); - } - return type; + else if (type.flags & TypeFlags.Union) { + resolveUnionTypeMembers((type)); } - - function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { - switch (expr.operatorToken.kind) { - case SyntaxKind.EqualsToken: - return narrowTypeByTruthiness(narrowType(type, expr.right, assumeTrue), expr.left, assumeTrue); - case SyntaxKind.EqualsEqualsToken: - case SyntaxKind.ExclamationEqualsToken: - case SyntaxKind.EqualsEqualsEqualsToken: - case SyntaxKind.ExclamationEqualsEqualsToken: - const operator = expr.operatorToken.kind; - const left = getReferenceCandidate(expr.left); - const right = getReferenceCandidate(expr.right); - if (left.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(right)) { - return narrowTypeByTypeof(type, left, operator, right, assumeTrue); - } - if (right.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(left)) { - return narrowTypeByTypeof(type, right, operator, left, assumeTrue); - } - if (isMatchingReference(reference, left)) { - return narrowTypeByEquality(type, operator, right, assumeTrue); - } - if (isMatchingReference(reference, right)) { - return narrowTypeByEquality(type, operator, left, assumeTrue); - } - if (strictNullChecks) { - if (optionalChainContainsReference(left, reference)) { - type = narrowTypeByOptionalChainContainment(type, operator, right, assumeTrue); - } - else if (optionalChainContainsReference(right, reference)) { - type = narrowTypeByOptionalChainContainment(type, operator, left, assumeTrue); - } - } - if (isMatchingReferenceDiscriminant(left, declaredType)) { - return narrowTypeByDiscriminant(type, left, t => narrowTypeByEquality(t, operator, right, assumeTrue)); - } - if (isMatchingReferenceDiscriminant(right, declaredType)) { - return narrowTypeByDiscriminant(type, right, t => narrowTypeByEquality(t, operator, left, assumeTrue)); - } - if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) { - return declaredType; - } - break; - case SyntaxKind.InstanceOfKeyword: - return narrowTypeByInstanceof(type, expr, assumeTrue); - case SyntaxKind.InKeyword: - const target = getReferenceCandidate(expr.right); - if (isStringLiteralLike(expr.left) && isMatchingReference(reference, target)) { - return narrowByInKeyword(type, expr.left, assumeTrue); - } - break; - case SyntaxKind.CommaToken: - return narrowType(type, expr.right, assumeTrue); - } - return type; + else if (type.flags & TypeFlags.Intersection) { + resolveIntersectionTypeMembers((type)); } - - function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { - // We are in a branch of obj?.foo === value or obj?.foo !== value. We remove undefined and null from - // the type of obj if (a) the operator is === and the type of value doesn't include undefined or (b) the - // operator is !== and the type of value is undefined. - const effectiveTrue = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken ? assumeTrue : !assumeTrue; - const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken; - const valueNonNullish = !(getTypeFacts(getTypeOfExpression(value)) & (doubleEquals ? TypeFacts.EQUndefinedOrNull : TypeFacts.EQUndefined)); - return effectiveTrue === valueNonNullish ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; + } + return type; + } + /** Return properties of an object type or an empty array for other types */ + function getPropertiesOfObjectType(type: ts.Type): ts.Symbol[] { + if (type.flags & TypeFlags.Object) { + return resolveStructuredTypeMembers((type)).properties; + } + return emptyArray; + } + /** If the given type is an object type and that type has a property by the given name, + * return the symbol for that property. Otherwise return undefined. + */ + function getPropertyOfObjectType(type: ts.Type, name: __String): ts.Symbol | undefined { + if (type.flags & TypeFlags.Object) { + const resolved = resolveStructuredTypeMembers((type)); + const symbol = resolved.members.get(name); + if (symbol && symbolIsValue(symbol)) { + return symbol; } - - function narrowTypeByEquality(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { - if (type.flags & TypeFlags.Any) { - return type; + } + } + function getPropertiesOfUnionOrIntersectionType(type: UnionOrIntersectionType): ts.Symbol[] { + if (!type.resolvedProperties) { + const members = createSymbolTable(); + for (const current of type.types) { + for (const prop of getPropertiesOfType(current)) { + if (!members.has(prop.escapedName)) { + const combinedProp = getPropertyOfUnionOrIntersectionType(type, prop.escapedName); + if (combinedProp) { + members.set(prop.escapedName, combinedProp); + } + } } - if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { - assumeTrue = !assumeTrue; + // The properties of a union type are those that are present in all constituent types, so + // we only need to check the properties of the first type + if (type.flags & TypeFlags.Union) { + break; } - const valueType = getTypeOfExpression(value); - if ((type.flags & TypeFlags.Unknown) && assumeTrue && (operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)) { - if (valueType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) { - return valueType; - } - if (valueType.flags & TypeFlags.Object) { - return nonPrimitiveType; - } - return type; + } + type.resolvedProperties = getNamedMembers(members); + } + return type.resolvedProperties; + } + function getPropertiesOfType(type: ts.Type): ts.Symbol[] { + type = getApparentType(type); + return type.flags & TypeFlags.UnionOrIntersection ? + getPropertiesOfUnionOrIntersectionType((type)) : + getPropertiesOfObjectType(type); + } + function isTypeInvalidDueToUnionDiscriminant(contextualType: ts.Type, obj: ObjectLiteralExpression | JsxAttributes): boolean { + const list = (obj.properties as NodeArray); + return list.some(property => { + const nameType = property.name && getLiteralTypeFromPropertyName(property.name); + const name = nameType && isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined; + const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name); + return !!expected && isLiteralType(expected) && !isTypeAssignableTo(getTypeOfNode(property), expected); + }); + } + function getAllPossiblePropertiesOfTypes(types: readonly ts.Type[]): ts.Symbol[] { + const unionType = getUnionType(types); + if (!(unionType.flags & TypeFlags.Union)) { + return getAugmentedPropertiesOfType(unionType); + } + const props = createSymbolTable(); + for (const memberType of types) { + for (const { escapedName } of getAugmentedPropertiesOfType(memberType)) { + if (!props.has(escapedName)) { + const prop = createUnionOrIntersectionProperty((unionType as UnionType), escapedName); + // May be undefined if the property is private + if (prop) + props.set(escapedName, prop); + } + } + } + return arrayFrom(props.values()); + } + function getConstraintOfType(type: InstantiableType | UnionOrIntersectionType): ts.Type | undefined { + return type.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter((type)) : + type.flags & TypeFlags.IndexedAccess ? getConstraintOfIndexedAccess((type)) : + type.flags & TypeFlags.Conditional ? getConstraintOfConditionalType((type)) : + getBaseConstraintOfType(type); + } + function getConstraintOfTypeParameter(typeParameter: TypeParameter): ts.Type | undefined { + return hasNonCircularBaseConstraint(typeParameter) ? getConstraintFromTypeParameter(typeParameter) : undefined; + } + function getConstraintOfIndexedAccess(type: IndexedAccessType) { + return hasNonCircularBaseConstraint(type) ? getConstraintFromIndexedAccess(type) : undefined; + } + function getSimplifiedTypeOrConstraint(type: ts.Type) { + const simplified = getSimplifiedType(type, /*writing*/ false); + return simplified !== type ? simplified : getConstraintOfType(type); + } + function getConstraintFromIndexedAccess(type: IndexedAccessType) { + const indexConstraint = getSimplifiedTypeOrConstraint(type.indexType); + if (indexConstraint && indexConstraint !== type.indexType) { + const indexedAccess = getIndexedAccessTypeOrUndefined(type.objectType, indexConstraint); + if (indexedAccess) { + return indexedAccess; + } + } + const objectConstraint = getSimplifiedTypeOrConstraint(type.objectType); + if (objectConstraint && objectConstraint !== type.objectType) { + return getIndexedAccessTypeOrUndefined(objectConstraint, type.indexType); + } + return undefined; + } + function getDefaultConstraintOfConditionalType(type: ConditionalType) { + if (!type.resolvedDefaultConstraint) { + // An `any` branch of a conditional type would normally be viral - specifically, without special handling here, + // a conditional type with a single branch of type `any` would be assignable to anything, since it's constraint would simplify to + // just `any`. This result is _usually_ unwanted - so instead here we elide an `any` branch from the constraint type, + // in effect treating `any` like `never` rather than `unknown` in this location. + const trueConstraint = getInferredTrueTypeFromConditionalType(type); + const falseConstraint = getFalseTypeFromConditionalType(type); + type.resolvedDefaultConstraint = isTypeAny(trueConstraint) ? falseConstraint : isTypeAny(falseConstraint) ? trueConstraint : getUnionType([trueConstraint, falseConstraint]); + } + return type.resolvedDefaultConstraint; + } + function getConstraintOfDistributiveConditionalType(type: ConditionalType): ts.Type | undefined { + // Check if we have a conditional type of the form 'T extends U ? X : Y', where T is a constrained + // type parameter. If so, create an instantiation of the conditional type where T is replaced + // with its constraint. We do this because if the constraint is a union type it will be distributed + // over the conditional type and possibly reduced. For example, 'T extends undefined ? never : T' + // removes 'undefined' from T. + // We skip returning a distributive constraint for a restrictive instantiation of a conditional type + // as the constraint for all type params (check type included) have been replace with `unknown`, which + // is going to produce even more false positive/negative results than the distribute constraint already does. + // Please note: the distributive constraint is a kludge for emulating what a negated type could to do filter + // a union - once negated types exist and are applied to the conditional false branch, this "constraint" + // likely doesn't need to exist. + if (type.root.isDistributive && type.restrictiveInstantiation !== type) { + const simplified = getSimplifiedType(type.checkType, /*writing*/ false); + const constraint = simplified === type.checkType ? getConstraintOfType(simplified) : simplified; + if (constraint && constraint !== type.checkType) { + const mapper = makeUnaryTypeMapper(type.root.checkType, constraint); + const instantiated = getConditionalTypeInstantiation(type, combineTypeMappers(mapper, type.mapper)); + if (!(instantiated.flags & TypeFlags.Never)) { + return instantiated; + } + } + } + return undefined; + } + function getConstraintFromConditionalType(type: ConditionalType) { + return getConstraintOfDistributiveConditionalType(type) || getDefaultConstraintOfConditionalType(type); + } + function getConstraintOfConditionalType(type: ConditionalType) { + return hasNonCircularBaseConstraint(type) ? getConstraintFromConditionalType(type) : undefined; + } + function getEffectiveConstraintOfIntersection(types: readonly ts.Type[], targetIsUnion: boolean) { + let constraints: ts.Type[] | undefined; + let hasDisjointDomainType = false; + for (const t of types) { + if (t.flags & TypeFlags.Instantiable) { + // We keep following constraints as long as we have an instantiable type that is known + // not to be circular or infinite (hence we stop on index access types). + let constraint = getConstraintOfType(t); + while (constraint && constraint.flags & (TypeFlags.TypeParameter | TypeFlags.Index | TypeFlags.Conditional)) { + constraint = getConstraintOfType(constraint); } - if (valueType.flags & TypeFlags.Nullable) { - if (!strictNullChecks) { - return type; + if (constraint) { + constraints = append(constraints, constraint); + if (targetIsUnion) { + constraints = append(constraints, t); } - const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken; - const facts = doubleEquals ? - assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull : - valueType.flags & TypeFlags.Null ? - assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull : - assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined; - return getTypeWithFacts(type, facts); - } - if (type.flags & TypeFlags.NotUnionOrUnit) { - return type; - } - if (assumeTrue) { - const filterFn: (t: Type) => boolean = operator === SyntaxKind.EqualsEqualsToken ? - (t => areTypesComparable(t, valueType) || isCoercibleUnderDoubleEquals(t, valueType)) : - t => areTypesComparable(t, valueType); - const narrowedType = filterType(type, filterFn); - return narrowedType.flags & TypeFlags.Never ? type : replacePrimitivesWithLiterals(narrowedType, valueType); - } - if (isUnitType(valueType)) { - const regularType = getRegularTypeOfLiteralType(valueType); - return filterType(type, t => getRegularTypeOfLiteralType(t) !== regularType); } - return type; } - - function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type { - // We have '==', '!=', '===', or !==' operator with 'typeof xxx' and string literal operands - if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { - assumeTrue = !assumeTrue; - } - const target = getReferenceCandidate(typeOfExpr.expression); - if (!isMatchingReference(reference, target)) { - if (strictNullChecks && optionalChainContainsReference(target, reference) && assumeTrue === (literal.text !== "undefined")) { - return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); - } - // For a reference of the form 'x.y', a 'typeof x === ...' type guard resets the - // narrowed type of 'y' to its declared type. - if (containsMatchingReference(reference, target)) { - return declaredType; + else if (t.flags & TypeFlags.DisjointDomains) { + hasDisjointDomainType = true; + } + } + // If the target is a union type or if we are intersecting with types belonging to one of the + // disjoint domains, we may end up producing a constraint that hasn't been examined before. + if (constraints && (targetIsUnion || hasDisjointDomainType)) { + if (hasDisjointDomainType) { + // We add any types belong to one of the disjoint domains because they might cause the final + // intersection operation to reduce the union constraints. + for (const t of types) { + if (t.flags & TypeFlags.DisjointDomains) { + constraints = append(constraints, t); } - return type; - } - if (type.flags & TypeFlags.Any && literal.text === "function") { - return type; } - const facts = assumeTrue ? - typeofEQFacts.get(literal.text) || TypeFacts.TypeofEQHostObject : - typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject; - return getTypeWithFacts(assumeTrue ? mapType(type, narrowTypeForTypeof) : type, facts); - - function narrowTypeForTypeof(type: Type) { - if (type.flags & TypeFlags.Unknown && literal.text === "object") { - return getUnionType([nonPrimitiveType, nullType]); - } - // We narrow a non-union type to an exact primitive type if the non-union type - // is a supertype of that primitive type. For example, type 'any' can be narrowed - // to one of the primitive types. - const targetType = literal.text === "function" ? globalFunctionType : typeofTypesByName.get(literal.text); - if (targetType) { - if (isTypeSubtypeOf(type, targetType)) { - return type; - } - if (isTypeSubtypeOf(targetType, type)) { - return targetType; - } - if (type.flags & TypeFlags.Instantiable) { - const constraint = getBaseConstraintOfType(type) || anyType; - if (isTypeSubtypeOf(targetType, constraint)) { - return getIntersectionType([type, targetType]); + } + return getIntersectionType(constraints); + } + return undefined; + } + function getBaseConstraintOfType(type: ts.Type): ts.Type | undefined { + if (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.UnionOrIntersection)) { + const constraint = getResolvedBaseConstraint((type)); + return constraint !== noConstraintType && constraint !== circularConstraintType ? constraint : undefined; + } + return type.flags & TypeFlags.Index ? keyofConstraintType : undefined; + } + /** + * This is similar to `getBaseConstraintOfType` except it returns the input type if there's no base constraint, instead of `undefined` + * It also doesn't map indexes to `string`, as where this is used this would be unneeded (and likely undesirable) + */ + function getBaseConstraintOrType(type: ts.Type) { + return getBaseConstraintOfType(type) || type; + } + function hasNonCircularBaseConstraint(type: InstantiableType): boolean { + return getResolvedBaseConstraint(type) !== circularConstraintType; + } + /** + * Return the resolved base constraint of a type variable. The noConstraintType singleton is returned if the + * type variable has no constraint, and the circularConstraintType singleton is returned if the constraint + * circularly references the type variable. + */ + function getResolvedBaseConstraint(type: InstantiableType | UnionOrIntersectionType): ts.Type { + let nonTerminating = false; + return type.resolvedBaseConstraint || + (type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), type)); + function getImmediateBaseConstraint(t: ts.Type): ts.Type { + if (!t.immediateBaseConstraint) { + if (!pushTypeResolution(t, TypeSystemPropertyName.ImmediateBaseConstraint)) { + return circularConstraintType; + } + if (constraintDepth >= 50) { + // We have reached 50 recursive invocations of getImmediateBaseConstraint and there is a + // very high likelihood we're dealing with an infinite generic type that perpetually generates + // new type identities as we descend into it. We stop the recursion here and mark this type + // and the outer types as having circular constraints. + error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); + nonTerminating = true; + return t.immediateBaseConstraint = noConstraintType; + } + constraintDepth++; + let result = computeBaseConstraint(getSimplifiedType(t, /*writing*/ false)); + constraintDepth--; + if (!popTypeResolution()) { + if (t.flags & TypeFlags.TypeParameter) { + const errorNode = getConstraintDeclaration((t)); + if (errorNode) { + const diagnostic = error(errorNode, Diagnostics.Type_parameter_0_has_a_circular_constraint, typeToString(t)); + if (currentNode && !isNodeDescendantOf(errorNode, currentNode) && !isNodeDescendantOf(currentNode, errorNode)) { + addRelatedInfo(diagnostic, createDiagnosticForNode(currentNode, Diagnostics.Circularity_originates_in_type_at_this_location)); } } } - return type; + result = circularConstraintType; + } + if (nonTerminating) { + result = circularConstraintType; } + t.immediateBaseConstraint = result || noConstraintType; } - - function narrowTypeBySwitchOptionalChainContainment(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number, clauseCheck: (type: Type) => boolean) { - const everyClauseChecks = clauseStart !== clauseEnd && every(getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd), clauseCheck); - return everyClauseChecks ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; + return t.immediateBaseConstraint; + } + function getBaseConstraint(t: ts.Type): ts.Type | undefined { + const c = getImmediateBaseConstraint(t); + return c !== noConstraintType && c !== circularConstraintType ? c : undefined; + } + function computeBaseConstraint(t: ts.Type): ts.Type | undefined { + if (t.flags & TypeFlags.TypeParameter) { + const constraint = getConstraintFromTypeParameter((t)); + return (t as TypeParameter).isThisType || !constraint ? + constraint : + getBaseConstraint(constraint); } - - function narrowTypeBySwitchOnDiscriminant(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { - // We only narrow if all case expressions specify - // values with unit types, except for the case where - // `type` is unknown. In this instance we map object - // types to the nonPrimitive type and narrow with that. - const switchTypes = getSwitchClauseTypes(switchStatement); - if (!switchTypes.length) { - return type; - } - const clauseTypes = switchTypes.slice(clauseStart, clauseEnd); - const hasDefaultClause = clauseStart === clauseEnd || contains(clauseTypes, neverType); - if ((type.flags & TypeFlags.Unknown) && !hasDefaultClause) { - let groundClauseTypes: Type[] | undefined; - for (let i = 0; i < clauseTypes.length; i += 1) { - const t = clauseTypes[i]; - if (t.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) { - if (groundClauseTypes !== undefined) { - groundClauseTypes.push(t); - } - } - else if (t.flags & TypeFlags.Object) { - if (groundClauseTypes === undefined) { - groundClauseTypes = clauseTypes.slice(0, i); - } - groundClauseTypes.push(nonPrimitiveType); - } - else { - return type; - } + if (t.flags & TypeFlags.UnionOrIntersection) { + const types = (t).types; + const baseTypes: ts.Type[] = []; + for (const type of types) { + const baseType = getBaseConstraint(type); + if (baseType) { + baseTypes.push(baseType); } - return getUnionType(groundClauseTypes === undefined ? clauseTypes : groundClauseTypes); } - const discriminantType = getUnionType(clauseTypes); - const caseType = - discriminantType.flags & TypeFlags.Never ? neverType : - replacePrimitivesWithLiterals(filterType(type, t => areTypesComparable(discriminantType, t)), discriminantType); - if (!hasDefaultClause) { - return caseType; - } - const defaultType = filterType(type, t => !(isUnitType(t) && contains(switchTypes, getRegularTypeOfLiteralType(t)))); - return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]); + return t.flags & TypeFlags.Union && baseTypes.length === types.length ? getUnionType(baseTypes) : + t.flags & TypeFlags.Intersection && baseTypes.length ? getIntersectionType(baseTypes) : + undefined; } - - function getImpliedTypeFromTypeofCase(type: Type, text: string) { - switch (text) { - case "function": - return type.flags & TypeFlags.Any ? type : globalFunctionType; - case "object": - return type.flags & TypeFlags.Unknown ? getUnionType([nonPrimitiveType, nullType]) : type; - default: - return typeofTypesByName.get(text) || type; - } + if (t.flags & TypeFlags.Index) { + return keyofConstraintType; } - - function narrowTypeForTypeofSwitch(candidate: Type) { - return (type: Type) => { - if (isTypeSubtypeOf(candidate, type)) { - return candidate; - } - if (type.flags & TypeFlags.Instantiable) { - const constraint = getBaseConstraintOfType(type) || anyType; - if (isTypeSubtypeOf(candidate, constraint)) { - return getIntersectionType([type, candidate]); - } - } - return type; - }; + if (t.flags & TypeFlags.IndexedAccess) { + const baseObjectType = getBaseConstraint((t).objectType); + const baseIndexType = getBaseConstraint((t).indexType); + const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType); + return baseIndexedAccess && getBaseConstraint(baseIndexedAccess); } - - function narrowBySwitchOnTypeOf(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type { - const switchWitnesses = getSwitchClauseTypeOfWitnesses(switchStatement); - if (!switchWitnesses.length) { - return type; - } - // Equal start and end denotes implicit fallthrough; undefined marks explicit default clause - const defaultCaseLocation = findIndex(switchWitnesses, elem => elem === undefined); - const hasDefaultClause = clauseStart === clauseEnd || (defaultCaseLocation >= clauseStart && defaultCaseLocation < clauseEnd); - let clauseWitnesses: string[]; - let switchFacts: TypeFacts; - if (defaultCaseLocation > -1) { - // We no longer need the undefined denoting an - // explicit default case. Remove the undefined and - // fix-up clauseStart and clauseEnd. This means - // that we don't have to worry about undefined - // in the witness array. - const witnesses = switchWitnesses.filter(witness => witness !== undefined); - // The adjusted clause start and end after removing the `default` statement. - const fixedClauseStart = defaultCaseLocation < clauseStart ? clauseStart - 1 : clauseStart; - const fixedClauseEnd = defaultCaseLocation < clauseEnd ? clauseEnd - 1 : clauseEnd; - clauseWitnesses = witnesses.slice(fixedClauseStart, fixedClauseEnd); - switchFacts = getFactsFromTypeofSwitch(fixedClauseStart, fixedClauseEnd, witnesses, hasDefaultClause); - } - else { - clauseWitnesses = switchWitnesses.slice(clauseStart, clauseEnd); - switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, switchWitnesses, hasDefaultClause); - } - if (hasDefaultClause) { - return filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts); - } - /* - The implied type is the raw type suggested by a - value being caught in this clause. - - When the clause contains a default case we ignore - the implied type and try to narrow using any facts - we can learn: see `switchFacts`. - - Example: - switch (typeof x) { - case 'number': - case 'string': break; - default: break; - case 'number': - case 'boolean': break - } - - In the first clause (case `number` and `string`) the - implied type is number | string. - - In the default clause we de not compute an implied type. - - In the third clause (case `number` and `boolean`) - the naive implied type is number | boolean, however - we use the type facts to narrow the implied type to - boolean. We know that number cannot be selected - because it is caught in the first clause. - */ - let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => getImpliedTypeFromTypeofCase(type, text))), switchFacts); - if (impliedType.flags & TypeFlags.Union) { - impliedType = getAssignmentReducedType(impliedType as UnionType, getBaseConstraintOrType(type)); + if (t.flags & TypeFlags.Conditional) { + const constraint = getConstraintFromConditionalType((t)); + constraintDepth++; // Penalize repeating conditional types (this captures the recursion within getConstraintFromConditionalType and carries it forward) + const result = constraint && getBaseConstraint(constraint); + constraintDepth--; + return result; + } + if (t.flags & TypeFlags.Substitution) { + return getBaseConstraint((t).substitute); + } + return t; + } + } + function getApparentTypeOfIntersectionType(type: IntersectionType) { + return type.resolvedApparentType || (type.resolvedApparentType = getTypeWithThisArgument(type, type, /*apparentType*/ true)); + } + function getResolvedTypeParameterDefault(typeParameter: TypeParameter): ts.Type | undefined { + if (!typeParameter.default) { + if (typeParameter.target) { + const targetDefault = getResolvedTypeParameterDefault(typeParameter.target); + typeParameter.default = targetDefault ? instantiateType(targetDefault, typeParameter.mapper) : noConstraintType; + } + else { + // To block recursion, set the initial value to the resolvingDefaultType. + typeParameter.default = resolvingDefaultType; + const defaultDeclaration = typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default); + const defaultType = defaultDeclaration ? getTypeFromTypeNode(defaultDeclaration) : noConstraintType; + if (typeParameter.default === resolvingDefaultType) { + // If we have not been called recursively, set the correct default type. + typeParameter.default = defaultType; } - return getTypeWithFacts(mapType(type, narrowTypeForTypeofSwitch(impliedType)), switchFacts); } - - function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { - const left = getReferenceCandidate(expr.left); - if (!isMatchingReference(reference, left)) { - if (assumeTrue && strictNullChecks && optionalChainContainsReference(left, reference)) { - return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); + } + else if (typeParameter.default === resolvingDefaultType) { + // If we are called recursively for this type parameter, mark the default as circular. + typeParameter.default = circularConstraintType; + } + return typeParameter.default; + } + /** + * Gets the default type for a type parameter. + * + * If the type parameter is the result of an instantiation, this gets the instantiated + * default type of its target. If the type parameter has no default type or the default is + * circular, `undefined` is returned. + */ + function getDefaultFromTypeParameter(typeParameter: TypeParameter): ts.Type | undefined { + const defaultType = getResolvedTypeParameterDefault(typeParameter); + return defaultType !== noConstraintType && defaultType !== circularConstraintType ? defaultType : undefined; + } + function hasNonCircularTypeParameterDefault(typeParameter: TypeParameter) { + return getResolvedTypeParameterDefault(typeParameter) !== circularConstraintType; + } + /** + * Indicates whether the declaration of a typeParameter has a default type. + */ + function hasTypeParameterDefault(typeParameter: TypeParameter): boolean { + return !!(typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default)); + } + function getApparentTypeOfMappedType(type: MappedType) { + return type.resolvedApparentType || (type.resolvedApparentType = getResolvedApparentTypeOfMappedType(type)); + } + function getResolvedApparentTypeOfMappedType(type: MappedType) { + const typeVariable = getHomomorphicTypeVariable(type); + if (typeVariable) { + const constraint = getConstraintOfTypeParameter(typeVariable); + if (constraint && (isArrayType(constraint) || isTupleType(constraint))) { + const mapper = makeUnaryTypeMapper(typeVariable, constraint); + return instantiateType(type, combineTypeMappers(mapper, type.mapper)); + } + } + return type; + } + /** + * For a type parameter, return the base constraint of the type parameter. For the string, number, + * boolean, and symbol primitive types, return the corresponding object types. Otherwise return the + * type itself. Note that the apparent type of a union type is the union type itself. + */ + function getApparentType(type: ts.Type): ts.Type { + const t = type.flags & TypeFlags.Instantiable ? getBaseConstraintOfType(type) || unknownType : type; + return getObjectFlags(t) & ObjectFlags.Mapped ? getApparentTypeOfMappedType((t)) : + t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType((t)) : + t.flags & TypeFlags.StringLike ? globalStringType : + t.flags & TypeFlags.NumberLike ? globalNumberType : + t.flags & TypeFlags.BigIntLike ? getGlobalBigIntType(/*reportErrors*/ languageVersion >= ScriptTarget.ESNext) : + t.flags & TypeFlags.BooleanLike ? globalBooleanType : + t.flags & TypeFlags.ESSymbolLike ? getGlobalESSymbolType(/*reportErrors*/ languageVersion >= ScriptTarget.ES2015) : + t.flags & TypeFlags.NonPrimitive ? emptyObjectType : + t.flags & TypeFlags.Index ? keyofConstraintType : + t.flags & TypeFlags.Unknown && !strictNullChecks ? emptyObjectType : + t; + } + function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String): ts.Symbol | undefined { + const propSet = createMap(); + let indexTypes: ts.Type[] | undefined; + const isUnion = containingType.flags & TypeFlags.Union; + const excludeModifiers = isUnion ? ModifierFlags.NonPublicAccessibilityModifier : 0; + // Flags we want to propagate to the result if they exist in all source symbols + let optionalFlag = isUnion ? SymbolFlags.None : SymbolFlags.Optional; + let syntheticFlag = CheckFlags.SyntheticMethod; + let checkFlags = 0; + for (const current of containingType.types) { + const type = getApparentType(current); + if (type !== errorType) { + const prop = getPropertyOfType(type, name); + const modifiers = prop ? getDeclarationModifierFlagsFromSymbol(prop) : 0; + if (prop && !(modifiers & excludeModifiers)) { + if (isUnion) { + optionalFlag |= (prop.flags & SymbolFlags.Optional); } - // For a reference of the form 'x.y', an 'x instanceof T' type guard resets the - // narrowed type of 'y' to its declared type. We do this because preceding 'x.y' - // references might reference a different 'y' property. However, we make an exception - // for property accesses where x is a synthetic 'this' expression, indicating that we - // were called from isPropertyInitializedInConstructor. Without this exception, - // initializations of 'this' properties that occur before a 'this instanceof XXX' - // check would not be considered. - if (containsMatchingReference(reference, left) && !isSyntheticThisPropertyAccess(reference)) { - return declaredType; + else { + optionalFlag &= prop.flags; } - return type; - } - - // Check that right operand is a function type with a prototype property - const rightType = getTypeOfExpression(expr.right); - if (!isTypeDerivedFrom(rightType, globalFunctionType)) { - return type; - } - - let targetType: Type | undefined; - const prototypeProperty = getPropertyOfType(rightType, "prototype" as __String); - if (prototypeProperty) { - // Target type is type of the prototype property - const prototypePropertyType = getTypeOfSymbol(prototypeProperty); - if (!isTypeAny(prototypePropertyType)) { - targetType = prototypePropertyType; + const id = "" + getSymbolId(prop); + if (!propSet.has(id)) { + propSet.set(id, prop); + } + checkFlags |= (isReadonlySymbol(prop) ? CheckFlags.Readonly : 0) | + (!(modifiers & ModifierFlags.NonPublicAccessibilityModifier) ? CheckFlags.ContainsPublic : 0) | + (modifiers & ModifierFlags.Protected ? CheckFlags.ContainsProtected : 0) | + (modifiers & ModifierFlags.Private ? CheckFlags.ContainsPrivate : 0) | + (modifiers & ModifierFlags.Static ? CheckFlags.ContainsStatic : 0); + if (!isPrototypeProperty(prop)) { + syntheticFlag = CheckFlags.SyntheticProperty; } } - - // Don't narrow from 'any' if the target type is exactly 'Object' or 'Function' - if (isTypeAny(type) && (targetType === globalObjectType || targetType === globalFunctionType)) { - return type; - } - - if (!targetType) { - const constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct); - targetType = constructSignatures.length ? - getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature)))) : - emptyObjectType; + else if (isUnion) { + const indexInfo = !isLateBoundName(name) && (isNumericLiteralName(name) && getIndexInfoOfType(type, IndexKind.Number) || getIndexInfoOfType(type, IndexKind.String)); + if (indexInfo) { + checkFlags |= CheckFlags.WritePartial | (indexInfo.isReadonly ? CheckFlags.Readonly : 0); + indexTypes = append(indexTypes, isTupleType(type) ? getRestTypeOfTupleType(type) || undefinedType : indexInfo.type); + } + else if (isObjectLiteralType(type)) { + checkFlags |= CheckFlags.WritePartial; + indexTypes = append(indexTypes, undefinedType); + } + else { + checkFlags |= CheckFlags.ReadPartial; + } } - - return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom); } - - function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, isRelated: (source: Type, target: Type) => boolean) { - if (!assumeTrue) { - return filterType(type, t => !isRelated(t, candidate)); - } - // If the current type is a union type, remove all constituents that couldn't be instances of - // the candidate type. If one or more constituents remain, return a union of those. - if (type.flags & TypeFlags.Union) { - const assignableType = filterType(type, t => isRelated(t, candidate)); - if (!(assignableType.flags & TypeFlags.Never)) { - return assignableType; - } - } - // If the candidate type is a subtype of the target type, narrow to the candidate type. - // Otherwise, if the target type is assignable to the candidate type, keep the target type. - // Otherwise, if the candidate type is assignable to the target type, narrow to the candidate - // type. Otherwise, the types are completely unrelated, so narrow to an intersection of the - // two types. - return isTypeSubtypeOf(candidate, type) ? candidate : - isTypeAssignableTo(type, candidate) ? type : - isTypeAssignableTo(candidate, type) ? candidate : - getIntersectionType([type, candidate]); + } + if (!propSet.size) { + return undefined; + } + const props = arrayFrom(propSet.values()); + if (props.length === 1 && !(checkFlags & CheckFlags.ReadPartial) && !indexTypes) { + return props[0]; + } + let declarations: Declaration[] | undefined; + let firstType: ts.Type | undefined; + let nameType: ts.Type | undefined; + const propTypes: ts.Type[] = []; + let firstValueDeclaration: Declaration | undefined; + let hasNonUniformValueDeclaration = false; + for (const prop of props) { + if (!firstValueDeclaration) { + firstValueDeclaration = prop.valueDeclaration; + } + else if (prop.valueDeclaration !== firstValueDeclaration) { + hasNonUniformValueDeclaration = true; + } + declarations = addRange(declarations, prop.declarations); + const type = getTypeOfSymbol(prop); + if (!firstType) { + firstType = type; + nameType = prop.nameType; + } + else if (type !== firstType) { + checkFlags |= CheckFlags.HasNonUniformType; + } + if (isLiteralType(type)) { + checkFlags |= CheckFlags.HasLiteralType; + } + propTypes.push(type); + } + addRange(propTypes, indexTypes); + const result = createSymbol(SymbolFlags.Property | optionalFlag, name, syntheticFlag | checkFlags); + result.containingType = containingType; + if (!hasNonUniformValueDeclaration && firstValueDeclaration) { + result.valueDeclaration = firstValueDeclaration; + // Inherit information about parent type. + if (firstValueDeclaration.symbol.parent) { + result.parent = firstValueDeclaration.symbol.parent; + } + } + result.declarations = declarations!; + result.nameType = nameType; + if (propTypes.length > 2) { + // When `propTypes` has the potential to explode in size when normalized, defer normalization until absolutely needed + result.checkFlags |= CheckFlags.DeferredType; + result.deferralParent = containingType; + result.deferralConstituents = propTypes; + } + else { + result.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes); + } + return result; + } + // Return the symbol for a given property in a union or intersection type, or undefined if the property + // does not exist in any constituent type. Note that the returned property may only be present in some + // constituents, in which case the isPartial flag is set when the containing type is union type. We need + // these partial properties when identifying discriminant properties, but otherwise they are filtered out + // and do not appear to be present in the union type. + function getUnionOrIntersectionProperty(type: UnionOrIntersectionType, name: __String): ts.Symbol | undefined { + const properties = type.propertyCache || (type.propertyCache = createSymbolTable()); + let property = properties.get(name); + if (!property) { + property = createUnionOrIntersectionProperty(type, name); + if (property) { + properties.set(name, property); + } + } + return property; + } + function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: __String): ts.Symbol | undefined { + const property = getUnionOrIntersectionProperty(type, name); + // We need to filter out partial properties in union types + return property && !(getCheckFlags(property) & CheckFlags.ReadPartial) ? property : undefined; + } + /** + * Return the symbol for the property with the given name in the given type. Creates synthetic union properties when + * necessary, maps primitive types and type parameters are to their apparent types, and augments with properties from + * Object and Function as appropriate. + * + * @param type a type to look up property from + * @param name a name of property to look up in a given type + */ + function getPropertyOfType(type: ts.Type, name: __String): ts.Symbol | undefined { + type = getApparentType(type); + if (type.flags & TypeFlags.Object) { + const resolved = resolveStructuredTypeMembers((type)); + const symbol = resolved.members.get(name); + if (symbol && symbolIsValue(symbol)) { + return symbol; } - - function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type { - if (hasMatchingArgument(callExpression, reference)) { - const signature = assumeTrue || !isCallChain(callExpression) ? getEffectsSignature(callExpression) : undefined; - const predicate = signature && getTypePredicateOfSignature(signature); - if (predicate && (predicate.kind === TypePredicateKind.This || predicate.kind === TypePredicateKind.Identifier)) { - return narrowTypeByTypePredicate(type, predicate, callExpression, assumeTrue); - } + const functionType = resolved === anyFunctionType ? globalFunctionType : + resolved.callSignatures.length ? globalCallableFunctionType : + resolved.constructSignatures.length ? globalNewableFunctionType : + undefined; + if (functionType) { + const symbol = getPropertyOfObjectType(functionType, name); + if (symbol) { + return symbol; } - return type; } - - function narrowTypeByTypePredicate(type: Type, predicate: TypePredicate, callExpression: CallExpression, assumeTrue: boolean): Type { - // Don't narrow from 'any' if the predicate type is exactly 'Object' or 'Function' - if (predicate.type && !(isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType))) { - const predicateArgument = getTypePredicateArgument(predicate, callExpression); - if (predicateArgument) { - if (isMatchingReference(reference, predicateArgument)) { - return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf); - } - if (strictNullChecks && assumeTrue && optionalChainContainsReference(predicateArgument, reference) && - !(getTypeFacts(predicate.type) & TypeFacts.EQUndefined)) { - return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); - } - if (containsMatchingReference(reference, predicateArgument)) { - return declaredType; - } - } + return getPropertyOfObjectType(globalObjectType, name); + } + if (type.flags & TypeFlags.UnionOrIntersection) { + return getPropertyOfUnionOrIntersectionType((type), name); + } + return undefined; + } + function getSignaturesOfStructuredType(type: ts.Type, kind: SignatureKind): readonly ts.Signature[] { + if (type.flags & TypeFlags.StructuredType) { + const resolved = resolveStructuredTypeMembers((type)); + return kind === SignatureKind.Call ? resolved.callSignatures : resolved.constructSignatures; + } + return emptyArray; + } + /** + * Return the signatures of the given kind in the given type. Creates synthetic union signatures when necessary and + * maps primitive types and type parameters are to their apparent types. + */ + function getSignaturesOfType(type: ts.Type, kind: SignatureKind): readonly ts.Signature[] { + return getSignaturesOfStructuredType(getApparentType(type), kind); + } + function getIndexInfoOfStructuredType(type: ts.Type, kind: IndexKind): IndexInfo | undefined { + if (type.flags & TypeFlags.StructuredType) { + const resolved = resolveStructuredTypeMembers((type)); + return kind === IndexKind.String ? resolved.stringIndexInfo : resolved.numberIndexInfo; + } + } + function getIndexTypeOfStructuredType(type: ts.Type, kind: IndexKind): ts.Type | undefined { + const info = getIndexInfoOfStructuredType(type, kind); + return info && info.type; + } + // Return the indexing info of the given kind in the given type. Creates synthetic union index types when necessary and + // maps primitive types and type parameters are to their apparent types. + function getIndexInfoOfType(type: ts.Type, kind: IndexKind): IndexInfo | undefined { + return getIndexInfoOfStructuredType(getApparentType(type), kind); + } + // Return the index type of the given kind in the given type. Creates synthetic union index types when necessary and + // maps primitive types and type parameters are to their apparent types. + function getIndexTypeOfType(type: ts.Type, kind: IndexKind): ts.Type | undefined { + return getIndexTypeOfStructuredType(getApparentType(type), kind); + } + function getImplicitIndexTypeOfType(type: ts.Type, kind: IndexKind): ts.Type | undefined { + if (isObjectTypeWithInferableIndex(type)) { + const propTypes: ts.Type[] = []; + for (const prop of getPropertiesOfType(type)) { + if (kind === IndexKind.String || isNumericLiteralName(prop.escapedName)) { + propTypes.push(getTypeOfSymbol(prop)); } - return type; } - - // Narrow the given type based on the given expression having the assumed boolean value. The returned type - // will be a subtype or the same type as the argument. - function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type { - // for `a?.b`, we emulate a synthetic `a !== null && a !== undefined` condition for `a` - if (isExpressionOfOptionalChainRoot(expr) || - isBinaryExpression(expr.parent) && expr.parent.operatorToken.kind === SyntaxKind.QuestionQuestionToken && expr.parent.left === expr) { - return narrowTypeByOptionality(type, expr, assumeTrue); - } - switch (expr.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.ThisKeyword: - case SyntaxKind.SuperKeyword: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - return narrowTypeByTruthiness(type, expr, assumeTrue); - case SyntaxKind.CallExpression: - return narrowTypeByCallExpression(type, expr, assumeTrue); - case SyntaxKind.ParenthesizedExpression: - return narrowType(type, (expr).expression, assumeTrue); - case SyntaxKind.BinaryExpression: - return narrowTypeByBinaryExpression(type, expr, assumeTrue); - case SyntaxKind.PrefixUnaryExpression: - if ((expr).operator === SyntaxKind.ExclamationToken) { - return narrowType(type, (expr).operand, !assumeTrue); - } - break; + if (kind === IndexKind.String) { + append(propTypes, getIndexTypeOfType(type, IndexKind.Number)); + } + if (propTypes.length) { + return getUnionType(propTypes, UnionReduction.Subtype); + } + } + return undefined; + } + // Return list of type parameters with duplicates removed (duplicate identifier errors are generated in the actual + // type checking functions). + function getTypeParametersFromDeclaration(declaration: DeclarationWithTypeParameters): TypeParameter[] | undefined { + let result: TypeParameter[] | undefined; + for (const node of getEffectiveTypeParameterDeclarations(declaration)) { + result = appendIfUnique(result, getDeclaredTypeOfTypeParameter(node.symbol)); + } + return result; + } + function symbolsToArray(symbols: SymbolTable): ts.Symbol[] { + const result: ts.Symbol[] = []; + symbols.forEach((symbol, id) => { + if (!isReservedMemberName(id)) { + result.push(symbol); + } + }); + return result; + } + function isJSDocOptionalParameter(node: ParameterDeclaration) { + return isInJSFile(node) && ( + // node.type should only be a JSDocOptionalType when node is a parameter of a JSDocFunctionType + node.type && node.type.kind === SyntaxKind.JSDocOptionalType + || getJSDocParameterTags(node).some(({ isBracketed, typeExpression }) => isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType)); + } + function tryFindAmbientModule(moduleName: string, withAugmentations: boolean) { + if (isExternalModuleNameRelative(moduleName)) { + return undefined; + } + const symbol = getSymbol(globals, ('"' + moduleName + '"' as __String), SymbolFlags.ValueModule); + // merged symbol is module declaration symbol combined with all augmentations + return symbol && withAugmentations ? getMergedSymbol(symbol) : symbol; + } + function isOptionalParameter(node: ParameterDeclaration | JSDocParameterTag) { + if (hasQuestionToken(node) || isOptionalJSDocParameterTag(node) || isJSDocOptionalParameter(node)) { + return true; + } + if (node.initializer) { + const signature = getSignatureFromDeclaration(node.parent); + const parameterIndex = node.parent.parameters.indexOf(node); + Debug.assert(parameterIndex >= 0); + return parameterIndex >= getMinArgumentCount(signature); + } + const iife = getImmediatelyInvokedFunctionExpression(node.parent); + if (iife) { + return !node.type && + !node.dotDotDotToken && + node.parent.parameters.indexOf(node) >= iife.arguments.length; + } + return false; + } + function isOptionalJSDocParameterTag(node: Node): node is JSDocParameterTag { + if (!isJSDocParameterTag(node)) { + return false; + } + const { isBracketed, typeExpression } = node; + return isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType; + } + function createTypePredicate(kind: TypePredicateKind, parameterName: string | undefined, parameterIndex: number | undefined, type: ts.Type | undefined): TypePredicate { + return { kind, parameterName, parameterIndex, type } as TypePredicate; + } + /** + * Gets the minimum number of type arguments needed to satisfy all non-optional type + * parameters. + */ + function getMinTypeArgumentCount(typeParameters: readonly TypeParameter[] | undefined): number { + let minTypeArgumentCount = 0; + if (typeParameters) { + for (let i = 0; i < typeParameters.length; i++) { + if (!hasTypeParameterDefault(typeParameters[i])) { + minTypeArgumentCount = i + 1; } - return type; } - - function narrowTypeByOptionality(type: Type, expr: Expression, assumePresent: boolean): Type { - if (isMatchingReference(reference, expr)) { - return getTypeWithFacts(type, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull); + } + return minTypeArgumentCount; + } + /** + * Fill in default types for unsupplied type arguments. If `typeArguments` is undefined + * when a default type is supplied, a new array will be created and returned. + * + * @param typeArguments The supplied type arguments. + * @param typeParameters The requested type parameters. + * @param minTypeArgumentCount The minimum number of required type arguments. + */ + function fillMissingTypeArguments(typeArguments: readonly ts.Type[], typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): ts.Type[]; + function fillMissingTypeArguments(typeArguments: readonly ts.Type[] | undefined, typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): ts.Type[] | undefined; + function fillMissingTypeArguments(typeArguments: readonly ts.Type[] | undefined, typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean) { + const numTypeParameters = length(typeParameters); + if (!numTypeParameters) { + return []; + } + const numTypeArguments = length(typeArguments); + if (isJavaScriptImplicitAny || (numTypeArguments >= minTypeArgumentCount && numTypeArguments <= numTypeParameters)) { + const result = typeArguments ? typeArguments.slice() : []; + // Map invalid forward references in default types to the error type + for (let i = numTypeArguments; i < numTypeParameters; i++) { + result[i] = errorType; + } + const baseDefaultType = getDefaultTypeArgumentType(isJavaScriptImplicitAny); + for (let i = numTypeArguments; i < numTypeParameters; i++) { + let defaultType = getDefaultFromTypeParameter(typeParameters![i]); + if (isJavaScriptImplicitAny && defaultType && (isTypeIdenticalTo(defaultType, unknownType) || isTypeIdenticalTo(defaultType, emptyObjectType))) { + defaultType = anyType; + } + result[i] = defaultType ? instantiateType(defaultType, createTypeMapper(typeParameters!, result)) : baseDefaultType; + } + result.length = typeParameters!.length; + return result; + } + return typeArguments && typeArguments.slice(); + } + function getSignatureFromDeclaration(declaration: SignatureDeclaration | JSDocSignature): ts.Signature { + const links = getNodeLinks(declaration); + if (!links.resolvedSignature) { + const parameters: ts.Symbol[] = []; + let flags = SignatureFlags.None; + let minArgumentCount = 0; + let thisParameter: ts.Symbol | undefined; + let hasThisParameter = false; + const iife = getImmediatelyInvokedFunctionExpression(declaration); + const isJSConstructSignature = isJSDocConstructSignature(declaration); + const isUntypedSignatureInJSFile = !iife && + isInJSFile(declaration) && + isValueSignatureDeclaration(declaration) && + !hasJSDocParameterTags(declaration) && + !getJSDocType(declaration); + // If this is a JSDoc construct signature, then skip the first parameter in the + // parameter list. The first parameter represents the return type of the construct + // signature. + for (let i = isJSConstructSignature ? 1 : 0; i < declaration.parameters.length; i++) { + const param = declaration.parameters[i]; + let paramSymbol = param.symbol; + const type = isJSDocParameterTag(param) ? (param.typeExpression && param.typeExpression.type) : param.type; + // Include parameter symbol instead of property symbol in the signature + if (paramSymbol && !!(paramSymbol.flags & SymbolFlags.Property) && !isBindingPattern(param.name)) { + const resolvedSymbol = resolveName(param, paramSymbol.escapedName, SymbolFlags.Value, undefined, undefined, /*isUse*/ false); + paramSymbol = resolvedSymbol!; + } + if (i === 0 && paramSymbol.escapedName === InternalSymbolName.This) { + hasThisParameter = true; + thisParameter = param.symbol; } - if (isMatchingReferenceDiscriminant(expr, declaredType)) { - return narrowTypeByDiscriminant(type, expr, t => getTypeWithFacts(t, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull)); + else { + parameters.push(paramSymbol); } - if (containsMatchingReferenceDiscriminant(reference, expr)) { - return declaredType; + if (type && type.kind === SyntaxKind.LiteralType) { + flags |= SignatureFlags.HasLiteralTypes; + } + // Record a new minimum argument count if this is not an optional parameter + const isOptionalParameter = isOptionalJSDocParameterTag(param) || + param.initializer || param.questionToken || param.dotDotDotToken || + iife && parameters.length > iife.arguments.length && !type || + isUntypedSignatureInJSFile || + isJSDocOptionalParameter(param); + if (!isOptionalParameter) { + minArgumentCount = parameters.length; } - return type; } - } - - function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) { - symbol = symbol.exportSymbol || symbol; - - // If we have an identifier or a property access at the given location, if the location is - // an dotted name expression, and if the location is not an assignment target, obtain the type - // of the expression (which will reflect control flow analysis). If the expression indeed - // resolved to the given symbol, return the narrowed type. - if (location.kind === SyntaxKind.Identifier) { - if (isRightSideOfQualifiedNameOrPropertyAccess(location)) { - location = location.parent; - } - if (isExpressionNode(location) && !isAssignmentTarget(location)) { - const type = getTypeOfExpression(location); - if (getExportSymbolOfValueSymbolIfExported(getNodeLinks(location).resolvedSymbol) === symbol) { - return type; - } + // If only one accessor includes a this-type annotation, the other behaves as if it had the same type annotation + if ((declaration.kind === SyntaxKind.GetAccessor || declaration.kind === SyntaxKind.SetAccessor) && + !hasNonBindableDynamicName(declaration) && + (!hasThisParameter || !thisParameter)) { + const otherKind = declaration.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; + const other = getDeclarationOfKind(getSymbolOfNode(declaration), otherKind); + if (other) { + thisParameter = getAnnotatedAccessorThisParameter(other); } } - // The location isn't a reference to the given symbol, meaning we're being asked - // a hypothetical question of what type the symbol would have if there was a reference - // to it at the given location. Since we have no control flow information for the - // hypothetical reference (control flow information is created and attached by the - // binder), we simply return the declared type of the symbol. - return getTypeOfSymbol(symbol); + const classType = declaration.kind === SyntaxKind.Constructor ? + getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent).symbol)) + : undefined; + const typeParameters = classType ? classType.localTypeParameters : getTypeParametersFromDeclaration(declaration); + if (hasRestParameter(declaration) || isInJSFile(declaration) && maybeAddJsSyntheticRestParameter(declaration, parameters)) { + flags |= SignatureFlags.HasRestParameter; + } + links.resolvedSignature = createSignature(declaration, typeParameters, thisParameter, parameters, + /*resolvedReturnType*/ undefined, /*resolvedTypePredicate*/ undefined, minArgumentCount, flags); } - - function getControlFlowContainer(node: Node): Node { - return findAncestor(node.parent, node => - isFunctionLike(node) && !getImmediatelyInvokedFunctionExpression(node) || - node.kind === SyntaxKind.ModuleBlock || - node.kind === SyntaxKind.SourceFile || - node.kind === SyntaxKind.PropertyDeclaration)!; + return links.resolvedSignature; + } + /** + * A JS function gets a synthetic rest parameter if it references `arguments` AND: + * 1. It has no parameters but at least one `@param` with a type that starts with `...` + * OR + * 2. It has at least one parameter, and the last parameter has a matching `@param` with a type that starts with `...` + */ + function maybeAddJsSyntheticRestParameter(declaration: SignatureDeclaration | JSDocSignature, parameters: ts.Symbol[]): boolean { + if (isJSDocSignature(declaration) || !containsArgumentsReference(declaration)) { + return false; } - - // Check if a parameter is assigned anywhere within its declaring function. - function isParameterAssigned(symbol: Symbol) { - const func = getRootDeclaration(symbol.valueDeclaration).parent; - const links = getNodeLinks(func); - if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) { - links.flags |= NodeCheckFlags.AssignmentsMarked; - if (!hasParentWithAssignmentsMarked(func)) { - markParameterAssignments(func); + const lastParam = lastOrUndefined(declaration.parameters); + const lastParamTags = lastParam ? getJSDocParameterTags(lastParam) : getJSDocTags(declaration).filter(isJSDocParameterTag); + const lastParamVariadicType = firstDefined(lastParamTags, p => p.typeExpression && isJSDocVariadicType(p.typeExpression.type) ? p.typeExpression.type : undefined); + const syntheticArgsSymbol = createSymbol(SymbolFlags.Variable, ("args" as __String), CheckFlags.RestParameter); + syntheticArgsSymbol.type = lastParamVariadicType ? createArrayType(getTypeFromTypeNode(lastParamVariadicType.type)) : anyArrayType; + if (lastParamVariadicType) { + // Replace the last parameter with a rest parameter. + parameters.pop(); + } + parameters.push(syntheticArgsSymbol); + return true; + } + function getSignatureOfTypeTag(node: SignatureDeclaration | JSDocSignature) { + const typeTag = isInJSFile(node) ? getJSDocTypeTag(node) : undefined; + const signature = typeTag && typeTag.typeExpression && getSingleCallSignature(getTypeFromTypeNode(typeTag.typeExpression)); + return signature && getErasedSignature(signature); + } + function getReturnTypeOfTypeTag(node: SignatureDeclaration | JSDocSignature) { + const signature = getSignatureOfTypeTag(node); + return signature && getReturnTypeOfSignature(signature); + } + function containsArgumentsReference(declaration: SignatureDeclaration): boolean { + const links = getNodeLinks(declaration); + if (links.containsArgumentsReference === undefined) { + if (links.flags & NodeCheckFlags.CaptureArguments) { + links.containsArgumentsReference = true; + } + else { + links.containsArgumentsReference = traverse(((declaration as FunctionLikeDeclaration).body!)); + } + } + return links.containsArgumentsReference; + function traverse(node: Node): boolean { + if (!node) + return false; + switch (node.kind) { + case SyntaxKind.Identifier: + return (node).escapedText === "arguments" && isExpressionNode(node); + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return (node).name!.kind === SyntaxKind.ComputedPropertyName + && traverse(((node).name!)); + default: + return !nodeStartsNewLexicalEnvironment(node) && !isPartOfTypeNode(node) && !!forEachChild(node, traverse); + } + } + } + function getSignaturesOfSymbol(symbol: ts.Symbol | undefined): ts.Signature[] { + if (!symbol) + return emptyArray; + const result: ts.Signature[] = []; + for (let i = 0; i < symbol.declarations.length; i++) { + const decl = symbol.declarations[i]; + if (!isFunctionLike(decl)) + continue; + // Don't include signature if node is the implementation of an overloaded function. A node is considered + // an implementation node if it has a body and the previous node is of the same kind and immediately + // precedes the implementation node (i.e. has the same parent and ends where the implementation starts). + if (i > 0 && (decl as FunctionLikeDeclaration).body) { + const previous = symbol.declarations[i - 1]; + if (decl.parent === previous.parent && decl.kind === previous.kind && decl.pos === previous.end) { + continue; } } - return symbol.isAssigned || false; + result.push(getSignatureFromDeclaration(decl)); } - - function hasParentWithAssignmentsMarked(node: Node) { - return !!findAncestor(node.parent, node => isFunctionLike(node) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked)); + return result; + } + function resolveExternalModuleTypeByLiteral(name: StringLiteral) { + const moduleSym = resolveExternalModuleName(name, name); + if (moduleSym) { + const resolvedModuleSymbol = resolveExternalModuleSymbol(moduleSym); + if (resolvedModuleSymbol) { + return getTypeOfSymbol(resolvedModuleSymbol); + } } - - function markParameterAssignments(node: Node) { - if (node.kind === SyntaxKind.Identifier) { - if (isAssignmentTarget(node)) { - const symbol = getResolvedSymbol(node); - if (symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration).kind === SyntaxKind.Parameter) { - symbol.isAssigned = true; + return anyType; + } + function getThisTypeOfSignature(signature: ts.Signature): ts.Type | undefined { + if (signature.thisParameter) { + return getTypeOfSymbol(signature.thisParameter); + } + } + function getTypePredicateOfSignature(signature: ts.Signature): TypePredicate | undefined { + if (!signature.resolvedTypePredicate) { + if (signature.target) { + const targetTypePredicate = getTypePredicateOfSignature(signature.target); + signature.resolvedTypePredicate = targetTypePredicate ? instantiateTypePredicate(targetTypePredicate, signature.mapper!) : noTypePredicate; + } + else if (signature.unionSignatures) { + signature.resolvedTypePredicate = getUnionTypePredicate(signature.unionSignatures) || noTypePredicate; + } + else { + const type = signature.declaration && getEffectiveReturnTypeNode(signature.declaration); + let jsdocPredicate: TypePredicate | undefined; + if (!type && isInJSFile(signature.declaration)) { + const jsdocSignature = getSignatureOfTypeTag(signature.declaration!); + if (jsdocSignature && signature !== jsdocSignature) { + jsdocPredicate = getTypePredicateOfSignature(jsdocSignature); + } + } + signature.resolvedTypePredicate = type && isTypePredicateNode(type) ? + createTypePredicateFromTypePredicateNode(type, signature) : + jsdocPredicate || noTypePredicate; + } + Debug.assert(!!signature.resolvedTypePredicate); + } + return signature.resolvedTypePredicate === noTypePredicate ? undefined : signature.resolvedTypePredicate; + } + function createTypePredicateFromTypePredicateNode(node: TypePredicateNode, signature: ts.Signature): TypePredicate { + const parameterName = node.parameterName; + const type = node.type && getTypeFromTypeNode(node.type); + return parameterName.kind === SyntaxKind.ThisType ? + createTypePredicate(node.assertsModifier ? TypePredicateKind.AssertsThis : TypePredicateKind.This, /*parameterName*/ undefined, /*parameterIndex*/ undefined, type) : + createTypePredicate(node.assertsModifier ? TypePredicateKind.AssertsIdentifier : TypePredicateKind.Identifier, (parameterName.escapedText as string), findIndex(signature.parameters, p => p.escapedName === parameterName.escapedText), type); + } + function getReturnTypeOfSignature(signature: ts.Signature): ts.Type { + if (!signature.resolvedReturnType) { + if (!pushTypeResolution(signature, TypeSystemPropertyName.ResolvedReturnType)) { + return errorType; + } + let type = signature.target ? instantiateType(getReturnTypeOfSignature(signature.target), signature.mapper) : + signature.unionSignatures ? getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), UnionReduction.Subtype) : + getReturnTypeFromAnnotation(signature.declaration!) || + (nodeIsMissing((signature.declaration).body) ? anyType : getReturnTypeFromBody((signature.declaration))); + if (signature.flags & SignatureFlags.IsInnerCallChain) { + type = addOptionalTypeMarker(type); + } + else if (signature.flags & SignatureFlags.IsOuterCallChain) { + type = getOptionalType(type); + } + if (!popTypeResolution()) { + if (signature.declaration) { + const typeNode = getEffectiveReturnTypeNode(signature.declaration); + if (typeNode) { + error(typeNode, Diagnostics.Return_type_annotation_circularly_references_itself); + } + else if (noImplicitAny) { + const declaration = (signature.declaration); + const name = getNameOfDeclaration(declaration); + if (name) { + error(name, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, declarationNameToString(name)); + } + else { + error(declaration, Diagnostics.Function_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions); + } } } + type = anyType; } - else { - forEachChild(node, markParameterAssignments); - } - } - - function isConstVariable(symbol: Symbol) { - return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0 && getTypeOfSymbol(symbol) !== autoArrayType; + signature.resolvedReturnType = type; } - - /** remove undefined from the annotated type of a parameter when there is an initializer (that doesn't include undefined) */ - function removeOptionalityFromDeclaredType(declaredType: Type, declaration: VariableLikeDeclaration): Type { - const annotationIncludesUndefined = strictNullChecks && - declaration.kind === SyntaxKind.Parameter && - declaration.initializer && - getFalsyFlags(declaredType) & TypeFlags.Undefined && - !(getFalsyFlags(checkExpression(declaration.initializer)) & TypeFlags.Undefined); - return annotationIncludesUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : declaredType; + return signature.resolvedReturnType; + } + function getReturnTypeFromAnnotation(declaration: SignatureDeclaration | JSDocSignature) { + if (declaration.kind === SyntaxKind.Constructor) { + return getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent).symbol)); } - - function isConstraintPosition(node: Node) { - const parent = node.parent; - return parent.kind === SyntaxKind.PropertyAccessExpression || - parent.kind === SyntaxKind.CallExpression && (parent).expression === node || - parent.kind === SyntaxKind.ElementAccessExpression && (parent).expression === node || - parent.kind === SyntaxKind.BindingElement && (parent).name === node && !!(parent).initializer; + if (isJSDocConstructSignature(declaration)) { + return getTypeFromTypeNode(((declaration.parameters[0] as ParameterDeclaration).type!)); // TODO: GH#18217 } - - function typeHasNullableConstraint(type: Type) { - return type.flags & TypeFlags.InstantiableNonPrimitive && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.Nullable); + const typeNode = getEffectiveReturnTypeNode(declaration); + if (typeNode) { + return getTypeFromTypeNode(typeNode); } - - function getConstraintForLocation(type: Type, node: Node): Type; - function getConstraintForLocation(type: Type | undefined, node: Node): Type | undefined; - function getConstraintForLocation(type: Type, node: Node): Type | undefined { - // When a node is the left hand expression of a property access, element access, or call expression, - // and the type of the node includes type variables with constraints that are nullable, we fetch the - // apparent type of the node *before* performing control flow analysis such that narrowings apply to - // the constraint type. - if (type && isConstraintPosition(node) && forEachType(type, typeHasNullableConstraint)) { - return mapType(getWidenedType(type), getBaseConstraintOrType); + if (declaration.kind === SyntaxKind.GetAccessor && !hasNonBindableDynamicName(declaration)) { + const jsDocType = isInJSFile(declaration) && getTypeForDeclarationFromJSDocComment(declaration); + if (jsDocType) { + return jsDocType; } - return type; - } - - function isExportOrExportExpression(location: Node) { - return !!findAncestor(location, e => e.parent && isExportAssignment(e.parent) && e.parent.expression === e && isEntityNameExpression(e)); - } - - function markAliasReferenced(symbol: Symbol, location: Node) { - if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location) && ((compilerOptions.preserveConstEnums && isExportOrExportExpression(location)) || !isConstEnumOrConstEnumOnlyModule(resolveAlias(symbol)))) { - markAliasSymbolAsReferenced(symbol); + const setter = getDeclarationOfKind(getSymbolOfNode(declaration), SyntaxKind.SetAccessor); + const setterType = getAnnotatedAccessorType(setter); + if (setterType) { + return setterType; } } - - function checkIdentifier(node: Identifier): Type { - const symbol = getResolvedSymbol(node); - if (symbol === unknownSymbol) { - return errorType; - } - - // As noted in ECMAScript 6 language spec, arrow functions never have an arguments objects. - // Although in down-level emit of arrow function, we emit it using function expression which means that - // arguments objects will be bound to the inner object; emitting arrow function natively in ES6, arguments objects - // will be bound to non-arrow function that contain this arrow function. This results in inconsistent behavior. - // To avoid that we will give an error to users if they use arguments objects in arrow function so that they - // can explicitly bound arguments objects - if (symbol === argumentsSymbol) { - const container = getContainingFunction(node)!; - if (languageVersion < ScriptTarget.ES2015) { - if (container.kind === SyntaxKind.ArrowFunction) { - error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_arrow_function_in_ES3_and_ES5_Consider_using_a_standard_function_expression); - } - else if (hasModifier(container, ModifierFlags.Async)) { - error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_async_function_or_method_in_ES3_and_ES5_Consider_using_a_standard_function_or_method); + return getReturnTypeOfTypeTag(declaration); + } + function isResolvingReturnTypeOfSignature(signature: ts.Signature) { + return !signature.resolvedReturnType && findResolutionCycleStartIndex(signature, TypeSystemPropertyName.ResolvedReturnType) >= 0; + } + function getRestTypeOfSignature(signature: ts.Signature): ts.Type { + return tryGetRestTypeOfSignature(signature) || anyType; + } + function tryGetRestTypeOfSignature(signature: ts.Signature): ts.Type | undefined { + if (signatureHasRestParameter(signature)) { + const sigRestType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); + const restType = isTupleType(sigRestType) ? getRestTypeOfTupleType(sigRestType) : sigRestType; + return restType && getIndexTypeOfType(restType, IndexKind.Number); + } + return undefined; + } + function getSignatureInstantiation(signature: ts.Signature, typeArguments: ts.Type[] | undefined, isJavascript: boolean, inferredTypeParameters?: readonly TypeParameter[]): ts.Signature { + const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, fillMissingTypeArguments(typeArguments, signature.typeParameters, getMinTypeArgumentCount(signature.typeParameters), isJavascript)); + if (inferredTypeParameters) { + const returnSignature = getSingleCallOrConstructSignature(getReturnTypeOfSignature(instantiatedSignature)); + if (returnSignature) { + const newReturnSignature = cloneSignature(returnSignature); + newReturnSignature.typeParameters = inferredTypeParameters; + const newInstantiatedSignature = cloneSignature(instantiatedSignature); + newInstantiatedSignature.resolvedReturnType = getOrCreateTypeFromSignature(newReturnSignature); + return newInstantiatedSignature; + } + } + return instantiatedSignature; + } + function getSignatureInstantiationWithoutFillingInTypeArguments(signature: ts.Signature, typeArguments: readonly ts.Type[] | undefined): ts.Signature { + const instantiations = signature.instantiations || (signature.instantiations = createMap()); + const id = getTypeListId(typeArguments); + let instantiation = instantiations.get(id); + if (!instantiation) { + instantiations.set(id, instantiation = createSignatureInstantiation(signature, typeArguments)); + } + return instantiation; + } + function createSignatureInstantiation(signature: ts.Signature, typeArguments: readonly ts.Type[] | undefined): ts.Signature { + return instantiateSignature(signature, createSignatureTypeMapper(signature, typeArguments), /*eraseTypeParameters*/ true); + } + function createSignatureTypeMapper(signature: ts.Signature, typeArguments: readonly ts.Type[] | undefined): TypeMapper { + return createTypeMapper(signature.typeParameters!, typeArguments); + } + function getErasedSignature(signature: ts.Signature): ts.Signature { + return signature.typeParameters ? + signature.erasedSignatureCache || (signature.erasedSignatureCache = createErasedSignature(signature)) : + signature; + } + function createErasedSignature(signature: ts.Signature) { + // Create an instantiation of the signature where all type arguments are the any type. + return instantiateSignature(signature, createTypeEraser(signature.typeParameters!), /*eraseTypeParameters*/ true); + } + function getCanonicalSignature(signature: ts.Signature): ts.Signature { + return signature.typeParameters ? + signature.canonicalSignatureCache || (signature.canonicalSignatureCache = createCanonicalSignature(signature)) : + signature; + } + function createCanonicalSignature(signature: ts.Signature) { + // Create an instantiation of the signature where each unconstrained type parameter is replaced with + // its original. When a generic class or interface is instantiated, each generic method in the class or + // interface is instantiated with a fresh set of cloned type parameters (which we need to handle scenarios + // where different generations of the same type parameter are in scope). This leads to a lot of new type + // identities, and potentially a lot of work comparing those identities, so here we create an instantiation + // that uses the original type identities for all unconstrained type parameters. + return getSignatureInstantiation(signature, map(signature.typeParameters, tp => tp.target && !getConstraintOfTypeParameter(tp.target) ? tp.target : tp), isInJSFile(signature.declaration)); + } + function getBaseSignature(signature: ts.Signature) { + const typeParameters = signature.typeParameters; + if (typeParameters) { + const typeEraser = createTypeEraser(typeParameters); + const baseConstraints = map(typeParameters, tp => instantiateType(getBaseConstraintOfType(tp), typeEraser) || unknownType); + return instantiateSignature(signature, createTypeMapper(typeParameters, baseConstraints), /*eraseTypeParameters*/ true); + } + return signature; + } + function getOrCreateTypeFromSignature(signature: ts.Signature): ObjectType { + // There are two ways to declare a construct signature, one is by declaring a class constructor + // using the constructor keyword, and the other is declaring a bare construct signature in an + // object type literal or interface (using the new keyword). Each way of declaring a constructor + // will result in a different declaration kind. + if (!signature.isolatedSignatureType) { + const kind = signature.declaration ? signature.declaration.kind : SyntaxKind.Unknown; + const isConstructor = kind === SyntaxKind.Constructor || kind === SyntaxKind.ConstructSignature || kind === SyntaxKind.ConstructorType; + const type = createObjectType(ObjectFlags.Anonymous); + type.members = emptySymbols; + type.properties = emptyArray; + type.callSignatures = !isConstructor ? [signature] : emptyArray; + type.constructSignatures = isConstructor ? [signature] : emptyArray; + signature.isolatedSignatureType = type; + } + return signature.isolatedSignatureType; + } + function getIndexSymbol(symbol: ts.Symbol): ts.Symbol | undefined { + return symbol.members!.get(InternalSymbolName.Index); + } + function getIndexDeclarationOfSymbol(symbol: ts.Symbol, kind: IndexKind): IndexSignatureDeclaration | undefined { + const syntaxKind = kind === IndexKind.Number ? SyntaxKind.NumberKeyword : SyntaxKind.StringKeyword; + const indexSymbol = getIndexSymbol(symbol); + if (indexSymbol) { + for (const decl of indexSymbol.declarations) { + const node = cast(decl, isIndexSignatureDeclaration); + if (node.parameters.length === 1) { + const parameter = node.parameters[0]; + if (parameter.type && parameter.type.kind === syntaxKind) { + return node; } } - - getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments; - return getTypeOfSymbol(symbol); - } - - // We should only mark aliases as referenced if there isn't a local value declaration - // for the symbol. Also, don't mark any property access expression LHS - checkPropertyAccessExpression will handle that - if (!(node.parent && isPropertyAccessExpression(node.parent) && node.parent.expression === node)) { - markAliasReferenced(symbol, node); } - - const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); - let declaration: Declaration | undefined = localOrExportSymbol.valueDeclaration; - - if (localOrExportSymbol.flags & SymbolFlags.Class) { - // Due to the emit for class decorators, any reference to the class from inside of the class body - // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind - // behavior of class names in ES6. - if (declaration.kind === SyntaxKind.ClassDeclaration - && nodeIsDecorated(declaration as ClassDeclaration)) { - let container = getContainingClass(node); - while (container !== undefined) { - if (container === declaration && container.name !== node) { - getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference; - getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass; - break; - } - - container = getContainingClass(container); - } - } - else if (declaration.kind === SyntaxKind.ClassExpression) { - // When we emit a class expression with static members that contain a reference - // to the constructor in the initializer, we will need to substitute that - // binding with an alias as the class name is not in scope. - let container = getThisContainer(node, /*includeArrowFunctions*/ false); - while (container.kind !== SyntaxKind.SourceFile) { - if (container.parent === declaration) { - if (container.kind === SyntaxKind.PropertyDeclaration && hasModifier(container, ModifierFlags.Static)) { - getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference; - getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass; + } + return undefined; + } + function createIndexInfo(type: ts.Type, isReadonly: boolean, declaration?: IndexSignatureDeclaration): IndexInfo { + return { type, isReadonly, declaration }; + } + function getIndexInfoOfSymbol(symbol: ts.Symbol, kind: IndexKind): IndexInfo | undefined { + const declaration = getIndexDeclarationOfSymbol(symbol, kind); + if (declaration) { + return createIndexInfo(declaration.type ? getTypeFromTypeNode(declaration.type) : anyType, hasModifier(declaration, ModifierFlags.Readonly), declaration); + } + return undefined; + } + function getConstraintDeclaration(type: TypeParameter): TypeNode | undefined { + return mapDefined(filter(type.symbol && type.symbol.declarations, isTypeParameterDeclaration), getEffectiveConstraintOfTypeParameter)[0]; + } + function getInferredTypeParameterConstraint(typeParameter: TypeParameter) { + let inferences: ts.Type[] | undefined; + if (typeParameter.symbol) { + for (const declaration of typeParameter.symbol.declarations) { + if (declaration.parent.kind === SyntaxKind.InferType) { + // When an 'infer T' declaration is immediately contained in a type reference node + // (such as 'Foo'), T's constraint is inferred from the constraint of the + // corresponding type parameter in 'Foo'. When multiple 'infer T' declarations are + // present, we form an intersection of the inferred constraint types. + const grandParent = declaration.parent.parent; + if (grandParent.kind === SyntaxKind.TypeReference) { + const typeReference = (grandParent); + const typeParameters = getTypeParametersForTypeReference(typeReference); + if (typeParameters) { + const index = typeReference.typeArguments!.indexOf((declaration.parent)); + if (index < typeParameters.length) { + const declaredConstraint = getConstraintOfTypeParameter(typeParameters[index]); + if (declaredConstraint) { + // Type parameter constraints can reference other type parameters so + // constraints need to be instantiated. If instantiation produces the + // type parameter itself, we discard that inference. For example, in + // type Foo = [T, U]; + // type Bar = T extends Foo ? Foo : T; + // the instantiated constraint for U is X, so we discard that inference. + const mapper = createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReference, typeParameters)); + const constraint = instantiateType(declaredConstraint, mapper); + if (constraint !== typeParameter) { + inferences = append(inferences, constraint); + } + } } - break; } - - container = getThisContainer(container, /*includeArrowFunctions*/ false); + } + // When an 'infer T' declaration is immediately contained in a rest parameter + // declaration, we infer an 'unknown[]' constraint. + else if (grandParent.kind === SyntaxKind.Parameter && (grandParent).dotDotDotToken) { + inferences = append(inferences, createArrayType(unknownType)); } } } - - checkNestedBlockScopedBinding(node, symbol); - - const type = getConstraintForLocation(getTypeOfSymbol(localOrExportSymbol), node); - const assignmentKind = getAssignmentTargetKind(node); - - if (assignmentKind) { - if (!(localOrExportSymbol.flags & SymbolFlags.Variable) && - !(isInJSFile(node) && localOrExportSymbol.flags & SymbolFlags.ValueModule)) { - error(node, Diagnostics.Cannot_assign_to_0_because_it_is_not_a_variable, symbolToString(symbol)); - return errorType; + } + return inferences && getIntersectionType(inferences); + } + /** This is a worker function. Use getConstraintOfTypeParameter which guards against circular constraints. */ + function getConstraintFromTypeParameter(typeParameter: TypeParameter): ts.Type | undefined { + if (!typeParameter.constraint) { + if (typeParameter.target) { + const targetConstraint = getConstraintOfTypeParameter(typeParameter.target); + typeParameter.constraint = targetConstraint ? instantiateType(targetConstraint, typeParameter.mapper) : noConstraintType; + } + else { + const constraintDeclaration = getConstraintDeclaration(typeParameter); + typeParameter.constraint = constraintDeclaration ? getTypeFromTypeNode(constraintDeclaration) : + getInferredTypeParameterConstraint(typeParameter) || noConstraintType; + } + } + return typeParameter.constraint === noConstraintType ? undefined : typeParameter.constraint; + } + function getParentSymbolOfTypeParameter(typeParameter: TypeParameter): ts.Symbol | undefined { + const tp = (getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter)!); + const host = isJSDocTemplateTag(tp.parent) ? getHostSignatureFromJSDoc(tp.parent) : tp.parent; + return host && getSymbolOfNode(host); + } + function getTypeListId(types: readonly ts.Type[] | undefined) { + let result = ""; + if (types) { + const length = types.length; + let i = 0; + while (i < length) { + const startId = types[i].id; + let count = 1; + while (i + count < length && types[i + count].id === startId + count) { + count++; } - if (isReadonlySymbol(localOrExportSymbol)) { - if (localOrExportSymbol.flags & SymbolFlags.Variable) { - error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_constant, symbolToString(symbol)); - } - else { - error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(symbol)); - } - return errorType; + if (result.length) { + result += ","; } - } - - const isAlias = localOrExportSymbol.flags & SymbolFlags.Alias; - - // We only narrow variables and parameters occurring in a non-assignment position. For all other - // entities we simply return the declared type. - if (localOrExportSymbol.flags & SymbolFlags.Variable) { - if (assignmentKind === AssignmentKind.Definite) { - return type; + result += startId; + if (count > 1) { + result += ":" + count; } + i += count; } - else if (isAlias) { - declaration = find(symbol.declarations, isSomeImportDeclaration); + } + return result; + } + // This function is used to propagate certain flags when creating new object type references and union types. + // It is only necessary to do so if a constituent type might be the undefined type, the null type, the type + // of an object literal or the anyFunctionType. This is because there are operations in the type checker + // that care about the presence of such types at arbitrary depth in a containing type. + function getPropagatingFlagsOfTypes(types: readonly ts.Type[], excludeKinds: TypeFlags): ObjectFlags { + let result: ObjectFlags = 0; + for (const type of types) { + if (!(type.flags & excludeKinds)) { + result |= getObjectFlags(type); + } + } + return result & ObjectFlags.PropagatingFlags; + } + function createTypeReference(target: GenericType, typeArguments: readonly ts.Type[] | undefined): TypeReference { + const id = getTypeListId(typeArguments); + let type = target.instantiations.get(id); + if (!type) { + type = (createObjectType(ObjectFlags.Reference, target.symbol)); + target.instantiations.set(id, type); + type.objectFlags |= typeArguments ? getPropagatingFlagsOfTypes(typeArguments, /*excludeKinds*/ 0) : 0; + type.target = target; + type.resolvedTypeArguments = typeArguments; + } + return type; + } + function cloneTypeReference(source: TypeReference): TypeReference { + const type = (createType(source.flags)); + type.symbol = source.symbol; + type.objectFlags = source.objectFlags; + type.target = source.target; + type.resolvedTypeArguments = source.resolvedTypeArguments; + return type; + } + function createDeferredTypeReference(target: GenericType, node: TypeReferenceNode | ArrayTypeNode | TupleTypeNode, mapper?: TypeMapper): DeferredTypeReference { + const aliasSymbol = getAliasSymbolForTypeNode(node); + const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); + const type = (createObjectType(ObjectFlags.Reference, target.symbol)); + type.target = target; + type.node = node; + type.mapper = mapper; + type.aliasSymbol = aliasSymbol; + type.aliasTypeArguments = mapper ? instantiateTypes(aliasTypeArguments, mapper) : aliasTypeArguments; + return type; + } + function getTypeArguments(type: TypeReference): readonly ts.Type[] { + if (!type.resolvedTypeArguments) { + if (!pushTypeResolution(type, TypeSystemPropertyName.ResolvedTypeArguments)) { + return type.target.localTypeParameters?.map(() => errorType) || emptyArray; + } + const node = type.node; + const typeArguments = !node ? emptyArray : + node.kind === SyntaxKind.TypeReference ? concatenate(type.target.outerTypeParameters, getEffectiveTypeArguments(node, type.target.localTypeParameters!)) : + node.kind === SyntaxKind.ArrayType ? [getTypeFromTypeNode(node.elementType)] : + map(node.elementTypes, getTypeFromTypeNode); + if (popTypeResolution()) { + type.resolvedTypeArguments = type.mapper ? instantiateTypes(typeArguments, type.mapper) : typeArguments; } else { - return type; - } - - if (!declaration) { - return type; + type.resolvedTypeArguments = type.target.localTypeParameters?.map(() => errorType) || emptyArray; + error(type.node || currentNode, type.target.symbol + ? Diagnostics.Type_arguments_for_0_circularly_reference_themselves + : Diagnostics.Tuple_type_arguments_circularly_reference_themselves, type.target.symbol && symbolToString(type.target.symbol)); } - - // The declaration container is the innermost function that encloses the declaration of the variable - // or parameter. The flow container is the innermost function starting with which we analyze the control - // flow graph to determine the control flow based type. - const isParameter = getRootDeclaration(declaration).kind === SyntaxKind.Parameter; - const declarationContainer = getControlFlowContainer(declaration); - let flowContainer = getControlFlowContainer(node); - const isOuterVariable = flowContainer !== declarationContainer; - const isSpreadDestructuringAssignmentTarget = node.parent && node.parent.parent && isSpreadAssignment(node.parent) && isDestructuringAssignmentTarget(node.parent.parent); - const isModuleExports = symbol.flags & SymbolFlags.ModuleExports; - // When the control flow originates in a function expression or arrow function and we are referencing - // a const variable or parameter from an outer function, we extend the origin of the control flow - // analysis to include the immediately enclosing function. - while (flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression || - flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethod(flowContainer)) && - (isConstVariable(localOrExportSymbol) || isParameter && !isParameterAssigned(localOrExportSymbol))) { - flowContainer = getControlFlowContainer(flowContainer); - } - // We only look for uninitialized variables in strict null checking mode, and only when we can analyze - // the entire control flow graph from the variable's declaration (i.e. when the flow container and - // declaration container are the same). - const assumeInitialized = isParameter || isAlias || isOuterVariable || isSpreadDestructuringAssignmentTarget || isModuleExports || isBindingElement(declaration) || - type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void)) !== 0 || - isInTypeQuery(node) || node.parent.kind === SyntaxKind.ExportSpecifier) || - node.parent.kind === SyntaxKind.NonNullExpression || - declaration.kind === SyntaxKind.VariableDeclaration && (declaration).exclamationToken || - declaration.flags & NodeFlags.Ambient; - const initialType = assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, declaration as VariableLikeDeclaration) : type) : - type === autoType || type === autoArrayType ? undefinedType : - getOptionalType(type); - const flowType = getFlowTypeOfReference(node, type, initialType, flowContainer, !assumeInitialized); - // A variable is considered uninitialized when it is possible to analyze the entire control flow graph - // from declaration to use, and when the variable's declared type doesn't include undefined but the - // control flow based type does include undefined. - if (!isEvolvingArrayOperationTarget(node) && (type === autoType || type === autoArrayType)) { - if (flowType === autoType || flowType === autoArrayType) { - if (noImplicitAny) { - error(getNameOfDeclaration(declaration), Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined, symbolToString(symbol), typeToString(flowType)); - error(node, Diagnostics.Variable_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); - } - return convertAutoToAny(flowType); + } + return type.resolvedTypeArguments; + } + function getTypeReferenceArity(type: TypeReference): number { + return length(type.target.typeParameters); + } + /** + * Get type from type-reference that reference to class or interface + */ + function getTypeFromClassOrInterfaceReference(node: NodeWithTypeArguments, symbol: ts.Symbol): ts.Type { + const type = (getDeclaredTypeOfSymbol(getMergedSymbol(symbol))); + const typeParameters = type.localTypeParameters; + if (typeParameters) { + const numTypeArguments = length(node.typeArguments); + const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters); + const isJs = isInJSFile(node); + const isJsImplicitAny = !noImplicitAny && isJs; + if (!isJsImplicitAny && (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length)) { + const missingAugmentsTag = isJs && isExpressionWithTypeArguments(node) && !isJSDocAugmentsTag(node.parent); + const diag = minTypeArgumentCount === typeParameters.length ? + missingAugmentsTag ? + Diagnostics.Expected_0_type_arguments_provide_these_with_an_extends_tag : + Diagnostics.Generic_type_0_requires_1_type_argument_s : + missingAugmentsTag ? + Diagnostics.Expected_0_1_type_arguments_provide_these_with_an_extends_tag : + Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments; + const typeStr = typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType); + error(node, diag, typeStr, minTypeArgumentCount, typeParameters.length); + if (!isJs) { + // TODO: Adopt same permissive behavior in TS as in JS to reduce follow-on editing experience failures (requires editing fillMissingTypeArguments) + return errorType; } } - else if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) { - const diag = error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol)); - - // See GH:32846 - if the user is using a variable whose type is () => T1 | ... | undefined - // they may have meant to specify the type as (() => T1 | ...) | undefined - // This is assumed if: the type is a FunctionType, the return type is a Union, the last constituent of - // the union is `undefined` - if (type.symbol && type.symbol.declarations.length === 1 && isFunctionTypeNode(type.symbol.declarations[0])) { - const funcTypeNode = type.symbol.declarations[0]; - const returnType = getReturnTypeFromAnnotation(funcTypeNode); - if (returnType && returnType.flags & TypeFlags.Union) { - const unionTypes = (funcTypeNode.type).types; - if (unionTypes && unionTypes[unionTypes.length - 1].kind === SyntaxKind.UndefinedKeyword) { - const parenedFuncType = getMutableClone(funcTypeNode); - // Highlight to the end of the second to last constituent of the union - parenedFuncType.end = unionTypes[unionTypes.length - 2].end; - addRelatedInfo(diag, createDiagnosticForNode(parenedFuncType, Diagnostics.Did_you_mean_to_parenthesize_this_function_type)); - } - } - } - - // Return the declared type to reduce follow-on errors - return type; + if (node.kind === SyntaxKind.TypeReference && isAliasedType(node)) { + return createDeferredTypeReference((type), (node), /*mapper*/ undefined); } - return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; + // In a type reference, the outer type parameters of the referenced class or interface are automatically + // supplied as type arguments and the type reference only specifies arguments for the local type parameters + // of the class or interface. + const typeArguments = concatenate(type.outerTypeParameters, fillMissingTypeArguments(typeArgumentsFromTypeReferenceNode(node), typeParameters, minTypeArgumentCount, isJs)); + return createTypeReference((type), typeArguments); } - - function isInsideFunction(node: Node, threshold: Node): boolean { - return !!findAncestor(node, n => n === threshold ? "quit" : isFunctionLike(n)); + return checkNoTypeArguments(node, symbol) ? type : errorType; + } + function getTypeAliasInstantiation(symbol: ts.Symbol, typeArguments: readonly ts.Type[] | undefined): ts.Type { + const type = getDeclaredTypeOfSymbol(symbol); + const links = getSymbolLinks(symbol); + const typeParameters = links.typeParameters!; + const id = getTypeListId(typeArguments); + let instantiation = links.instantiations!.get(id); + if (!instantiation) { + links.instantiations!.set(id, instantiation = instantiateType(type, createTypeMapper(typeParameters, fillMissingTypeArguments(typeArguments, typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(symbol.valueDeclaration))))); + } + return instantiation; + } + /** + * Get type from reference to type alias. When a type alias is generic, the declared type of the type alias may include + * references to the type parameters of the alias. We replace those with the actual type arguments by instantiating the + * declared type. Instantiations are cached using the type identities of the type arguments as the key. + */ + function getTypeFromTypeAliasReference(node: NodeWithTypeArguments, symbol: ts.Symbol): ts.Type { + const type = getDeclaredTypeOfSymbol(symbol); + const typeParameters = getSymbolLinks(symbol).typeParameters; + if (typeParameters) { + const numTypeArguments = length(node.typeArguments); + const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters); + if (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length) { + error(node, minTypeArgumentCount === typeParameters.length ? + Diagnostics.Generic_type_0_requires_1_type_argument_s : + Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments, symbolToString(symbol), minTypeArgumentCount, typeParameters.length); + return errorType; + } + return getTypeAliasInstantiation(symbol, typeArgumentsFromTypeReferenceNode(node)); } - - function getPartOfForStatementContainingNode(node: Node, container: ForStatement) { - return findAncestor(node, n => n === container ? "quit" : n === container.initializer || n === container.condition || n === container.incrementor || n === container.statement); + return checkNoTypeArguments(node, symbol) ? type : errorType; + } + function getTypeReferenceName(node: TypeReferenceType): EntityNameOrEntityNameExpression | undefined { + switch (node.kind) { + case SyntaxKind.TypeReference: + return node.typeName; + case SyntaxKind.ExpressionWithTypeArguments: + // We only support expressions that are simple qualified names. For other + // expressions this produces undefined. + const expr = node.expression; + if (isEntityNameExpression(expr)) { + return expr; + } + // fall through; + } + return undefined; + } + function resolveTypeReferenceName(typeReferenceName: EntityNameExpression | EntityName | undefined, meaning: SymbolFlags, ignoreErrors?: boolean) { + if (!typeReferenceName) { + return unknownSymbol; } - - function checkNestedBlockScopedBinding(node: Identifier, symbol: Symbol): void { - if (languageVersion >= ScriptTarget.ES2015 || - (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.Class)) === 0 || - isSourceFile(symbol.valueDeclaration) || - symbol.valueDeclaration.parent.kind === SyntaxKind.CatchClause) { - return; + return resolveEntityName(typeReferenceName, meaning, ignoreErrors) || unknownSymbol; + } + function getTypeReferenceType(node: NodeWithTypeArguments, symbol: ts.Symbol): ts.Type { + if (symbol === unknownSymbol) { + return errorType; + } + symbol = getExpandoSymbol(symbol) || symbol; + if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + return getTypeFromClassOrInterfaceReference(node, symbol); + } + if (symbol.flags & SymbolFlags.TypeAlias) { + return getTypeFromTypeAliasReference(node, symbol); + } + // Get type from reference to named type that cannot be generic (enum or type parameter) + const res = tryGetDeclaredTypeOfSymbol(symbol); + if (res) { + return checkNoTypeArguments(node, symbol) ? + res.flags & TypeFlags.TypeParameter ? getConstrainedTypeVariable((res), node) : getRegularTypeOfLiteralType(res) : + errorType; + } + if (symbol.flags & SymbolFlags.Value && isJSDocTypeReference(node)) { + const jsdocType = getTypeFromJSDocValueReference(node, symbol); + if (jsdocType) { + return jsdocType; } - - // 1. walk from the use site up to the declaration and check - // if there is anything function like between declaration and use-site (is binding/class is captured in function). - // 2. walk from the declaration up to the boundary of lexical environment and check - // if there is an iteration statement in between declaration and boundary (is binding/class declared inside iteration statement) - - const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration); - const usedInFunction = isInsideFunction(node.parent, container); - let current = container; - - let containedInIterationStatement = false; - while (current && !nodeStartsNewLexicalEnvironment(current)) { - if (isIterationStatement(current, /*lookInLabeledStatements*/ false)) { - containedInIterationStatement = true; - break; + else { + // Resolve the type reference as a Type for the purpose of reporting errors. + resolveTypeReferenceName(getTypeReferenceName(node), SymbolFlags.Type); + return getTypeOfSymbol(symbol); + } + } + return errorType; + } + /** + * A JSdoc TypeReference may be to a value, but resolve it as a type anyway. + * Note: If the value is imported from commonjs, it should really be an alias, + * but this function's special-case code fakes alias resolution as well. + */ + function getTypeFromJSDocValueReference(node: NodeWithTypeArguments, symbol: ts.Symbol): ts.Type | undefined { + const valueType = getTypeOfSymbol(symbol); + let typeType = valueType; + if (symbol.valueDeclaration) { + const decl = getRootDeclaration(symbol.valueDeclaration); + let isRequireAlias = false; + if (isVariableDeclaration(decl) && decl.initializer) { + let expr = decl.initializer; + // skip past entity names, eg `require("x").a.b.c` + while (isPropertyAccessExpression(expr)) { + expr = expr.expression; + } + isRequireAlias = isCallExpression(expr) && isRequireCall(expr, /*requireStringLiteralLikeArgument*/ true) && !!valueType.symbol; + } + const isImportTypeWithQualifier = node.kind === SyntaxKind.ImportType && (node as ImportTypeNode).qualifier; + // valueType might not have a symbol, eg, {import('./b').STRING_LITERAL} + if (valueType.symbol && (isRequireAlias || isImportTypeWithQualifier)) { + typeType = getTypeReferenceType(node, valueType.symbol); + } + } + return getSymbolLinks(symbol).resolvedJSDocType = typeType; + } + function getSubstitutionType(typeVariable: TypeVariable, substitute: ts.Type) { + if (substitute.flags & TypeFlags.AnyOrUnknown || substitute === typeVariable) { + return typeVariable; + } + const id = `${getTypeId(typeVariable)}>${getTypeId(substitute)}`; + const cached = substitutionTypes.get(id); + if (cached) { + return cached; + } + const result = (createType(TypeFlags.Substitution)); + result.typeVariable = typeVariable; + result.substitute = substitute; + substitutionTypes.set(id, result); + return result; + } + function isUnaryTupleTypeNode(node: TypeNode) { + return node.kind === SyntaxKind.TupleType && (node).elementTypes.length === 1; + } + function getImpliedConstraint(typeVariable: TypeVariable, checkNode: TypeNode, extendsNode: TypeNode): ts.Type | undefined { + return isUnaryTupleTypeNode(checkNode) && isUnaryTupleTypeNode(extendsNode) ? getImpliedConstraint(typeVariable, (checkNode).elementTypes[0], (extendsNode).elementTypes[0]) : + getActualTypeVariable(getTypeFromTypeNode(checkNode)) === typeVariable ? getTypeFromTypeNode(extendsNode) : + undefined; + } + function getConstrainedTypeVariable(typeVariable: TypeVariable, node: Node) { + let constraints: ts.Type[] | undefined; + while (node && !isStatement(node) && node.kind !== SyntaxKind.JSDocComment) { + const parent = node.parent; + if (parent.kind === SyntaxKind.ConditionalType && node === (parent).trueType) { + const constraint = getImpliedConstraint(typeVariable, (parent).checkType, (parent).extendsType); + if (constraint) { + constraints = append(constraints, constraint); } - current = current.parent; } - - if (containedInIterationStatement) { - if (usedInFunction) { - // mark iteration statement as containing block-scoped binding captured in some function - let capturesBlockScopeBindingInLoopBody = true; - if (isForStatement(container) && - getAncestor(symbol.valueDeclaration, SyntaxKind.VariableDeclarationList)!.parent === container) { - const part = getPartOfForStatementContainingNode(node.parent, container); - if (part) { - const links = getNodeLinks(part); - links.flags |= NodeCheckFlags.ContainsCapturedBlockScopeBinding; - - const capturedBindings = links.capturedBlockScopeBindings || (links.capturedBlockScopeBindings = []); - pushIfUnique(capturedBindings, symbol); - - if (part === container.initializer) { - capturesBlockScopeBindingInLoopBody = false; // Initializer is outside of loop body - } + node = parent; + } + return constraints ? getSubstitutionType(typeVariable, getIntersectionType(append(constraints, typeVariable))) : typeVariable; + } + function isJSDocTypeReference(node: Node): node is TypeReferenceNode { + return !!(node.flags & NodeFlags.JSDoc) && (node.kind === SyntaxKind.TypeReference || node.kind === SyntaxKind.ImportType); + } + function checkNoTypeArguments(node: NodeWithTypeArguments, symbol?: ts.Symbol) { + if (node.typeArguments) { + error(node, Diagnostics.Type_0_is_not_generic, symbol ? symbolToString(symbol) : (node).typeName ? declarationNameToString((node).typeName) : "(anonymous)"); + return false; + } + return true; + } + function getIntendedTypeFromJSDocTypeReference(node: TypeReferenceNode): ts.Type | undefined { + if (isIdentifier(node.typeName)) { + const typeArgs = node.typeArguments; + switch (node.typeName.escapedText) { + case "String": + checkNoTypeArguments(node); + return stringType; + case "Number": + checkNoTypeArguments(node); + return numberType; + case "Boolean": + checkNoTypeArguments(node); + return booleanType; + case "Void": + checkNoTypeArguments(node); + return voidType; + case "Undefined": + checkNoTypeArguments(node); + return undefinedType; + case "Null": + checkNoTypeArguments(node); + return nullType; + case "Function": + case "function": + checkNoTypeArguments(node); + return globalFunctionType; + case "array": + return (!typeArgs || !typeArgs.length) && !noImplicitAny ? anyArrayType : undefined; + case "promise": + return (!typeArgs || !typeArgs.length) && !noImplicitAny ? createPromiseType(anyType) : undefined; + case "Object": + if (typeArgs && typeArgs.length === 2) { + if (isJSDocIndexSignature(node)) { + const indexed = getTypeFromTypeNode(typeArgs[0]); + const target = getTypeFromTypeNode(typeArgs[1]); + const index = createIndexInfo(target, /*isReadonly*/ false); + return createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, indexed === stringType ? index : undefined, indexed === numberType ? index : undefined); } + return anyType; } - if (capturesBlockScopeBindingInLoopBody) { - getNodeLinks(current).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; + checkNoTypeArguments(node); + return !noImplicitAny ? anyType : undefined; + } + } + } + function getTypeFromJSDocNullableTypeNode(node: JSDocNullableType) { + const type = getTypeFromTypeNode(node.type); + return strictNullChecks ? getNullableType(type, TypeFlags.Null) : type; + } + function getTypeFromTypeReference(node: TypeReferenceType): ts.Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + let symbol: ts.Symbol | undefined; + let type: ts.Type | undefined; + const meaning = SymbolFlags.Type; + if (isJSDocTypeReference(node)) { + type = getIntendedTypeFromJSDocTypeReference(node); + if (!type) { + symbol = resolveTypeReferenceName(getTypeReferenceName(node), meaning, /*ignoreErrors*/ true); + if (symbol === unknownSymbol) { + symbol = resolveTypeReferenceName(getTypeReferenceName(node), meaning | SymbolFlags.Value); } + else { + resolveTypeReferenceName(getTypeReferenceName(node), meaning); // Resolve again to mark errors, if any + } + type = getTypeReferenceType(node, symbol); } - - // mark variables that are declared in loop initializer and reassigned inside the body of ForStatement. - // if body of ForStatement will be converted to function then we'll need a extra machinery to propagate reassigned values back. - if (container.kind === SyntaxKind.ForStatement && - getAncestor(symbol.valueDeclaration, SyntaxKind.VariableDeclarationList)!.parent === container && - isAssignedInBodyOfForStatement(node, container)) { - getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.NeedsLoopOutParameter; - } - - // set 'declared inside loop' bit on the block-scoped binding - getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop; } - - if (usedInFunction) { - getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.CapturedBlockScopedBinding; + if (!type) { + symbol = resolveTypeReferenceName(getTypeReferenceName(node), meaning); + type = getTypeReferenceType(node, symbol); } + // Cache both the resolved symbol and the resolved type. The resolved symbol is needed when we check the + // type reference in checkTypeReferenceNode. + links.resolvedSymbol = symbol; + links.resolvedType = type; } - - function isBindingCapturedByNode(node: Node, decl: VariableDeclaration | BindingElement) { - const links = getNodeLinks(node); - return !!links && contains(links.capturedBlockScopeBindings, getSymbolOfNode(decl)); + return links.resolvedType; + } + function typeArgumentsFromTypeReferenceNode(node: NodeWithTypeArguments): ts.Type[] | undefined { + return map(node.typeArguments, getTypeFromTypeNode); + } + function getTypeFromTypeQueryNode(node: TypeQueryNode): ts.Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + // TypeScript 1.0 spec (April 2014): 3.6.3 + // The expression is processed as an identifier expression (section 4.3) + // or property access expression(section 4.10), + // the widened type(section 3.9) of which becomes the result. + links.resolvedType = getRegularTypeOfLiteralType(getWidenedType(checkExpression(node.exprName))); } - - function isAssignedInBodyOfForStatement(node: Identifier, container: ForStatement): boolean { - // skip parenthesized nodes - let current: Node = node; - while (current.parent.kind === SyntaxKind.ParenthesizedExpression) { - current = current.parent; + return links.resolvedType; + } + function getTypeOfGlobalSymbol(symbol: ts.Symbol | undefined, arity: number): ObjectType { + function getTypeDeclaration(symbol: ts.Symbol): Declaration | undefined { + const declarations = symbol.declarations; + for (const declaration of declarations) { + switch (declaration.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + return declaration; + } } - - // check if node is used as LHS in some assignment expression - let isAssigned = false; - if (isAssignmentTarget(current)) { - isAssigned = true; + } + if (!symbol) { + return arity ? emptyGenericType : emptyObjectType; + } + const type = getDeclaredTypeOfSymbol(symbol); + if (!(type.flags & TypeFlags.Object)) { + error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_be_a_class_or_interface_type, symbolName(symbol)); + return arity ? emptyGenericType : emptyObjectType; + } + if (length((type).typeParameters) !== arity) { + error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_have_1_type_parameter_s, symbolName(symbol), arity); + return arity ? emptyGenericType : emptyObjectType; + } + return type; + } + function getGlobalValueSymbol(name: __String, reportErrors: boolean): ts.Symbol | undefined { + return getGlobalSymbol(name, SymbolFlags.Value, reportErrors ? Diagnostics.Cannot_find_global_value_0 : undefined); + } + function getGlobalTypeSymbol(name: __String, reportErrors: boolean): ts.Symbol | undefined { + return getGlobalSymbol(name, SymbolFlags.Type, reportErrors ? Diagnostics.Cannot_find_global_type_0 : undefined); + } + function getGlobalSymbol(name: __String, meaning: SymbolFlags, diagnostic: DiagnosticMessage | undefined): ts.Symbol | undefined { + // Don't track references for global symbols anyway, so value if `isReference` is arbitrary + return resolveName(undefined, name, meaning, diagnostic, name, /*isUse*/ false); + } + function getGlobalType(name: __String, arity: 0, reportErrors: boolean): ObjectType; + function getGlobalType(name: __String, arity: number, reportErrors: boolean): GenericType; + function getGlobalType(name: __String, arity: number, reportErrors: boolean): ObjectType | undefined { + const symbol = getGlobalTypeSymbol(name, reportErrors); + return symbol || reportErrors ? getTypeOfGlobalSymbol(symbol, arity) : undefined; + } + function getGlobalTypedPropertyDescriptorType() { + return deferredGlobalTypedPropertyDescriptorType || (deferredGlobalTypedPropertyDescriptorType = getGlobalType(("TypedPropertyDescriptor" as __String), /*arity*/ 1, /*reportErrors*/ true)) || emptyGenericType; + } + function getGlobalTemplateStringsArrayType() { + return deferredGlobalTemplateStringsArrayType || (deferredGlobalTemplateStringsArrayType = getGlobalType(("TemplateStringsArray" as __String), /*arity*/ 0, /*reportErrors*/ true)) || emptyObjectType; + } + function getGlobalImportMetaType() { + return deferredGlobalImportMetaType || (deferredGlobalImportMetaType = getGlobalType(("ImportMeta" as __String), /*arity*/ 0, /*reportErrors*/ true)) || emptyObjectType; + } + function getGlobalESSymbolConstructorSymbol(reportErrors: boolean) { + return deferredGlobalESSymbolConstructorSymbol || (deferredGlobalESSymbolConstructorSymbol = getGlobalValueSymbol(("Symbol" as __String), reportErrors)); + } + function getGlobalESSymbolType(reportErrors: boolean) { + return deferredGlobalESSymbolType || (deferredGlobalESSymbolType = getGlobalType(("Symbol" as __String), /*arity*/ 0, reportErrors)) || emptyObjectType; + } + function getGlobalPromiseType(reportErrors: boolean) { + return deferredGlobalPromiseType || (deferredGlobalPromiseType = getGlobalType(("Promise" as __String), /*arity*/ 1, reportErrors)) || emptyGenericType; + } + function getGlobalPromiseLikeType(reportErrors: boolean) { + return deferredGlobalPromiseLikeType || (deferredGlobalPromiseLikeType = getGlobalType(("PromiseLike" as __String), /*arity*/ 1, reportErrors)) || emptyGenericType; + } + function getGlobalPromiseConstructorSymbol(reportErrors: boolean): ts.Symbol | undefined { + return deferredGlobalPromiseConstructorSymbol || (deferredGlobalPromiseConstructorSymbol = getGlobalValueSymbol(("Promise" as __String), reportErrors)); + } + function getGlobalPromiseConstructorLikeType(reportErrors: boolean) { + return deferredGlobalPromiseConstructorLikeType || (deferredGlobalPromiseConstructorLikeType = getGlobalType(("PromiseConstructorLike" as __String), /*arity*/ 0, reportErrors)) || emptyObjectType; + } + function getGlobalAsyncIterableType(reportErrors: boolean) { + return deferredGlobalAsyncIterableType || (deferredGlobalAsyncIterableType = getGlobalType(("AsyncIterable" as __String), /*arity*/ 1, reportErrors)) || emptyGenericType; + } + function getGlobalAsyncIteratorType(reportErrors: boolean) { + return deferredGlobalAsyncIteratorType || (deferredGlobalAsyncIteratorType = getGlobalType(("AsyncIterator" as __String), /*arity*/ 3, reportErrors)) || emptyGenericType; + } + function getGlobalAsyncIterableIteratorType(reportErrors: boolean) { + return deferredGlobalAsyncIterableIteratorType || (deferredGlobalAsyncIterableIteratorType = getGlobalType(("AsyncIterableIterator" as __String), /*arity*/ 1, reportErrors)) || emptyGenericType; + } + function getGlobalAsyncGeneratorType(reportErrors: boolean) { + return deferredGlobalAsyncGeneratorType || (deferredGlobalAsyncGeneratorType = getGlobalType(("AsyncGenerator" as __String), /*arity*/ 3, reportErrors)) || emptyGenericType; + } + function getGlobalIterableType(reportErrors: boolean) { + return deferredGlobalIterableType || (deferredGlobalIterableType = getGlobalType(("Iterable" as __String), /*arity*/ 1, reportErrors)) || emptyGenericType; + } + function getGlobalIteratorType(reportErrors: boolean) { + return deferredGlobalIteratorType || (deferredGlobalIteratorType = getGlobalType(("Iterator" as __String), /*arity*/ 3, reportErrors)) || emptyGenericType; + } + function getGlobalIterableIteratorType(reportErrors: boolean) { + return deferredGlobalIterableIteratorType || (deferredGlobalIterableIteratorType = getGlobalType(("IterableIterator" as __String), /*arity*/ 1, reportErrors)) || emptyGenericType; + } + function getGlobalGeneratorType(reportErrors: boolean) { + return deferredGlobalGeneratorType || (deferredGlobalGeneratorType = getGlobalType(("Generator" as __String), /*arity*/ 3, reportErrors)) || emptyGenericType; + } + function getGlobalIteratorYieldResultType(reportErrors: boolean) { + return deferredGlobalIteratorYieldResultType || (deferredGlobalIteratorYieldResultType = getGlobalType(("IteratorYieldResult" as __String), /*arity*/ 1, reportErrors)) || emptyGenericType; + } + function getGlobalIteratorReturnResultType(reportErrors: boolean) { + return deferredGlobalIteratorReturnResultType || (deferredGlobalIteratorReturnResultType = getGlobalType(("IteratorReturnResult" as __String), /*arity*/ 1, reportErrors)) || emptyGenericType; + } + function getGlobalTypeOrUndefined(name: __String, arity = 0): ObjectType | undefined { + const symbol = getGlobalSymbol(name, SymbolFlags.Type, /*diagnostic*/ undefined); + return symbol && (getTypeOfGlobalSymbol(symbol, arity)); + } + function getGlobalExtractSymbol(): ts.Symbol { + return deferredGlobalExtractSymbol || (deferredGlobalExtractSymbol = (getGlobalSymbol(("Extract" as __String), SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!)); // TODO: GH#18217 + } + function getGlobalOmitSymbol(): ts.Symbol { + return deferredGlobalOmitSymbol || (deferredGlobalOmitSymbol = (getGlobalSymbol(("Omit" as __String), SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!)); // TODO: GH#18217 + } + function getGlobalBigIntType(reportErrors: boolean) { + return deferredGlobalBigIntType || (deferredGlobalBigIntType = getGlobalType(("BigInt" as __String), /*arity*/ 0, reportErrors)) || emptyObjectType; + } + /** + * Instantiates a global type that is generic with some element type, and returns that instantiation. + */ + function createTypeFromGenericGlobalType(genericGlobalType: GenericType, typeArguments: readonly ts.Type[]): ObjectType { + return genericGlobalType !== emptyGenericType ? createTypeReference(genericGlobalType, typeArguments) : emptyObjectType; + } + function createTypedPropertyDescriptorType(propertyType: ts.Type): ts.Type { + return createTypeFromGenericGlobalType(getGlobalTypedPropertyDescriptorType(), [propertyType]); + } + function createIterableType(iteratedType: ts.Type): ts.Type { + return createTypeFromGenericGlobalType(getGlobalIterableType(/*reportErrors*/ true), [iteratedType]); + } + function createArrayType(elementType: ts.Type, readonly?: boolean): ObjectType { + return createTypeFromGenericGlobalType(readonly ? globalReadonlyArrayType : globalArrayType, [elementType]); + } + function getArrayOrTupleTargetType(node: ArrayTypeNode | TupleTypeNode): GenericType { + const readonly = isReadonlyTypeOperator(node.parent); + if (node.kind === SyntaxKind.ArrayType || node.elementTypes.length === 1 && node.elementTypes[0].kind === SyntaxKind.RestType) { + return readonly ? globalReadonlyArrayType : globalArrayType; + } + const lastElement = lastOrUndefined(node.elementTypes); + const restElement = lastElement && lastElement.kind === SyntaxKind.RestType ? lastElement : undefined; + const minLength = findLastIndex(node.elementTypes, n => n.kind !== SyntaxKind.OptionalType && n !== restElement) + 1; + return getTupleTypeOfArity(node.elementTypes.length, minLength, !!restElement, readonly, /*associatedNames*/ undefined); + } + // Return true when the given node is transitively contained in type constructs that eagerly + // resolve their constituent types. We include SyntaxKind.TypeReference because type arguments + // of type aliases are eagerly resolved. + function isAliasedType(node: Node): boolean { + const parent = node.parent; + switch (parent.kind) { + case SyntaxKind.ParenthesizedType: + case SyntaxKind.TypeReference: + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + case SyntaxKind.IndexedAccessType: + case SyntaxKind.ConditionalType: + case SyntaxKind.TypeOperator: + return isAliasedType(parent); + case SyntaxKind.TypeAliasDeclaration: + return true; + } + return false; + } + function getTypeFromArrayOrTupleTypeNode(node: ArrayTypeNode | TupleTypeNode): ts.Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const target = getArrayOrTupleTargetType(node); + if (target === emptyGenericType) { + links.resolvedType = emptyObjectType; } - else if ((current.parent.kind === SyntaxKind.PrefixUnaryExpression || current.parent.kind === SyntaxKind.PostfixUnaryExpression)) { - const expr = current.parent; - isAssigned = expr.operator === SyntaxKind.PlusPlusToken || expr.operator === SyntaxKind.MinusMinusToken; + else if (isAliasedType(node)) { + links.resolvedType = node.kind === SyntaxKind.TupleType && node.elementTypes.length === 0 ? target : + createDeferredTypeReference(target, node, /*mapper*/ undefined); } - - if (!isAssigned) { - return false; + else { + const elementTypes = node.kind === SyntaxKind.ArrayType ? [getTypeFromTypeNode(node.elementType)] : map(node.elementTypes, getTypeFromTypeNode); + links.resolvedType = createTypeReference(target, elementTypes); } - - // at this point we know that node is the target of assignment - // now check that modification happens inside the statement part of the ForStatement - return !!findAncestor(current, n => n === container ? "quit" : n === container.statement); } - - function captureLexicalThis(node: Node, container: Node): void { - getNodeLinks(node).flags |= NodeCheckFlags.LexicalThis; - if (container.kind === SyntaxKind.PropertyDeclaration || container.kind === SyntaxKind.Constructor) { - const classNode = container.parent; - getNodeLinks(classNode).flags |= NodeCheckFlags.CaptureThis; + return links.resolvedType; + } + function isReadonlyTypeOperator(node: Node) { + return isTypeOperatorNode(node) && node.operator === SyntaxKind.ReadonlyKeyword; + } + // We represent tuple types as type references to synthesized generic interface types created by + // this function. The types are of the form: + // + // interface Tuple extends Array { 0: T0, 1: T1, 2: T2, ... } + // + // Note that the generic type created by this function has no symbol associated with it. The same + // is true for each of the synthesized type parameters. + function createTupleTypeOfArity(arity: number, minLength: number, hasRestElement: boolean, readonly: boolean, associatedNames: __String[] | undefined): TupleType { + let typeParameters: TypeParameter[] | undefined; + const properties: ts.Symbol[] = []; + const maxLength = hasRestElement ? arity - 1 : arity; + if (arity) { + typeParameters = new Array(arity); + for (let i = 0; i < arity; i++) { + const typeParameter = typeParameters[i] = createTypeParameter(); + if (i < maxLength) { + const property = createSymbol(SymbolFlags.Property | (i >= minLength ? SymbolFlags.Optional : 0), ("" + i as __String), readonly ? CheckFlags.Readonly : 0); + property.type = typeParameter; + properties.push(property); + } + } + } + const literalTypes = []; + for (let i = minLength; i <= maxLength; i++) + literalTypes.push(getLiteralType(i)); + const lengthSymbol = createSymbol(SymbolFlags.Property, ("length" as __String)); + lengthSymbol.type = hasRestElement ? numberType : getUnionType(literalTypes); + properties.push(lengthSymbol); + const type = (createObjectType(ObjectFlags.Tuple | ObjectFlags.Reference)); + type.typeParameters = typeParameters; + type.outerTypeParameters = undefined; + type.localTypeParameters = typeParameters; + type.instantiations = createMap(); + type.instantiations.set(getTypeListId(type.typeParameters), (type)); + type.target = (type); + type.resolvedTypeArguments = type.typeParameters; + type.thisType = createTypeParameter(); + type.thisType.isThisType = true; + type.thisType.constraint = type; + type.declaredProperties = properties; + type.declaredCallSignatures = emptyArray; + type.declaredConstructSignatures = emptyArray; + type.declaredStringIndexInfo = undefined; + type.declaredNumberIndexInfo = undefined; + type.minLength = minLength; + type.hasRestElement = hasRestElement; + type.readonly = readonly; + type.associatedNames = associatedNames; + return type; + } + function getTupleTypeOfArity(arity: number, minLength: number, hasRestElement: boolean, readonly: boolean, associatedNames?: __String[]): GenericType { + const key = arity + (hasRestElement ? "+" : ",") + minLength + (readonly ? "R" : "") + (associatedNames && associatedNames.length ? "," + associatedNames.join(",") : ""); + let type = tupleTypes.get(key); + if (!type) { + tupleTypes.set(key, type = createTupleTypeOfArity(arity, minLength, hasRestElement, readonly, associatedNames)); + } + return type; + } + function createTupleType(elementTypes: readonly ts.Type[], minLength = elementTypes.length, hasRestElement = false, readonly = false, associatedNames?: __String[]) { + const arity = elementTypes.length; + if (arity === 1 && hasRestElement) { + return createArrayType(elementTypes[0], readonly); + } + const tupleType = getTupleTypeOfArity(arity, minLength, arity > 0 && hasRestElement, readonly, associatedNames); + return elementTypes.length ? createTypeReference(tupleType, elementTypes) : tupleType; + } + function sliceTupleType(type: TupleTypeReference, index: number) { + const tuple = type.target; + if (tuple.hasRestElement) { + // don't slice off rest element + index = Math.min(index, getTypeReferenceArity(type) - 1); + } + return createTupleType(getTypeArguments(type).slice(index), Math.max(0, tuple.minLength - index), tuple.hasRestElement, tuple.readonly, tuple.associatedNames && tuple.associatedNames.slice(index)); + } + function getTypeFromOptionalTypeNode(node: OptionalTypeNode): ts.Type { + const type = getTypeFromTypeNode(node.type); + return strictNullChecks ? getOptionalType(type) : type; + } + function getTypeId(type: ts.Type) { + return type.id; + } + function containsType(types: readonly ts.Type[], type: ts.Type): boolean { + return binarySearch(types, type, getTypeId, compareValues) >= 0; + } + function insertType(types: ts.Type[], type: ts.Type): boolean { + const index = binarySearch(types, type, getTypeId, compareValues); + if (index < 0) { + types.splice(~index, 0, type); + return true; + } + return false; + } + function addTypeToUnion(typeSet: ts.Type[], includes: TypeFlags, type: ts.Type) { + const flags = type.flags; + if (flags & TypeFlags.Union) { + return addTypesToUnion(typeSet, includes, (type).types); + } + // We ignore 'never' types in unions + if (!(flags & TypeFlags.Never)) { + includes |= flags & TypeFlags.IncludesMask; + if (flags & TypeFlags.StructuredOrInstantiable) + includes |= TypeFlags.IncludesStructuredOrInstantiable; + if (type === wildcardType) + includes |= TypeFlags.IncludesWildcard; + if (!strictNullChecks && flags & TypeFlags.Nullable) { + if (!(getObjectFlags(type) & ObjectFlags.ContainsWideningType)) + includes |= TypeFlags.IncludesNonWideningType; } else { - getNodeLinks(container).flags |= NodeCheckFlags.CaptureThis; + const len = typeSet.length; + const index = len && type.id > typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues); + if (index < 0) { + typeSet.splice(~index, 0, type); + } } } - - function findFirstSuperCall(n: Node): SuperCall | undefined { - if (isSuperCall(n)) { - return n; - } - else if (isFunctionLike(n)) { - return undefined; - } - return forEachChild(n, findFirstSuperCall); + return includes; + } + // Add the given types to the given type set. Order is preserved, duplicates are removed, + // and nested types of the given kind are flattened into the set. + function addTypesToUnion(typeSet: ts.Type[], includes: TypeFlags, types: readonly ts.Type[]): TypeFlags { + for (const type of types) { + includes = addTypeToUnion(typeSet, includes, type); } - - /** - * Return a cached result if super-statement is already found. - * Otherwise, find a super statement in a given constructor function and cache the result in the node-links of the constructor - * - * @param constructor constructor-function to look for super statement - */ - function getSuperCallInConstructor(constructor: ConstructorDeclaration): SuperCall | undefined { - const links = getNodeLinks(constructor); - - // Only trying to find super-call if we haven't yet tried to find one. Once we try, we will record the result - if (links.hasSuperCall === undefined) { - links.superCall = findFirstSuperCall(constructor.body!); - links.hasSuperCall = links.superCall ? true : false; + return includes; + } + function isSetOfLiteralsFromSameEnum(types: readonly ts.Type[]): boolean { + const first = types[0]; + if (first.flags & TypeFlags.EnumLiteral) { + const firstEnum = getParentOfSymbol(first.symbol); + for (let i = 1; i < types.length; i++) { + const other = types[i]; + if (!(other.flags & TypeFlags.EnumLiteral) || (firstEnum !== getParentOfSymbol(other.symbol))) { + return false; + } } - return links.superCall!; + return true; } - - /** - * Check if the given class-declaration extends null then return true. - * Otherwise, return false - * @param classDecl a class declaration to check if it extends null - */ - function classDeclarationExtendsNull(classDecl: ClassDeclaration): boolean { - const classSymbol = getSymbolOfNode(classDecl); - const classInstanceType = getDeclaredTypeOfSymbol(classSymbol); - const baseConstructorType = getBaseConstructorTypeOfClass(classInstanceType); - - return baseConstructorType === nullWideningType; + return false; + } + function removeSubtypes(types: ts.Type[], primitivesOnly: boolean): boolean { + const len = types.length; + if (len === 0 || isSetOfLiteralsFromSameEnum(types)) { + return true; } - - function checkThisBeforeSuper(node: Node, container: Node, diagnosticMessage: DiagnosticMessage) { - const containingClassDecl = container.parent; - const baseTypeNode = getClassExtendsHeritageElement(containingClassDecl); - - // If a containing class does not have extends clause or the class extends null - // skip checking whether super statement is called before "this" accessing. - if (baseTypeNode && !classDeclarationExtendsNull(containingClassDecl)) { - const superCall = getSuperCallInConstructor(container); - - // We should give an error in the following cases: - // - No super-call - // - "this" is accessing before super-call. - // i.e super(this) - // this.x; super(); - // We want to make sure that super-call is done before accessing "this" so that - // "this" is not accessed as a parameter of the super-call. - if (!superCall || superCall.end > node.pos) { - // In ES6, super inside constructor of class-declaration has to precede "this" accessing - error(node, diagnosticMessage); + let i = len; + let count = 0; + while (i > 0) { + i--; + const source = types[i]; + for (const target of types) { + if (source !== target) { + if (count === 100000) { + // After 100000 subtype checks we estimate the remaining amount of work by assuming the + // same ratio of checks per element. If the estimated number of remaining type checks is + // greater than an upper limit we deem the union type too complex to represent. The + // upper limit is 25M for unions of primitives only, and 1M otherwise. This for example + // caps union types at 5000 unique literal types and 1000 unique object types. + const estimatedCount = (count / (len - i)) * len; + if (estimatedCount > (primitivesOnly ? 25000000 : 1000000)) { + error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent); + return false; + } + } + count++; + if (isTypeSubtypeOf(source, target) && (!(getObjectFlags(getTargetType(source)) & ObjectFlags.Class) || + !(getObjectFlags(getTargetType(target)) & ObjectFlags.Class) || + isTypeDerivedFrom(source, target))) { + orderedRemoveItemAt(types, i); + break; + } } } } - - function checkThisExpression(node: Node): Type { - // Stop at the first arrow function so that we can - // tell whether 'this' needs to be captured. - let container = getThisContainer(node, /* includeArrowFunctions */ true); - let capturedByArrowFunction = false; - - if (container.kind === SyntaxKind.Constructor) { - checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class); + return true; + } + function removeRedundantLiteralTypes(types: ts.Type[], includes: TypeFlags) { + let i = types.length; + while (i > 0) { + i--; + const t = types[i]; + const remove = t.flags & TypeFlags.StringLiteral && includes & TypeFlags.String || + t.flags & TypeFlags.NumberLiteral && includes & TypeFlags.Number || + t.flags & TypeFlags.BigIntLiteral && includes & TypeFlags.BigInt || + t.flags & TypeFlags.UniqueESSymbol && includes & TypeFlags.ESSymbol || + isFreshLiteralType(t) && containsType(types, (t).regularType); + if (remove) { + orderedRemoveItemAt(types, i); } - - // Now skip arrow functions to get the "real" owner of 'this'. - if (container.kind === SyntaxKind.ArrowFunction) { - container = getThisContainer(container, /* includeArrowFunctions */ false); - capturedByArrowFunction = true; + } + } + // We sort and deduplicate the constituent types based on object identity. If the subtypeReduction + // flag is specified we also reduce the constituent type set to only include types that aren't subtypes + // of other types. Subtype reduction is expensive for large union types and is possible only when union + // types are known not to circularly reference themselves (as is the case with union types created by + // expression constructs such as array literals and the || and ?: operators). Named types can + // circularly reference themselves and therefore cannot be subtype reduced during their declaration. + // For example, "type Item = string | (() => Item" is a named type that circularly references itself. + function getUnionType(types: readonly ts.Type[], unionReduction: UnionReduction = UnionReduction.Literal, aliasSymbol?: ts.Symbol, aliasTypeArguments?: readonly ts.Type[]): ts.Type { + if (types.length === 0) { + return neverType; + } + if (types.length === 1) { + return types[0]; + } + const typeSet: ts.Type[] = []; + const includes = addTypesToUnion(typeSet, 0, types); + if (unionReduction !== UnionReduction.None) { + if (includes & TypeFlags.AnyOrUnknown) { + return includes & TypeFlags.Any ? includes & TypeFlags.IncludesWildcard ? wildcardType : anyType : unknownType; } - - switch (container.kind) { - case SyntaxKind.ModuleDeclaration: - error(node, Diagnostics.this_cannot_be_referenced_in_a_module_or_namespace_body); - // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks - break; - case SyntaxKind.EnumDeclaration: - error(node, Diagnostics.this_cannot_be_referenced_in_current_location); - // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks - break; - case SyntaxKind.Constructor: - if (isInConstructorArgumentInitializer(node, container)) { - error(node, Diagnostics.this_cannot_be_referenced_in_constructor_arguments); - // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks + switch (unionReduction) { + case UnionReduction.Literal: + if (includes & (TypeFlags.Literal | TypeFlags.UniqueESSymbol)) { + removeRedundantLiteralTypes(typeSet, includes); } break; - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - if (hasModifier(container, ModifierFlags.Static)) { - error(node, Diagnostics.this_cannot_be_referenced_in_a_static_property_initializer); - // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks + case UnionReduction.Subtype: + if (!removeSubtypes(typeSet, !(includes & TypeFlags.IncludesStructuredOrInstantiable))) { + return errorType; } break; - case SyntaxKind.ComputedPropertyName: - error(node, Diagnostics.this_cannot_be_referenced_in_a_computed_property_name); - break; } - - // When targeting es6, mark that we'll need to capture `this` in its lexically bound scope. - if (capturedByArrowFunction && languageVersion < ScriptTarget.ES2015) { - captureLexicalThis(node, container); + if (typeSet.length === 0) { + return includes & TypeFlags.Null ? includes & TypeFlags.IncludesNonWideningType ? nullType : nullWideningType : + includes & TypeFlags.Undefined ? includes & TypeFlags.IncludesNonWideningType ? undefinedType : undefinedWideningType : + neverType; } - - const type = tryGetThisTypeAt(node, /*includeGlobalThis*/ true, container); - if (noImplicitThis) { - const globalThisType = getTypeOfSymbol(globalThisSymbol); - if (type === globalThisType && capturedByArrowFunction) { - error(node, Diagnostics.The_containing_arrow_function_captures_the_global_value_of_this); - } - else if (!type) { - // With noImplicitThis, functions may not reference 'this' if it has type 'any' - const diag = error(node, Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation); - if (!isSourceFile(container)) { - const outsideThis = tryGetThisTypeAt(container); - if (outsideThis && outsideThis !== globalThisType) { - addRelatedInfo(diag, createDiagnosticForNode(container, Diagnostics.An_outer_value_of_this_is_shadowed_by_this_container)); - } - } + } + return getUnionTypeFromSortedList(typeSet, includes & TypeFlags.NotPrimitiveUnion ? 0 : ObjectFlags.PrimitiveUnion, aliasSymbol, aliasTypeArguments); + } + function getUnionTypePredicate(signatures: readonly ts.Signature[]): TypePredicate | undefined { + let first: TypePredicate | undefined; + const types: ts.Type[] = []; + for (const sig of signatures) { + const pred = getTypePredicateOfSignature(sig); + if (!pred || pred.kind === TypePredicateKind.AssertsThis || pred.kind === TypePredicateKind.AssertsIdentifier) { + continue; + } + if (first) { + if (!typePredicateKindsMatch(first, pred)) { + // No common type predicate. + return undefined; } } - return type || anyType; + else { + first = pred; + } + types.push(pred.type); } - - function tryGetThisTypeAt(node: Node, includeGlobalThis = true, container = getThisContainer(node, /*includeArrowFunctions*/ false)): Type | undefined { - const isInJS = isInJSFile(node); - if (isFunctionLike(container) && - (!isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container))) { - // Note: a parameter initializer should refer to class-this unless function-this is explicitly annotated. - // If this is a function in a JS file, it might be a class method. - const className = getClassNameFromPrototypeMethod(container); - if (isInJS && className) { - const classSymbol = checkExpression(className).symbol; - if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) { - const classType = (getDeclaredTypeOfSymbol(classSymbol) as InterfaceType).thisType!; - return getFlowTypeOfReference(node, classType); - } - } - // Check if it's a constructor definition, can be either a variable decl or function decl - // i.e. - // * /** @constructor */ function [name]() { ... } - // * /** @constructor */ var x = function() { ... } - else if (isInJS && - (container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.FunctionDeclaration) && - getJSDocClassTag(container)) { - const classType = (getDeclaredTypeOfSymbol(getMergedSymbol(container.symbol)) as InterfaceType).thisType!; - return getFlowTypeOfReference(node, classType); - } - - const thisType = getThisTypeOfDeclaration(container) || getContextualThisParameterType(container); - if (thisType) { - return getFlowTypeOfReference(node, thisType); - } + if (!first) { + // No union signatures had a type predicate. + return undefined; + } + const unionType = getUnionType(types); + return createTypePredicate(first.kind, first.parameterName, first.parameterIndex, unionType); + } + function typePredicateKindsMatch(a: TypePredicate, b: TypePredicate): boolean { + return a.kind === b.kind && a.parameterIndex === b.parameterIndex; + } + // This function assumes the constituent type list is sorted and deduplicated. + function getUnionTypeFromSortedList(types: ts.Type[], objectFlags: ObjectFlags, aliasSymbol?: ts.Symbol, aliasTypeArguments?: readonly ts.Type[]): ts.Type { + if (types.length === 0) { + return neverType; + } + if (types.length === 1) { + return types[0]; + } + const id = getTypeListId(types); + let type = unionTypes.get(id); + if (!type) { + type = (createType(TypeFlags.Union)); + unionTypes.set(id, type); + type.objectFlags = objectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); + type.types = types; + /* + Note: This is the alias symbol (or lack thereof) that we see when we first encounter this union type. + For aliases of identical unions, eg `type T = A | B; type U = A | B`, the symbol of the first alias encountered is the aliasSymbol. + (In the language service, the order may depend on the order in which a user takes actions, such as hovering over symbols.) + It's important that we create equivalent union types only once, so that's an unfortunate side effect. + */ + type.aliasSymbol = aliasSymbol; + type.aliasTypeArguments = aliasTypeArguments; + } + return type; + } + function getTypeFromUnionTypeNode(node: UnionTypeNode): ts.Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const aliasSymbol = getAliasSymbolForTypeNode(node); + links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), UnionReduction.Literal, aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)); + } + return links.resolvedType; + } + function addTypeToIntersection(typeSet: ts.Map, includes: TypeFlags, type: ts.Type) { + const flags = type.flags; + if (flags & TypeFlags.Intersection) { + return addTypesToIntersection(typeSet, includes, (type).types); + } + if (isEmptyAnonymousObjectType(type)) { + if (!(includes & TypeFlags.IncludesEmptyObject)) { + includes |= TypeFlags.IncludesEmptyObject; + typeSet.set(type.id.toString(), type); } - - if (isClassLike(container.parent)) { - const symbol = getSymbolOfNode(container.parent); - const type = hasModifier(container, ModifierFlags.Static) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol) as InterfaceType).thisType!; - return getFlowTypeOfReference(node, type); + } + else { + if (flags & TypeFlags.AnyOrUnknown) { + if (type === wildcardType) + includes |= TypeFlags.IncludesWildcard; } - - if (isInJS) { - const type = getTypeForThisExpressionFromJSDoc(container); - if (type && type !== errorType) { - return getFlowTypeOfReference(node, type); + else if ((strictNullChecks || !(flags & TypeFlags.Nullable)) && !typeSet.has(type.id.toString())) { + if (type.flags & TypeFlags.Unit && includes & TypeFlags.Unit) { + // We have seen two distinct unit types which means we should reduce to an + // empty intersection. Adding TypeFlags.NonPrimitive causes that to happen. + includes |= TypeFlags.NonPrimitive; } + typeSet.set(type.id.toString(), type); } - if (isSourceFile(container)) { - // look up in the source file's locals or exports - if (container.commonJsModuleIndicator) { - const fileSymbol = getSymbolOfNode(container); - return fileSymbol && getTypeOfSymbol(fileSymbol); - } - else if (includeGlobalThis) { - return getTypeOfSymbol(globalThisSymbol); - } + includes |= flags & TypeFlags.IncludesMask; + } + return includes; + } + // Add the given types to the given type set. Order is preserved, freshness is removed from literal + // types, duplicates are removed, and nested types of the given kind are flattened into the set. + function addTypesToIntersection(typeSet: ts.Map, includes: TypeFlags, types: readonly ts.Type[]) { + for (const type of types) { + includes = addTypeToIntersection(typeSet, includes, getRegularTypeOfLiteralType(type)); + } + return includes; + } + function removeRedundantPrimitiveTypes(types: ts.Type[], includes: TypeFlags) { + let i = types.length; + while (i > 0) { + i--; + const t = types[i]; + const remove = t.flags & TypeFlags.String && includes & TypeFlags.StringLiteral || + t.flags & TypeFlags.Number && includes & TypeFlags.NumberLiteral || + t.flags & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral || + t.flags & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol; + if (remove) { + orderedRemoveItemAt(types, i); } } - - function getExplicitThisType(node: Expression) { - const container = getThisContainer(node, /*includeArrowFunctions*/ false); - if (isFunctionLike(container)) { - const signature = getSignatureFromDeclaration(container); - if (signature.thisParameter) { - return getExplicitTypeOfSymbol(signature.thisParameter); + } + // Check that the given type has a match in every union. A given type is matched by + // an identical type, and a literal type is additionally matched by its corresponding + // primitive type. + function eachUnionContains(unionTypes: UnionType[], type: ts.Type) { + for (const u of unionTypes) { + if (!containsType(u.types, type)) { + const primitive = type.flags & TypeFlags.StringLiteral ? stringType : + type.flags & TypeFlags.NumberLiteral ? numberType : + type.flags & TypeFlags.BigIntLiteral ? bigintType : + type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : + undefined; + if (!primitive || !containsType(u.types, primitive)) { + return false; } } - if (isClassLike(container.parent)) { - const symbol = getSymbolOfNode(container.parent); - return hasModifier(container, ModifierFlags.Static) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol) as InterfaceType).thisType!; - } } - - function getClassNameFromPrototypeMethod(container: Node) { - // Check if it's the RHS of a x.prototype.y = function [name]() { .... } - if (container.kind === SyntaxKind.FunctionExpression && - isBinaryExpression(container.parent) && - getAssignmentDeclarationKind(container.parent) === AssignmentDeclarationKind.PrototypeProperty) { - // Get the 'x' of 'x.prototype.y = container' - return ((container.parent // x.prototype.y = container - .left as PropertyAccessExpression) // x.prototype.y - .expression as PropertyAccessExpression) // x.prototype - .expression; // x - } - // x.prototype = { method() { } } - else if (container.kind === SyntaxKind.MethodDeclaration && - container.parent.kind === SyntaxKind.ObjectLiteralExpression && - isBinaryExpression(container.parent.parent) && - getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.Prototype) { - return (container.parent.parent.left as PropertyAccessExpression).expression; - } - // x.prototype = { method: function() { } } - else if (container.kind === SyntaxKind.FunctionExpression && - container.parent.kind === SyntaxKind.PropertyAssignment && - container.parent.parent.kind === SyntaxKind.ObjectLiteralExpression && - isBinaryExpression(container.parent.parent.parent) && - getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.Prototype) { - return (container.parent.parent.parent.left as PropertyAccessExpression).expression; - } - // Object.defineProperty(x, "method", { value: function() { } }); - // Object.defineProperty(x, "method", { set: (x: () => void) => void }); - // Object.defineProperty(x, "method", { get: () => function() { }) }); - else if (container.kind === SyntaxKind.FunctionExpression && - isPropertyAssignment(container.parent) && - isIdentifier(container.parent.name) && - (container.parent.name.escapedText === "value" || container.parent.name.escapedText === "get" || container.parent.name.escapedText === "set") && - isObjectLiteralExpression(container.parent.parent) && - isCallExpression(container.parent.parent.parent) && - container.parent.parent.parent.arguments[2] === container.parent.parent && - getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty) { - return (container.parent.parent.parent.arguments[0] as PropertyAccessExpression).expression; - } - // Object.defineProperty(x, "method", { value() { } }); - // Object.defineProperty(x, "method", { set(x: () => void) {} }); - // Object.defineProperty(x, "method", { get() { return () => {} } }); - else if (isMethodDeclaration(container) && - isIdentifier(container.name) && - (container.name.escapedText === "value" || container.name.escapedText === "get" || container.name.escapedText === "set") && - isObjectLiteralExpression(container.parent) && - isCallExpression(container.parent.parent) && - container.parent.parent.arguments[2] === container.parent && - getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty) { - return (container.parent.parent.arguments[0] as PropertyAccessExpression).expression; + return true; + } + function extractIrreducible(types: ts.Type[], flag: TypeFlags) { + if (every(types, t => !!(t.flags & TypeFlags.Union) && some((t as UnionType).types, tt => !!(tt.flags & flag)))) { + for (let i = 0; i < types.length; i++) { + types[i] = filterType(types[i], t => !(t.flags & flag)); } + return true; } - - function getTypeForThisExpressionFromJSDoc(node: Node) { - const jsdocType = getJSDocType(node); - if (jsdocType && jsdocType.kind === SyntaxKind.JSDocFunctionType) { - const jsDocFunctionType = jsdocType; - if (jsDocFunctionType.parameters.length > 0 && - jsDocFunctionType.parameters[0].name && - (jsDocFunctionType.parameters[0].name as Identifier).escapedText === InternalSymbolName.This) { - return getTypeFromTypeNode(jsDocFunctionType.parameters[0].type!); - } + return false; + } + // If the given list of types contains more than one union of primitive types, replace the + // first with a union containing an intersection of those primitive types, then remove the + // other unions and return true. Otherwise, do nothing and return false. + function intersectUnionsOfPrimitiveTypes(types: ts.Type[]) { + let unionTypes: UnionType[] | undefined; + const index = findIndex(types, t => !!(getObjectFlags(t) & ObjectFlags.PrimitiveUnion)); + if (index < 0) { + return false; + } + let i = index + 1; + // Remove all but the first union of primitive types and collect them in + // the unionTypes array. + while (i < types.length) { + const t = types[i]; + if (getObjectFlags(t) & ObjectFlags.PrimitiveUnion) { + (unionTypes || (unionTypes = [(types[index])])).push((t)); + orderedRemoveItemAt(types, i); } - const thisTag = getJSDocThisTag(node); - if (thisTag && thisTag.typeExpression) { - return getTypeFromTypeNode(thisTag.typeExpression); + else { + i++; } } - - function isInConstructorArgumentInitializer(node: Node, constructorDecl: Node): boolean { - return !!findAncestor(node, n => isFunctionLikeDeclaration(n) ? "quit" : n.kind === SyntaxKind.Parameter && n.parent === constructorDecl); + // Return false if there was only one union of primitive types + if (!unionTypes) { + return false; } - - function checkSuperExpression(node: Node): Type { - const isCallExpression = node.parent.kind === SyntaxKind.CallExpression && (node.parent).expression === node; - - let container = getSuperContainer(node, /*stopOnFunctions*/ true); - let needToCaptureLexicalThis = false; - - // adjust the container reference in case if super is used inside arrow functions with arbitrarily deep nesting - if (!isCallExpression) { - while (container && container.kind === SyntaxKind.ArrowFunction) { - container = getSuperContainer(container, /*stopOnFunctions*/ true); - needToCaptureLexicalThis = languageVersion < ScriptTarget.ES2015; + // We have more than one union of primitive types, now intersect them. For each + // type in each union we check if the type is matched in every union and if so + // we include it in the result. + const checked: ts.Type[] = []; + const result: ts.Type[] = []; + for (const u of unionTypes) { + for (const t of u.types) { + if (insertType(checked, t)) { + if (eachUnionContains(unionTypes, t)) { + insertType(result, t); + } } } - - const canUseSuperExpression = isLegalUsageOfSuperExpression(container); - let nodeCheckFlag: NodeCheckFlags = 0; - - if (!canUseSuperExpression) { - // issue more specific error if super is used in computed property name - // class A { foo() { return "1" }} - // class B { - // [super.foo()]() {} - // } - const current = findAncestor(node, n => n === container ? "quit" : n.kind === SyntaxKind.ComputedPropertyName); - if (current && current.kind === SyntaxKind.ComputedPropertyName) { - error(node, Diagnostics.super_cannot_be_referenced_in_a_computed_property_name); - } - else if (isCallExpression) { - error(node, Diagnostics.Super_calls_are_not_permitted_outside_constructors_or_in_nested_functions_inside_constructors); - } - else if (!container || !container.parent || !(isClassLike(container.parent) || container.parent.kind === SyntaxKind.ObjectLiteralExpression)) { - error(node, Diagnostics.super_can_only_be_referenced_in_members_of_derived_classes_or_object_literal_expressions); + } + // Finally replace the first union with the result + types[index] = getUnionTypeFromSortedList(result, ObjectFlags.PrimitiveUnion); + return true; + } + function createIntersectionType(types: ts.Type[], aliasSymbol?: ts.Symbol, aliasTypeArguments?: readonly ts.Type[]) { + const result = (createType(TypeFlags.Intersection)); + result.objectFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); + result.types = types; + result.aliasSymbol = aliasSymbol; // See comment in `getUnionTypeFromSortedList`. + result.aliasTypeArguments = aliasTypeArguments; + return result; + } + // We normalize combinations of intersection and union types based on the distributive property of the '&' + // operator. Specifically, because X & (A | B) is equivalent to X & A | X & B, we can transform intersection + // types with union type constituents into equivalent union types with intersection type constituents and + // effectively ensure that union types are always at the top level in type representations. + // + // We do not perform structural deduplication on intersection types. Intersection types are created only by the & + // type operator and we can't reduce those because we want to support recursive intersection types. For example, + // a type alias of the form "type List = T & { next: List }" cannot be reduced during its declaration. + // Also, unlike union types, the order of the constituent types is preserved in order that overload resolution + // for intersections of types with signatures can be deterministic. + function getIntersectionType(types: readonly ts.Type[], aliasSymbol?: ts.Symbol, aliasTypeArguments?: readonly ts.Type[]): ts.Type { + const typeMembershipMap: ts.Map = createMap(); + const includes = addTypesToIntersection(typeMembershipMap, 0, types); + const typeSet: ts.Type[] = arrayFrom(typeMembershipMap.values()); + // An intersection type is considered empty if it contains + // the type never, or + // more than one unit type or, + // an object type and a nullable type (null or undefined), or + // a string-like type and a type known to be non-string-like, or + // a number-like type and a type known to be non-number-like, or + // a symbol-like type and a type known to be non-symbol-like, or + // a void-like type and a type known to be non-void-like, or + // a non-primitive type and a type known to be primitive. + if (includes & TypeFlags.Never || + strictNullChecks && includes & TypeFlags.Nullable && includes & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.IncludesEmptyObject) || + includes & TypeFlags.NonPrimitive && includes & (TypeFlags.DisjointDomains & ~TypeFlags.NonPrimitive) || + includes & TypeFlags.StringLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.StringLike) || + includes & TypeFlags.NumberLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.NumberLike) || + includes & TypeFlags.BigIntLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.BigIntLike) || + includes & TypeFlags.ESSymbolLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.ESSymbolLike) || + includes & TypeFlags.VoidLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.VoidLike)) { + return neverType; + } + if (includes & TypeFlags.Any) { + return includes & TypeFlags.IncludesWildcard ? wildcardType : anyType; + } + if (!strictNullChecks && includes & TypeFlags.Nullable) { + return includes & TypeFlags.Undefined ? undefinedType : nullType; + } + if (includes & TypeFlags.String && includes & TypeFlags.StringLiteral || + includes & TypeFlags.Number && includes & TypeFlags.NumberLiteral || + includes & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral || + includes & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol) { + removeRedundantPrimitiveTypes(typeSet, includes); + } + if (includes & TypeFlags.IncludesEmptyObject && includes & TypeFlags.Object) { + orderedRemoveItemAt(typeSet, findIndex(typeSet, isEmptyAnonymousObjectType)); + } + if (typeSet.length === 0) { + return unknownType; + } + if (typeSet.length === 1) { + return typeSet[0]; + } + const id = getTypeListId(typeSet); + let result = intersectionTypes.get(id); + if (!result) { + if (includes & TypeFlags.Union) { + if (intersectUnionsOfPrimitiveTypes(typeSet)) { + // When the intersection creates a reduced set (which might mean that *all* union types have + // disappeared), we restart the operation to get a new set of combined flags. Once we have + // reduced we'll never reduce again, so this occurs at most once. + result = getIntersectionType(typeSet, aliasSymbol, aliasTypeArguments); } - else { - error(node, Diagnostics.super_property_access_is_permitted_only_in_a_constructor_member_function_or_member_accessor_of_a_derived_class); + else if (extractIrreducible(typeSet, TypeFlags.Undefined)) { + result = getUnionType([getIntersectionType(typeSet), undefinedType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); } - return errorType; - } - - if (!isCallExpression && container.kind === SyntaxKind.Constructor) { - checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_a_property_of_super_in_the_constructor_of_a_derived_class); - } - - if (hasModifier(container, ModifierFlags.Static) || isCallExpression) { - nodeCheckFlag = NodeCheckFlags.SuperStatic; - } - else { - nodeCheckFlag = NodeCheckFlags.SuperInstance; - } - - getNodeLinks(node).flags |= nodeCheckFlag; - - // Due to how we emit async functions, we need to specialize the emit for an async method that contains a `super` reference. - // This is due to the fact that we emit the body of an async function inside of a generator function. As generator - // functions cannot reference `super`, we emit a helper inside of the method body, but outside of the generator. This helper - // uses an arrow function, which is permitted to reference `super`. - // - // There are two primary ways we can access `super` from within an async method. The first is getting the value of a property - // or indexed access on super, either as part of a right-hand-side expression or call expression. The second is when setting the value - // of a property or indexed access, either as part of an assignment expression or destructuring assignment. - // - // The simplest case is reading a value, in which case we will emit something like the following: - // - // // ts - // ... - // async asyncMethod() { - // let x = await super.asyncMethod(); - // return x; - // } - // ... - // - // // js - // ... - // asyncMethod() { - // const _super = Object.create(null, { - // asyncMethod: { get: () => super.asyncMethod }, - // }); - // return __awaiter(this, arguments, Promise, function *() { - // let x = yield _super.asyncMethod.call(this); - // return x; - // }); - // } - // ... - // - // The more complex case is when we wish to assign a value, especially as part of a destructuring assignment. As both cases - // are legal in ES6, but also likely less frequent, we only emit setters if there is an assignment: - // - // // ts - // ... - // async asyncMethod(ar: Promise) { - // [super.a, super.b] = await ar; - // } - // ... - // - // // js - // ... - // asyncMethod(ar) { - // const _super = Object.create(null, { - // a: { get: () => super.a, set: (v) => super.a = v }, - // b: { get: () => super.b, set: (v) => super.b = v } - // }; - // return __awaiter(this, arguments, Promise, function *() { - // [_super.a, _super.b] = yield ar; - // }); - // } - // ... - // - // Creating an object that has getter and setters instead of just an accessor function is required for destructuring assignments - // as a call expression cannot be used as the target of a destructuring assignment while a property access can. - // - // For element access expressions (`super[x]`), we emit a generic helper that forwards the element access in both situations. - if (container.kind === SyntaxKind.MethodDeclaration && hasModifier(container, ModifierFlags.Async)) { - if (isSuperProperty(node.parent) && isAssignmentTarget(node.parent)) { - getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuperBinding; + else if (extractIrreducible(typeSet, TypeFlags.Null)) { + result = getUnionType([getIntersectionType(typeSet), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); } else { - getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuper; + // We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of + // the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain. + // If the estimated size of the resulting union type exceeds 100000 constituents, report an error. + const size = reduceLeft(typeSet, (n, t) => n * (t.flags & TypeFlags.Union ? (t).types.length : 1), 1); + if (size >= 100000) { + error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent); + return errorType; + } + const unionIndex = findIndex(typeSet, t => (t.flags & TypeFlags.Union) !== 0); + const unionType = (typeSet[unionIndex]); + result = getUnionType(map(unionType.types, t => getIntersectionType(replaceElement(typeSet, unionIndex, t))), UnionReduction.Literal, aliasSymbol, aliasTypeArguments); } } - - if (needToCaptureLexicalThis) { - // call expressions are allowed only in constructors so they should always capture correct 'this' - // super property access expressions can also appear in arrow functions - - // in this case they should also use correct lexical this - captureLexicalThis(node.parent, container); + else { + result = createIntersectionType(typeSet, aliasSymbol, aliasTypeArguments); } - - if (container.parent.kind === SyntaxKind.ObjectLiteralExpression) { - if (languageVersion < ScriptTarget.ES2015) { - error(node, Diagnostics.super_is_only_allowed_in_members_of_object_literal_expressions_when_option_target_is_ES2015_or_higher); - return errorType; + intersectionTypes.set(id, result); + } + return result; + } + function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): ts.Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const aliasSymbol = getAliasSymbolForTypeNode(node); + links.resolvedType = getIntersectionType(map(node.types, getTypeFromTypeNode), aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)); + } + return links.resolvedType; + } + function createIndexType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) { + const result = (createType(TypeFlags.Index)); + result.type = type; + result.stringsOnly = stringsOnly; + return result; + } + function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) { + return stringsOnly ? + type.resolvedStringIndexType || (type.resolvedStringIndexType = createIndexType(type, /*stringsOnly*/ true)) : + type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, /*stringsOnly*/ false)); + } + function getLiteralTypeFromPropertyName(name: PropertyName) { + return isIdentifier(name) ? getLiteralType(unescapeLeadingUnderscores(name.escapedText)) : + getRegularTypeOfLiteralType(isComputedPropertyName(name) ? checkComputedPropertyName(name) : checkExpression(name)); + } + function getBigIntLiteralType(node: BigIntLiteral): LiteralType { + return getLiteralType({ + negative: false, + base10Value: parsePseudoBigInt(node.text) + }); + } + function getLiteralTypeFromProperty(prop: ts.Symbol, include: TypeFlags) { + if (!(getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier)) { + let type = getLateBoundSymbol(prop).nameType; + if (!type && !isKnownSymbol(prop)) { + if (prop.escapedName === InternalSymbolName.Default) { + type = getLiteralType("default"); } else { - // for object literal assume that type of 'super' is 'any' - return anyType; + const name = prop.valueDeclaration && (getNameOfDeclaration(prop.valueDeclaration) as PropertyName); + type = name && getLiteralTypeFromPropertyName(name) || getLiteralType(symbolName(prop)); } } - - // at this point the only legal case for parent is ClassLikeDeclaration - const classLikeDeclaration = container.parent; - if (!getClassExtendsHeritageElement(classLikeDeclaration)) { - error(node, Diagnostics.super_can_only_be_referenced_in_a_derived_class); - return errorType; - } - - const classType = getDeclaredTypeOfSymbol(getSymbolOfNode(classLikeDeclaration)); - const baseClassType = classType && getBaseTypes(classType)[0]; - if (!baseClassType) { - return errorType; - } - - if (container.kind === SyntaxKind.Constructor && isInConstructorArgumentInitializer(node, container)) { - // issue custom error message for super property access in constructor arguments (to be aligned with old compiler) - error(node, Diagnostics.super_cannot_be_referenced_in_constructor_arguments); - return errorType; + if (type && type.flags & include) { + return type; } - - return nodeCheckFlag === NodeCheckFlags.SuperStatic - ? getBaseConstructorTypeOfClass(classType) - : getTypeWithThisArgument(baseClassType, classType.thisType); - - function isLegalUsageOfSuperExpression(container: Node): boolean { - if (!container) { - return false; - } - - if (isCallExpression) { - // TS 1.0 SPEC (April 2014): 4.8.1 - // Super calls are only permitted in constructors of derived classes - return container.kind === SyntaxKind.Constructor; - } - else { - // TS 1.0 SPEC (April 2014) - // 'super' property access is allowed - // - In a constructor, instance member function, instance member accessor, or instance member variable initializer where this references a derived class instance - // - In a static member function or static member accessor - - // topmost container must be something that is directly nested in the class declaration\object literal expression - if (isClassLike(container.parent) || container.parent.kind === SyntaxKind.ObjectLiteralExpression) { - if (hasModifier(container, ModifierFlags.Static)) { - return container.kind === SyntaxKind.MethodDeclaration || - container.kind === SyntaxKind.MethodSignature || - container.kind === SyntaxKind.GetAccessor || - container.kind === SyntaxKind.SetAccessor; - } - else { - return container.kind === SyntaxKind.MethodDeclaration || - container.kind === SyntaxKind.MethodSignature || - container.kind === SyntaxKind.GetAccessor || - container.kind === SyntaxKind.SetAccessor || - container.kind === SyntaxKind.PropertyDeclaration || - container.kind === SyntaxKind.PropertySignature || - container.kind === SyntaxKind.Constructor; - } - } - } - - return false; + } + return neverType; + } + function getLiteralTypeFromProperties(type: ts.Type, include: TypeFlags) { + return getUnionType(map(getPropertiesOfType(type), p => getLiteralTypeFromProperty(p, include))); + } + function getNonEnumNumberIndexInfo(type: ts.Type) { + const numberIndexInfo = getIndexInfoOfType(type, IndexKind.Number); + return numberIndexInfo !== enumNumberIndexInfo ? numberIndexInfo : undefined; + } + function getIndexType(type: ts.Type, stringsOnly = keyofStringsOnly, noIndexSignatures?: boolean): ts.Type { + return type.flags & TypeFlags.Union ? getIntersectionType(map((type).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : + type.flags & TypeFlags.Intersection ? getUnionType(map((type).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : + maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive) ? getIndexTypeForGenericType((type), stringsOnly) : + getObjectFlags(type) & ObjectFlags.Mapped ? filterType(getConstraintTypeFromMappedType((type)), t => !(noIndexSignatures && t.flags & (TypeFlags.Any | TypeFlags.String))) : + type === wildcardType ? wildcardType : + type.flags & TypeFlags.Unknown ? neverType : + type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType : + stringsOnly ? !noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? stringType : getLiteralTypeFromProperties(type, TypeFlags.StringLiteral) : + !noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? getUnionType([stringType, numberType, getLiteralTypeFromProperties(type, TypeFlags.UniqueESSymbol)]) : + getNonEnumNumberIndexInfo(type) ? getUnionType([numberType, getLiteralTypeFromProperties(type, TypeFlags.StringLiteral | TypeFlags.UniqueESSymbol)]) : + getLiteralTypeFromProperties(type, TypeFlags.StringOrNumberLiteralOrUnique); + } + function getExtractStringType(type: ts.Type) { + if (keyofStringsOnly) { + return type; + } + const extractTypeAlias = getGlobalExtractSymbol(); + return extractTypeAlias ? getTypeAliasInstantiation(extractTypeAlias, [type, stringType]) : stringType; + } + function getIndexTypeOrString(type: ts.Type): ts.Type { + const indexType = getExtractStringType(getIndexType(type)); + return indexType.flags & TypeFlags.Never ? stringType : indexType; + } + function getTypeFromTypeOperatorNode(node: TypeOperatorNode): ts.Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + switch (node.operator) { + case SyntaxKind.KeyOfKeyword: + links.resolvedType = getIndexType(getTypeFromTypeNode(node.type)); + break; + case SyntaxKind.UniqueKeyword: + links.resolvedType = node.type.kind === SyntaxKind.SymbolKeyword + ? getESSymbolLikeTypeForNode(walkUpParenthesizedTypes(node.parent)) + : errorType; + break; + case SyntaxKind.ReadonlyKeyword: + links.resolvedType = getTypeFromTypeNode(node.type); + break; + default: + throw Debug.assertNever(node.operator); } } - - function getContainingObjectLiteral(func: SignatureDeclaration): ObjectLiteralExpression | undefined { - return (func.kind === SyntaxKind.MethodDeclaration || - func.kind === SyntaxKind.GetAccessor || - func.kind === SyntaxKind.SetAccessor) && func.parent.kind === SyntaxKind.ObjectLiteralExpression ? func.parent : - func.kind === SyntaxKind.FunctionExpression && func.parent.kind === SyntaxKind.PropertyAssignment ? func.parent.parent : - undefined; + return links.resolvedType; + } + function createIndexedAccessType(objectType: ts.Type, indexType: ts.Type) { + const type = (createType(TypeFlags.IndexedAccess)); + type.objectType = objectType; + type.indexType = indexType; + return type; + } + /** + * Returns if a type is or consists of a JSLiteral object type + * In addition to objects which are directly literals, + * * unions where every element is a jsliteral + * * intersections where at least one element is a jsliteral + * * and instantiable types constrained to a jsliteral + * Should all count as literals and not print errors on access or assignment of possibly existing properties. + * This mirrors the behavior of the index signature propagation, to which this behaves similarly (but doesn't affect assignability or inference). + */ + function isJSLiteralType(type: ts.Type): boolean { + if (noImplicitAny) { + return false; // Flag is meaningless under `noImplicitAny` mode + } + if (getObjectFlags(type) & ObjectFlags.JSLiteral) { + return true; } - - function getThisTypeArgument(type: Type): Type | undefined { - return getObjectFlags(type) & ObjectFlags.Reference && (type).target === globalThisType ? getTypeArguments(type)[0] : undefined; + if (type.flags & TypeFlags.Union) { + return every((type as UnionType).types, isJSLiteralType); } - - function getThisTypeFromContextualType(type: Type): Type | undefined { - return mapType(type, t => { - return t.flags & TypeFlags.Intersection ? forEach((t).types, getThisTypeArgument) : getThisTypeArgument(t); - }); + if (type.flags & TypeFlags.Intersection) { + return some((type as IntersectionType).types, isJSLiteralType); } - - function getContextualThisParameterType(func: SignatureDeclaration): Type | undefined { - if (func.kind === SyntaxKind.ArrowFunction) { - return undefined; + if (type.flags & TypeFlags.Instantiable) { + return isJSLiteralType(getResolvedBaseConstraint(type)); + } + return false; + } + function getPropertyNameFromIndex(indexType: ts.Type, accessNode: StringLiteral | Identifier | ObjectBindingPattern | ArrayBindingPattern | ComputedPropertyName | NumericLiteral | IndexedAccessTypeNode | ElementAccessExpression | SyntheticExpression | undefined) { + const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; + return isTypeUsableAsPropertyName(indexType) ? + getPropertyNameFromType(indexType) : + accessExpression && checkThatExpressionIsProperSymbolReference(accessExpression.argumentExpression, indexType, /*reportError*/ false) ? + getPropertyNameForKnownSymbolName(idText((accessExpression.argumentExpression).name)) : + accessNode && isPropertyName(accessNode) ? + // late bound names are handled in the first branch, so here we only need to handle normal names + getPropertyNameForPropertyNameNode(accessNode) : + undefined; + } + function getPropertyTypeForIndexType(originalObjectType: ts.Type, objectType: ts.Type, indexType: ts.Type, fullIndexType: ts.Type, suppressNoImplicitAnyError: boolean, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) { + const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; + const propName = getPropertyNameFromIndex(indexType, accessNode); + if (propName !== undefined) { + const prop = getPropertyOfType(objectType, propName); + if (prop) { + if (accessExpression) { + markPropertyAsReferenced(prop, accessExpression, /*isThisAccess*/ accessExpression.expression.kind === SyntaxKind.ThisKeyword); + if (isAssignmentTarget(accessExpression) && (isReferenceToReadonlyEntity(accessExpression, prop) || isReferenceThroughNamespaceImport(accessExpression))) { + error(accessExpression.argumentExpression, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(prop)); + return undefined; + } + if (accessFlags & AccessFlags.CacheSymbol) { + getNodeLinks(accessNode!).resolvedSymbol = prop; + } + } + const propType = getTypeOfSymbol(prop); + return accessExpression && getAssignmentTargetKind(accessExpression) !== AssignmentKind.Definite ? + getFlowTypeOfReference(accessExpression, propType) : + propType; } - if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) { - const contextualSignature = getContextualSignature(func); - if (contextualSignature) { - const thisParameter = contextualSignature.thisParameter; - if (thisParameter) { - return getTypeOfSymbol(thisParameter); - } - } - } - const inJs = isInJSFile(func); - if (noImplicitThis || inJs) { - const containingLiteral = getContainingObjectLiteral(func); - if (containingLiteral) { - // We have an object literal method. Check if the containing object literal has a contextual type - // that includes a ThisType. If so, T is the contextual type for 'this'. We continue looking in - // any directly enclosing object literals. - const contextualType = getApparentTypeOfContextualType(containingLiteral); - let literal = containingLiteral; - let type = contextualType; - while (type) { - const thisType = getThisTypeFromContextualType(type); - if (thisType) { - return instantiateType(thisType, getMapperFromContext(getInferenceContext(containingLiteral))); - } - if (literal.parent.kind !== SyntaxKind.PropertyAssignment) { - break; - } - literal = literal.parent.parent; - type = getApparentTypeOfContextualType(literal); - } - // There was no contextual ThisType for the containing object literal, so the contextual type - // for 'this' is the non-null form of the contextual type for the containing object literal or - // the type of the object literal itself. - return getWidenedType(contextualType ? getNonNullableType(contextualType) : checkExpressionCached(containingLiteral)); - } - // In an assignment of the form 'obj.xxx = function(...)' or 'obj[xxx] = function(...)', the - // contextual type for 'this' is 'obj'. - const parent = func.parent; - if (parent.kind === SyntaxKind.BinaryExpression && (parent).operatorToken.kind === SyntaxKind.EqualsToken) { - const target = (parent).left; - if (target.kind === SyntaxKind.PropertyAccessExpression || target.kind === SyntaxKind.ElementAccessExpression) { - const { expression } = target as AccessExpression; - // Don't contextually type `this` as `exports` in `exports.Point = function(x, y) { this.x = x; this.y = y; }` - if (inJs && isIdentifier(expression)) { - const sourceFile = getSourceFileOfNode(parent); - if (sourceFile.commonJsModuleIndicator && getResolvedSymbol(expression) === sourceFile.symbol) { - return undefined; - } - } - - return getWidenedType(checkExpressionCached(expression)); + if (everyType(objectType, isTupleType) && isNumericLiteralName(propName) && +propName >= 0) { + if (accessNode && everyType(objectType, t => !(t).target.hasRestElement) && !(accessFlags & AccessFlags.NoTupleBoundsCheck)) { + const indexNode = getIndexNodeForAccessExpression(accessNode); + if (isTupleType(objectType)) { + error(indexNode, Diagnostics.Tuple_type_0_of_length_1_has_no_element_at_index_2, typeToString(objectType), getTypeReferenceArity(objectType), unescapeLeadingUnderscores(propName)); + } + else { + error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); } } + errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, IndexKind.Number)); + return mapType(objectType, t => getRestTypeOfTupleType((t)) || undefinedType); } - return undefined; } - - // Return contextual type of parameter or undefined if no contextual type is available - function getContextuallyTypedParameterType(parameter: ParameterDeclaration, forCache: boolean): Type | undefined { - const func = parameter.parent; - if (!isContextSensitiveFunctionOrObjectLiteralMethod(func)) { - return undefined; + if (!(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike)) { + if (objectType.flags & (TypeFlags.Any | TypeFlags.Never)) { + return objectType; } - const iife = getImmediatelyInvokedFunctionExpression(func); - if (iife && iife.arguments) { - const args = getEffectiveCallArguments(iife); - const indexOfParameter = func.parameters.indexOf(parameter); - if (parameter.dotDotDotToken) { - return getSpreadArgumentType(args, indexOfParameter, args.length, anyType, /*context*/ undefined); - } - const links = getNodeLinks(iife); - const cached = links.resolvedSignature; - links.resolvedSignature = anySignature; - const type = indexOfParameter < args.length ? - getWidenedLiteralType(checkExpression(args[indexOfParameter])) : - parameter.initializer ? undefined : undefinedWideningType; - links.resolvedSignature = cached; - return type; + const stringIndexInfo = getIndexInfoOfType(objectType, IndexKind.String); + const indexInfo = isTypeAssignableToKind(indexType, TypeFlags.NumberLike) && getIndexInfoOfType(objectType, IndexKind.Number) || stringIndexInfo; + if (indexInfo) { + if (accessFlags & AccessFlags.NoIndexSignatures && indexInfo === stringIndexInfo) { + if (accessExpression) { + error(accessExpression, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(originalObjectType)); + } + return undefined; + } + if (accessNode && !isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { + const indexNode = getIndexNodeForAccessExpression(accessNode); + error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType)); + return indexInfo.type; + } + errorIfWritingToReadonlyIndex(indexInfo); + return indexInfo.type; } - let contextualSignature = getContextualSignature(func); - if (contextualSignature) { - if (forCache) { - // Calling the below guarantees the types are primed and assigned in the same way - // as when the parameter is reached via `checkFunctionExpressionOrObjectLiteralMethod`. - // This should prevent any uninstantiated inference variables in the contextual signature - // from leaking, and should lock in cached parameter types via `assignContextualParameterTypes` - // which we will then immediately use the results of below. - contextuallyCheckFunctionExpressionOrObjectLiteralMethod(func); - const type = getTypeOfSymbol(getMergedSymbol(func.symbol)); - if (isTypeAny(type)) { - return type; + if (indexType.flags & TypeFlags.Never) { + return neverType; + } + if (isJSLiteralType(objectType)) { + return anyType; + } + if (accessExpression && !isConstEnumObjectType(objectType)) { + if (objectType.symbol === globalThisSymbol && propName !== undefined && globalThisSymbol.exports!.has(propName) && (globalThisSymbol.exports!.get(propName)!.flags & SymbolFlags.BlockScoped)) { + error(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); + } + else if (noImplicitAny && !compilerOptions.suppressImplicitAnyIndexErrors && !suppressNoImplicitAnyError) { + if (propName !== undefined && typeHasStaticProperty(propName, objectType)) { + error(accessExpression, Diagnostics.Property_0_is_a_static_member_of_type_1, (propName as string), typeToString(objectType)); + } + else if (getIndexTypeOfType(objectType, IndexKind.Number)) { + error(accessExpression.argumentExpression, Diagnostics.Element_implicitly_has_an_any_type_because_index_expression_is_not_of_type_number); + } + else { + let suggestion: string | undefined; + if (propName !== undefined && (suggestion = getSuggestionForNonexistentProperty(propName as string, objectType))) { + if (suggestion !== undefined) { + error(accessExpression.argumentExpression, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, (propName as string), typeToString(objectType), suggestion); + } + } + else { + const suggestion = getSuggestionForNonexistentIndexSignature(objectType, accessExpression, indexType); + if (suggestion !== undefined) { + error(accessExpression, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature_Did_you_mean_to_call_1, typeToString(objectType), suggestion); + } + else { + let errorInfo: DiagnosticMessageChain | undefined; + if (indexType.flags & TypeFlags.EnumLiteral) { + errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + typeToString(indexType) + "]", typeToString(objectType)); + } + else if (indexType.flags & TypeFlags.UniqueESSymbol) { + const symbolName = getFullyQualifiedName((indexType as UniqueESSymbolType).symbol, accessExpression); + errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + symbolName + "]", typeToString(objectType)); + } + else if (indexType.flags & TypeFlags.StringLiteral) { + errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as StringLiteralType).value, typeToString(objectType)); + } + else if (indexType.flags & TypeFlags.NumberLiteral) { + errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as NumberLiteralType).value, typeToString(objectType)); + } + else if (indexType.flags & (TypeFlags.Number | TypeFlags.String)) { + errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.No_index_signature_with_a_parameter_of_type_0_was_found_on_type_1, typeToString(indexType), typeToString(objectType)); + } + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Element_implicitly_has_an_any_type_because_expression_of_type_0_can_t_be_used_to_index_type_1, typeToString(fullIndexType), typeToString(objectType)); + diagnostics.add(createDiagnosticForNodeFromMessageChain(accessExpression, errorInfo)); + } + } } - contextualSignature = getSignaturesOfType(type, SignatureKind.Call)[0]; } - const index = func.parameters.indexOf(parameter) - (getThisParameter(func) ? 1 : 0); - return parameter.dotDotDotToken && lastOrUndefined(func.parameters) === parameter ? - getRestTypeAtPosition(contextualSignature, index) : - tryGetTypeAtPosition(contextualSignature, index); + return undefined; } } - - function getContextualTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration): Type | undefined { - const typeNode = getEffectiveTypeAnnotationNode(declaration); - if (typeNode) { - return getTypeFromTypeNode(typeNode); - } - switch (declaration.kind) { - case SyntaxKind.Parameter: - return getContextuallyTypedParameterType(declaration, /*forCache*/ false); - case SyntaxKind.BindingElement: - return getContextualTypeForBindingElement(declaration); - // By default, do nothing and return undefined - only parameters and binding elements have context implied by a parent - } + if (isJSLiteralType(objectType)) { + return anyType; } - - function getContextualTypeForBindingElement(declaration: BindingElement): Type | undefined { - const parentDeclaration = declaration.parent.parent; - const name = declaration.propertyName || declaration.name; - const parentType = getContextualTypeForVariableLikeDeclaration(parentDeclaration); - if (parentType && !isBindingPattern(name) && !isComputedNonLiteralName(name)) { - const nameType = getLiteralTypeFromPropertyName(name); - if (isTypeUsableAsPropertyName(nameType)) { - const text = getPropertyNameFromType(nameType); - return getTypeOfPropertyOfType(parentType, text); - } + if (accessNode) { + const indexNode = getIndexNodeForAccessExpression(accessNode); + if (indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { + error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, "" + (indexType).value, typeToString(objectType)); } - } - - // In a variable, parameter or property declaration with a type annotation, - // the contextual type of an initializer expression is the type of the variable, parameter or property. - // Otherwise, in a parameter declaration of a contextually typed function expression, - // the contextual type of an initializer expression is the contextual type of the parameter. - // Otherwise, in a variable or parameter declaration with a binding pattern name, - // the contextual type of an initializer expression is the type implied by the binding pattern. - // Otherwise, in a binding pattern inside a variable or parameter declaration, - // the contextual type of an initializer expression is the type annotation of the containing declaration, if present. - function getContextualTypeForInitializerExpression(node: Expression): Type | undefined { - const declaration = node.parent; - if (hasInitializer(declaration) && node === declaration.initializer) { - const result = getContextualTypeForVariableLikeDeclaration(declaration); - if (result) { - return result; - } - if (isBindingPattern(declaration.name)) { // This is less a contextual type and more an implied shape - in some cases, this may be undesirable - return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false); - } + else if (indexType.flags & (TypeFlags.String | TypeFlags.Number)) { + error(indexNode, Diagnostics.Type_0_has_no_matching_index_signature_for_type_1, typeToString(objectType), typeToString(indexType)); } - return undefined; - } - - function getContextualTypeForReturnExpression(node: Expression): Type | undefined { - const func = getContainingFunction(node); - if (func) { - const functionFlags = getFunctionFlags(func); - if (functionFlags & FunctionFlags.Generator) { // AsyncGenerator function or Generator function - return undefined; - } - - const contextualReturnType = getContextualReturnType(func); - if (contextualReturnType) { - if (functionFlags & FunctionFlags.Async) { // Async function - const contextualAwaitedType = getAwaitedTypeOfPromise(contextualReturnType); - return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); - } - return contextualReturnType; // Regular function - } + else { + error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType)); } - return undefined; } - - function getContextualTypeForAwaitOperand(node: AwaitExpression): Type | undefined { - const contextualType = getContextualType(node); - if (contextualType) { - const contextualAwaitedType = getAwaitedType(contextualType); - return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); - } - return undefined; + if (isTypeAny(indexType)) { + return indexType; } - - function getContextualTypeForYieldOperand(node: YieldExpression): Type | undefined { - const func = getContainingFunction(node); - if (func) { - const functionFlags = getFunctionFlags(func); - const contextualReturnType = getContextualReturnType(func); - if (contextualReturnType) { - return node.asteriskToken - ? contextualReturnType - : getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, contextualReturnType, (functionFlags & FunctionFlags.Async) !== 0); - } + return undefined; + function errorIfWritingToReadonlyIndex(indexInfo: IndexInfo | undefined): void { + if (indexInfo && indexInfo.isReadonly && accessExpression && (isAssignmentTarget(accessExpression) || isDeleteTarget(accessExpression))) { + error(accessExpression, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); } - - return undefined; } - - function isInParameterInitializerBeforeContainingFunction(node: Node) { - let inBindingInitializer = false; - while (node.parent && !isFunctionLike(node.parent)) { - if (isParameter(node.parent) && (inBindingInitializer || node.parent.initializer === node)) { - return true; - } - if (isBindingElement(node.parent) && node.parent.initializer === node) { - inBindingInitializer = true; - } - - node = node.parent; - } - - return false; + } + function getIndexNodeForAccessExpression(accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression) { + return accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression : + accessNode.kind === SyntaxKind.IndexedAccessType ? accessNode.indexType : + accessNode.kind === SyntaxKind.ComputedPropertyName ? accessNode.expression : + accessNode; + } + function isGenericObjectType(type: ts.Type): boolean { + return maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive | TypeFlags.GenericMappedType); + } + function isGenericIndexType(type: ts.Type): boolean { + return maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive | TypeFlags.Index); + } + function isThisTypeParameter(type: ts.Type): boolean { + return !!(type.flags & TypeFlags.TypeParameter && (type).isThisType); + } + function getSimplifiedType(type: ts.Type, writing: boolean): ts.Type { + return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType((type), writing) : + type.flags & TypeFlags.Conditional ? getSimplifiedConditionalType((type), writing) : + type; + } + function distributeIndexOverObjectType(objectType: ts.Type, indexType: ts.Type, writing: boolean) { + // (T | U)[K] -> T[K] | U[K] (reading) + // (T | U)[K] -> T[K] & U[K] (writing) + // (T & U)[K] -> T[K] & U[K] + if (objectType.flags & TypeFlags.UnionOrIntersection) { + const types = map((objectType as UnionOrIntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType), writing)); + return objectType.flags & TypeFlags.Intersection || writing ? getIntersectionType(types) : getUnionType(types); } - - function getContextualIterationType(kind: IterationTypeKind, functionDecl: SignatureDeclaration): Type | undefined { - const isAsync = !!(getFunctionFlags(functionDecl) & FunctionFlags.Async); - const contextualReturnType = getContextualReturnType(functionDecl); - if (contextualReturnType) { - return getIterationTypeOfGeneratorFunctionReturnType(kind, contextualReturnType, isAsync) - || undefined; - } - - return undefined; + } + function distributeObjectOverIndexType(objectType: ts.Type, indexType: ts.Type, writing: boolean) { + // T[A | B] -> T[A] | T[B] (reading) + // T[A | B] -> T[A] & T[B] (writing) + if (indexType.flags & TypeFlags.Union) { + const types = map((indexType as UnionType).types, t => getSimplifiedType(getIndexedAccessType(objectType, t), writing)); + return writing ? getIntersectionType(types) : getUnionType(types); } - - function getContextualReturnType(functionDecl: SignatureDeclaration): Type | undefined { - // If the containing function has a return type annotation, is a constructor, or is a get accessor whose - // corresponding set accessor has a type annotation, return statements in the function are contextually typed - const returnType = getReturnTypeFromAnnotation(functionDecl); - if (returnType) { - return returnType; - } - // Otherwise, if the containing function is contextually typed by a function type with exactly one call signature - // and that call signature is non-generic, return statements are contextually typed by the return type of the signature - const signature = getContextualSignatureForFunctionLikeDeclaration(functionDecl); - if (signature && !isResolvingReturnTypeOfSignature(signature)) { - return getReturnTypeOfSignature(signature); + } + // Transform an indexed access to a simpler form, if possible. Return the simpler form, or return + // the type itself if no transformation is possible. The writing flag indicates that the type is + // the target of an assignment. + function getSimplifiedIndexedAccessType(type: IndexedAccessType, writing: boolean): ts.Type { + const cache = writing ? "simplifiedForWriting" : "simplifiedForReading"; + if (type[cache]) { + return type[cache] === circularConstraintType ? type : type[cache]!; + } + type[cache] = circularConstraintType; + // We recursively simplify the object type as it may in turn be an indexed access type. For example, with + // '{ [P in T]: { [Q in U]: number } }[T][U]' we want to first simplify the inner indexed access type. + const objectType = getSimplifiedType(type.objectType, writing); + const indexType = getSimplifiedType(type.indexType, writing); + // T[A | B] -> T[A] | T[B] (reading) + // T[A | B] -> T[A] & T[B] (writing) + const distributedOverIndex = distributeObjectOverIndexType(objectType, indexType, writing); + if (distributedOverIndex) { + return type[cache] = distributedOverIndex; + } + // Only do the inner distributions if the index can no longer be instantiated to cause index distribution again + if (!(indexType.flags & TypeFlags.Instantiable)) { + // (T | U)[K] -> T[K] | U[K] (reading) + // (T | U)[K] -> T[K] & U[K] (writing) + // (T & U)[K] -> T[K] & U[K] + const distributedOverObject = distributeIndexOverObjectType(objectType, indexType, writing); + if (distributedOverObject) { + return type[cache] = distributedOverObject; } - return undefined; } - - // In a typed function call, an argument or substitution expression is contextually typed by the type of the corresponding parameter. - function getContextualTypeForArgument(callTarget: CallLikeExpression, arg: Expression, contextFlags?: ContextFlags): Type | undefined { - const args = getEffectiveCallArguments(callTarget); - const argIndex = args.indexOf(arg); // -1 for e.g. the expression of a CallExpression, or the tag of a TaggedTemplateExpression - return argIndex === -1 ? undefined : getContextualTypeForArgumentAtIndex(callTarget, argIndex, contextFlags); + // So ultimately (reading): + // ((A & B) | C)[K1 | K2] -> ((A & B) | C)[K1] | ((A & B) | C)[K2] -> (A & B)[K1] | C[K1] | (A & B)[K2] | C[K2] -> (A[K1] & B[K1]) | C[K1] | (A[K2] & B[K2]) | C[K2] + // If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper + // that substitutes the index type for P. For example, for an index access { [P in K]: Box }[X], we + // construct the type Box. + if (isGenericMappedType(objectType)) { + return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t, writing)); } - - function getContextualTypeForArgumentAtIndex(callTarget: CallLikeExpression, argIndex: number, contextFlags?: ContextFlags): Type { - // If we're already in the process of resolving the given signature, don't resolve again as - // that could cause infinite recursion. Instead, return anySignature. - const signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget); - if (isJsxOpeningLikeElement(callTarget) && argIndex === 0) { - return getEffectiveFirstArgumentForJsxSignature(signature, callTarget); - } - if (contextFlags && contextFlags & ContextFlags.BaseConstraint && signature.target && !hasTypeArguments(callTarget)) { - const baseSignature = getBaseSignature(signature.target); - return getTypeAtPosition(baseSignature, argIndex); + return type[cache] = type; + } + function getSimplifiedConditionalType(type: ConditionalType, writing: boolean) { + const checkType = type.checkType; + const extendsType = type.extendsType; + const trueType = getTrueTypeFromConditionalType(type); + const falseType = getFalseTypeFromConditionalType(type); + // Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`. + if (falseType.flags & TypeFlags.Never && getActualTypeVariable(trueType) === getActualTypeVariable(checkType)) { + if (checkType.flags & TypeFlags.Any || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true + return getSimplifiedType(trueType, writing); + } + else if (isIntersectionEmpty(checkType, extendsType)) { // Always false + return neverType; } - return getTypeAtPosition(signature, argIndex); } - - function getContextualTypeForSubstitutionExpression(template: TemplateExpression, substitutionExpression: Expression) { - if (template.parent.kind === SyntaxKind.TaggedTemplateExpression) { - return getContextualTypeForArgument(template.parent, substitutionExpression); + else if (trueType.flags & TypeFlags.Never && getActualTypeVariable(falseType) === getActualTypeVariable(checkType)) { + if (!(checkType.flags & TypeFlags.Any) && isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true + return neverType; } - - return undefined; - } - - function getContextualTypeForBinaryOperand(node: Expression, contextFlags?: ContextFlags): Type | undefined { - const binaryExpression = node.parent; - const { left, operatorToken, right } = binaryExpression; - switch (operatorToken.kind) { - case SyntaxKind.EqualsToken: - if (node !== right) { - return undefined; - } - const contextSensitive = getIsContextSensitiveAssignmentOrContextType(binaryExpression); - if (!contextSensitive) { - return undefined; - } - return contextSensitive === true ? getTypeOfExpression(left) : contextSensitive; - case SyntaxKind.BarBarToken: - case SyntaxKind.QuestionQuestionToken: - // When an || expression has a contextual type, the operands are contextually typed by that type, except - // when that type originates in a binding pattern, the right operand is contextually typed by the type of - // the left operand. When an || expression has no contextual type, the right operand is contextually typed - // by the type of the left operand, except for the special case of Javascript declarations of the form - // `namespace.prop = namespace.prop || {}`. - const type = getContextualType(binaryExpression, contextFlags); - return node === right && (type && type.pattern || !type && !isDefaultedExpandoInitializer(binaryExpression)) ? - getTypeOfExpression(left) : type; - case SyntaxKind.AmpersandAmpersandToken: - case SyntaxKind.CommaToken: - return node === right ? getContextualType(binaryExpression, contextFlags) : undefined; - default: - return undefined; + else if (checkType.flags & TypeFlags.Any || isIntersectionEmpty(checkType, extendsType)) { // Always false + return getSimplifiedType(falseType, writing); } } - - // In an assignment expression, the right operand is contextually typed by the type of the left operand. - // Don't do this for assignment declarations unless there is a type tag on the assignment, to avoid circularity from checking the right operand. - function getIsContextSensitiveAssignmentOrContextType(binaryExpression: BinaryExpression): boolean | Type { - const kind = getAssignmentDeclarationKind(binaryExpression); - switch (kind) { - case AssignmentDeclarationKind.None: - return true; - case AssignmentDeclarationKind.Property: - case AssignmentDeclarationKind.ExportsProperty: - case AssignmentDeclarationKind.Prototype: - case AssignmentDeclarationKind.PrototypeProperty: - // If `binaryExpression.left` was assigned a symbol, then this is a new declaration; otherwise it is an assignment to an existing declaration. - // See `bindStaticPropertyAssignment` in `binder.ts`. - if (!binaryExpression.left.symbol) { - return true; - } - else { - const decl = binaryExpression.left.symbol.valueDeclaration; - if (!decl) { - return false; - } - const lhs = cast(binaryExpression.left, isAccessExpression); - const overallAnnotation = getEffectiveTypeAnnotationNode(decl); - if (overallAnnotation) { - return getTypeFromTypeNode(overallAnnotation); - } - else if (isIdentifier(lhs.expression)) { - const id = lhs.expression; - const parentSymbol = resolveName(id, id.escapedText, SymbolFlags.Value, undefined, id.escapedText, /*isUse*/ true); - if (parentSymbol) { - const annotated = getEffectiveTypeAnnotationNode(parentSymbol.valueDeclaration); - if (annotated) { - const nameStr = getElementOrPropertyAccessName(lhs); - if (nameStr !== undefined) { - const type = getTypeOfPropertyOfContextualType(getTypeFromTypeNode(annotated), nameStr); - return type || false; - } - } - return false; - } - } - return !isInJSFile(decl); - } - case AssignmentDeclarationKind.ModuleExports: - case AssignmentDeclarationKind.ThisProperty: - if (!binaryExpression.symbol) return true; - if (binaryExpression.symbol.valueDeclaration) { - const annotated = getEffectiveTypeAnnotationNode(binaryExpression.symbol.valueDeclaration); - if (annotated) { - const type = getTypeFromTypeNode(annotated); - if (type) { - return type; - } - } - } - if (kind === AssignmentDeclarationKind.ModuleExports) return false; - const thisAccess = cast(binaryExpression.left, isAccessExpression); - if (!isObjectLiteralMethod(getThisContainer(thisAccess.expression, /*includeArrowFunctions*/ false))) { - return false; - } - const thisType = checkThisExpression(thisAccess.expression); - const nameStr = getElementOrPropertyAccessName(thisAccess); - return nameStr !== undefined && thisType && getTypeOfPropertyOfContextualType(thisType, nameStr) || false; - case AssignmentDeclarationKind.ObjectDefinePropertyValue: - case AssignmentDeclarationKind.ObjectDefinePropertyExports: - case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: - return Debug.fail("Does not apply"); - default: - return Debug.assertNever(kind); + return type; + } + /** + * Invokes union simplification logic to determine if an intersection is considered empty as a union constituent + */ + function isIntersectionEmpty(type1: ts.Type, type2: ts.Type) { + return !!(getUnionType([intersectTypes(type1, type2), neverType]).flags & TypeFlags.Never); + } + function substituteIndexedMappedType(objectType: MappedType, index: ts.Type) { + const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [index]); + const templateMapper = combineTypeMappers(objectType.mapper, mapper); + return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper); + } + function getIndexedAccessType(objectType: ts.Type, indexType: ts.Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression): ts.Type { + return getIndexedAccessTypeOrUndefined(objectType, indexType, accessNode, AccessFlags.None) || (accessNode ? errorType : unknownType); + } + function getIndexedAccessTypeOrUndefined(objectType: ts.Type, indexType: ts.Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, accessFlags = AccessFlags.None): ts.Type | undefined { + if (objectType === wildcardType || indexType === wildcardType) { + return wildcardType; + } + // If the object type has a string index signature and no other members we know that the result will + // always be the type of that index signature and we can simplify accordingly. + if (isStringIndexSignatureOnlyType(objectType) && !(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { + indexType = stringType; + } + // If the index type is generic, or if the object type is generic and doesn't originate in an expression, + // we are performing a higher-order index access where we cannot meaningfully access the properties of the + // object type. Note that for a generic T and a non-generic K, we eagerly resolve T[K] if it originates in + // an expression. This is to preserve backwards compatibility. For example, an element access 'this["foo"]' + // has always been resolved eagerly using the constraint type of 'this' at the given location. + if (isGenericIndexType(indexType) || !(accessNode && accessNode.kind !== SyntaxKind.IndexedAccessType) && isGenericObjectType(objectType)) { + if (objectType.flags & TypeFlags.AnyOrUnknown) { + return objectType; + } + // Defer the operation by creating an indexed access type. + const id = objectType.id + "," + indexType.id; + let type = indexedAccessTypes.get(id); + if (!type) { + indexedAccessTypes.set(id, type = createIndexedAccessType(objectType, indexType)); } + return type; } - - function getTypeOfPropertyOfContextualType(type: Type, name: __String) { - return mapType(type, t => { - if (isGenericMappedType(t)) { - const constraint = getConstraintTypeFromMappedType(t); - const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint; - const propertyNameType = getLiteralType(unescapeLeadingUnderscores(name)); - if (isTypeAssignableTo(propertyNameType, constraintOfConstraint)) { - return substituteIndexedMappedType(t, propertyNameType); - } - } - else if (t.flags & TypeFlags.StructuredType) { - const prop = getPropertyOfType(t, name); - if (prop) { - return getTypeOfSymbol(prop); - } - if (isTupleType(t)) { - const restType = getRestTypeOfTupleType(t); - if (restType && isNumericLiteralName(name) && +name >= 0) { - return restType; - } - } - return isNumericLiteralName(name) && getIndexTypeOfContextualType(t, IndexKind.Number) || - getIndexTypeOfContextualType(t, IndexKind.String); + // In the following we resolve T[K] to the type of the property in T selected by K. + // We treat boolean as different from other unions to improve errors; + // skipping straight to getPropertyTypeForIndexType gives errors with 'boolean' instead of 'true'. + const apparentObjectType = getApparentType(objectType); + if (indexType.flags & TypeFlags.Union && !(indexType.flags & TypeFlags.Boolean)) { + const propTypes: ts.Type[] = []; + let wasMissingProp = false; + for (const t of (indexType).types) { + const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, indexType, wasMissingProp, accessNode, accessFlags); + if (propType) { + propTypes.push(propType); + } + else if (!accessNode) { + // If there's no error node, we can immeditely stop, since error reporting is off + return undefined; + } + else { + // Otherwise we set a flag and return at the end of the loop so we still mark all errors + wasMissingProp = true; } + } + if (wasMissingProp) { return undefined; - }, /*noReductions*/ true); + } + return accessFlags & AccessFlags.Writing ? getIntersectionType(propTypes) : getUnionType(propTypes); } - - function getIndexTypeOfContextualType(type: Type, kind: IndexKind) { - return mapType(type, t => getIndexTypeOfStructuredType(t, kind), /*noReductions*/ true); + return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, /* supressNoImplicitAnyError */ false, accessNode, accessFlags | AccessFlags.CacheSymbol); + } + function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const objectType = getTypeFromTypeNode(node.objectType); + const indexType = getTypeFromTypeNode(node.indexType); + const resolved = getIndexedAccessType(objectType, indexType, node); + links.resolvedType = resolved.flags & TypeFlags.IndexedAccess && + (resolved).objectType === objectType && + (resolved).indexType === indexType ? + getConstrainedTypeVariable((resolved), node) : resolved; + } + return links.resolvedType; + } + function getTypeFromMappedTypeNode(node: MappedTypeNode): ts.Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const type = (createObjectType(ObjectFlags.Mapped, node.symbol)); + type.declaration = node; + type.aliasSymbol = getAliasSymbolForTypeNode(node); + type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(type.aliasSymbol); + links.resolvedType = type; + // Eagerly resolve the constraint type which forces an error if the constraint type circularly + // references itself through one or more type aliases. + getConstraintTypeFromMappedType(type); + } + return links.resolvedType; + } + function getActualTypeVariable(type: ts.Type): ts.Type { + if (type.flags & TypeFlags.Substitution) { + return (type).typeVariable; } - - // In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of - // the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one - // exists. Otherwise, it is the type of the string index signature in T, if one exists. - function getContextualTypeForObjectLiteralMethod(node: MethodDeclaration, contextFlags?: ContextFlags): Type | undefined { - Debug.assert(isObjectLiteralMethod(node)); - if (node.flags & NodeFlags.InWithStatement) { - // We cannot answer semantic questions within a with block, do not proceed any further - return undefined; - } - return getContextualTypeForObjectLiteralElement(node, contextFlags); + if (type.flags & TypeFlags.IndexedAccess && ((type).objectType.flags & TypeFlags.Substitution || + (type).indexType.flags & TypeFlags.Substitution)) { + return getIndexedAccessType(getActualTypeVariable((type).objectType), getActualTypeVariable((type).indexType)); } - - function getContextualTypeForObjectLiteralElement(element: ObjectLiteralElementLike, contextFlags?: ContextFlags) { - const objectLiteral = element.parent; - const type = getApparentTypeOfContextualType(objectLiteral, contextFlags); - if (type) { - if (!hasNonBindableDynamicName(element)) { - // For a (non-symbol) computed property, there is no reason to look up the name - // in the type. It will just be "__computed", which does not appear in any - // SymbolTable. - const symbolName = getSymbolOfNode(element).escapedName; - const propertyType = getTypeOfPropertyOfContextualType(type, symbolName); - if (propertyType) { - return propertyType; - } + return type; + } + function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined): ts.Type { + const checkType = instantiateType(root.checkType, mapper); + const extendsType = instantiateType(root.extendsType, mapper); + if (checkType === wildcardType || extendsType === wildcardType) { + return wildcardType; + } + const checkTypeInstantiable = maybeTypeOfKind(checkType, TypeFlags.Instantiable | TypeFlags.GenericMappedType); + let combinedMapper: TypeMapper | undefined; + if (root.inferTypeParameters) { + const context = createInferenceContext(root.inferTypeParameters, /*signature*/ undefined, InferenceFlags.None); + if (!checkTypeInstantiable) { + // We don't want inferences from constraints as they may cause us to eagerly resolve the + // conditional type instead of deferring resolution. Also, we always want strict function + // types rules (i.e. proper contravariance) for inferences. + inferTypes(context.inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); + } + combinedMapper = combineTypeMappers(mapper, context.mapper); + } + // Instantiate the extends type including inferences for 'infer T' type parameters + const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType; + // We attempt to resolve the conditional type only when the check and extends types are non-generic + if (!checkTypeInstantiable && !maybeTypeOfKind(inferredExtendsType, TypeFlags.Instantiable | TypeFlags.GenericMappedType)) { + if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown) { + return instantiateType(root.trueType, combinedMapper || mapper); + } + // Return union of trueType and falseType for 'any' since it matches anything + if (checkType.flags & TypeFlags.Any) { + return getUnionType([instantiateType(root.trueType, combinedMapper || mapper), instantiateType(root.falseType, mapper)]); + } + // Return falseType for a definitely false extends check. We check an instantiations of the two + // types with type parameters mapped to the wildcard type, the most permissive instantiations + // possible (the wildcard type is assignable to and from all types). If those are not related, + // then no instantiations will be and we can just return the false branch type. + if (!isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType))) { + return instantiateType(root.falseType, mapper); + } + // Return trueType for a definitely true extends check. We check instantiations of the two + // types with type parameters mapped to their restrictive form, i.e. a form of the type parameter + // that has no constraint. This ensures that, for example, the type + // type Foo = T extends { x: string } ? string : number + // doesn't immediately resolve to 'string' instead of being deferred. + if (isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) { + return instantiateType(root.trueType, combinedMapper || mapper); + } + } + // Return a deferred type for a check that is neither definitely true nor definitely false + const erasedCheckType = getActualTypeVariable(checkType); + const result = (createType(TypeFlags.Conditional)); + result.root = root; + result.checkType = erasedCheckType; + result.extendsType = extendsType; + result.mapper = mapper; + result.combinedMapper = combinedMapper; + result.aliasSymbol = root.aliasSymbol; + result.aliasTypeArguments = instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217 + return result; + } + function getTrueTypeFromConditionalType(type: ConditionalType) { + return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(type.root.trueType, type.mapper)); + } + function getFalseTypeFromConditionalType(type: ConditionalType) { + return type.resolvedFalseType || (type.resolvedFalseType = instantiateType(type.root.falseType, type.mapper)); + } + function getInferredTrueTypeFromConditionalType(type: ConditionalType) { + return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = type.combinedMapper ? instantiateType(type.root.trueType, type.combinedMapper) : getTrueTypeFromConditionalType(type)); + } + function getInferTypeParameters(node: ConditionalTypeNode): TypeParameter[] | undefined { + let result: TypeParameter[] | undefined; + if (node.locals) { + node.locals.forEach(symbol => { + if (symbol.flags & SymbolFlags.TypeParameter) { + result = append(result, getDeclaredTypeOfSymbol(symbol)); } - return isNumericName(element.name!) && getIndexTypeOfContextualType(type, IndexKind.Number) || - getIndexTypeOfContextualType(type, IndexKind.String); + }); + } + return result; + } + function getTypeFromConditionalTypeNode(node: ConditionalTypeNode): ts.Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const checkType = getTypeFromTypeNode(node.checkType); + const aliasSymbol = getAliasSymbolForTypeNode(node); + const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); + const allOuterTypeParameters = getOuterTypeParameters(node, /*includeThisTypes*/ true); + const outerTypeParameters = aliasTypeArguments ? allOuterTypeParameters : filter(allOuterTypeParameters, tp => isTypeParameterPossiblyReferenced(tp, node)); + const root: ConditionalRoot = { + node, + checkType, + extendsType: getTypeFromTypeNode(node.extendsType), + trueType: getTypeFromTypeNode(node.trueType), + falseType: getTypeFromTypeNode(node.falseType), + isDistributive: !!(checkType.flags & TypeFlags.TypeParameter), + inferTypeParameters: getInferTypeParameters(node), + outerTypeParameters, + instantiations: undefined, + aliasSymbol, + aliasTypeArguments + }; + links.resolvedType = getConditionalType(root, /*mapper*/ undefined); + if (outerTypeParameters) { + root.instantiations = createMap(); + root.instantiations.set(getTypeListId(outerTypeParameters), links.resolvedType); } - return undefined; } - - // In an array literal contextually typed by a type T, the contextual type of an element expression at index N is - // the type of the property with the numeric name N in T, if one exists. Otherwise, if T has a numeric index signature, - // it is the type of the numeric index signature in T. Otherwise, in ES6 and higher, the contextual type is the iterated - // type of T. - function getContextualTypeForElementExpression(arrayContextualType: Type | undefined, index: number): Type | undefined { - return arrayContextualType && ( - getTypeOfPropertyOfContextualType(arrayContextualType, "" + index as __String) - || getIteratedTypeOrElementType(IterationUse.Element, arrayContextualType, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false)); + return links.resolvedType; + } + function getTypeFromInferTypeNode(node: InferTypeNode): ts.Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node.typeParameter)); } - - // In a contextually typed conditional expression, the true/false expressions are contextually typed by the same type. - function getContextualTypeForConditionalOperand(node: Expression, contextFlags?: ContextFlags): Type | undefined { - const conditional = node.parent; - return node === conditional.whenTrue || node === conditional.whenFalse ? getContextualType(conditional, contextFlags) : undefined; + return links.resolvedType; + } + function getIdentifierChain(node: EntityName): Identifier[] { + if (isIdentifier(node)) { + return [node]; } - - function getContextualTypeForChildJsxExpression(node: JsxElement, child: JsxChild) { - const attributesType = getApparentTypeOfContextualType(node.openingElement.tagName); - // JSX expression is in children of JSX Element, we will look for an "children" atttribute (we get the name from JSX.ElementAttributesProperty) - const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); - if (!(attributesType && !isTypeAny(attributesType) && jsxChildrenPropertyName && jsxChildrenPropertyName !== "")) { - return undefined; + else { + return append(getIdentifierChain(node.left), node.right); + } + } + function getTypeFromImportTypeNode(node: ImportTypeNode): ts.Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + if (node.isTypeOf && node.typeArguments) { // Only the non-typeof form can make use of type arguments + error(node, Diagnostics.Type_arguments_cannot_be_used_here); + links.resolvedSymbol = unknownSymbol; + return links.resolvedType = errorType; + } + if (!isLiteralImportTypeNode(node)) { + error(node.argument, Diagnostics.String_literal_expected); + links.resolvedSymbol = unknownSymbol; + return links.resolvedType = errorType; + } + const targetMeaning = node.isTypeOf ? SymbolFlags.Value : node.flags & NodeFlags.JSDoc ? SymbolFlags.Value | SymbolFlags.Type : SymbolFlags.Type; + // TODO: Future work: support unions/generics/whatever via a deferred import-type + const innerModuleSymbol = resolveExternalModuleName(node, node.argument.literal); + if (!innerModuleSymbol) { + links.resolvedSymbol = unknownSymbol; + return links.resolvedType = errorType; + } + const moduleSymbol = resolveExternalModuleSymbol(innerModuleSymbol, /*dontResolveAlias*/ false); + if (!nodeIsMissing(node.qualifier)) { + const nameStack: Identifier[] = getIdentifierChain(node.qualifier!); + let currentNamespace = moduleSymbol; + let current: Identifier | undefined; + while (current = nameStack.shift()) { + const meaning = nameStack.length ? SymbolFlags.Namespace : targetMeaning; + const next = getSymbol(getExportsOfSymbol(getMergedSymbol(resolveSymbol(currentNamespace))), current.escapedText, meaning); + if (!next) { + error(current, Diagnostics.Namespace_0_has_no_exported_member_1, getFullyQualifiedName(currentNamespace), declarationNameToString(current)); + return links.resolvedType = errorType; + } + getNodeLinks(current).resolvedSymbol = next; + getNodeLinks(current.parent).resolvedSymbol = next; + currentNamespace = next; + } + links.resolvedType = resolveImportSymbolType(node, links, currentNamespace, targetMeaning); } - const realChildren = getSemanticJsxChildren(node.children); - const childIndex = realChildren.indexOf(child); - const childFieldType = getTypeOfPropertyOfContextualType(attributesType, jsxChildrenPropertyName); - return childFieldType && (realChildren.length === 1 ? childFieldType : mapType(childFieldType, t => { - if (isArrayLikeType(t)) { - return getIndexedAccessType(t, getLiteralType(childIndex)); + else { + if (moduleSymbol.flags & targetMeaning) { + links.resolvedType = resolveImportSymbolType(node, links, moduleSymbol, targetMeaning); } else { - return t; + const errorMessage = targetMeaning === SymbolFlags.Value + ? Diagnostics.Module_0_does_not_refer_to_a_value_but_is_used_as_a_value_here + : Diagnostics.Module_0_does_not_refer_to_a_type_but_is_used_as_a_type_here_Did_you_mean_typeof_import_0; + error(node, errorMessage, node.argument.literal.text); + links.resolvedSymbol = unknownSymbol; + links.resolvedType = errorType; } - }, /*noReductions*/ true)); + } } - - function getContextualTypeForJsxExpression(node: JsxExpression): Type | undefined { - const exprParent = node.parent; - return isJsxAttributeLike(exprParent) - ? getContextualType(node) - : isJsxElement(exprParent) - ? getContextualTypeForChildJsxExpression(exprParent, node) - : undefined; + return links.resolvedType; + } + function resolveImportSymbolType(node: ImportTypeNode, links: NodeLinks, symbol: ts.Symbol, meaning: SymbolFlags) { + const resolvedSymbol = resolveSymbol(symbol); + links.resolvedSymbol = resolvedSymbol; + if (meaning === SymbolFlags.Value) { + return getTypeOfSymbol(symbol); // intentionally doesn't use resolved symbol so type is cached as expected on the alias } - - function getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute): Type | undefined { - // When we trying to resolve JsxOpeningLikeElement as a stateless function element, we will already give its attributes a contextual type - // which is a type of the parameter of the signature we are trying out. - // If there is no contextual type (e.g. we are trying to resolve stateful component), get attributes type from resolving element's tagName - if (isJsxAttribute(attribute)) { - const attributesType = getApparentTypeOfContextualType(attribute.parent); - if (!attributesType || isTypeAny(attributesType)) { - return undefined; - } - return getTypeOfPropertyOfContextualType(attributesType, attribute.name.escapedText); + else { + return getTypeReferenceType(node, resolvedSymbol); // getTypeReferenceType doesn't handle aliases - it must get the resolved symbol + } + } + function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: TypeNode): ts.Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + // Deferred resolution of members is handled by resolveObjectTypeMembers + const aliasSymbol = getAliasSymbolForTypeNode(node); + if (getMembersOfSymbol(node.symbol).size === 0 && !aliasSymbol) { + links.resolvedType = emptyTypeLiteralType; } else { - return getContextualType(attribute.parent); + let type = createObjectType(ObjectFlags.Anonymous, node.symbol); + type.aliasSymbol = aliasSymbol; + type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); + if (isJSDocTypeLiteral(node) && node.isArrayType) { + type = createArrayType(type); + } + links.resolvedType = type; } } - - // Return true if the given expression is possibly a discriminant value. We limit the kinds of - // expressions we check to those that don't depend on their contextual type in order not to cause - // recursive (and possibly infinite) invocations of getContextualType. - function isPossiblyDiscriminantValue(node: Expression): boolean { - switch (node.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.NullKeyword: - case SyntaxKind.Identifier: - case SyntaxKind.UndefinedKeyword: - return true; - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ParenthesizedExpression: - return isPossiblyDiscriminantValue((node).expression); - case SyntaxKind.JsxExpression: - return !(node as JsxExpression).expression || isPossiblyDiscriminantValue((node as JsxExpression).expression!); + return links.resolvedType; + } + function getAliasSymbolForTypeNode(node: TypeNode) { + let host = node.parent; + while (isParenthesizedTypeNode(host) || isTypeOperatorNode(host) && host.operator === SyntaxKind.ReadonlyKeyword) { + host = host.parent; + } + return isTypeAlias(host) ? getSymbolOfNode(host) : undefined; + } + function getTypeArgumentsForAliasSymbol(symbol: ts.Symbol | undefined) { + return symbol ? getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol) : undefined; + } + function isNonGenericObjectType(type: ts.Type) { + return !!(type.flags & TypeFlags.Object) && !isGenericMappedType(type); + } + function isEmptyObjectTypeOrSpreadsIntoEmptyObject(type: ts.Type) { + return isEmptyObjectType(type) || !!(type.flags & (TypeFlags.Null | TypeFlags.Undefined | TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)); + } + function isSinglePropertyAnonymousObjectType(type: ts.Type) { + return !!(type.flags & TypeFlags.Object) && + !!(getObjectFlags(type) & ObjectFlags.Anonymous) && + (length(getPropertiesOfType(type)) === 1 || every(getPropertiesOfType(type), p => !!(p.flags & SymbolFlags.Optional))); + } + function tryMergeUnionOfObjectTypeAndEmptyObject(type: UnionType, readonly: boolean): ts.Type | undefined { + if (type.types.length === 2) { + const firstType = type.types[0]; + const secondType = type.types[1]; + if (every(type.types, isEmptyObjectTypeOrSpreadsIntoEmptyObject)) { + return isEmptyObjectType(firstType) ? firstType : isEmptyObjectType(secondType) ? secondType : emptyObjectType; + } + if (isEmptyObjectTypeOrSpreadsIntoEmptyObject(firstType) && isSinglePropertyAnonymousObjectType(secondType)) { + return getAnonymousPartialType(secondType); + } + if (isEmptyObjectTypeOrSpreadsIntoEmptyObject(secondType) && isSinglePropertyAnonymousObjectType(firstType)) { + return getAnonymousPartialType(firstType); } - return false; } - - function discriminateContextualTypeByObjectMembers(node: ObjectLiteralExpression, contextualType: UnionType) { - return discriminateTypeByDiscriminableItems(contextualType, - map( - filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.PropertyAssignment && isPossiblyDiscriminantValue(p.initializer) && isDiscriminantProperty(contextualType, p.symbol.escapedName)), - prop => ([() => checkExpression((prop as PropertyAssignment).initializer), prop.symbol.escapedName] as [() => Type, __String]) - ), - isTypeAssignableTo, - contextualType - ); + function getAnonymousPartialType(type: ts.Type) { + // gets the type as if it had been spread, but where everything in the spread is made optional + const members = createSymbolTable(); + for (const prop of getPropertiesOfType(type)) { + if (getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) { + // do nothing, skip privates + } + else if (isSpreadableProperty(prop)) { + const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); + const flags = SymbolFlags.Property | SymbolFlags.Optional; + const result = createSymbol(flags, prop.escapedName, readonly ? CheckFlags.Readonly : 0); + result.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop); + result.declarations = prop.declarations; + result.nameType = prop.nameType; + result.syntheticOrigin = prop; + members.set(prop.escapedName, result); + } + } + const spread = createAnonymousType(type.symbol, members, emptyArray, emptyArray, getIndexInfoOfType(type, IndexKind.String), getIndexInfoOfType(type, IndexKind.Number)); + spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + return spread; } - - function discriminateContextualTypeByJSXAttributes(node: JsxAttributes, contextualType: UnionType) { - return discriminateTypeByDiscriminableItems(contextualType, - map( - filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.JsxAttribute && isDiscriminantProperty(contextualType, p.symbol.escapedName) && (!p.initializer || isPossiblyDiscriminantValue(p.initializer))), - prop => ([!(prop as JsxAttribute).initializer ? (() => trueType) : (() => checkExpression((prop as JsxAttribute).initializer!)), prop.symbol.escapedName] as [() => Type, __String]) - ), - isTypeAssignableTo, - contextualType - ); + } + /** + * Since the source of spread types are object literals, which are not binary, + * this function should be called in a left folding style, with left = previous result of getSpreadType + * and right = the new element to be spread. + */ + function getSpreadType(left: ts.Type, right: ts.Type, symbol: ts.Symbol | undefined, objectFlags: ObjectFlags, readonly: boolean): ts.Type { + if (left.flags & TypeFlags.Any || right.flags & TypeFlags.Any) { + return anyType; } - - // Return the contextual type for a given expression node. During overload resolution, a contextual type may temporarily - // be "pushed" onto a node using the contextualType property. - function getApparentTypeOfContextualType(node: Expression | MethodDeclaration, contextFlags?: ContextFlags): Type | undefined { - const contextualType = isObjectLiteralMethod(node) ? - getContextualTypeForObjectLiteralMethod(node, contextFlags) : - getContextualType(node, contextFlags); - const instantiatedType = instantiateContextualType(contextualType, node, contextFlags); - if (instantiatedType && !(contextFlags && contextFlags & ContextFlags.NoConstraints && instantiatedType.flags & TypeFlags.TypeVariable)) { - const apparentType = mapType(instantiatedType, getApparentType, /*noReductions*/ true); - if (apparentType.flags & TypeFlags.Union) { - if (isObjectLiteralExpression(node)) { - return discriminateContextualTypeByObjectMembers(node, apparentType as UnionType); - } - else if (isJsxAttributes(node)) { - return discriminateContextualTypeByJSXAttributes(node, apparentType as UnionType); - } - } - return apparentType; + if (left.flags & TypeFlags.Unknown || right.flags & TypeFlags.Unknown) { + return unknownType; + } + if (left.flags & TypeFlags.Never) { + return right; + } + if (right.flags & TypeFlags.Never) { + return left; + } + if (left.flags & TypeFlags.Union) { + const merged = tryMergeUnionOfObjectTypeAndEmptyObject((left as UnionType), readonly); + if (merged) { + return getSpreadType(merged, right, symbol, objectFlags, readonly); } + return mapType(left, t => getSpreadType(t, right, symbol, objectFlags, readonly)); } - - // If the given contextual type contains instantiable types and if a mapper representing - // return type inferences is available, instantiate those types using that mapper. - function instantiateContextualType(contextualType: Type | undefined, node: Node, contextFlags?: ContextFlags): Type | undefined { - if (contextualType && maybeTypeOfKind(contextualType, TypeFlags.Instantiable)) { - const inferenceContext = getInferenceContext(node); - // If no inferences have been made, nothing is gained from instantiating as type parameters - // would just be replaced with their defaults similar to the apparent type. - if (inferenceContext && some(inferenceContext.inferences, hasInferenceCandidates)) { - // For contextual signatures we incorporate all inferences made so far, e.g. from return - // types as well as arguments to the left in a function call. - if (contextFlags && contextFlags & ContextFlags.Signature) { - return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper); - } - // For other purposes (e.g. determining whether to produce literal types) we only - // incorporate inferences made from the return type in a function call. - if (inferenceContext.returnMapper) { - return instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper); - } - } + if (right.flags & TypeFlags.Union) { + const merged = tryMergeUnionOfObjectTypeAndEmptyObject((right as UnionType), readonly); + if (merged) { + return getSpreadType(left, merged, symbol, objectFlags, readonly); } - return contextualType; + return mapType(right, t => getSpreadType(left, t, symbol, objectFlags, readonly)); } - - // This function is similar to instantiateType, except that (a) it only instantiates types that - // are classified as instantiable (i.e. it doesn't instantiate object types), and (b) it performs - // no reductions on instantiated union types. - function instantiateInstantiableTypes(type: Type, mapper: TypeMapper): Type { - if (type.flags & TypeFlags.Instantiable) { - return instantiateType(type, mapper); + if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)) { + return left; + } + if (isGenericObjectType(left) || isGenericObjectType(right)) { + if (isEmptyObjectType(left)) { + return right; } - if (type.flags & TypeFlags.Union) { - return getUnionType(map((type).types, t => instantiateInstantiableTypes(t, mapper)), UnionReduction.None); + // When the left type is an intersection, we may need to merge the last constituent of the + // intersection with the right type. For example when the left type is 'T & { a: string }' + // and the right type is '{ b: string }' we produce 'T & { a: string, b: string }'. + if (left.flags & TypeFlags.Intersection) { + const types = (left).types; + const lastLeft = types[types.length - 1]; + if (isNonGenericObjectType(lastLeft) && isNonGenericObjectType(right)) { + return getIntersectionType(concatenate(types.slice(0, types.length - 1), [getSpreadType(lastLeft, right, symbol, objectFlags, readonly)])); + } + } + return getIntersectionType([left, right]); + } + const members = createSymbolTable(); + const skippedPrivateMembers = createUnderscoreEscapedMap(); + let stringIndexInfo: IndexInfo | undefined; + let numberIndexInfo: IndexInfo | undefined; + if (left === emptyObjectType) { + // for the first spread element, left === emptyObjectType, so take the right's string indexer + stringIndexInfo = getIndexInfoOfType(right, IndexKind.String); + numberIndexInfo = getIndexInfoOfType(right, IndexKind.Number); + } + else { + stringIndexInfo = unionSpreadIndexInfos(getIndexInfoOfType(left, IndexKind.String), getIndexInfoOfType(right, IndexKind.String)); + numberIndexInfo = unionSpreadIndexInfos(getIndexInfoOfType(left, IndexKind.Number), getIndexInfoOfType(right, IndexKind.Number)); + } + for (const rightProp of getPropertiesOfType(right)) { + if (getDeclarationModifierFlagsFromSymbol(rightProp) & (ModifierFlags.Private | ModifierFlags.Protected)) { + skippedPrivateMembers.set(rightProp.escapedName, true); + } + else if (isSpreadableProperty(rightProp)) { + members.set(rightProp.escapedName, getSpreadSymbol(rightProp, readonly)); + } + } + for (const leftProp of getPropertiesOfType(left)) { + if (skippedPrivateMembers.has(leftProp.escapedName) || !isSpreadableProperty(leftProp)) { + continue; + } + if (members.has(leftProp.escapedName)) { + const rightProp = members.get(leftProp.escapedName)!; + const rightType = getTypeOfSymbol(rightProp); + if (rightProp.flags & SymbolFlags.Optional) { + const declarations = concatenate(leftProp.declarations, rightProp.declarations); + const flags = SymbolFlags.Property | (leftProp.flags & SymbolFlags.Optional); + const result = createSymbol(flags, leftProp.escapedName); + result.type = getUnionType([getTypeOfSymbol(leftProp), getTypeWithFacts(rightType, TypeFacts.NEUndefined)]); + result.leftSpread = leftProp; + result.rightSpread = rightProp; + result.declarations = declarations; + result.nameType = leftProp.nameType; + members.set(leftProp.escapedName, result); + } } - if (type.flags & TypeFlags.Intersection) { - return getIntersectionType(map((type).types, t => instantiateInstantiableTypes(t, mapper))); + else { + members.set(leftProp.escapedName, getSpreadSymbol(leftProp, readonly)); } - return type; } - - /** - * Whoa! Do you really want to use this function? - * - * Unless you're trying to get the *non-apparent* type for a - * value-literal type or you're authoring relevant portions of this algorithm, - * you probably meant to use 'getApparentTypeOfContextualType'. - * Otherwise this may not be very useful. - * - * In cases where you *are* working on this function, you should understand - * when it is appropriate to use 'getContextualType' and 'getApparentTypeOfContextualType'. - * - * - Use 'getContextualType' when you are simply going to propagate the result to the expression. - * - Use 'getApparentTypeOfContextualType' when you're going to need the members of the type. - * - * @param node the expression whose contextual type will be returned. - * @returns the contextual type of an expression. - */ - function getContextualType(node: Expression, contextFlags?: ContextFlags): Type | undefined { - if (node.flags & NodeFlags.InWithStatement) { - // We cannot answer semantic questions within a with block, do not proceed any further - return undefined; - } - if (node.contextualType) { - return node.contextualType; - } - const { parent } = node; - switch (parent.kind) { - case SyntaxKind.VariableDeclaration: - case SyntaxKind.Parameter: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.BindingElement: - return getContextualTypeForInitializerExpression(node); - case SyntaxKind.ArrowFunction: - case SyntaxKind.ReturnStatement: - return getContextualTypeForReturnExpression(node); - case SyntaxKind.YieldExpression: - return getContextualTypeForYieldOperand(parent); - case SyntaxKind.AwaitExpression: - return getContextualTypeForAwaitOperand(parent); - case SyntaxKind.CallExpression: - if ((parent).expression.kind === SyntaxKind.ImportKeyword) { - return stringType; - } - /* falls through */ - case SyntaxKind.NewExpression: - return getContextualTypeForArgument(parent, node, contextFlags); - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.AsExpression: - return isConstTypeReference((parent).type) ? undefined : getTypeFromTypeNode((parent).type); - case SyntaxKind.BinaryExpression: - return getContextualTypeForBinaryOperand(node, contextFlags); - case SyntaxKind.PropertyAssignment: - case SyntaxKind.ShorthandPropertyAssignment: - return getContextualTypeForObjectLiteralElement(parent, contextFlags); - case SyntaxKind.SpreadAssignment: - return getApparentTypeOfContextualType(parent.parent as ObjectLiteralExpression, contextFlags); - case SyntaxKind.ArrayLiteralExpression: { - const arrayLiteral = parent; - const type = getApparentTypeOfContextualType(arrayLiteral, contextFlags); - return getContextualTypeForElementExpression(type, indexOfNode(arrayLiteral.elements, node)); - } - case SyntaxKind.ConditionalExpression: - return getContextualTypeForConditionalOperand(node, contextFlags); - case SyntaxKind.TemplateSpan: - Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression); - return getContextualTypeForSubstitutionExpression(parent.parent, node); - case SyntaxKind.ParenthesizedExpression: { - // Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast. - const tag = isInJSFile(parent) ? getJSDocTypeTag(parent) : undefined; - return tag ? getTypeFromTypeNode(tag.typeExpression.type) : getContextualType(parent, contextFlags); - } - case SyntaxKind.JsxExpression: - return getContextualTypeForJsxExpression(parent); - case SyntaxKind.JsxAttribute: - case SyntaxKind.JsxSpreadAttribute: - return getContextualTypeForJsxAttribute(parent); - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxSelfClosingElement: - return getContextualJsxElementAttributesType(parent); + const spread = createAnonymousType(symbol, members, emptyArray, emptyArray, getIndexInfoWithReadonly(stringIndexInfo, readonly), getIndexInfoWithReadonly(numberIndexInfo, readonly)); + spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral | ObjectFlags.ContainsSpread | objectFlags; + return spread; + } + /** We approximate own properties as non-methods plus methods that are inside the object literal */ + function isSpreadableProperty(prop: ts.Symbol): boolean { + return !(prop.flags & (SymbolFlags.Method | SymbolFlags.GetAccessor | SymbolFlags.SetAccessor)) || + !prop.declarations.some(decl => isClassLike(decl.parent)); + } + function getSpreadSymbol(prop: ts.Symbol, readonly: boolean) { + const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); + if (!isSetonlyAccessor && readonly === isReadonlySymbol(prop)) { + return prop; + } + const flags = SymbolFlags.Property | (prop.flags & SymbolFlags.Optional); + const result = createSymbol(flags, prop.escapedName, readonly ? CheckFlags.Readonly : 0); + result.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop); + result.declarations = prop.declarations; + result.nameType = prop.nameType; + result.syntheticOrigin = prop; + return result; + } + function getIndexInfoWithReadonly(info: IndexInfo | undefined, readonly: boolean) { + return info && info.isReadonly !== readonly ? createIndexInfo(info.type, readonly, info.declaration) : info; + } + function createLiteralType(flags: TypeFlags, value: string | number | PseudoBigInt, symbol: ts.Symbol | undefined) { + const type = (createType(flags)); + type.symbol = symbol!; + type.value = value; + return type; + } + function getFreshTypeOfLiteralType(type: ts.Type): ts.Type { + if (type.flags & TypeFlags.Literal) { + if (!(type).freshType) { + const freshType = createLiteralType(type.flags, (type).value, (type).symbol); + freshType.regularType = (type); + freshType.freshType = freshType; + (type).freshType = freshType; } - return undefined; + return (type).freshType; } - - function getInferenceContext(node: Node) { - const ancestor = findAncestor(node, n => !!n.inferenceContext); - return ancestor && ancestor.inferenceContext!; + return type; + } + function getRegularTypeOfLiteralType(type: ts.Type): ts.Type { + return type.flags & TypeFlags.Literal ? (type).regularType : + type.flags & TypeFlags.Union ? getUnionType(sameMap((type).types, getRegularTypeOfLiteralType)) : + type; + } + function isFreshLiteralType(type: ts.Type) { + return !!(type.flags & TypeFlags.Literal) && (type).freshType === type; + } + function getLiteralType(value: string | number | PseudoBigInt, enumId?: number, symbol?: ts.Symbol) { + // We store all literal types in a single map with keys of the form '#NNN' and '@SSS', + // where NNN is the text representation of a numeric literal and SSS are the characters + // of a string literal. For literal enum members we use 'EEE#NNN' and 'EEE@SSS', where + // EEE is a unique id for the containing enum type. + const qualifier = typeof value === "number" ? "#" : typeof value === "string" ? "@" : "n"; + const key = (enumId ? enumId : "") + qualifier + (typeof value === "object" ? pseudoBigIntToString(value) : value); + let type = literalTypes.get(key); + if (!type) { + const flags = (typeof value === "number" ? TypeFlags.NumberLiteral : + typeof value === "string" ? TypeFlags.StringLiteral : TypeFlags.BigIntLiteral) | + (enumId ? TypeFlags.EnumLiteral : 0); + literalTypes.set(key, type = createLiteralType(flags, value, symbol)); + type.regularType = type; + } + return type; + } + function getTypeFromLiteralTypeNode(node: LiteralTypeNode): ts.Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = getRegularTypeOfLiteralType(checkExpression(node.literal)); } - - function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement) { - if (isJsxOpeningElement(node) && node.parent.contextualType) { - // Contextually applied type is moved from attributes up to the outer jsx attributes so when walking up from the children they get hit - // _However_ to hit them from the _attributes_ we must look for them here; otherwise we'll used the declared type - // (as below) instead! - return node.parent.contextualType; - } - return getContextualTypeForArgumentAtIndex(node, 0); + return links.resolvedType; + } + function createUniqueESSymbolType(symbol: ts.Symbol) { + const type = (createType(TypeFlags.UniqueESSymbol)); + type.symbol = symbol; + type.escapedName = (`__@${type.symbol.escapedName}@${getSymbolId(type.symbol)}` as __String); + return type; + } + function getESSymbolLikeTypeForNode(node: Node) { + if (isValidESSymbolDeclaration(node)) { + const symbol = getSymbolOfNode(node); + const links = getSymbolLinks(symbol); + return links.uniqueESSymbolType || (links.uniqueESSymbolType = createUniqueESSymbolType(symbol)); } - - function getEffectiveFirstArgumentForJsxSignature(signature: Signature, node: JsxOpeningLikeElement) { - return getJsxReferenceKind(node) !== JsxReferenceKind.Component ? getJsxPropsTypeFromCallSignature(signature, node) : getJsxPropsTypeFromClassType(signature, node); + return esSymbolType; + } + function getThisType(node: Node): ts.Type { + const container = getThisContainer(node, /*includeArrowFunctions*/ false); + const parent = container && container.parent; + if (parent && (isClassLike(parent) || parent.kind === SyntaxKind.InterfaceDeclaration)) { + if (!hasModifier(container, ModifierFlags.Static) && + (!isConstructorDeclaration(container) || isNodeDescendantOf(node, container.body))) { + return getDeclaredTypeOfClassOrInterface(getSymbolOfNode((parent as ClassLikeDeclaration | InterfaceDeclaration))).thisType!; + } + } + // inside x.prototype = { ... } + if (parent && isObjectLiteralExpression(parent) && isBinaryExpression(parent.parent) && getAssignmentDeclarationKind(parent.parent) === AssignmentDeclarationKind.Prototype) { + return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent.parent.left)!.parent!).thisType!; + } + // /** @return {this} */ + // x.prototype.m = function() { ... } + const host = node.flags & NodeFlags.JSDoc ? getHostSignatureFromJSDoc(node) : undefined; + if (host && isFunctionExpression(host) && isBinaryExpression(host.parent) && getAssignmentDeclarationKind(host.parent) === AssignmentDeclarationKind.PrototypeProperty) { + return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(host.parent.left)!.parent!).thisType!; + } + // inside constructor function C() { ... } + if (isJSConstructor(container) && isNodeDescendantOf(node, container.body)) { + return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(container)).thisType!; + } + error(node, Diagnostics.A_this_type_is_available_only_in_a_non_static_member_of_a_class_or_interface); + return errorType; + } + function getTypeFromThisTypeNode(node: ThisExpression | ThisTypeNode): ts.Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = getThisType(node); } - - function getJsxPropsTypeFromCallSignature(sig: Signature, context: JsxOpeningLikeElement) { - let propsType = getTypeOfFirstParameterOfSignatureWithFallback(sig, unknownType); - propsType = getJsxManagedAttributesFromLocatedAttributes(context, getJsxNamespaceAt(context), propsType); - const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context); - if (intrinsicAttribs !== errorType) { - propsType = intersectTypes(intrinsicAttribs, propsType); - } - return propsType; + return links.resolvedType; + } + function getTypeFromTypeNode(node: TypeNode): ts.Type { + switch (node.kind) { + case SyntaxKind.AnyKeyword: + case SyntaxKind.JSDocAllType: + case SyntaxKind.JSDocUnknownType: + return anyType; + case SyntaxKind.UnknownKeyword: + return unknownType; + case SyntaxKind.StringKeyword: + return stringType; + case SyntaxKind.NumberKeyword: + return numberType; + case SyntaxKind.BigIntKeyword: + return bigintType; + case SyntaxKind.BooleanKeyword: + return booleanType; + case SyntaxKind.SymbolKeyword: + return esSymbolType; + case SyntaxKind.VoidKeyword: + return voidType; + case SyntaxKind.UndefinedKeyword: + return undefinedType; + case SyntaxKind.NullKeyword: + return nullType; + case SyntaxKind.NeverKeyword: + return neverType; + case SyntaxKind.ObjectKeyword: + return node.flags & NodeFlags.JavaScriptFile ? anyType : nonPrimitiveType; + case SyntaxKind.ThisType: + case SyntaxKind.ThisKeyword: + return getTypeFromThisTypeNode((node as ThisExpression | ThisTypeNode)); + case SyntaxKind.LiteralType: + return getTypeFromLiteralTypeNode((node)); + case SyntaxKind.TypeReference: + return getTypeFromTypeReference((node)); + case SyntaxKind.TypePredicate: + return (node).assertsModifier ? voidType : booleanType; + case SyntaxKind.ExpressionWithTypeArguments: + return getTypeFromTypeReference((node)); + case SyntaxKind.TypeQuery: + return getTypeFromTypeQueryNode((node)); + case SyntaxKind.ArrayType: + case SyntaxKind.TupleType: + return getTypeFromArrayOrTupleTypeNode((node)); + case SyntaxKind.OptionalType: + return getTypeFromOptionalTypeNode((node)); + case SyntaxKind.UnionType: + return getTypeFromUnionTypeNode((node)); + case SyntaxKind.IntersectionType: + return getTypeFromIntersectionTypeNode((node)); + case SyntaxKind.JSDocNullableType: + return getTypeFromJSDocNullableTypeNode((node)); + case SyntaxKind.JSDocOptionalType: + return addOptionality(getTypeFromTypeNode((node as JSDocOptionalType).type)); + case SyntaxKind.ParenthesizedType: + case SyntaxKind.JSDocNonNullableType: + case SyntaxKind.JSDocTypeExpression: + return getTypeFromTypeNode((node).type); + case SyntaxKind.RestType: + return getElementTypeOfArrayType(getTypeFromTypeNode((node).type)) || errorType; + case SyntaxKind.JSDocVariadicType: + return getTypeFromJSDocVariadicType((node as JSDocVariadicType)); + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: + case SyntaxKind.JSDocFunctionType: + case SyntaxKind.JSDocSignature: + return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); + case SyntaxKind.TypeOperator: + return getTypeFromTypeOperatorNode((node)); + case SyntaxKind.IndexedAccessType: + return getTypeFromIndexedAccessTypeNode((node)); + case SyntaxKind.MappedType: + return getTypeFromMappedTypeNode((node)); + case SyntaxKind.ConditionalType: + return getTypeFromConditionalTypeNode((node)); + case SyntaxKind.InferType: + return getTypeFromInferTypeNode((node)); + case SyntaxKind.ImportType: + return getTypeFromImportTypeNode((node)); + // This function assumes that an identifier or qualified name is a type expression + // Callers should first ensure this by calling isTypeNode + case SyntaxKind.Identifier: + case SyntaxKind.QualifiedName: + const symbol = getSymbolAtLocation(node); + return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType; + default: + return errorType; } - - function getJsxPropsTypeForSignatureFromMember(sig: Signature, forcedLookupLocation: __String) { - if (sig.unionSignatures) { - // JSX Elements using the legacy `props`-field based lookup (eg, react class components) need to treat the `props` member as an input - // instead of an output position when resolving the signature. We need to go back to the input signatures of the composite signature, - // get the type of `props` on each return type individually, and then _intersect them_, rather than union them (as would normally occur - // for a union signature). It's an unfortunate quirk of looking in the output of the signature for the type we want to use for the input. - // The default behavior of `getTypeOfFirstParameterOfSignatureWithFallback` when no `props` member name is defined is much more sane. - const results: Type[] = []; - for (const signature of sig.unionSignatures) { - const instance = getReturnTypeOfSignature(signature); - if (isTypeAny(instance)) { - return instance; - } - const propType = getTypeOfPropertyOfType(instance, forcedLookupLocation); - if (!propType) { - return; + } + function instantiateList(items: readonly T[], mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[]; + function instantiateList(items: readonly T[] | undefined, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[] | undefined; + function instantiateList(items: readonly T[] | undefined, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[] | undefined { + if (items && items.length) { + for (let i = 0; i < items.length; i++) { + const item = items[i]; + const mapped = instantiator(item, mapper); + if (item !== mapped) { + const result = i === 0 ? [] : items.slice(0, i); + result.push(mapped); + for (i++; i < items.length; i++) { + result.push(instantiator(items[i], mapper)); } - results.push(propType); - } - return getIntersectionType(results); - } - const instanceType = getReturnTypeOfSignature(sig); - return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation); - } - - function getStaticTypeOfReferencedJsxConstructor(context: JsxOpeningLikeElement) { - if (isJsxIntrinsicIdentifier(context.tagName)) { - const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(context); - const fakeSignature = createSignatureForJSXIntrinsic(context, result); - return getOrCreateTypeFromSignature(fakeSignature); - } - const tagType = checkExpressionCached(context.tagName); - if (tagType.flags & TypeFlags.StringLiteral) { - const result = getIntrinsicAttributesTypeFromStringLiteralType(tagType as StringLiteralType, context); - if (!result) { - return errorType; + return result; } - const fakeSignature = createSignatureForJSXIntrinsic(context, result); - return getOrCreateTypeFromSignature(fakeSignature); } - return tagType; } - - function getJsxManagedAttributesFromLocatedAttributes(context: JsxOpeningLikeElement, ns: Symbol, attributesType: Type) { - const managedSym = getJsxLibraryManagedAttributes(ns); - if (managedSym) { - const declaredManagedType = getDeclaredTypeOfSymbol(managedSym); - const ctorType = getStaticTypeOfReferencedJsxConstructor(context); - if (length((declaredManagedType as GenericType).typeParameters) >= 2) { - const args = fillMissingTypeArguments([ctorType, attributesType], (declaredManagedType as GenericType).typeParameters, 2, isInJSFile(context)); - return createTypeReference((declaredManagedType as GenericType), args); - } - else if (length(declaredManagedType.aliasTypeArguments) >= 2) { - const args = fillMissingTypeArguments([ctorType, attributesType], declaredManagedType.aliasTypeArguments, 2, isInJSFile(context)); - return getTypeAliasInstantiation(declaredManagedType.aliasSymbol!, args); + return items; + } + function instantiateTypes(types: readonly ts.Type[], mapper: TypeMapper): readonly ts.Type[]; + function instantiateTypes(types: readonly ts.Type[] | undefined, mapper: TypeMapper): readonly ts.Type[] | undefined; + function instantiateTypes(types: readonly ts.Type[] | undefined, mapper: TypeMapper): readonly ts.Type[] | undefined { + return instantiateList(types, mapper, instantiateType); + } + function instantiateSignatures(signatures: readonly ts.Signature[], mapper: TypeMapper): readonly ts.Signature[] { + return instantiateList(signatures, mapper, instantiateSignature); + } + function makeUnaryTypeMapper(source: ts.Type, target: ts.Type) { + return (t: ts.Type) => t === source ? target : t; + } + function makeBinaryTypeMapper(source1: ts.Type, target1: ts.Type, source2: ts.Type, target2: ts.Type) { + return (t: ts.Type) => t === source1 ? target1 : t === source2 ? target2 : t; + } + function makeArrayTypeMapper(sources: readonly ts.Type[], targets: readonly ts.Type[] | undefined) { + return (t: ts.Type) => { + for (let i = 0; i < sources.length; i++) { + if (t === sources[i]) { + return targets ? targets[i] : anyType; } } - return attributesType; + return t; + }; + } + function createTypeMapper(sources: readonly TypeParameter[], targets: readonly ts.Type[] | undefined): TypeMapper { + Debug.assert(targets === undefined || sources.length === targets.length); + return sources.length === 1 ? makeUnaryTypeMapper(sources[0], targets ? targets[0] : anyType) : + sources.length === 2 ? makeBinaryTypeMapper(sources[0], targets ? targets[0] : anyType, sources[1], targets ? targets[1] : anyType) : + makeArrayTypeMapper(sources, targets); + } + function createTypeEraser(sources: readonly TypeParameter[]): TypeMapper { + return createTypeMapper(sources, /*targets*/ undefined); + } + /** + * Maps forward-references to later types parameters to the empty object type. + * This is used during inference when instantiating type parameter defaults. + */ + function createBackreferenceMapper(context: InferenceContext, index: number): TypeMapper { + return t => findIndex(context.inferences, info => info.typeParameter === t) >= index ? unknownType : t; + } + function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper; + function combineTypeMappers(mapper1: TypeMapper, mapper2: TypeMapper | undefined): TypeMapper; + function combineTypeMappers(mapper1: TypeMapper, mapper2: TypeMapper): TypeMapper { + if (!mapper1) + return mapper2; + if (!mapper2) + return mapper1; + return t => instantiateType(mapper1(t), mapper2); + } + function createReplacementMapper(source: ts.Type, target: ts.Type, baseMapper: TypeMapper): TypeMapper { + return t => t === source ? target : baseMapper(t); + } + function permissiveMapper(type: ts.Type) { + return type.flags & TypeFlags.TypeParameter ? wildcardType : type; + } + function getRestrictiveTypeParameter(tp: TypeParameter) { + return tp.constraint === unknownType ? tp : tp.restrictiveInstantiation || (tp.restrictiveInstantiation = createTypeParameter(tp.symbol), + (tp.restrictiveInstantiation as TypeParameter).constraint = unknownType, + tp.restrictiveInstantiation); + } + function restrictiveMapper(type: ts.Type) { + return type.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter((type)) : type; + } + function cloneTypeParameter(typeParameter: TypeParameter): TypeParameter { + const result = createTypeParameter(typeParameter.symbol); + result.target = typeParameter; + return result; + } + function instantiateTypePredicate(predicate: TypePredicate, mapper: TypeMapper): TypePredicate { + return createTypePredicate(predicate.kind, predicate.parameterName, predicate.parameterIndex, instantiateType(predicate.type, mapper)); + } + function instantiateSignature(signature: ts.Signature, mapper: TypeMapper, eraseTypeParameters?: boolean): ts.Signature { + let freshTypeParameters: TypeParameter[] | undefined; + if (signature.typeParameters && !eraseTypeParameters) { + // First create a fresh set of type parameters, then include a mapping from the old to the + // new type parameters in the mapper function. Finally store this mapper in the new type + // parameters such that we can use it when instantiating constraints. + freshTypeParameters = map(signature.typeParameters, cloneTypeParameter); + mapper = combineTypeMappers(createTypeMapper(signature.typeParameters, freshTypeParameters), mapper); + for (const tp of freshTypeParameters) { + tp.mapper = mapper; + } + } + // Don't compute resolvedReturnType and resolvedTypePredicate now, + // because using `mapper` now could trigger inferences to become fixed. (See `createInferenceContext`.) + // See GH#17600. + const result = createSignature(signature.declaration, freshTypeParameters, signature.thisParameter && instantiateSymbol(signature.thisParameter, mapper), instantiateList(signature.parameters, mapper, instantiateSymbol), + /*resolvedReturnType*/ undefined, + /*resolvedTypePredicate*/ undefined, signature.minArgumentCount, signature.flags & SignatureFlags.PropagatingFlags); + result.target = signature; + result.mapper = mapper; + return result; + } + function instantiateSymbol(symbol: ts.Symbol, mapper: TypeMapper): ts.Symbol { + const links = getSymbolLinks(symbol); + if (links.type && !maybeTypeOfKind(links.type, TypeFlags.Object | TypeFlags.Instantiable)) { + // If the type of the symbol is already resolved, and if that type could not possibly + // be affected by instantiation, simply return the symbol itself. + return symbol; } - - function getJsxPropsTypeFromClassType(sig: Signature, context: JsxOpeningLikeElement) { - const ns = getJsxNamespaceAt(context); - const forcedLookupLocation = getJsxElementPropertiesName(ns); - let attributesType = forcedLookupLocation === undefined - // If there is no type ElementAttributesProperty, return the type of the first parameter of the signature, which should be the props type - ? getTypeOfFirstParameterOfSignatureWithFallback(sig, unknownType) - : forcedLookupLocation === "" - // If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead - ? getReturnTypeOfSignature(sig) - // Otherwise get the type of the property on the signature return type - : getJsxPropsTypeForSignatureFromMember(sig, forcedLookupLocation); - - if (!attributesType) { - // There is no property named 'props' on this instance type - if (!!forcedLookupLocation && !!length(context.attributes.properties)) { - error(context, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, unescapeLeadingUnderscores(forcedLookupLocation)); + if (getCheckFlags(symbol) & CheckFlags.Instantiated) { + // If symbol being instantiated is itself a instantiation, fetch the original target and combine the + // type mappers. This ensures that original type identities are properly preserved and that aliases + // always reference a non-aliases. + symbol = links.target!; + mapper = combineTypeMappers(links.mapper, mapper); + } + // Keep the flags from the symbol we're instantiating. Mark that is instantiated, and + // also transient so that we can just store data on it directly. + const result = createSymbol(symbol.flags, symbol.escapedName, CheckFlags.Instantiated | getCheckFlags(symbol) & (CheckFlags.Readonly | CheckFlags.Late | CheckFlags.OptionalParameter | CheckFlags.RestParameter)); + result.declarations = symbol.declarations; + result.parent = symbol.parent; + result.target = symbol; + result.mapper = mapper; + if (symbol.valueDeclaration) { + result.valueDeclaration = symbol.valueDeclaration; + } + if (symbol.nameType) { + result.nameType = symbol.nameType; + } + return result; + } + function getObjectTypeInstantiation(type: AnonymousType | DeferredTypeReference, mapper: TypeMapper) { + const target = type.objectFlags & ObjectFlags.Instantiated ? type.target! : type; + const node = type.objectFlags & ObjectFlags.Reference ? (type).node! : type.symbol.declarations[0]; + const links = getNodeLinks(node); + let typeParameters = links.outerTypeParameters; + if (!typeParameters) { + // The first time an anonymous type is instantiated we compute and store a list of the type + // parameters that are in scope (and therefore potentially referenced). For type literals that + // aren't the right hand side of a generic type alias declaration we optimize by reducing the + // set of type parameters to those that are possibly referenced in the literal. + let declaration = node; + if (isInJSFile(declaration)) { + const paramTag = findAncestor(declaration, isJSDocParameterTag); + if (paramTag) { + const paramSymbol = getParameterSymbolFromJSDoc(paramTag); + if (paramSymbol) { + declaration = paramSymbol.valueDeclaration; + } } - return unknownType; } - - attributesType = getJsxManagedAttributesFromLocatedAttributes(context, ns, attributesType); - - if (isTypeAny(attributesType)) { - // Props is of type 'any' or unknown - return attributesType; + let outerTypeParameters = getOuterTypeParameters(declaration, /*includeThisTypes*/ true); + if (isJSConstructor(declaration)) { + const templateTagParameters = getTypeParametersFromDeclaration((declaration as DeclarationWithTypeParameters)); + outerTypeParameters = addRange(outerTypeParameters, templateTagParameters); } - else { - // Normal case -- add in IntrinsicClassElements and IntrinsicElements - let apparentAttributesType = attributesType; - const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes, context); - if (intrinsicClassAttribs !== errorType) { - const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol); - const hostClassType = getReturnTypeOfSignature(sig); - apparentAttributesType = intersectTypes( - typeParams - ? createTypeReference(intrinsicClassAttribs, fillMissingTypeArguments([hostClassType], typeParams, getMinTypeArgumentCount(typeParams), isInJSFile(context))) - : intrinsicClassAttribs, - apparentAttributesType - ); - } - - const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context); - if (intrinsicAttribs !== errorType) { - apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType); - } - - return apparentAttributesType; + typeParameters = outerTypeParameters || emptyArray; + typeParameters = (target.objectFlags & ObjectFlags.Reference || target.symbol.flags & SymbolFlags.TypeLiteral) && !target.aliasTypeArguments ? + filter(typeParameters, tp => isTypeParameterPossiblyReferenced(tp, declaration)) : + typeParameters; + links.outerTypeParameters = typeParameters; + if (typeParameters.length) { + links.instantiations = createMap(); + links.instantiations.set(getTypeListId(typeParameters), target); } } - - // If the given type is an object or union type with a single signature, and if that signature has at - // least as many parameters as the given function, return the signature. Otherwise return undefined. - function getContextualCallSignature(type: Type, node: SignatureDeclaration): Signature | undefined { - const signatures = getSignaturesOfType(type, SignatureKind.Call); - if (signatures.length === 1) { - const signature = signatures[0]; - if (!isAritySmaller(signature, node)) { - return signature; - } + if (typeParameters.length) { + // We are instantiating an anonymous type that has one or more type parameters in scope. Apply the + // mapper to the type parameters to produce the effective list of type arguments, and compute the + // instantiation cache key from the type IDs of the type arguments. + const typeArguments = map(typeParameters, combineTypeMappers(type.mapper, mapper)); + const id = getTypeListId(typeArguments); + let result = links.instantiations!.get(id); + if (!result) { + const newMapper = createTypeMapper(typeParameters, typeArguments); + result = target.objectFlags & ObjectFlags.Reference ? createDeferredTypeReference((type).target, (type).node, newMapper) : + target.objectFlags & ObjectFlags.Mapped ? instantiateMappedType((target), newMapper) : + instantiateAnonymousType(target, newMapper); + links.instantiations!.set(id, result); } + return result; } - - /** If the contextual signature has fewer parameters than the function expression, do not use it */ - function isAritySmaller(signature: Signature, target: SignatureDeclaration) { - let targetParameterCount = 0; - for (; targetParameterCount < target.parameters.length; targetParameterCount++) { - const param = target.parameters[targetParameterCount]; - if (param.initializer || param.questionToken || param.dotDotDotToken || isJSDocOptionalParameter(param)) { - break; + return type; + } + function maybeTypeParameterReference(node: Node) { + return !(node.kind === SyntaxKind.QualifiedName || + node.parent.kind === SyntaxKind.TypeReference && (node.parent).typeArguments && node === (node.parent).typeName || + node.parent.kind === SyntaxKind.ImportType && (node.parent as ImportTypeNode).typeArguments && node === (node.parent as ImportTypeNode).qualifier); + } + function isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node) { + // If the type parameter doesn't have exactly one declaration, if there are invening statement blocks + // between the node and the type parameter declaration, if the node contains actual references to the + // type parameter, or if the node contains type queries, we consider the type parameter possibly referenced. + if (tp.symbol && tp.symbol.declarations && tp.symbol.declarations.length === 1) { + const container = tp.symbol.declarations[0].parent; + for (let n = node; n !== container; n = n.parent) { + if (!n || n.kind === SyntaxKind.Block || n.kind === SyntaxKind.ConditionalType && forEachChild((n).extendsType, containsReference)) { + return true; } } - if (target.parameters.length && parameterIsThisKeyword(target.parameters[0])) { - targetParameterCount--; - } - return !hasEffectiveRestParameter(signature) && getParameterCount(signature) < targetParameterCount; - } - - function isFunctionExpressionOrArrowFunction(node: Node): node is FunctionExpression | ArrowFunction { - return node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.ArrowFunction; + return !!forEachChild(node, containsReference); } - - function getContextualSignatureForFunctionLikeDeclaration(node: FunctionLikeDeclaration): Signature | undefined { - // Only function expressions, arrow functions, and object literal methods are contextually typed. - return isFunctionExpressionOrArrowFunction(node) || isObjectLiteralMethod(node) - ? getContextualSignature(node) - : undefined; - } - - // Return the contextual signature for a given expression node. A contextual type provides a - // contextual signature if it has a single call signature and if that call signature is non-generic. - // If the contextual type is a union type, get the signature from each type possible and if they are - // all identical ignoring their return type, the result is same signature but with return type as - // union type of return types from these signatures - function getContextualSignature(node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature | undefined { - Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); - const typeTagSignature = getSignatureOfTypeTag(node); - if (typeTagSignature) { - return typeTagSignature; - } - const type = getApparentTypeOfContextualType(node, ContextFlags.Signature); - if (!type) { - return undefined; + return true; + function containsReference(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ThisType: + return !!tp.isThisType; + case SyntaxKind.Identifier: + return !tp.isThisType && isPartOfTypeNode(node) && maybeTypeParameterReference(node) && + getTypeFromTypeNode((node)) === tp; + case SyntaxKind.TypeQuery: + return true; } - if (!(type.flags & TypeFlags.Union)) { - return getContextualCallSignature(type, node); + return !!forEachChild(node, containsReference); + } + } + function getHomomorphicTypeVariable(type: MappedType) { + const constraintType = getConstraintTypeFromMappedType(type); + if (constraintType.flags & TypeFlags.Index) { + const typeVariable = getActualTypeVariable((constraintType).type); + if (typeVariable.flags & TypeFlags.TypeParameter) { + return typeVariable; } - let signatureList: Signature[] | undefined; - const types = (type).types; - for (const current of types) { - const signature = getContextualCallSignature(current, node); - if (signature) { - if (!signatureList) { - // This signature will contribute to contextual union signature - signatureList = [signature]; - } - else if (!compareSignaturesIdentical(signatureList[0], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ true, /*ignoreReturnTypes*/ true, compareTypesIdentical)) { - // Signatures aren't identical, do not use - return undefined; - } - else { - // Use this signature for contextual union signature - signatureList.push(signature); + } + return undefined; + } + function instantiateMappedType(type: MappedType, mapper: TypeMapper): ts.Type { + // For a homomorphic mapped type { [P in keyof T]: X }, where T is some type variable, the mapping + // operation depends on T as follows: + // * If T is a primitive type no mapping is performed and the result is simply T. + // * If T is a union type we distribute the mapped type over the union. + // * If T is an array we map to an array where the element type has been transformed. + // * If T is a tuple we map to a tuple where the element types have been transformed. + // * Otherwise we map to an object type where the type of each property has been transformed. + // For example, when T is instantiated to a union type A | B, we produce { [P in keyof A]: X } | + // { [P in keyof B]: X }, and when when T is instantiated to a union type A | undefined, we produce + // { [P in keyof A]: X } | undefined. + const typeVariable = getHomomorphicTypeVariable(type); + if (typeVariable) { + const mappedTypeVariable = instantiateType(typeVariable, mapper); + if (typeVariable !== mappedTypeVariable) { + return mapType(mappedTypeVariable, t => { + if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && t !== errorType) { + const replacementMapper = createReplacementMapper(typeVariable, t, mapper); + return isArrayType(t) ? instantiateMappedArrayType(t, type, replacementMapper) : + isTupleType(t) ? instantiateMappedTupleType(t, type, replacementMapper) : + instantiateAnonymousType(type, replacementMapper); } - } - } - // Result is union of signatures collected (return type is union of return types of this signature set) - if (signatureList) { - return signatureList.length === 1 ? signatureList[0] : createUnionSignature(signatureList[0], signatureList); + return t; + }); } } - - function checkSpreadExpression(node: SpreadElement, checkMode?: CheckMode): Type { - if (languageVersion < ScriptTarget.ES2015) { - checkExternalEmitHelpers(node, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArrays); + return instantiateAnonymousType(type, mapper); + } + function getModifiedReadonlyState(state: boolean, modifiers: MappedTypeModifiers) { + return modifiers & MappedTypeModifiers.IncludeReadonly ? true : modifiers & MappedTypeModifiers.ExcludeReadonly ? false : state; + } + function instantiateMappedArrayType(arrayType: ts.Type, mappedType: MappedType, mapper: TypeMapper) { + const elementType = instantiateMappedTypeTemplate(mappedType, numberType, /*isOptional*/ true, mapper); + return elementType === errorType ? errorType : + createArrayType(elementType, getModifiedReadonlyState(isReadonlyArrayType(arrayType), getMappedTypeModifiers(mappedType))); + } + function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) { + const minLength = tupleType.target.minLength; + const elementTypes = map(getTypeArguments(tupleType), (_, i) => instantiateMappedTypeTemplate(mappedType, getLiteralType("" + i), i >= minLength, mapper)); + const modifiers = getMappedTypeModifiers(mappedType); + const newMinLength = modifiers & MappedTypeModifiers.IncludeOptional ? 0 : + modifiers & MappedTypeModifiers.ExcludeOptional ? getTypeReferenceArity(tupleType) - (tupleType.target.hasRestElement ? 1 : 0) : + minLength; + const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, modifiers); + return contains(elementTypes, errorType) ? errorType : + createTupleType(elementTypes, newMinLength, tupleType.target.hasRestElement, newReadonly, tupleType.target.associatedNames); + } + function instantiateMappedTypeTemplate(type: MappedType, key: ts.Type, isOptional: boolean, mapper: TypeMapper) { + const templateMapper = combineTypeMappers(mapper, createTypeMapper([getTypeParameterFromMappedType(type)], [key])); + const propType = instantiateType(getTemplateTypeFromMappedType((type.target) || type), templateMapper); + const modifiers = getMappedTypeModifiers(type); + return strictNullChecks && modifiers & MappedTypeModifiers.IncludeOptional && !isTypeAssignableTo(undefinedType, propType) ? getOptionalType(propType) : + strictNullChecks && modifiers & MappedTypeModifiers.ExcludeOptional && isOptional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) : + propType; + } + function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper): AnonymousType { + const result = (createObjectType(type.objectFlags | ObjectFlags.Instantiated, type.symbol)); + if (type.objectFlags & ObjectFlags.Mapped) { + (result).declaration = (type).declaration; + // C.f. instantiateSignature + const origTypeParameter = getTypeParameterFromMappedType((type)); + const freshTypeParameter = cloneTypeParameter(origTypeParameter); + (result).typeParameter = freshTypeParameter; + mapper = combineTypeMappers(makeUnaryTypeMapper(origTypeParameter, freshTypeParameter), mapper); + freshTypeParameter.mapper = mapper; + } + result.target = type; + result.mapper = mapper; + result.aliasSymbol = type.aliasSymbol; + result.aliasTypeArguments = instantiateTypes(type.aliasTypeArguments, mapper); + return result; + } + function getConditionalTypeInstantiation(type: ConditionalType, mapper: TypeMapper): ts.Type { + const root = type.root; + if (root.outerTypeParameters) { + // We are instantiating a conditional type that has one or more type parameters in scope. Apply the + // mapper to the type parameters to produce the effective list of type arguments, and compute the + // instantiation cache key from the type IDs of the type arguments. + const typeArguments = map(root.outerTypeParameters, mapper); + const id = getTypeListId(typeArguments); + let result = root.instantiations!.get(id); + if (!result) { + const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); + result = instantiateConditionalType(root, newMapper); + root.instantiations!.set(id, result); } - - const arrayOrIterableType = checkExpression(node.expression, checkMode); - return checkIteratedTypeOrElementType(IterationUse.Spread, arrayOrIterableType, undefinedType, node.expression); + return result; } - - function hasDefaultValue(node: BindingElement | Expression): boolean { - return (node.kind === SyntaxKind.BindingElement && !!(node).initializer) || - (node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.EqualsToken); + return type; + } + function instantiateConditionalType(root: ConditionalRoot, mapper: TypeMapper): ts.Type { + // Check if we have a conditional type where the check type is a naked type parameter. If so, + // the conditional type is distributive over union types and when T is instantiated to a union + // type A | B, we produce (A extends U ? X : Y) | (B extends U ? X : Y). + if (root.isDistributive) { + const checkType = (root.checkType); + const instantiatedType = mapper(checkType); + if (checkType !== instantiatedType && instantiatedType.flags & (TypeFlags.Union | TypeFlags.Never)) { + return mapType(instantiatedType, t => getConditionalType(root, createReplacementMapper(checkType, t, mapper))); + } + } + return getConditionalType(root, mapper); + } + function instantiateType(type: ts.Type, mapper: TypeMapper | undefined): ts.Type; + function instantiateType(type: ts.Type | undefined, mapper: TypeMapper | undefined): ts.Type | undefined; + function instantiateType(type: ts.Type | undefined, mapper: TypeMapper | undefined): ts.Type | undefined { + if (!type || !mapper || mapper === identityMapper) { + return type; } - - function checkArrayLiteral(node: ArrayLiteralExpression, checkMode: CheckMode | undefined, forceTuple: boolean | undefined): Type { - const elements = node.elements; - const elementCount = elements.length; - let hasNonEndingSpreadElement = false; - const elementTypes: Type[] = []; - const inDestructuringPattern = isAssignmentTarget(node); - const contextualType = getApparentTypeOfContextualType(node); - const inConstContext = isConstContext(node); - for (let index = 0; index < elementCount; index++) { - const e = elements[index]; - if (inDestructuringPattern && e.kind === SyntaxKind.SpreadElement) { - // Given the following situation: - // var c: {}; - // [...c] = ["", 0]; - // - // c is represented in the tree as a spread element in an array literal. - // But c really functions as a rest element, and its purpose is to provide - // a contextual type for the right hand side of the assignment. Therefore, - // instead of calling checkExpression on "...c", which will give an error - // if c is not iterable/array-like, we need to act as if we are trying to - // get the contextual element type from it. So we do something similar to - // getContextualTypeForElementExpression, which will crucially not error - // if there is no index type / iterated type. - const restArrayType = checkExpression((e).expression, checkMode, forceTuple); - const restElementType = getIndexTypeOfType(restArrayType, IndexKind.Number) || - getIteratedTypeOrElementType(IterationUse.Destructuring, restArrayType, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false); - if (restElementType) { - elementTypes.push(restElementType); - } - } - else { - const elementContextualType = getContextualTypeForElementExpression(contextualType, index); - const type = checkExpressionForMutableLocation(e, checkMode, elementContextualType, forceTuple); - elementTypes.push(type); - } - if (index < elementCount - 1 && e.kind === SyntaxKind.SpreadElement) { - hasNonEndingSpreadElement = true; - } - } - if (!hasNonEndingSpreadElement) { - const hasRestElement = elementCount > 0 && elements[elementCount - 1].kind === SyntaxKind.SpreadElement; - const minLength = elementCount - (hasRestElement ? 1 : 0); - // If array literal is actually a destructuring pattern, mark it as an implied type. We do this such - // that we get the same behavior for "var [x, y] = []" and "[x, y] = []". - let tupleResult; - if (inDestructuringPattern && minLength > 0) { - const type = cloneTypeReference(createTupleType(elementTypes, minLength, hasRestElement)); - type.pattern = node; - return type; - } - else if (tupleResult = getArrayLiteralTupleTypeIfApplicable(elementTypes, contextualType, hasRestElement, elementCount, inConstContext)) { - return createArrayLiteralType(tupleResult); - } - else if (forceTuple) { - return createArrayLiteralType(createTupleType(elementTypes, minLength, hasRestElement)); - } - } - return createArrayLiteralType(createArrayType(elementTypes.length ? - getUnionType(elementTypes, UnionReduction.Subtype) : - strictNullChecks ? implicitNeverType : undefinedWideningType, inConstContext)); + if (instantiationDepth === 50 || instantiationCount >= 5000000) { + // We have reached 50 recursive type instantiations and there is a very high likelyhood we're dealing + // with a combination of infinite generic types that perpetually generate new type identities. We stop + // the recursion here by yielding the error type. + error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); + return errorType; } - - function createArrayLiteralType(type: ObjectType) { - if (!(getObjectFlags(type) & ObjectFlags.Reference)) { - return type; - } - let literalType = (type).literalType; - if (!literalType) { - literalType = (type).literalType = cloneTypeReference(type); - literalType.objectFlags |= ObjectFlags.ArrayLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + instantiationCount++; + instantiationDepth++; + const result = instantiateTypeWorker(type, mapper); + instantiationDepth--; + return result; + } + function instantiateTypeWorker(type: ts.Type, mapper: TypeMapper): ts.Type { + const flags = type.flags; + if (flags & TypeFlags.TypeParameter) { + return mapper(type); + } + if (flags & TypeFlags.Object) { + const objectFlags = (type).objectFlags; + if (objectFlags & ObjectFlags.Anonymous) { + // If the anonymous type originates in a declaration of a function, method, class, or + // interface, in an object type literal, or in an object literal expression, we may need + // to instantiate the type because it might reference a type parameter. + return couldContainTypeVariables(type) ? + getObjectTypeInstantiation((type), mapper) : type; + } + if (objectFlags & ObjectFlags.Mapped) { + return getObjectTypeInstantiation((type), mapper); + } + if (objectFlags & ObjectFlags.Reference) { + if ((type).node) { + return getObjectTypeInstantiation((type), mapper); + } + const resolvedTypeArguments = (type).resolvedTypeArguments; + const newTypeArguments = instantiateTypes(resolvedTypeArguments, mapper); + return newTypeArguments !== resolvedTypeArguments ? createTypeReference((type).target, newTypeArguments) : type; } - return literalType; + return type; } - - function getArrayLiteralTupleTypeIfApplicable(elementTypes: Type[], contextualType: Type | undefined, hasRestElement: boolean, elementCount = elementTypes.length, readonly = false) { - // Infer a tuple type when the contextual type is or contains a tuple-like type - if (readonly || (contextualType && forEachType(contextualType, isTupleLikeType))) { - return createTupleType(elementTypes, elementCount - (hasRestElement ? 1 : 0), hasRestElement, readonly); - } + if (flags & TypeFlags.Union && !(flags & TypeFlags.Primitive)) { + const types = (type).types; + const newTypes = instantiateTypes(types, mapper); + return newTypes !== types ? getUnionType(newTypes, UnionReduction.Literal, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) : type; } - - function isNumericName(name: DeclarationName): boolean { - switch (name.kind) { - case SyntaxKind.ComputedPropertyName: - return isNumericComputedName(name); - case SyntaxKind.Identifier: - return isNumericLiteralName(name.escapedText); - case SyntaxKind.NumericLiteral: - case SyntaxKind.StringLiteral: - return isNumericLiteralName(name.text); - default: - return false; - } + if (flags & TypeFlags.Intersection) { + const types = (type).types; + const newTypes = instantiateTypes(types, mapper); + return newTypes !== types ? getIntersectionType(newTypes, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) : type; } - - function isNumericComputedName(name: ComputedPropertyName): boolean { - // It seems odd to consider an expression of type Any to result in a numeric name, - // but this behavior is consistent with checkIndexedAccess - return isTypeAssignableToKind(checkComputedPropertyName(name), TypeFlags.NumberLike); + if (flags & TypeFlags.Index) { + return getIndexType(instantiateType((type).type, mapper)); } - - function isInfinityOrNaNString(name: string | __String): boolean { - return name === "Infinity" || name === "-Infinity" || name === "NaN"; + if (flags & TypeFlags.IndexedAccess) { + return getIndexedAccessType(instantiateType((type).objectType, mapper), instantiateType((type).indexType, mapper)); } - - function isNumericLiteralName(name: string | __String) { - // The intent of numeric names is that - // - they are names with text in a numeric form, and that - // - setting properties/indexing with them is always equivalent to doing so with the numeric literal 'numLit', - // acquired by applying the abstract 'ToNumber' operation on the name's text. - // - // The subtlety is in the latter portion, as we cannot reliably say that anything that looks like a numeric literal is a numeric name. - // In fact, it is the case that the text of the name must be equal to 'ToString(numLit)' for this to hold. - // - // Consider the property name '"0xF00D"'. When one indexes with '0xF00D', they are actually indexing with the value of 'ToString(0xF00D)' - // according to the ECMAScript specification, so it is actually as if the user indexed with the string '"61453"'. - // Thus, the text of all numeric literals equivalent to '61543' such as '0xF00D', '0xf00D', '0170015', etc. are not valid numeric names - // because their 'ToString' representation is not equal to their original text. - // This is motivated by ECMA-262 sections 9.3.1, 9.8.1, 11.1.5, and 11.2.1. - // - // Here, we test whether 'ToString(ToNumber(name))' is exactly equal to 'name'. - // The '+' prefix operator is equivalent here to applying the abstract ToNumber operation. - // Applying the 'toString()' method on a number gives us the abstract ToString operation on a number. - // - // Note that this accepts the values 'Infinity', '-Infinity', and 'NaN', and that this is intentional. - // This is desired behavior, because when indexing with them as numeric entities, you are indexing - // with the strings '"Infinity"', '"-Infinity"', and '"NaN"' respectively. - return (+name).toString() === name; + if (flags & TypeFlags.Conditional) { + return getConditionalTypeInstantiation((type), combineTypeMappers((type).mapper, mapper)); } - - function checkComputedPropertyName(node: ComputedPropertyName): Type { - const links = getNodeLinks(node.expression); - if (!links.resolvedType) { - links.resolvedType = checkExpression(node.expression); - // This will allow types number, string, symbol or any. It will also allow enums, the unknown - // type, and any union of these types (like string | number). - if (links.resolvedType.flags & TypeFlags.Nullable || - !isTypeAssignableToKind(links.resolvedType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike) && - !isTypeAssignableTo(links.resolvedType, stringNumberSymbolType)) { - error(node, Diagnostics.A_computed_property_name_must_be_of_type_string_number_symbol_or_any); - } - else { - checkThatExpressionIsProperSymbolReference(node.expression, links.resolvedType, /*reportError*/ true); + if (flags & TypeFlags.Substitution) { + const maybeVariable = instantiateType((type).typeVariable, mapper); + if (maybeVariable.flags & TypeFlags.TypeVariable) { + return getSubstitutionType((maybeVariable as TypeVariable), instantiateType((type).substitute, mapper)); + } + else { + const sub = instantiateType((type).substitute, mapper); + if (sub.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(maybeVariable), getRestrictiveInstantiation(sub))) { + return maybeVariable; } + return sub; } - - return links.resolvedType; } - - function getObjectLiteralIndexInfo(node: ObjectLiteralExpression, offset: number, properties: Symbol[], kind: IndexKind): IndexInfo { - const propTypes: Type[] = []; - for (let i = 0; i < properties.length; i++) { - if (kind === IndexKind.String || isNumericName(node.properties[i + offset].name!)) { - propTypes.push(getTypeOfSymbol(properties[i])); - } + return type; + } + function getPermissiveInstantiation(type: ts.Type) { + return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type : + type.permissiveInstantiation || (type.permissiveInstantiation = instantiateType(type, permissiveMapper)); + } + function getRestrictiveInstantiation(type: ts.Type) { + if (type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never)) { + return type; + } + if (type.restrictiveInstantiation) { + return type.restrictiveInstantiation; + } + type.restrictiveInstantiation = instantiateType(type, restrictiveMapper); + // We set the following so we don't attempt to set the restrictive instance of a restrictive instance + // which is redundant - we'll produce new type identities, but all type params have already been mapped. + // This also gives us a way to detect restrictive instances upon comparisons and _disable_ the "distributeive constraint" + // assignability check for them, which is distinctly unsafe, as once you have a restrctive instance, all the type parameters + // are constrained to `unknown` and produce tons of false positives/negatives! + type.restrictiveInstantiation.restrictiveInstantiation = type.restrictiveInstantiation; + return type.restrictiveInstantiation; + } + function instantiateIndexInfo(info: IndexInfo | undefined, mapper: TypeMapper): IndexInfo | undefined { + return info && createIndexInfo(instantiateType(info.type, mapper), info.isReadonly, info.declaration); + } + // Returns true if the given expression contains (at any level of nesting) a function or arrow expression + // that is subject to contextual typing. + function isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean { + Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); + switch (node.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.FunctionDeclaration: // Function declarations can have context when annotated with a jsdoc @type + return isContextSensitiveFunctionLikeDeclaration((node)); + case SyntaxKind.ObjectLiteralExpression: + return some((node).properties, isContextSensitive); + case SyntaxKind.ArrayLiteralExpression: + return some((node).elements, isContextSensitive); + case SyntaxKind.ConditionalExpression: + return isContextSensitive((node).whenTrue) || + isContextSensitive((node).whenFalse); + case SyntaxKind.BinaryExpression: + return ((node).operatorToken.kind === SyntaxKind.BarBarToken || (node).operatorToken.kind === SyntaxKind.QuestionQuestionToken) && + (isContextSensitive((node).left) || isContextSensitive((node).right)); + case SyntaxKind.PropertyAssignment: + return isContextSensitive((node).initializer); + case SyntaxKind.ParenthesizedExpression: + return isContextSensitive((node).expression); + case SyntaxKind.JsxAttributes: + return some((node).properties, isContextSensitive) || isJsxOpeningElement(node.parent) && some(node.parent.parent.children, isContextSensitive); + case SyntaxKind.JsxAttribute: { + // If there is no initializer, JSX attribute has a boolean value of true which is not context sensitive. + const { initializer } = (node as JsxAttribute); + return !!initializer && isContextSensitive(initializer); + } + case SyntaxKind.JsxExpression: { + // It is possible to that node.expression is undefined (e.g
) + const { expression } = (node as JsxExpression); + return !!expression && isContextSensitive(expression); + } + } + return false; + } + function isContextSensitiveFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { + if (isFunctionDeclaration(node) && (!isInJSFile(node) || !getTypeForDeclarationFromJSDocComment(node))) { + return false; + } + // Functions with type parameters are not context sensitive. + if (node.typeParameters) { + return false; + } + // Functions with any parameters that lack type annotations are context sensitive. + if (some(node.parameters, p => !getEffectiveTypeAnnotationNode(p))) { + return true; + } + if (node.kind !== SyntaxKind.ArrowFunction) { + // If the first parameter is not an explicit 'this' parameter, then the function has + // an implicit 'this' parameter which is subject to contextual typing. + const parameter = firstOrUndefined(node.parameters); + if (!(parameter && parameterIsThisKeyword(parameter))) { + return true; } - const unionType = propTypes.length ? getUnionType(propTypes, UnionReduction.Subtype) : undefinedType; - return createIndexInfo(unionType, isConstContext(node)); } - - function getImmediateAliasedSymbol(symbol: Symbol): Symbol | undefined { - Debug.assert((symbol.flags & SymbolFlags.Alias) !== 0, "Should only get Alias here."); - const links = getSymbolLinks(symbol); - if (!links.immediateTarget) { - const node = getDeclarationOfAliasSymbol(symbol); - if (!node) return Debug.fail(); - links.immediateTarget = getTargetOfAliasDeclaration(node, /*dontRecursivelyResolve*/ true); + return hasContextSensitiveReturnExpression(node); + } + function hasContextSensitiveReturnExpression(node: FunctionLikeDeclaration) { + // TODO(anhans): A block should be context-sensitive if it has a context-sensitive return value. + return !!node.body && node.body.kind !== SyntaxKind.Block && isContextSensitive(node.body); + } + function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration { + return (isInJSFile(func) && isFunctionDeclaration(func) || isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) && + isContextSensitiveFunctionLikeDeclaration(func); + } + function getTypeWithoutSignatures(type: ts.Type): ts.Type { + if (type.flags & TypeFlags.Object) { + const resolved = resolveStructuredTypeMembers((type)); + if (resolved.constructSignatures.length || resolved.callSignatures.length) { + const result = createObjectType(ObjectFlags.Anonymous, type.symbol); + result.members = resolved.members; + result.properties = resolved.properties; + result.callSignatures = emptyArray; + result.constructSignatures = emptyArray; + return result; } - - return links.immediateTarget; } - - function checkObjectLiteral(node: ObjectLiteralExpression, checkMode?: CheckMode): Type { - const inDestructuringPattern = isAssignmentTarget(node); - // Grammar checking - checkGrammarObjectLiteralExpression(node, inDestructuringPattern); - - let propertiesTable: SymbolTable; - let propertiesArray: Symbol[] = []; - let spread: Type = emptyObjectType; - - const contextualType = getApparentTypeOfContextualType(node); - const contextualTypeHasPattern = contextualType && contextualType.pattern && - (contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern || contextualType.pattern.kind === SyntaxKind.ObjectLiteralExpression); - const inConstContext = isConstContext(node); - const checkFlags = inConstContext ? CheckFlags.Readonly : 0; - const isInJavascript = isInJSFile(node) && !isInJsonFile(node); - const enumTag = getJSDocEnumTag(node); - const isJSObjectLiteral = !contextualType && isInJavascript && !enumTag; - let objectFlags: ObjectFlags = freshObjectLiteralFlag; - let patternWithComputedProperties = false; - let hasComputedStringProperty = false; - let hasComputedNumberProperty = false; - propertiesTable = createSymbolTable(); - - let offset = 0; - for (let i = 0; i < node.properties.length; i++) { - const memberDecl = node.properties[i]; - let member = getSymbolOfNode(memberDecl); - const computedNameType = memberDecl.name && memberDecl.name.kind === SyntaxKind.ComputedPropertyName && !isWellKnownSymbolSyntactically(memberDecl.name.expression) ? - checkComputedPropertyName(memberDecl.name) : undefined; - if (memberDecl.kind === SyntaxKind.PropertyAssignment || - memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment || - isObjectLiteralMethod(memberDecl)) { - let type = memberDecl.kind === SyntaxKind.PropertyAssignment ? checkPropertyAssignment(memberDecl, checkMode) : - memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment ? checkExpressionForMutableLocation(memberDecl.name, checkMode) : - checkObjectLiteralMethod(memberDecl, checkMode); - if (isInJavascript) { - const jsDocType = getTypeForDeclarationFromJSDocComment(memberDecl); - if (jsDocType) { - checkTypeAssignableTo(type, jsDocType, memberDecl); - type = jsDocType; - } - else if (enumTag && enumTag.typeExpression) { - checkTypeAssignableTo(type, getTypeFromTypeNode(enumTag.typeExpression), memberDecl); - } - } - objectFlags |= getObjectFlags(type) & ObjectFlags.PropagatingFlags; - const nameType = computedNameType && isTypeUsableAsPropertyName(computedNameType) ? computedNameType : undefined; - const prop = nameType ? - createSymbol(SymbolFlags.Property | member.flags, getPropertyNameFromType(nameType), checkFlags | CheckFlags.Late) : - createSymbol(SymbolFlags.Property | member.flags, member.escapedName, checkFlags); - if (nameType) { - prop.nameType = nameType; - } - - if (inDestructuringPattern) { - // If object literal is an assignment pattern and if the assignment pattern specifies a default value - // for the property, make the property optional. - const isOptional = - (memberDecl.kind === SyntaxKind.PropertyAssignment && hasDefaultValue(memberDecl.initializer)) || - (memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment && memberDecl.objectAssignmentInitializer); - if (isOptional) { - prop.flags |= SymbolFlags.Optional; - } - } - else if (contextualTypeHasPattern && !(getObjectFlags(contextualType!) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) { - // If object literal is contextually typed by the implied type of a binding pattern, and if the - // binding pattern specifies a default value for the property, make the property optional. - const impliedProp = getPropertyOfType(contextualType!, member.escapedName); - if (impliedProp) { - prop.flags |= impliedProp.flags & SymbolFlags.Optional; - } - - else if (!compilerOptions.suppressExcessPropertyErrors && !getIndexInfoOfType(contextualType!, IndexKind.String)) { - error(memberDecl.name, Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, - symbolToString(member), typeToString(contextualType!)); - } - } - - prop.declarations = member.declarations; - prop.parent = member.parent; - if (member.valueDeclaration) { - prop.valueDeclaration = member.valueDeclaration; - } - - prop.type = type; - prop.target = member; - member = prop; - } - else if (memberDecl.kind === SyntaxKind.SpreadAssignment) { - if (languageVersion < ScriptTarget.ES2015) { - checkExternalEmitHelpers(memberDecl, ExternalEmitHelpers.Assign); - } - if (propertiesArray.length > 0) { - spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext); - propertiesArray = []; - propertiesTable = createSymbolTable(); - hasComputedStringProperty = false; - hasComputedNumberProperty = false; - } - const type = checkExpression(memberDecl.expression); - if (!isValidSpreadType(type)) { - error(memberDecl, Diagnostics.Spread_types_may_only_be_created_from_object_types); - return errorType; - } - spread = getSpreadType(spread, type, node.symbol, objectFlags, inConstContext); - offset = i + 1; - continue; + else if (type.flags & TypeFlags.Intersection) { + return getIntersectionType(map((type).types, getTypeWithoutSignatures)); + } + return type; + } + // TYPE CHECKING + function isTypeIdenticalTo(source: ts.Type, target: ts.Type): boolean { + return isTypeRelatedTo(source, target, identityRelation); + } + function compareTypesIdentical(source: ts.Type, target: ts.Type): Ternary { + return isTypeRelatedTo(source, target, identityRelation) ? Ternary.True : Ternary.False; + } + function compareTypesAssignable(source: ts.Type, target: ts.Type): Ternary { + return isTypeRelatedTo(source, target, assignableRelation) ? Ternary.True : Ternary.False; + } + function compareTypesSubtypeOf(source: ts.Type, target: ts.Type): Ternary { + return isTypeRelatedTo(source, target, subtypeRelation) ? Ternary.True : Ternary.False; + } + function isTypeSubtypeOf(source: ts.Type, target: ts.Type): boolean { + return isTypeRelatedTo(source, target, subtypeRelation); + } + function isTypeAssignableTo(source: ts.Type, target: ts.Type): boolean { + return isTypeRelatedTo(source, target, assignableRelation); + } + // An object type S is considered to be derived from an object type T if + // S is a union type and every constituent of S is derived from T, + // T is a union type and S is derived from at least one constituent of T, or + // S is a type variable with a base constraint that is derived from T, + // T is one of the global types Object and Function and S is a subtype of T, or + // T occurs directly or indirectly in an 'extends' clause of S. + // Note that this check ignores type parameters and only considers the + // inheritance hierarchy. + function isTypeDerivedFrom(source: ts.Type, target: ts.Type): boolean { + return source.flags & TypeFlags.Union ? every((source).types, t => isTypeDerivedFrom(t, target)) : + target.flags & TypeFlags.Union ? some((target).types, t => isTypeDerivedFrom(source, t)) : + source.flags & TypeFlags.InstantiableNonPrimitive ? isTypeDerivedFrom(getBaseConstraintOfType(source) || unknownType, target) : + target === globalObjectType ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) : + target === globalFunctionType ? !!(source.flags & TypeFlags.Object) && isFunctionObjectType((source as ObjectType)) : + hasBaseType(source, getTargetType(target)); + } + /** + * This is *not* a bi-directional relationship. + * If one needs to check both directions for comparability, use a second call to this function or 'checkTypeComparableTo'. + * + * A type S is comparable to a type T if some (but not necessarily all) of the possible values of S are also possible values of T. + * It is used to check following cases: + * - the types of the left and right sides of equality/inequality operators (`===`, `!==`, `==`, `!=`). + * - the types of `case` clause expressions and their respective `switch` expressions. + * - the type of an expression in a type assertion with the type being asserted. + */ + function isTypeComparableTo(source: ts.Type, target: ts.Type): boolean { + return isTypeRelatedTo(source, target, comparableRelation); + } + function areTypesComparable(type1: ts.Type, type2: ts.Type): boolean { + return isTypeComparableTo(type1, type2) || isTypeComparableTo(type2, type1); + } + function checkTypeAssignableTo(source: ts.Type, target: ts.Type, errorNode: Node | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined, errorOutputObject?: { + errors?: Diagnostic[]; + }): boolean { + return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain, errorOutputObject); + } + /** + * Like `checkTypeAssignableTo`, but if it would issue an error, instead performs structural comparisons of the types using the given expression node to + * attempt to issue more specific errors on, for example, specific object literal properties or tuple members. + */ + function checkTypeAssignableToAndOptionallyElaborate(source: ts.Type, target: ts.Type, errorNode: Node | undefined, expr: Expression | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean { + return checkTypeRelatedToAndOptionallyElaborate(source, target, assignableRelation, errorNode, expr, headMessage, containingMessageChain, /*errorOutputContainer*/ undefined); + } + function checkTypeRelatedToAndOptionallyElaborate(source: ts.Type, target: ts.Type, relation: ts.Map, errorNode: Node | undefined, expr: Expression | undefined, headMessage: DiagnosticMessage | undefined, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: { + errors?: Diagnostic[]; + skipLogging?: boolean; + } | undefined): boolean { + if (isTypeRelatedTo(source, target, relation)) + return true; + if (!errorNode || !elaborateError(expr, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)) { + return checkTypeRelatedTo(source, target, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer); + } + return false; + } + function isOrHasGenericConditional(type: ts.Type): boolean { + return !!(type.flags & TypeFlags.Conditional || (type.flags & TypeFlags.Intersection && some((type as IntersectionType).types, isOrHasGenericConditional))); + } + function elaborateError(node: Expression | undefined, source: ts.Type, target: ts.Type, relation: ts.Map, headMessage: DiagnosticMessage | undefined, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: { + errors?: Diagnostic[]; + skipLogging?: boolean; + } | undefined): boolean { + if (!node || isOrHasGenericConditional(target)) + return false; + if (!checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined) + && elaborateDidYouMeanToCallOrConstruct(node, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)) { + return true; + } + switch (node.kind) { + case SyntaxKind.JsxExpression: + case SyntaxKind.ParenthesizedExpression: + return elaborateError((node as ParenthesizedExpression | JsxExpression).expression, source, target, relation, headMessage, containingMessageChain, errorOutputContainer); + case SyntaxKind.BinaryExpression: + switch ((node as BinaryExpression).operatorToken.kind) { + case SyntaxKind.EqualsToken: + case SyntaxKind.CommaToken: + return elaborateError((node as BinaryExpression).right, source, target, relation, headMessage, containingMessageChain, errorOutputContainer); + } + break; + case SyntaxKind.ObjectLiteralExpression: + return elaborateObjectLiteral((node as ObjectLiteralExpression), source, target, relation, containingMessageChain, errorOutputContainer); + case SyntaxKind.ArrayLiteralExpression: + return elaborateArrayLiteral((node as ArrayLiteralExpression), source, target, relation, containingMessageChain, errorOutputContainer); + case SyntaxKind.JsxAttributes: + return elaborateJsxComponents((node as JsxAttributes), source, target, relation, containingMessageChain, errorOutputContainer); + case SyntaxKind.ArrowFunction: + return elaborateArrowFunction((node as ArrowFunction), source, target, relation, containingMessageChain, errorOutputContainer); + } + return false; + } + function elaborateDidYouMeanToCallOrConstruct(node: Expression, source: ts.Type, target: ts.Type, relation: ts.Map, headMessage: DiagnosticMessage | undefined, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: { + errors?: Diagnostic[]; + skipLogging?: boolean; + } | undefined): boolean { + const callSignatures = getSignaturesOfType(source, SignatureKind.Call); + const constructSignatures = getSignaturesOfType(source, SignatureKind.Construct); + for (const signatures of [constructSignatures, callSignatures]) { + if (some(signatures, s => { + const returnType = getReturnTypeOfSignature(s); + return !(returnType.flags & (TypeFlags.Any | TypeFlags.Never)) && checkTypeRelatedTo(returnType, target, relation, /*errorNode*/ undefined); + })) { + const resultObj: { + errors?: Diagnostic[]; + } = errorOutputContainer || {}; + checkTypeAssignableTo(source, target, node, headMessage, containingMessageChain, resultObj); + const diagnostic = resultObj.errors![resultObj.errors!.length - 1]; + addRelatedInfo(diagnostic, createDiagnosticForNode(node, signatures === constructSignatures ? Diagnostics.Did_you_mean_to_use_new_with_this_expression : Diagnostics.Did_you_mean_to_call_this_expression)); + return true; + } + } + return false; + } + function elaborateArrowFunction(node: ArrowFunction, source: ts.Type, target: ts.Type, relation: ts.Map, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: { + errors?: Diagnostic[]; + skipLogging?: boolean; + } | undefined): boolean { + // Don't elaborate blocks + if (isBlock(node.body)) { + return false; + } + // Or functions with annotated parameter types + if (some(node.parameters, ts.hasType)) { + return false; + } + const sourceSig = getSingleCallSignature(source); + if (!sourceSig) { + return false; + } + const targetSignatures = getSignaturesOfType(target, SignatureKind.Call); + if (!length(targetSignatures)) { + return false; + } + const returnExpression = node.body; + const sourceReturn = getReturnTypeOfSignature(sourceSig); + const targetReturn = getUnionType(map(targetSignatures, getReturnTypeOfSignature)); + if (!checkTypeRelatedTo(sourceReturn, targetReturn, relation, /*errorNode*/ undefined)) { + const elaborated = returnExpression && elaborateError(returnExpression, sourceReturn, targetReturn, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer); + if (elaborated) { + return elaborated; + } + const resultObj: { + errors?: Diagnostic[]; + } = errorOutputContainer || {}; + checkTypeRelatedTo(sourceReturn, targetReturn, relation, returnExpression, /*message*/ undefined, containingMessageChain, resultObj); + if (resultObj.errors) { + if (target.symbol && length(target.symbol.declarations)) { + addRelatedInfo(resultObj.errors[resultObj.errors.length - 1], createDiagnosticForNode(target.symbol.declarations[0], Diagnostics.The_expected_type_comes_from_the_return_type_of_this_signature)); } - else { - // TypeScript 1.0 spec (April 2014) - // A get accessor declaration is processed in the same manner as - // an ordinary function declaration(section 6.1) with no parameters. - // A set accessor declaration is processed in the same manner - // as an ordinary function declaration with a single parameter and a Void return type. - Debug.assert(memberDecl.kind === SyntaxKind.GetAccessor || memberDecl.kind === SyntaxKind.SetAccessor); - checkNodeDeferred(memberDecl); + return true; + } + } + return false; + } + type ElaborationIterator = IterableIterator<{ + errorNode: Node; + innerExpression: Expression | undefined; + nameType: ts.Type; + errorMessage?: DiagnosticMessage | undefined; + }>; + /** + * For every element returned from the iterator, checks that element to issue an error on a property of that element's type + * If that element would issue an error, we first attempt to dive into that element's inner expression and issue a more specific error by recuring into `elaborateError` + * Otherwise, we issue an error on _every_ element which fail the assignability check + */ + function elaborateElementwise(iterator: ElaborationIterator, source: ts.Type, target: ts.Type, relation: ts.Map, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: { + errors?: Diagnostic[]; + skipLogging?: boolean; + } | undefined) { + // Assignability failure - check each prop individually, and if that fails, fall back on the bad error span + let reportedError = false; + for (let status = iterator.next(); !status.done; status = iterator.next()) { + const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.value; + const targetPropType = getIndexedAccessTypeOrUndefined(target, nameType); + if (!targetPropType || targetPropType.flags & TypeFlags.IndexedAccess) + continue; // Don't elaborate on indexes on generic variables + const sourcePropType = getIndexedAccessTypeOrUndefined(source, nameType); + if (sourcePropType && !checkTypeRelatedTo(sourcePropType, targetPropType, relation, /*errorNode*/ undefined)) { + const elaborated = next && elaborateError(next, sourcePropType, targetPropType, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer); + if (elaborated) { + reportedError = true; } - - if (computedNameType && !(computedNameType.flags & TypeFlags.StringOrNumberLiteralOrUnique)) { - if (isTypeAssignableTo(computedNameType, stringNumberSymbolType)) { - if (isTypeAssignableTo(computedNameType, numberType)) { - hasComputedNumberProperty = true; - } - else { - hasComputedStringProperty = true; + else { + // Issue error on the prop itself, since the prop couldn't elaborate the error + const resultObj: { + errors?: Diagnostic[]; + } = errorOutputContainer || {}; + // Use the expression type, if available + const specificSource = next ? checkExpressionForMutableLocation(next, CheckMode.Normal, sourcePropType) : sourcePropType; + const result = checkTypeRelatedTo(specificSource, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj); + if (result && specificSource !== sourcePropType) { + // If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType + checkTypeRelatedTo(sourcePropType, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj); + } + if (resultObj.errors) { + const reportedDiag = resultObj.errors[resultObj.errors.length - 1]; + const propertyName = isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined; + const targetProp = propertyName !== undefined ? getPropertyOfType(target, propertyName) : undefined; + let issuedElaboration = false; + if (!targetProp) { + const indexInfo = isTypeAssignableToKind(nameType, TypeFlags.NumberLike) && getIndexInfoOfType(target, IndexKind.Number) || + getIndexInfoOfType(target, IndexKind.String) || + undefined; + if (indexInfo && indexInfo.declaration && !getSourceFileOfNode(indexInfo.declaration).hasNoDefaultLib) { + issuedElaboration = true; + addRelatedInfo(reportedDiag, createDiagnosticForNode(indexInfo.declaration, Diagnostics.The_expected_type_comes_from_this_index_signature)); + } } - if (inDestructuringPattern) { - patternWithComputedProperties = true; + if (!issuedElaboration && (targetProp && length(targetProp.declarations) || target.symbol && length(target.symbol.declarations))) { + const targetNode = targetProp && length(targetProp.declarations) ? targetProp.declarations[0] : target.symbol.declarations[0]; + if (!getSourceFileOfNode(targetNode).hasNoDefaultLib) { + addRelatedInfo(reportedDiag, createDiagnosticForNode(targetNode, Diagnostics.The_expected_type_comes_from_property_0_which_is_declared_here_on_type_1, propertyName && !(nameType.flags & TypeFlags.UniqueESSymbol) ? unescapeLeadingUnderscores(propertyName) : typeToString(nameType), typeToString(target))); + } } } + reportedError = true; } - else { - propertiesTable.set(member.escapedName, member); - } - propertiesArray.push(member); } - - // If object literal is contextually typed by the implied type of a binding pattern, augment the result - // type with those properties for which the binding pattern specifies a default value. - if (contextualTypeHasPattern) { - for (const prop of getPropertiesOfType(contextualType!)) { - if (!propertiesTable.get(prop.escapedName) && !(spread && getPropertyOfType(spread, prop.escapedName))) { - if (!(prop.flags & SymbolFlags.Optional)) { - error(prop.valueDeclaration || (prop).bindingElement, - Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value); - } - propertiesTable.set(prop.escapedName, prop); - propertiesArray.push(prop); - } - } + } + return reportedError; + } + function* generateJsxAttributes(node: JsxAttributes): ElaborationIterator { + if (!length(node.properties)) + return; + for (const prop of node.properties) { + if (isJsxSpreadAttribute(prop)) + continue; + yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getLiteralType(idText(prop.name)) }; + } + } + function* generateJsxChildren(node: JsxElement, getInvalidTextDiagnostic: () => DiagnosticMessage): ElaborationIterator { + if (!length(node.children)) + return; + let memberOffset = 0; + for (let i = 0; i < node.children.length; i++) { + const child = node.children[i]; + const nameType = getLiteralType(i - memberOffset); + const elem = getElaborationElementForJsxChild(child, nameType, getInvalidTextDiagnostic); + if (elem) { + yield elem; } - - if (spread !== emptyObjectType) { - if (propertiesArray.length > 0) { - spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext); - propertiesArray = []; - propertiesTable = createSymbolTable(); - hasComputedStringProperty = false; - hasComputedNumberProperty = false; - } - // remap the raw emptyObjectType fed in at the top into a fresh empty object literal type, unique to this use site - return mapType(spread, t => t === emptyObjectType ? createObjectLiteralType() : t); + else { + memberOffset++; } - - return createObjectLiteralType(); - - function createObjectLiteralType() { - const stringIndexInfo = hasComputedStringProperty ? getObjectLiteralIndexInfo(node, offset, propertiesArray, IndexKind.String) : undefined; - const numberIndexInfo = hasComputedNumberProperty ? getObjectLiteralIndexInfo(node, offset, propertiesArray, IndexKind.Number) : undefined; - const result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); - result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; - if (isJSObjectLiteral) { - result.objectFlags |= ObjectFlags.JSLiteral; - } - if (patternWithComputedProperties) { - result.objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties; - } - if (inDestructuringPattern) { - result.pattern = node; - } + } + } + function getElaborationElementForJsxChild(child: JsxChild, nameType: LiteralType, getInvalidTextDiagnostic: () => DiagnosticMessage) { + switch (child.kind) { + case SyntaxKind.JsxExpression: + // child is of the type of the expression + return { errorNode: child, innerExpression: child.expression, nameType }; + case SyntaxKind.JsxText: + if (child.containsOnlyTriviaWhiteSpaces) { + break; // Whitespace only jsx text isn't real jsx text + } + // child is a string + return { errorNode: child, innerExpression: undefined, nameType, errorMessage: getInvalidTextDiagnostic() }; + case SyntaxKind.JsxElement: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxFragment: + // child is of type JSX.Element + return { errorNode: child, innerExpression: child, nameType }; + default: + return Debug.assertNever(child, "Found invalid jsx child"); + } + } + function getSemanticJsxChildren(children: NodeArray) { + return filter(children, i => !isJsxText(i) || !i.containsOnlyTriviaWhiteSpaces); + } + function elaborateJsxComponents(node: JsxAttributes, source: ts.Type, target: ts.Type, relation: ts.Map, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: { + errors?: Diagnostic[]; + skipLogging?: boolean; + } | undefined) { + let result = elaborateElementwise(generateJsxAttributes(node), source, target, relation, containingMessageChain, errorOutputContainer); + let invalidTextDiagnostic: DiagnosticMessage | undefined; + if (isJsxOpeningElement(node.parent) && isJsxElement(node.parent.parent)) { + const containingElement = node.parent.parent; + const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); + const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName); + const childrenNameType = getLiteralType(childrenPropName); + const childrenTargetType = getIndexedAccessType(target, childrenNameType); + const validChildren = getSemanticJsxChildren(containingElement.children); + if (!length(validChildren)) { return result; } - } - - function isValidSpreadType(type: Type): boolean { - if (type.flags & TypeFlags.Instantiable) { - const constraint = getBaseConstraintOfType(type); - if (constraint !== undefined) { - return isValidSpreadType(constraint); + const moreThanOneRealChildren = length(validChildren) > 1; + const arrayLikeTargetParts = filterType(childrenTargetType, isArrayOrTupleLikeType); + const nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isArrayOrTupleLikeType(t)); + if (moreThanOneRealChildren) { + if (arrayLikeTargetParts !== neverType) { + const realSource = createTupleType(checkJsxChildren(containingElement, CheckMode.Normal)); + const children = generateJsxChildren(containingElement, getInvalidTextualChildDiagnostic); + result = elaborateElementwise(children, realSource, arrayLikeTargetParts, relation, containingMessageChain, errorOutputContainer) || result; + } + else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) { + // arity mismatch + result = true; + const diag = error(containingElement.openingElement.tagName, Diagnostics.This_JSX_tag_s_0_prop_expects_a_single_child_of_type_1_but_multiple_children_were_provided, childrenPropName, typeToString(childrenTargetType)); + if (errorOutputContainer && errorOutputContainer.skipLogging) { + (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); + } } - } - return !!(type.flags & (TypeFlags.Any | TypeFlags.NonPrimitive | TypeFlags.Object | TypeFlags.InstantiableNonPrimitive) || - getFalsyFlags(type) & TypeFlags.DefinitelyFalsy && isValidSpreadType(removeDefinitelyFalsyTypes(type)) || - type.flags & TypeFlags.UnionOrIntersection && every((type).types, isValidSpreadType)); - } - - function checkJsxSelfClosingElementDeferred(node: JsxSelfClosingElement) { - checkJsxOpeningLikeElementOrOpeningFragment(node); - } - - function checkJsxSelfClosingElement(node: JsxSelfClosingElement, _checkMode: CheckMode | undefined): Type { - checkNodeDeferred(node); - return getJsxElementTypeAt(node) || anyType; - } - - function checkJsxElementDeferred(node: JsxElement) { - // Check attributes - checkJsxOpeningLikeElementOrOpeningFragment(node.openingElement); - - // Perform resolution on the closing tag so that rename/go to definition/etc work - if (isJsxIntrinsicIdentifier(node.closingElement.tagName)) { - getIntrinsicTagSymbol(node.closingElement); } else { - checkExpression(node.closingElement.tagName); + if (nonArrayLikeTargetParts !== neverType) { + const child = validChildren[0]; + const elem = getElaborationElementForJsxChild(child, childrenNameType, getInvalidTextualChildDiagnostic); + if (elem) { + result = elaborateElementwise((function* () { yield elem; })(), source, target, relation, containingMessageChain, errorOutputContainer) || result; + } + } + else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) { + // arity mismatch + result = true; + const diag = error(containingElement.openingElement.tagName, Diagnostics.This_JSX_tag_s_0_prop_expects_type_1_which_requires_multiple_children_but_only_a_single_child_was_provided, childrenPropName, typeToString(childrenTargetType)); + if (errorOutputContainer && errorOutputContainer.skipLogging) { + (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); + } + } } - - checkJsxChildren(node); } - - function checkJsxElement(node: JsxElement, _checkMode: CheckMode | undefined): Type { - checkNodeDeferred(node); - - return getJsxElementTypeAt(node) || anyType; - } - - function checkJsxFragment(node: JsxFragment): Type { - checkJsxOpeningLikeElementOrOpeningFragment(node.openingFragment); - - if (compilerOptions.jsx === JsxEmit.React && (compilerOptions.jsxFactory || getSourceFileOfNode(node).pragmas.has("jsx"))) { - error(node, compilerOptions.jsxFactory - ? Diagnostics.JSX_fragment_is_not_supported_when_using_jsxFactory - : Diagnostics.JSX_fragment_is_not_supported_when_using_an_inline_JSX_factory_pragma); + return result; + function getInvalidTextualChildDiagnostic() { + if (!invalidTextDiagnostic) { + const tagNameText = getTextOfNode(node.parent.tagName); + const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); + const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName); + const childrenTargetType = getIndexedAccessType(target, getLiteralType(childrenPropName)); + const diagnostic = Diagnostics._0_components_don_t_accept_text_as_child_elements_Text_in_JSX_has_the_type_string_but_the_expected_type_of_1_is_2; + invalidTextDiagnostic = { ...diagnostic, key: "!!ALREADY FORMATTED!!", message: formatMessage(/*_dummy*/ undefined, diagnostic, tagNameText, childrenPropName, typeToString(childrenTargetType)) }; } - - checkJsxChildren(node); - return getJsxElementTypeAt(node) || anyType; + return invalidTextDiagnostic; } - - /** - * Returns true iff the JSX element name would be a valid JS identifier, ignoring restrictions about keywords not being identifiers - */ - function isUnhyphenatedJsxName(name: string | __String) { - // - is the only character supported in JSX attribute names that isn't valid in JavaScript identifiers - return !stringContains(name as string, "-"); + } + function* generateLimitedTupleElements(node: ArrayLiteralExpression, target: ts.Type): ElaborationIterator { + const len = length(node.elements); + if (!len) + return; + for (let i = 0; i < len; i++) { + // Skip elements which do not exist in the target - a length error on the tuple overall is likely better than an error on a mismatched index signature + if (isTupleLikeType(target) && !getPropertyOfType(target, (("" + i) as __String))) + continue; + const elem = node.elements[i]; + if (isOmittedExpression(elem)) + continue; + const nameType = getLiteralType(i); + yield { errorNode: elem, innerExpression: elem, nameType }; } - - /** - * Returns true iff React would emit this tag name as a string rather than an identifier or qualified name - */ - function isJsxIntrinsicIdentifier(tagName: JsxTagNameExpression): boolean { - return tagName.kind === SyntaxKind.Identifier && isIntrinsicJsxName(tagName.escapedText); + } + function elaborateArrayLiteral(node: ArrayLiteralExpression, source: ts.Type, target: ts.Type, relation: ts.Map, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: { + errors?: Diagnostic[]; + skipLogging?: boolean; + } | undefined) { + if (target.flags & TypeFlags.Primitive) + return false; + if (isTupleLikeType(source)) { + return elaborateElementwise(generateLimitedTupleElements(node, target), source, target, relation, containingMessageChain, errorOutputContainer); } - - function checkJsxAttribute(node: JsxAttribute, checkMode?: CheckMode) { - return node.initializer - ? checkExpressionForMutableLocation(node.initializer, checkMode) - : trueType; // is sugar for + // recreate a tuple from the elements, if possible + const tupleizedType = checkArrayLiteral(node, CheckMode.Contextual, /*forceTuple*/ true); + if (isTupleLikeType(tupleizedType)) { + return elaborateElementwise(generateLimitedTupleElements(node, target), tupleizedType, target, relation, containingMessageChain, errorOutputContainer); } - - /** - * Get attributes type of the JSX opening-like element. The result is from resolving "attributes" property of the opening-like element. - * - * @param openingLikeElement a JSX opening-like element - * @param filter a function to remove attributes that will not participate in checking whether attributes are assignable - * @return an anonymous type (similar to the one returned by checkObjectLiteral) in which its properties are attributes property. - * @remarks Because this function calls getSpreadType, it needs to use the same checks as checkObjectLiteral, - * which also calls getSpreadType. - */ - function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxOpeningLikeElement, checkMode: CheckMode | undefined) { - const attributes = openingLikeElement.attributes; - let attributesTable = createSymbolTable(); - let spread: Type = emptyJsxObjectType; - let hasSpreadAnyType = false; - let typeToIntersect: Type | undefined; - let explicitlySpecifyChildrenAttribute = false; - let objectFlags: ObjectFlags = ObjectFlags.JsxAttributes; - const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(openingLikeElement)); - - for (const attributeDecl of attributes.properties) { - const member = attributeDecl.symbol; - if (isJsxAttribute(attributeDecl)) { - const exprType = checkJsxAttribute(attributeDecl, checkMode); - objectFlags |= getObjectFlags(exprType) & ObjectFlags.PropagatingFlags; - - const attributeSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient | member.flags, member.escapedName); - attributeSymbol.declarations = member.declarations; - attributeSymbol.parent = member.parent; - if (member.valueDeclaration) { - attributeSymbol.valueDeclaration = member.valueDeclaration; - } - attributeSymbol.type = exprType; - attributeSymbol.target = member; - attributesTable.set(attributeSymbol.escapedName, attributeSymbol); - if (attributeDecl.name.escapedText === jsxChildrenPropertyName) { - explicitlySpecifyChildrenAttribute = true; - } - } - else { - Debug.assert(attributeDecl.kind === SyntaxKind.JsxSpreadAttribute); - if (attributesTable.size > 0) { - spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false); - attributesTable = createSymbolTable(); - } - const exprType = checkExpressionCached(attributeDecl.expression, checkMode); - if (isTypeAny(exprType)) { - hasSpreadAnyType = true; - } - if (isValidSpreadType(exprType)) { - spread = getSpreadType(spread, exprType, attributes.symbol, objectFlags, /*readonly*/ false); - } - else { - typeToIntersect = typeToIntersect ? getIntersectionType([typeToIntersect, exprType]) : exprType; + return false; + } + function* generateObjectLiteralElements(node: ObjectLiteralExpression): ElaborationIterator { + if (!length(node.properties)) + return; + for (const prop of node.properties) { + if (isSpreadAssignment(prop)) + continue; + const type = getLiteralTypeFromProperty(getSymbolOfNode(prop), TypeFlags.StringOrNumberLiteralOrUnique); + if (!type || (type.flags & TypeFlags.Never)) { + continue; + } + switch (prop.kind) { + case SyntaxKind.SetAccessor: + case SyntaxKind.GetAccessor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.ShorthandPropertyAssignment: + yield { errorNode: prop.name, innerExpression: undefined, nameType: type }; + break; + case SyntaxKind.PropertyAssignment: + yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: type, errorMessage: isComputedNonLiteralName(prop.name) ? Diagnostics.Type_of_computed_property_s_value_is_0_which_is_not_assignable_to_type_1 : undefined }; + break; + default: + Debug.assertNever(prop); + } + } + } + function elaborateObjectLiteral(node: ObjectLiteralExpression, source: ts.Type, target: ts.Type, relation: ts.Map, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: { + errors?: Diagnostic[]; + skipLogging?: boolean; + } | undefined) { + if (target.flags & TypeFlags.Primitive) + return false; + return elaborateElementwise(generateObjectLiteralElements(node), source, target, relation, containingMessageChain, errorOutputContainer); + } + /** + * This is *not* a bi-directional relationship. + * If one needs to check both directions for comparability, use a second call to this function or 'isTypeComparableTo'. + */ + function checkTypeComparableTo(source: ts.Type, target: ts.Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean { + return checkTypeRelatedTo(source, target, comparableRelation, errorNode, headMessage, containingMessageChain); + } + function isSignatureAssignableTo(source: ts.Signature, target: ts.Signature, ignoreReturnTypes: boolean): boolean { + return compareSignaturesRelated(source, target, CallbackCheck.None, ignoreReturnTypes, /*reportErrors*/ false, + /*errorReporter*/ undefined, /*errorReporter*/ undefined, compareTypesAssignable, /*reportUnreliableMarkers*/ undefined) !== Ternary.False; + } + type ErrorReporter = (message: DiagnosticMessage, arg0?: string, arg1?: string) => void; + /** + * Returns true if `s` is `(...args: any[]) => any` or `(this: any, ...args: any[]) => any` + */ + function isAnySignature(s: ts.Signature) { + return !s.typeParameters && (!s.thisParameter || isTypeAny(getTypeOfParameter(s.thisParameter))) && s.parameters.length === 1 && + signatureHasRestParameter(s) && (getTypeOfParameter(s.parameters[0]) === anyArrayType || isTypeAny(getTypeOfParameter(s.parameters[0]))) && + isTypeAny(getReturnTypeOfSignature(s)); + } + /** + * See signatureRelatedTo, compareSignaturesIdentical + */ + function compareSignaturesRelated(source: ts.Signature, target: ts.Signature, callbackCheck: CallbackCheck, ignoreReturnTypes: boolean, reportErrors: boolean, errorReporter: ErrorReporter | undefined, incompatibleErrorReporter: ((source: ts.Type, target: ts.Type) => void) | undefined, compareTypes: TypeComparer, reportUnreliableMarkers: TypeMapper | undefined): Ternary { + // TODO (drosen): De-duplicate code between related functions. + if (source === target) { + return Ternary.True; + } + if (isAnySignature(target)) { + return Ternary.True; + } + const targetCount = getParameterCount(target); + if (!hasEffectiveRestParameter(target) && getMinArgumentCount(source) > targetCount) { + return Ternary.False; + } + if (source.typeParameters && source.typeParameters !== target.typeParameters) { + target = getCanonicalSignature(target); + source = instantiateSignatureInContextOf(source, target, /*inferenceContext*/ undefined, compareTypes); + } + const sourceCount = getParameterCount(source); + const sourceRestType = getNonArrayRestType(source); + const targetRestType = getNonArrayRestType(target); + if (sourceRestType || targetRestType) { + void instantiateType(sourceRestType || targetRestType, reportUnreliableMarkers); + } + if (sourceRestType && targetRestType && sourceCount !== targetCount) { + // We're not able to relate misaligned complex rest parameters + return Ternary.False; + } + const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown; + const strictVariance = !callbackCheck && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration && + kind !== SyntaxKind.MethodSignature && kind !== SyntaxKind.Constructor; + let result = Ternary.True; + const sourceThisType = getThisTypeOfSignature(source); + if (sourceThisType && sourceThisType !== voidType) { + const targetThisType = getThisTypeOfSignature(target); + if (targetThisType) { + // void sources are assignable to anything. + const related = !strictVariance && compareTypes(sourceThisType, targetThisType, /*reportErrors*/ false) + || compareTypes(targetThisType, sourceThisType, reportErrors); + if (!related) { + if (reportErrors) { + errorReporter!(Diagnostics.The_this_types_of_each_signature_are_incompatible); } + return Ternary.False; } + result &= related; } - - if (!hasSpreadAnyType) { - if (attributesTable.size > 0) { - spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false); + } + const paramCount = sourceRestType || targetRestType ? Math.min(sourceCount, targetCount) : Math.max(sourceCount, targetCount); + const restIndex = sourceRestType || targetRestType ? paramCount - 1 : -1; + for (let i = 0; i < paramCount; i++) { + const sourceType = i === restIndex ? getRestTypeAtPosition(source, i) : getTypeAtPosition(source, i); + const targetType = i === restIndex ? getRestTypeAtPosition(target, i) : getTypeAtPosition(target, i); + // In order to ensure that any generic type Foo is at least co-variant with respect to T no matter + // how Foo uses T, we need to relate parameters bi-variantly (given that parameters are input positions, + // they naturally relate only contra-variantly). However, if the source and target parameters both have + // function types with a single call signature, we know we are relating two callback parameters. In + // that case it is sufficient to only relate the parameters of the signatures co-variantly because, + // similar to return values, callback parameters are output positions. This means that a Promise, + // where T is used only in callback parameter positions, will be co-variant (as opposed to bi-variant) + // with respect to T. + const sourceSig = callbackCheck ? undefined : getSingleCallSignature(getNonNullableType(sourceType)); + const targetSig = callbackCheck ? undefined : getSingleCallSignature(getNonNullableType(targetType)); + const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) && + (getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable); + const related = callbacks ? + // TODO: GH#18217 It will work if they're both `undefined`, but not if only one is + compareSignaturesRelated(targetSig!, sourceSig!, strictVariance ? CallbackCheck.Strict : CallbackCheck.Bivariant, /*ignoreReturnTypes*/ false, reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) : + !callbackCheck && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors); + if (!related) { + if (reportErrors) { + errorReporter!(Diagnostics.Types_of_parameters_0_and_1_are_incompatible, unescapeLeadingUnderscores(getParameterNameAtPosition(source, i)), unescapeLeadingUnderscores(getParameterNameAtPosition(target, i))); } + return Ternary.False; } - - // Handle children attribute - const parent = openingLikeElement.parent.kind === SyntaxKind.JsxElement ? openingLikeElement.parent as JsxElement : undefined; - // We have to check that openingElement of the parent is the one we are visiting as this may not be true for selfClosingElement - if (parent && parent.openingElement === openingLikeElement && parent.children.length > 0) { - const childrenTypes: Type[] = checkJsxChildren(parent, checkMode); - - if (!hasSpreadAnyType && jsxChildrenPropertyName && jsxChildrenPropertyName !== "") { - // Error if there is a attribute named "children" explicitly specified and children element. - // This is because children element will overwrite the value from attributes. - // Note: we will not warn "children" attribute overwritten if "children" attribute is specified in object spread. - if (explicitlySpecifyChildrenAttribute) { - error(attributes, Diagnostics._0_are_specified_twice_The_attribute_named_0_will_be_overwritten, unescapeLeadingUnderscores(jsxChildrenPropertyName)); + result &= related; + } + if (!ignoreReturnTypes) { + // If a signature resolution is already in-flight, skip issuing a circularity error + // here and just use the `any` type directly + const targetReturnType = isResolvingReturnTypeOfSignature(target) ? anyType + : target.declaration && isJSConstructor(target.declaration) ? getDeclaredTypeOfClassOrInterface(getMergedSymbol(target.declaration.symbol)) + : getReturnTypeOfSignature(target); + if (targetReturnType === voidType) { + return result; + } + const sourceReturnType = isResolvingReturnTypeOfSignature(source) ? anyType + : source.declaration && isJSConstructor(source.declaration) ? getDeclaredTypeOfClassOrInterface(getMergedSymbol(source.declaration.symbol)) + : getReturnTypeOfSignature(source); + // The following block preserves behavior forbidding boolean returning functions from being assignable to type guard returning functions + const targetTypePredicate = getTypePredicateOfSignature(target); + if (targetTypePredicate) { + const sourceTypePredicate = getTypePredicateOfSignature(source); + if (sourceTypePredicate) { + result &= compareTypePredicateRelatedTo(sourceTypePredicate, targetTypePredicate, reportErrors, errorReporter, compareTypes); + } + else if (isIdentifierTypePredicate(targetTypePredicate)) { + if (reportErrors) { + errorReporter!(Diagnostics.Signature_0_must_be_a_type_predicate, signatureToString(source)); } - - const contextualType = getApparentTypeOfContextualType(openingLikeElement.attributes); - const childrenContextualType = contextualType && getTypeOfPropertyOfContextualType(contextualType, jsxChildrenPropertyName); - // If there are children in the body of JSX element, create dummy attribute "children" with the union of children types so that it will pass the attribute checking process - const childrenPropSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, jsxChildrenPropertyName); - childrenPropSymbol.type = childrenTypes.length === 1 ? - childrenTypes[0] : - (getArrayLiteralTupleTypeIfApplicable(childrenTypes, childrenContextualType, /*hasRestElement*/ false) || createArrayType(getUnionType(childrenTypes))); - // Fake up a property declaration for the children - childrenPropSymbol.valueDeclaration = createPropertySignature(/*modifiers*/ undefined, unescapeLeadingUnderscores(jsxChildrenPropertyName), /*questionToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined); - childrenPropSymbol.valueDeclaration.parent = attributes; - childrenPropSymbol.valueDeclaration.symbol = childrenPropSymbol; - const childPropMap = createSymbolTable(); - childPropMap.set(jsxChildrenPropertyName, childrenPropSymbol); - spread = getSpreadType(spread, createAnonymousType(attributes.symbol, childPropMap, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined), - attributes.symbol, objectFlags, /*readonly*/ false); - + return Ternary.False; } } - - if (hasSpreadAnyType) { - return anyType; - } - if (typeToIntersect && spread !== emptyJsxObjectType) { - return getIntersectionType([typeToIntersect, spread]); + else { + // When relating callback signatures, we still need to relate return types bi-variantly as otherwise + // the containing type wouldn't be co-variant. For example, interface Foo { add(cb: () => T): void } + // wouldn't be co-variant for T without this rule. + result &= callbackCheck === CallbackCheck.Bivariant && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) || + compareTypes(sourceReturnType, targetReturnType, reportErrors); + if (!result && reportErrors && incompatibleErrorReporter) { + incompatibleErrorReporter(sourceReturnType, targetReturnType); + } } - return typeToIntersect || (spread === emptyJsxObjectType ? createJsxAttributesType() : spread); - - /** - * Create anonymous type from given attributes symbol table. - * @param symbol a symbol of JsxAttributes containing attributes corresponding to attributesTable - * @param attributesTable a symbol table of attributes property - */ - function createJsxAttributesType() { - objectFlags |= freshObjectLiteralFlag; - const result = createAnonymousType(attributes.symbol, attributesTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined); - result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; - return result; + } + return result; + } + function compareTypePredicateRelatedTo(source: TypePredicate, target: TypePredicate, reportErrors: boolean, errorReporter: ErrorReporter | undefined, compareTypes: (s: ts.Type, t: ts.Type, reportErrors?: boolean) => Ternary): Ternary { + if (source.kind !== target.kind) { + if (reportErrors) { + errorReporter!(Diagnostics.A_this_based_type_guard_is_not_compatible_with_a_parameter_based_type_guard); + errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); } + return Ternary.False; } - - function checkJsxChildren(node: JsxElement | JsxFragment, checkMode?: CheckMode) { - const childrenTypes: Type[] = []; - for (const child of node.children) { - // In React, JSX text that contains only whitespaces will be ignored so we don't want to type-check that - // because then type of children property will have constituent of string type. - if (child.kind === SyntaxKind.JsxText) { - if (!child.containsOnlyTriviaWhiteSpaces) { - childrenTypes.push(stringType); - } - } - else { - childrenTypes.push(checkExpressionForMutableLocation(child, checkMode)); + if (source.kind === TypePredicateKind.Identifier || source.kind === TypePredicateKind.AssertsIdentifier) { + if (source.parameterIndex !== (target as IdentifierTypePredicate).parameterIndex) { + if (reportErrors) { + errorReporter!(Diagnostics.Parameter_0_is_not_in_the_same_position_as_parameter_1, source.parameterName, (target as IdentifierTypePredicate).parameterName); + errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); } + return Ternary.False; } - return childrenTypes; } - - /** - * Check attributes property of opening-like element. This function is called during chooseOverload to get call signature of a JSX opening-like element. - * (See "checkApplicableSignatureForJsxOpeningLikeElement" for how the function is used) - * @param node a JSXAttributes to be resolved of its type - */ - function checkJsxAttributes(node: JsxAttributes, checkMode: CheckMode | undefined) { - return createJsxAttributesTypeFromAttributesProperty(node.parent, checkMode); + const related = source.type === target.type ? Ternary.True : + source.type && target.type ? compareTypes(source.type, target.type, reportErrors) : + Ternary.False; + if (related === Ternary.False && reportErrors) { + errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); } - - function getJsxType(name: __String, location: Node | undefined) { - const namespace = getJsxNamespaceAt(location); - const exports = namespace && getExportsOfSymbol(namespace); - const typeSymbol = exports && getSymbol(exports, name, SymbolFlags.Type); - return typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType; + return related; + } + function isImplementationCompatibleWithOverload(implementation: ts.Signature, overload: ts.Signature): boolean { + const erasedSource = getErasedSignature(implementation); + const erasedTarget = getErasedSignature(overload); + // First see if the return types are compatible in either direction. + const sourceReturnType = getReturnTypeOfSignature(erasedSource); + const targetReturnType = getReturnTypeOfSignature(erasedTarget); + if (targetReturnType === voidType + || isTypeRelatedTo(targetReturnType, sourceReturnType, assignableRelation) + || isTypeRelatedTo(sourceReturnType, targetReturnType, assignableRelation)) { + return isSignatureAssignableTo(erasedSource, erasedTarget, /*ignoreReturnTypes*/ true); + } + return false; + } + function isEmptyResolvedType(t: ResolvedType) { + return t.properties.length === 0 && + t.callSignatures.length === 0 && + t.constructSignatures.length === 0 && + !t.stringIndexInfo && + !t.numberIndexInfo; + } + function isEmptyObjectType(type: ts.Type): boolean { + return type.flags & TypeFlags.Object ? !isGenericMappedType(type) && isEmptyResolvedType(resolveStructuredTypeMembers((type))) : + type.flags & TypeFlags.NonPrimitive ? true : + type.flags & TypeFlags.Union ? some((type).types, isEmptyObjectType) : + type.flags & TypeFlags.Intersection ? every((type).types, isEmptyObjectType) : + false; + } + function isEmptyAnonymousObjectType(type: ts.Type) { + return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && isEmptyObjectType(type); + } + function isStringIndexSignatureOnlyType(type: ts.Type): boolean { + return type.flags & TypeFlags.Object && !isGenericMappedType(type) && getPropertiesOfType(type).length === 0 && getIndexInfoOfType(type, IndexKind.String) && !getIndexInfoOfType(type, IndexKind.Number) || + type.flags & TypeFlags.UnionOrIntersection && every((type).types, isStringIndexSignatureOnlyType) || + false; + } + function isEnumTypeRelatedTo(sourceSymbol: ts.Symbol, targetSymbol: ts.Symbol, errorReporter?: ErrorReporter) { + if (sourceSymbol === targetSymbol) { + return true; } - - /** - * Looks up an intrinsic tag name and returns a symbol that either points to an intrinsic - * property (in which case nodeLinks.jsxFlags will be IntrinsicNamedElement) or an intrinsic - * string index signature (in which case nodeLinks.jsxFlags will be IntrinsicIndexedElement). - * May also return unknownSymbol if both of these lookups fail. - */ - function getIntrinsicTagSymbol(node: JsxOpeningLikeElement | JsxClosingElement): Symbol { - const links = getNodeLinks(node); - if (!links.resolvedSymbol) { - const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, node); - if (intrinsicElementsType !== errorType) { - // Property case - if (!isIdentifier(node.tagName)) return Debug.fail(); - const intrinsicProp = getPropertyOfType(intrinsicElementsType, node.tagName.escapedText); - if (intrinsicProp) { - links.jsxFlags |= JsxFlags.IntrinsicNamedElement; - return links.resolvedSymbol = intrinsicProp; - } - - // Intrinsic string indexer case - const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, IndexKind.String); - if (indexSignatureType) { - links.jsxFlags |= JsxFlags.IntrinsicIndexedElement; - return links.resolvedSymbol = intrinsicElementsType.symbol; + const id = getSymbolId(sourceSymbol) + "," + getSymbolId(targetSymbol); + const entry = enumRelation.get(id); + if (entry !== undefined && !(!(entry & RelationComparisonResult.Reported) && entry & RelationComparisonResult.Failed && errorReporter)) { + return !!(entry & RelationComparisonResult.Succeeded); + } + if (sourceSymbol.escapedName !== targetSymbol.escapedName || !(sourceSymbol.flags & SymbolFlags.RegularEnum) || !(targetSymbol.flags & SymbolFlags.RegularEnum)) { + enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); + return false; + } + const targetEnumType = getTypeOfSymbol(targetSymbol); + for (const property of getPropertiesOfType(getTypeOfSymbol(sourceSymbol))) { + if (property.flags & SymbolFlags.EnumMember) { + const targetProperty = getPropertyOfType(targetEnumType, property.escapedName); + if (!targetProperty || !(targetProperty.flags & SymbolFlags.EnumMember)) { + if (errorReporter) { + errorReporter(Diagnostics.Property_0_is_missing_in_type_1, symbolName(property), typeToString(getDeclaredTypeOfSymbol(targetSymbol), /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType)); + enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); } - - // Wasn't found - error(node, Diagnostics.Property_0_does_not_exist_on_type_1, idText(node.tagName), "JSX." + JsxNames.IntrinsicElements); - return links.resolvedSymbol = unknownSymbol; - } - else { - if (noImplicitAny) { - error(node, Diagnostics.JSX_element_implicitly_has_type_any_because_no_interface_JSX_0_exists, unescapeLeadingUnderscores(JsxNames.IntrinsicElements)); + else { + enumRelation.set(id, RelationComparisonResult.Failed); } - return links.resolvedSymbol = unknownSymbol; + return false; } } - return links.resolvedSymbol; } - - function getJsxNamespaceAt(location: Node | undefined): Symbol { - const links = location && getNodeLinks(location); - if (links && links.jsxNamespace) { - return links.jsxNamespace; - } - if (!links || links.jsxNamespace !== false) { - const namespaceName = getJsxNamespace(location); - const resolvedNamespace = resolveName(location, namespaceName, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined, namespaceName, /*isUse*/ false); - if (resolvedNamespace) { - const candidate = resolveSymbol(getSymbol(getExportsOfSymbol(resolveSymbol(resolvedNamespace)), JsxNames.JSX, SymbolFlags.Namespace)); - if (candidate) { - if (links) { - links.jsxNamespace = candidate; - } - return candidate; - } - if (links) { - links.jsxNamespace = false; - } - } + enumRelation.set(id, RelationComparisonResult.Succeeded); + return true; + } + function isSimpleTypeRelatedTo(source: ts.Type, target: ts.Type, relation: ts.Map, errorReporter?: ErrorReporter) { + const s = source.flags; + const t = target.flags; + if (t & TypeFlags.AnyOrUnknown || s & TypeFlags.Never || source === wildcardType) + return true; + if (t & TypeFlags.Never) + return false; + if (s & TypeFlags.StringLike && t & TypeFlags.String) + return true; + if (s & TypeFlags.StringLiteral && s & TypeFlags.EnumLiteral && + t & TypeFlags.StringLiteral && !(t & TypeFlags.EnumLiteral) && + (source).value === (target).value) + return true; + if (s & TypeFlags.NumberLike && t & TypeFlags.Number) + return true; + if (s & TypeFlags.NumberLiteral && s & TypeFlags.EnumLiteral && + t & TypeFlags.NumberLiteral && !(t & TypeFlags.EnumLiteral) && + (source).value === (target).value) + return true; + if (s & TypeFlags.BigIntLike && t & TypeFlags.BigInt) + return true; + if (s & TypeFlags.BooleanLike && t & TypeFlags.Boolean) + return true; + if (s & TypeFlags.ESSymbolLike && t & TypeFlags.ESSymbol) + return true; + if (s & TypeFlags.Enum && t & TypeFlags.Enum && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) + return true; + if (s & TypeFlags.EnumLiteral && t & TypeFlags.EnumLiteral) { + if (s & TypeFlags.Union && t & TypeFlags.Union && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) + return true; + if (s & TypeFlags.Literal && t & TypeFlags.Literal && + (source).value === (target).value && + isEnumTypeRelatedTo(getParentOfSymbol(source.symbol)!, getParentOfSymbol(target.symbol)!, errorReporter)) + return true; + } + if (s & TypeFlags.Undefined && (!strictNullChecks || t & (TypeFlags.Undefined | TypeFlags.Void))) + return true; + if (s & TypeFlags.Null && (!strictNullChecks || t & TypeFlags.Null)) + return true; + if (s & TypeFlags.Object && t & TypeFlags.NonPrimitive) + return true; + if (relation === assignableRelation || relation === comparableRelation) { + if (s & TypeFlags.Any) + return true; + // Type number or any numeric literal type is assignable to any numeric enum type or any + // numeric enum literal type. This rule exists for backwards compatibility reasons because + // bit-flag enum types sometimes look like literal enum types with numeric literal values. + if (s & (TypeFlags.Number | TypeFlags.NumberLiteral) && !(s & TypeFlags.EnumLiteral) && (t & TypeFlags.Enum || t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral)) + return true; + } + return false; + } + function isTypeRelatedTo(source: ts.Type, target: ts.Type, relation: ts.Map) { + if (isFreshLiteralType(source)) { + source = (source).regularType; + } + if (isFreshLiteralType(target)) { + target = (target).regularType; + } + if (source === target || + relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || + relation !== identityRelation && isSimpleTypeRelatedTo(source, target, relation)) { + return true; + } + if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { + const related = relation.get(getRelationKey(source, target, /*isIntersectionConstituent*/ false, relation)); + if (related !== undefined) { + return !!(related & RelationComparisonResult.Succeeded); } - // JSX global fallback - return getGlobalSymbol(JsxNames.JSX, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined)!; // TODO: GH#18217 } - - /** - * Look into JSX namespace and then look for container with matching name as nameOfAttribPropContainer. - * Get a single property from that container if existed. Report an error if there are more than one property. - * - * @param nameOfAttribPropContainer a string of value JsxNames.ElementAttributesPropertyNameContainer or JsxNames.ElementChildrenAttributeNameContainer - * if other string is given or the container doesn't exist, return undefined. - */ - function getNameFromJsxElementAttributesContainer(nameOfAttribPropContainer: __String, jsxNamespace: Symbol): __String | undefined { - // JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute [symbol] - const jsxElementAttribPropInterfaceSym = jsxNamespace && getSymbol(jsxNamespace.exports!, nameOfAttribPropContainer, SymbolFlags.Type); - // JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute [type] - const jsxElementAttribPropInterfaceType = jsxElementAttribPropInterfaceSym && getDeclaredTypeOfSymbol(jsxElementAttribPropInterfaceSym); - // The properties of JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute - const propertiesOfJsxElementAttribPropInterface = jsxElementAttribPropInterfaceType && getPropertiesOfType(jsxElementAttribPropInterfaceType); - if (propertiesOfJsxElementAttribPropInterface) { - // Element Attributes has zero properties, so the element attributes type will be the class instance type - if (propertiesOfJsxElementAttribPropInterface.length === 0) { - return "" as __String; - } - // Element Attributes has one property, so the element attributes type will be the type of the corresponding - // property of the class instance type - else if (propertiesOfJsxElementAttribPropInterface.length === 1) { - return propertiesOfJsxElementAttribPropInterface[0].escapedName; - } - else if (propertiesOfJsxElementAttribPropInterface.length > 1) { - // More than one property on ElementAttributesProperty is an error - error(jsxElementAttribPropInterfaceSym!.declarations[0], Diagnostics.The_global_type_JSX_0_may_not_have_more_than_one_property, unescapeLeadingUnderscores(nameOfAttribPropContainer)); - } + if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) { + return checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined); + } + return false; + } + function isIgnoredJsxProperty(source: ts.Type, sourceProp: ts.Symbol) { + return getObjectFlags(source) & ObjectFlags.JsxAttributes && !isUnhyphenatedJsxName(sourceProp.escapedName); + } + function getNormalizedType(type: ts.Type, writing: boolean): ts.Type { + return isFreshLiteralType(type) ? (type).regularType : + getObjectFlags(type) & ObjectFlags.Reference && (type).node ? createTypeReference((type).target, getTypeArguments((type))) : + type.flags & TypeFlags.Substitution ? writing ? (type).typeVariable : (type).substitute : + type.flags & TypeFlags.Simplifiable ? getSimplifiedType(type, writing) : + type; + } + /** + * Checks if 'source' is related to 'target' (e.g.: is a assignable to). + * @param source The left-hand-side of the relation. + * @param target The right-hand-side of the relation. + * @param relation The relation considered. One of 'identityRelation', 'subtypeRelation', 'assignableRelation', or 'comparableRelation'. + * Used as both to determine which checks are performed and as a cache of previously computed results. + * @param errorNode The suggested node upon which all errors will be reported, if defined. This may or may not be the actual node used. + * @param headMessage If the error chain should be prepended by a head message, then headMessage will be used. + * @param containingMessageChain A chain of errors to prepend any new errors found. + * @param errorOutputContainer Return the diagnostic. Do not log if 'skipLogging' is truthy. + */ + function checkTypeRelatedTo(source: ts.Type, target: ts.Type, relation: ts.Map, errorNode: Node | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined, errorOutputContainer?: { + errors?: Diagnostic[]; + skipLogging?: boolean; + }): boolean { + let errorInfo: DiagnosticMessageChain | undefined; + let relatedInfo: [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined; + let maybeKeys: string[]; + let sourceStack: ts.Type[]; + let targetStack: ts.Type[]; + let maybeCount = 0; + let depth = 0; + let expandingFlags = ExpandingFlags.None; + let overflow = false; + let overrideNextErrorInfo = 0; // How many `reportRelationError` calls should be skipped in the elaboration pyramid + let lastSkippedInfo: [ts.Type, ts.Type] | undefined; + let incompatibleStack: [DiagnosticMessage, (string | number)?, (string | number)?, (string | number)?, (string | number)?][] = []; + Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking"); + const result = isRelatedTo(source, target, /*reportErrors*/ !!errorNode, headMessage); + if (incompatibleStack.length) { + reportIncompatibleStack(); + } + if (overflow) { + const diag = error(errorNode, Diagnostics.Excessive_stack_depth_comparing_types_0_and_1, typeToString(source), typeToString(target)); + if (errorOutputContainer) { + (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); + } + } + else if (errorInfo) { + if (containingMessageChain) { + const chain = containingMessageChain(); + if (chain) { + concatenateDiagnosticMessageChains(chain, errorInfo); + errorInfo = chain; + } + } + let relatedInformation: DiagnosticRelatedInformation[] | undefined; + // Check if we should issue an extra diagnostic to produce a quickfix for a slightly incorrect import statement + if (headMessage && errorNode && !result && source.symbol) { + const links = getSymbolLinks(source.symbol); + if (links.originatingImport && !isImportCall(links.originatingImport)) { + const helpfulRetry = checkTypeRelatedTo(getTypeOfSymbol(links.target!), target, relation, /*errorNode*/ undefined); + if (helpfulRetry) { + // Likely an incorrect import. Issue a helpful diagnostic to produce a quickfix to change the import + const diag = createDiagnosticForNode(links.originatingImport, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead); + relatedInformation = append(relatedInformation, diag); // Cause the error to appear with the error that triggered it + } + } + } + const diag = createDiagnosticForNodeFromMessageChain((errorNode!), errorInfo, relatedInformation); + if (relatedInfo) { + addRelatedInfo(diag, ...relatedInfo); + } + if (errorOutputContainer) { + (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); + } + if (!errorOutputContainer || !errorOutputContainer.skipLogging) { + diagnostics.add(diag); } - return undefined; } - - function getJsxLibraryManagedAttributes(jsxNamespace: Symbol) { - // JSX.LibraryManagedAttributes [symbol] - return jsxNamespace && getSymbol(jsxNamespace.exports!, JsxNames.LibraryManagedAttributes, SymbolFlags.Type); + if (errorNode && errorOutputContainer && errorOutputContainer.skipLogging && result === Ternary.False) { + Debug.assert(!!errorOutputContainer.errors, "missed opportunity to interact with error."); } - - /// e.g. "props" for React.d.ts, - /// or 'undefined' if ElementAttributesProperty doesn't exist (which means all - /// non-intrinsic elements' attributes type is 'any'), - /// or '' if it has 0 properties (which means every - /// non-intrinsic elements' attributes type is the element instance type) - function getJsxElementPropertiesName(jsxNamespace: Symbol) { - return getNameFromJsxElementAttributesContainer(JsxNames.ElementAttributesPropertyNameContainer, jsxNamespace); + return result !== Ternary.False; + function resetErrorInfo(saved: ReturnType) { + errorInfo = saved.errorInfo; + lastSkippedInfo = saved.lastSkippedInfo; + incompatibleStack = saved.incompatibleStack; + overrideNextErrorInfo = saved.overrideNextErrorInfo; + relatedInfo = saved.relatedInfo; } - - function getJsxElementChildrenPropertyName(jsxNamespace: Symbol): __String | undefined { - return getNameFromJsxElementAttributesContainer(JsxNames.ElementChildrenAttributeNameContainer, jsxNamespace); + function captureErrorCalculationState() { + return { + errorInfo, + lastSkippedInfo, + incompatibleStack: incompatibleStack.slice(), + overrideNextErrorInfo, + relatedInfo: !relatedInfo ? undefined : relatedInfo.slice() as ([DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined) + }; } - - function getUninstantiatedJsxSignaturesOfType(elementType: Type, caller: JsxOpeningLikeElement): readonly Signature[] { - if (elementType.flags & TypeFlags.String) { - return [anySignature]; - } - else if (elementType.flags & TypeFlags.StringLiteral) { - const intrinsicType = getIntrinsicAttributesTypeFromStringLiteralType(elementType as StringLiteralType, caller); - if (!intrinsicType) { - error(caller, Diagnostics.Property_0_does_not_exist_on_type_1, (elementType as StringLiteralType).value, "JSX." + JsxNames.IntrinsicElements); - return emptyArray; + function reportIncompatibleError(message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number) { + overrideNextErrorInfo++; // Suppress the next relation error + lastSkippedInfo = undefined; // Reset skipped info cache + incompatibleStack.push([message, arg0, arg1, arg2, arg3]); + } + function reportIncompatibleStack() { + const stack = incompatibleStack; + incompatibleStack = []; + const info = lastSkippedInfo; + lastSkippedInfo = undefined; + if (stack.length === 1) { + reportError(...stack[0]); + if (info) { + // Actually do the last relation error + reportRelationError(/*headMessage*/ undefined, ...info); } - else { - const fakeSignature = createSignatureForJSXIntrinsic(caller, intrinsicType); - return [fakeSignature]; + return; + } + // The first error will be the innermost, while the last will be the outermost - so by popping off the end, + // we can build from left to right + let path = ""; + const secondaryRootErrors: typeof incompatibleStack = []; + while (stack.length) { + const [msg, ...args] = stack.pop()!; + switch (msg.code) { + case Diagnostics.Types_of_property_0_are_incompatible.code: { + // Parenthesize a `new` if there is one + if (path.indexOf("new ") === 0) { + path = `(${path})`; + } + const str = "" + args[0]; + // If leading, just print back the arg (irrespective of if it's a valid identifier) + if (path.length === 0) { + path = `${str}`; + } + // Otherwise write a dotted name if possible + else if (isIdentifierText(str, compilerOptions.target)) { + path = `${path}.${str}`; + } + // Failing that, check if the name is already a computed name + else if (str[0] === "[" && str[str.length - 1] === "]") { + path = `${path}${str}`; + } + // And finally write out a computed name as a last resort + else { + path = `${path}[${str}]`; + } + break; + } + case Diagnostics.Call_signature_return_types_0_and_1_are_incompatible.code: + case Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible.code: + case Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code: + case Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code: { + if (path.length === 0) { + // Don't flatten signature compatability errors at the start of a chain - instead prefer + // to unify (the with no arguments bit is excessive for printback) and print them back + let mappedMsg = msg; + if (msg.code === Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) { + mappedMsg = Diagnostics.Call_signature_return_types_0_and_1_are_incompatible; + } + else if (msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) { + mappedMsg = Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible; + } + secondaryRootErrors.unshift([mappedMsg, args[0], args[1]]); + } + else { + const prefix = (msg.code === Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible.code || + msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) + ? "new " + : ""; + const params = (msg.code === Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code || + msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) + ? "" + : "..."; + path = `${prefix}${path}(${params})`; + } + break; + } + default: + return Debug.fail(`Unhandled Diagnostic: ${msg.code}`); } } - const apparentElemType = getApparentType(elementType); - // Resolve the signatures, preferring constructor - let signatures = getSignaturesOfType(apparentElemType, SignatureKind.Construct); - if (signatures.length === 0) { - // No construct signatures, try call signatures - signatures = getSignaturesOfType(apparentElemType, SignatureKind.Call); + if (path) { + reportError(path[path.length - 1] === ")" + ? Diagnostics.The_types_returned_by_0_are_incompatible_between_these_types + : Diagnostics.The_types_of_0_are_incompatible_between_these_types, path); } - if (signatures.length === 0 && apparentElemType.flags & TypeFlags.Union) { - // If each member has some combination of new/call signatures; make a union signature list for those - signatures = getUnionSignatures(map((apparentElemType as UnionType).types, t => getUninstantiatedJsxSignaturesOfType(t, caller))); + else { + // Remove the innermost secondary error as it will duplicate the error already reported by `reportRelationError` on entry + secondaryRootErrors.shift(); } - return signatures; - } - - function getIntrinsicAttributesTypeFromStringLiteralType(type: StringLiteralType, location: Node): Type | undefined { - // If the elemType is a stringLiteral type, we can then provide a check to make sure that the string literal type is one of the Jsx intrinsic element type - // For example: - // var CustomTag: "h1" = "h1"; - // Hello World - const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, location); - if (intrinsicElementsType !== errorType) { - const stringLiteralTypeName = type.value; - const intrinsicProp = getPropertyOfType(intrinsicElementsType, escapeLeadingUnderscores(stringLiteralTypeName)); - if (intrinsicProp) { - return getTypeOfSymbol(intrinsicProp); - } - const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, IndexKind.String); - if (indexSignatureType) { - return indexSignatureType; - } - return undefined; + for (const [msg, ...args] of secondaryRootErrors) { + const originalValue = msg.elidedInCompatabilityPyramid; + msg.elidedInCompatabilityPyramid = false; // Teporarily override elision to ensure error is reported + reportError(msg, ...args); + msg.elidedInCompatabilityPyramid = originalValue; } - // If we need to report an error, we already done so here. So just return any to prevent any more error downstream - return anyType; - } - - function checkJsxReturnAssignableToAppropriateBound(refKind: JsxReferenceKind, elemInstanceType: Type, openingLikeElement: Node) { - if (refKind === JsxReferenceKind.Function) { - const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement); - if (sfcReturnConstraint) { - checkTypeRelatedTo(elemInstanceType, sfcReturnConstraint, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements); - } + if (info) { + // Actually do the last relation error + reportRelationError(/*headMessage*/ undefined, ...info); } - else if (refKind === JsxReferenceKind.Component) { - const classConstraint = getJsxElementClassTypeAt(openingLikeElement); - if (classConstraint) { - // Issue an error if this return type isn't assignable to JSX.ElementClass or JSX.Element, failing that - checkTypeRelatedTo(elemInstanceType, classConstraint, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements); - } + } + function reportError(message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void { + Debug.assert(!!errorNode); + if (incompatibleStack.length) + reportIncompatibleStack(); + if (message.elidedInCompatabilityPyramid) + return; + errorInfo = chainDiagnosticMessages(errorInfo, message, arg0, arg1, arg2, arg3); + } + function associateRelatedInfo(info: DiagnosticRelatedInformation) { + Debug.assert(!!errorInfo); + if (!relatedInfo) { + relatedInfo = [info]; } - else { // Mixed - const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement); - const classConstraint = getJsxElementClassTypeAt(openingLikeElement); - if (!sfcReturnConstraint || !classConstraint) { - return; - } - const combined = getUnionType([sfcReturnConstraint, classConstraint]); - checkTypeRelatedTo(elemInstanceType, combined, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements); + else { + relatedInfo.push(info); } } - - /** - * Get attributes type of the given intrinsic opening-like Jsx element by resolving the tag name. - * The function is intended to be called from a function which has checked that the opening element is an intrinsic element. - * @param node an intrinsic JSX opening-like element - */ - function getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node: JsxOpeningLikeElement): Type { - Debug.assert(isJsxIntrinsicIdentifier(node.tagName)); - const links = getNodeLinks(node); - if (!links.resolvedJsxElementAttributesType) { - const symbol = getIntrinsicTagSymbol(node); - if (links.jsxFlags & JsxFlags.IntrinsicNamedElement) { - return links.resolvedJsxElementAttributesType = getTypeOfSymbol(symbol); + function reportRelationError(message: DiagnosticMessage | undefined, source: ts.Type, target: ts.Type) { + if (incompatibleStack.length) + reportIncompatibleStack(); + const [sourceType, targetType] = getTypeNamesForErrorDisplay(source, target); + if (target.flags & TypeFlags.TypeParameter && target.immediateBaseConstraint !== undefined && isTypeAssignableTo(source, target.immediateBaseConstraint)) { + reportError(Diagnostics._0_is_assignable_to_the_constraint_of_type_1_but_1_could_be_instantiated_with_a_different_subtype_of_constraint_2, sourceType, targetType, typeToString(target.immediateBaseConstraint)); + } + if (!message) { + if (relation === comparableRelation) { + message = Diagnostics.Type_0_is_not_comparable_to_type_1; } - else if (links.jsxFlags & JsxFlags.IntrinsicIndexedElement) { - return links.resolvedJsxElementAttributesType = - getIndexTypeOfType(getDeclaredTypeOfSymbol(symbol), IndexKind.String)!; + else if (sourceType === targetType) { + message = Diagnostics.Type_0_is_not_assignable_to_type_1_Two_different_types_with_this_name_exist_but_they_are_unrelated; } else { - return links.resolvedJsxElementAttributesType = errorType; + message = Diagnostics.Type_0_is_not_assignable_to_type_1; } } - return links.resolvedJsxElementAttributesType; - } - - function getJsxElementClassTypeAt(location: Node): Type | undefined { - const type = getJsxType(JsxNames.ElementClass, location); - if (type === errorType) return undefined; - return type; - } - - function getJsxElementTypeAt(location: Node): Type { - return getJsxType(JsxNames.Element, location); + reportError(message, sourceType, targetType); } - - function getJsxStatelessElementTypeAt(location: Node): Type | undefined { - const jsxElementType = getJsxElementTypeAt(location); - if (jsxElementType) { - return getUnionType([jsxElementType, nullType]); + function tryElaborateErrorsForPrimitivesAndObjects(source: ts.Type, target: ts.Type) { + const sourceType = symbolValueDeclarationIsContextSensitive(source.symbol) ? typeToString(source, source.symbol.valueDeclaration) : typeToString(source); + const targetType = symbolValueDeclarationIsContextSensitive(target.symbol) ? typeToString(target, target.symbol.valueDeclaration) : typeToString(target); + if ((globalStringType === source && stringType === target) || + (globalNumberType === source && numberType === target) || + (globalBooleanType === source && booleanType === target) || + (getGlobalESSymbolType(/*reportErrors*/ false) === source && esSymbolType === target)) { + reportError(Diagnostics._0_is_a_primitive_but_1_is_a_wrapper_object_Prefer_using_0_when_possible, targetType, sourceType); } } - /** - * Returns all the properties of the Jsx.IntrinsicElements interface + * Try and elaborate array and tuple errors. Returns false + * if we have found an elaboration, or we should ignore + * any other elaborations when relating the `source` and + * `target` types. */ - function getJsxIntrinsicTagNamesAt(location: Node): Symbol[] { - const intrinsics = getJsxType(JsxNames.IntrinsicElements, location); - return intrinsics ? getPropertiesOfType(intrinsics) : emptyArray; - } - - function checkJsxPreconditions(errorNode: Node) { - // Preconditions for using JSX - if ((compilerOptions.jsx || JsxEmit.None) === JsxEmit.None) { - error(errorNode, Diagnostics.Cannot_use_JSX_unless_the_jsx_flag_is_provided); - } - - if (getJsxElementTypeAt(errorNode) === undefined) { - if (noImplicitAny) { - error(errorNode, Diagnostics.JSX_element_implicitly_has_type_any_because_the_global_type_JSX_Element_does_not_exist); + function tryElaborateArrayLikeErrors(source: ts.Type, target: ts.Type, reportErrors: boolean): boolean { + /** + * The spec for elaboration is: + * - If the source is a readonly tuple and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations. + * - If the source is a tuple then skip property elaborations if the target is an array or tuple. + * - If the source is a readonly array and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations. + * - If the source an array then skip property elaborations if the target is a tuple. + */ + if (isTupleType(source)) { + if (source.target.readonly && isMutableArrayOrTuple(target)) { + if (reportErrors) { + reportError(Diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, typeToString(source), typeToString(target)); + } + return false; } + return isTupleType(target) || isArrayType(target); } - } - - function checkJsxOpeningLikeElementOrOpeningFragment(node: JsxOpeningLikeElement | JsxOpeningFragment) { - const isNodeOpeningLikeElement = isJsxOpeningLikeElement(node); - - if (isNodeOpeningLikeElement) { - checkGrammarJsxElement(node); - } - checkJsxPreconditions(node); - // The reactNamespace/jsxFactory's root symbol should be marked as 'used' so we don't incorrectly elide its import. - // And if there is no reactNamespace/jsxFactory's symbol in scope when targeting React emit, we should issue an error. - const reactRefErr = diagnostics && compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined; - const reactNamespace = getJsxNamespace(node); - const reactLocation = isNodeOpeningLikeElement ? (node).tagName : node; - const reactSym = resolveName(reactLocation, reactNamespace, SymbolFlags.Value, reactRefErr, reactNamespace, /*isUse*/ true); - if (reactSym) { - // Mark local symbol as referenced here because it might not have been marked - // if jsx emit was not react as there wont be error being emitted - reactSym.isReferenced = SymbolFlags.All; - - // If react symbol is alias, mark it as refereced - if (reactSym.flags & SymbolFlags.Alias) { - markAliasSymbolAsReferenced(reactSym); + if (isReadonlyArrayType(source) && isMutableArrayOrTuple(target)) { + if (reportErrors) { + reportError(Diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, typeToString(source), typeToString(target)); } + return false; } - - if (isNodeOpeningLikeElement) { - const sig = getResolvedSignature(node as JsxOpeningLikeElement); - checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(node as JsxOpeningLikeElement), getReturnTypeOfSignature(sig), node); + if (isTupleType(target)) { + return isArrayType(source); } + return true; } - /** - * Check if a property with the given name is known anywhere in the given type. In an object type, a property - * is considered known if - * 1. the object type is empty and the check is for assignability, or - * 2. if the object type has index signatures, or - * 3. if the property is actually declared in the object type - * (this means that 'toString', for example, is not usually a known property). - * 4. In a union or intersection type, - * a property is considered known if it is known in any constituent type. - * @param targetType a type to search a given name in - * @param name a property name to search - * @param isComparingJsxAttributes a boolean flag indicating whether we are searching in JsxAttributesType + * Compare two types and return + * * Ternary.True if they are related with no assumptions, + * * Ternary.Maybe if they are related with assumptions of other relationships, or + * * Ternary.False if they are not related. */ - function isKnownProperty(targetType: Type, name: __String, isComparingJsxAttributes: boolean): boolean { - if (targetType.flags & TypeFlags.Object) { - const resolved = resolveStructuredTypeMembers(targetType as ObjectType); - if (resolved.stringIndexInfo || - resolved.numberIndexInfo && isNumericLiteralName(name) || - getPropertyOfObjectType(targetType, name) || - isComparingJsxAttributes && !isUnhyphenatedJsxName(name)) { - // For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known. - return true; - } + function isRelatedTo(originalSource: ts.Type, originalTarget: ts.Type, reportErrors = false, headMessage?: DiagnosticMessage, isApparentIntersectionConstituent?: boolean): Ternary { + // Normalize the source and target types: Turn fresh literal types into regular literal types, + // turn deferred type references into regular type references, simplify indexed access and + // conditional types, and resolve substitution types to either the substitution (on the source + // side) or the type variable (on the target side). + let source = getNormalizedType(originalSource, /*writing*/ false); + let target = getNormalizedType(originalTarget, /*writing*/ true); + // Try to see if we're relating something like `Foo` -> `Bar | null | undefined`. + // If so, reporting the `null` and `undefined` in the type is hardly useful. + // First, see if we're even relating an object type to a union. + // Then see if the target is stripped down to a single non-union type. + // Note + // * We actually want to remove null and undefined naively here (rather than using getNonNullableType), + // since we don't want to end up with a worse error like "`Foo` is not assignable to `NonNullable`" + // when dealing with generics. + // * We also don't deal with primitive source types, since we already halt elaboration below. + if (target.flags & TypeFlags.Union && source.flags & TypeFlags.Object && + (target as UnionType).types.length <= 3 && maybeTypeOfKind(target, TypeFlags.Nullable)) { + const nullStrippedTarget = extractTypesOfKind(target, ~TypeFlags.Nullable); + if (!(nullStrippedTarget.flags & (TypeFlags.Union | TypeFlags.Never))) { + target = nullStrippedTarget; + } + } + // both types are the same - covers 'they are the same primitive type or both are Any' or the same type parameter cases + if (source === target) + return Ternary.True; + if (relation === identityRelation) { + return isIdenticalTo(source, target); } - else if (targetType.flags & TypeFlags.UnionOrIntersection && isExcessPropertyCheckTarget(targetType)) { - for (const t of (targetType as UnionOrIntersectionType).types) { - if (isKnownProperty(t, name, isComparingJsxAttributes)) { - return true; + if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || + isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) + return Ternary.True; + const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); + const isPerformingExcessPropertyChecks = !isApparentIntersectionConstituent && (isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral); + if (isPerformingExcessPropertyChecks) { + if (hasExcessProperties((source), target, reportErrors)) { + if (reportErrors) { + reportRelationError(headMessage, source, target); } + return Ternary.False; } } - return false; - } - - function isExcessPropertyCheckTarget(type: Type): boolean { - return !!(type.flags & TypeFlags.Object && !(getObjectFlags(type) & ObjectFlags.ObjectLiteralPatternWithComputedProperties) || - type.flags & TypeFlags.NonPrimitive || - type.flags & TypeFlags.Union && some((type).types, isExcessPropertyCheckTarget) || - type.flags & TypeFlags.Intersection && every((type).types, isExcessPropertyCheckTarget)); - } - - function checkJsxExpression(node: JsxExpression, checkMode?: CheckMode) { - checkGrammarJsxExpression(node); - if (node.expression) { - const type = checkExpression(node.expression, checkMode); - if (node.dotDotDotToken && type !== anyType && !isArrayType(type)) { - error(node, Diagnostics.JSX_spread_child_must_be_an_array_type); + const isPerformingCommonPropertyChecks = relation !== comparableRelation && !isApparentIntersectionConstituent && + source.flags & (TypeFlags.Primitive | TypeFlags.Object | TypeFlags.Intersection) && source !== globalObjectType && + target.flags & (TypeFlags.Object | TypeFlags.Intersection) && isWeakType(target) && + (getPropertiesOfType(source).length > 0 || typeHasCallOrConstructSignatures(source)); + if (isPerformingCommonPropertyChecks && !hasCommonProperties(source, target, isComparingJsxAttributes)) { + if (reportErrors) { + const calls = getSignaturesOfType(source, SignatureKind.Call); + const constructs = getSignaturesOfType(source, SignatureKind.Construct); + if (calls.length > 0 && isRelatedTo(getReturnTypeOfSignature(calls[0]), target, /*reportErrors*/ false) || + constructs.length > 0 && isRelatedTo(getReturnTypeOfSignature(constructs[0]), target, /*reportErrors*/ false)) { + reportError(Diagnostics.Value_of_type_0_has_no_properties_in_common_with_type_1_Did_you_mean_to_call_it, typeToString(source), typeToString(target)); + } + else { + reportError(Diagnostics.Type_0_has_no_properties_in_common_with_type_1, typeToString(source), typeToString(target)); + } } - return type; - } - else { - return errorType; - } - } - - function getDeclarationNodeFlagsFromSymbol(s: Symbol): NodeFlags { - return s.valueDeclaration ? getCombinedNodeFlags(s.valueDeclaration) : 0; - } - - /** - * Return whether this symbol is a member of a prototype somewhere - * Note that this is not tracked well within the compiler, so the answer may be incorrect. - */ - function isPrototypeProperty(symbol: Symbol) { - if (symbol.flags & SymbolFlags.Method || getCheckFlags(symbol) & CheckFlags.SyntheticMethod) { - return true; - } - if (isInJSFile(symbol.valueDeclaration)) { - const parent = symbol.valueDeclaration.parent; - return parent && isBinaryExpression(parent) && - getAssignmentDeclarationKind(parent) === AssignmentDeclarationKind.PrototypeProperty; + return Ternary.False; } - } - - /** - * Check whether the requested property access is valid. - * Returns true if node is a valid property access, and false otherwise. - * @param node The node to be checked. - * @param isSuper True if the access is from `super.`. - * @param type The type of the object whose property is being accessed. (Not the type of the property.) - * @param prop The symbol for the property being accessed. - */ - function checkPropertyAccessibility( - node: PropertyAccessExpression | QualifiedName | PropertyAccessExpression | VariableDeclaration | ParameterDeclaration | ImportTypeNode | PropertyAssignment | ShorthandPropertyAssignment | BindingElement, - isSuper: boolean, type: Type, prop: Symbol): boolean { - const flags = getDeclarationModifierFlagsFromSymbol(prop); - const errorNode = node.kind === SyntaxKind.QualifiedName ? node.right : node.kind === SyntaxKind.ImportType ? node : node.name; - - if (getCheckFlags(prop) & CheckFlags.ContainsPrivate) { - // Synthetic property with private constituent property - error(errorNode, Diagnostics.Property_0_has_conflicting_declarations_and_is_inaccessible_in_type_1, symbolToString(prop), typeToString(type)); - return false; + let result = Ternary.False; + const saveErrorInfo = captureErrorCalculationState(); + let isIntersectionConstituent = !!isApparentIntersectionConstituent; + // Note that these checks are specifically ordered to produce correct results. In particular, + // we need to deconstruct unions before intersections (because unions are always at the top), + // and we need to handle "each" relations before "some" relations for the same kind of type. + if (source.flags & TypeFlags.Union) { + result = relation === comparableRelation ? + someTypeRelatedToType((source as UnionType), target, reportErrors && !(source.flags & TypeFlags.Primitive)) : + eachTypeRelatedToType((source as UnionType), target, reportErrors && !(source.flags & TypeFlags.Primitive)); } - - if (isSuper) { - // TS 1.0 spec (April 2014): 4.8.2 - // - In a constructor, instance member function, instance member accessor, or - // instance member variable initializer where this references a derived class instance, - // a super property access is permitted and must specify a public instance member function of the base class. - // - In a static member function or static member accessor - // where this references the constructor function object of a derived class, - // a super property access is permitted and must specify a public static member function of the base class. - if (languageVersion < ScriptTarget.ES2015) { - if (symbolHasNonMethodDeclaration(prop)) { - error(errorNode, Diagnostics.Only_public_and_protected_methods_of_the_base_class_are_accessible_via_the_super_keyword); - return false; + else { + if (target.flags & TypeFlags.Union) { + result = typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), (target), reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive)); + } + else if (target.flags & TypeFlags.Intersection) { + isIntersectionConstituent = true; // set here to affect the following trio of checks + result = typeRelatedToEachType(getRegularTypeOfObjectLiteral(source), (target as IntersectionType), reportErrors); + if (result && (isPerformingExcessPropertyChecks || isPerformingCommonPropertyChecks)) { + // Validate against excess props using the original `source` + if (!propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, /*isIntersectionConstituent*/ false)) { + return Ternary.False; + } } } - if (flags & ModifierFlags.Abstract) { - // A method cannot be accessed in a super property access if the method is abstract. - // This error could mask a private property access error. But, a member - // cannot simultaneously be private and abstract, so this will trigger an - // additional error elsewhere. - error(errorNode, Diagnostics.Abstract_method_0_in_class_1_cannot_be_accessed_via_super_expression, symbolToString(prop), typeToString(getDeclaringClass(prop)!)); - return false; + else if (source.flags & TypeFlags.Intersection) { + // Check to see if any constituents of the intersection are immediately related to the target. + // + // Don't report errors though. Checking whether a constituent is related to the source is not actually + // useful and leads to some confusing error messages. Instead it is better to let the below checks + // take care of this, or to not elaborate at all. For instance, + // + // - For an object type (such as 'C = A & B'), users are usually more interested in structural errors. + // + // - For a union type (such as '(A | B) = (C & D)'), it's better to hold onto the whole intersection + // than to report that 'D' is not assignable to 'A' or 'B'. + // + // - For a primitive type or type parameter (such as 'number = A & B') there is no point in + // breaking the intersection apart. + result = someTypeRelatedToType((source), target, /*reportErrors*/ false); } - } - - // Referencing abstract properties within their own constructors is not allowed - if ((flags & ModifierFlags.Abstract) && isThisProperty(node) && symbolHasNonMethodDeclaration(prop)) { - const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)!); - if (declaringClassDeclaration && isNodeUsedDuringClassInitialization(node)) { - error(errorNode, Diagnostics.Abstract_property_0_in_class_1_cannot_be_accessed_in_the_constructor, symbolToString(prop), getTextOfIdentifierOrLiteral(declaringClassDeclaration.name!)); // TODO: GH#18217 - return false; + if (!result && (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable)) { + if (result = recursiveTypeRelatedTo(source, target, reportErrors, isIntersectionConstituent)) { + resetErrorInfo(saveErrorInfo); + } } } - - // Public properties are otherwise accessible. - if (!(flags & ModifierFlags.NonPublicAccessibilityModifier)) { - return true; - } - - // Property is known to be private or protected at this point - - // Private property is accessible if the property is within the declaring class - if (flags & ModifierFlags.Private) { - const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)!)!; - if (!isNodeWithinClass(node, declaringClassDeclaration)) { - error(errorNode, Diagnostics.Property_0_is_private_and_only_accessible_within_class_1, symbolToString(prop), typeToString(getDeclaringClass(prop)!)); - return false; + if (!result && source.flags & (TypeFlags.Intersection | TypeFlags.TypeParameter)) { + // The combined constraint of an intersection type is the intersection of the constraints of + // the constituents. When an intersection type contains instantiable types with union type + // constraints, there are situations where we need to examine the combined constraint. One is + // when the target is a union type. Another is when the intersection contains types belonging + // to one of the disjoint domains. For example, given type variables T and U, each with the + // constraint 'string | number', the combined constraint of 'T & U' is 'string | number' and + // we need to check this constraint against a union on the target side. Also, given a type + // variable V constrained to 'string | number', 'V & number' has a combined constraint of + // 'string & number | number & number' which reduces to just 'number'. + // This also handles type parameters, as a type parameter with a union constraint compared against a union + // needs to have its constraint hoisted into an intersection with said type parameter, this way + // the type param can be compared with itself in the target (with the influence of its constraint to match other parts) + // For example, if `T extends 1 | 2` and `U extends 2 | 3` and we compare `T & U` to `T & U & (1 | 2 | 3)` + const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (source).types : [source], !!(target.flags & TypeFlags.Union)); + if (constraint && (source.flags & TypeFlags.Intersection || target.flags & TypeFlags.Union)) { + if (everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself + // TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this + if (result = isRelatedTo(constraint, target, /*reportErrors*/ false, /*headMessage*/ undefined, isIntersectionConstituent)) { + resetErrorInfo(saveErrorInfo); + } + } } - return true; - } - - // Property is known to be protected at this point - - // All protected properties of a supertype are accessible in a super access - if (isSuper) { - return true; } - - // Find the first enclosing class that has the declaring classes of the protected constituents - // of the property as base classes - let enclosingClass = forEachEnclosingClass(node, enclosingDeclaration => { - const enclosingClass = getDeclaredTypeOfSymbol(getSymbolOfNode(enclosingDeclaration)!); - return isClassDerivedFromDeclaringClasses(enclosingClass, prop) ? enclosingClass : undefined; - }); - // A protected property is accessible if the property is within the declaring class or classes derived from it - if (!enclosingClass) { - // allow PropertyAccessibility if context is in function with this parameter - // static member access is disallow - let thisParameter: ParameterDeclaration | undefined; - if (flags & ModifierFlags.Static || !(thisParameter = getThisParameterFromNodeContext(node)) || !thisParameter.type) { - error(errorNode, Diagnostics.Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses, symbolToString(prop), typeToString(getDeclaringClass(prop) || type)); - return false; + if (!result && reportErrors) { + source = originalSource.aliasSymbol ? originalSource : source; + target = originalTarget.aliasSymbol ? originalTarget : target; + let maybeSuppress = overrideNextErrorInfo > 0; + if (maybeSuppress) { + overrideNextErrorInfo--; } - - const thisType = getTypeFromTypeNode(thisParameter.type); - enclosingClass = ((thisType.flags & TypeFlags.TypeParameter) ? getConstraintOfTypeParameter(thisType) : thisType) as InterfaceType; - } - // No further restrictions for static properties - if (flags & ModifierFlags.Static) { - return true; - } - if (type.flags & TypeFlags.TypeParameter) { - // get the original type -- represented as the type constraint of the 'this' type - type = (type as TypeParameter).isThisType ? getConstraintOfTypeParameter(type)! : getBaseConstraintOfType(type)!; // TODO: GH#18217 Use a different variable that's allowed to be undefined - } - if (!type || !hasBaseType(type, enclosingClass)) { - error(errorNode, Diagnostics.Property_0_is_protected_and_only_accessible_through_an_instance_of_class_1, symbolToString(prop), typeToString(enclosingClass)); - return false; + if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { + const currentError = errorInfo; + tryElaborateArrayLikeErrors(source, target, reportErrors); + if (errorInfo !== currentError) { + maybeSuppress = !!errorInfo; + } + } + if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Primitive) { + tryElaborateErrorsForPrimitivesAndObjects(source, target); + } + else if (source.symbol && source.flags & TypeFlags.Object && globalObjectType === source) { + reportError(Diagnostics.The_Object_type_is_assignable_to_very_few_other_types_Did_you_mean_to_use_the_any_type_instead); + } + else if (isComparingJsxAttributes && target.flags & TypeFlags.Intersection) { + const targetTypes = (target as IntersectionType).types; + const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes, errorNode); + const intrinsicClassAttributes = getJsxType(JsxNames.IntrinsicClassAttributes, errorNode); + if (intrinsicAttributes !== errorType && intrinsicClassAttributes !== errorType && + (contains(targetTypes, intrinsicAttributes) || contains(targetTypes, intrinsicClassAttributes))) { + // do not report top error + return result; + } + } + if (!headMessage && maybeSuppress) { + lastSkippedInfo = [source, target]; + // Used by, eg, missing property checking to replace the top-level message with a more informative one + return result; + } + reportRelationError(headMessage, source, target); } - return true; - } - - function getThisParameterFromNodeContext (node: Node) { - const thisContainer = getThisContainer(node, /* includeArrowFunctions */ false); - return thisContainer && isFunctionLike(thisContainer) ? getThisParameter(thisContainer) : undefined; - } - - function symbolHasNonMethodDeclaration(symbol: Symbol) { - return !!forEachProperty(symbol, prop => !(prop.flags & SymbolFlags.Method)); - } - - function checkNonNullExpression(node: Expression | QualifiedName) { - return checkNonNullType(checkExpression(node), node); - } - - function isNullableType(type: Type) { - return !!((strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable); - } - - function getNonNullableTypeIfNeeded(type: Type) { - return isNullableType(type) ? getNonNullableType(type) : type; - } - - function reportObjectPossiblyNullOrUndefinedError(node: Node, flags: TypeFlags) { - error(node, flags & TypeFlags.Undefined ? flags & TypeFlags.Null ? - Diagnostics.Object_is_possibly_null_or_undefined : - Diagnostics.Object_is_possibly_undefined : - Diagnostics.Object_is_possibly_null - ); - } - - function reportCannotInvokePossiblyNullOrUndefinedError(node: Node, flags: TypeFlags) { - error(node, flags & TypeFlags.Undefined ? flags & TypeFlags.Null ? - Diagnostics.Cannot_invoke_an_object_which_is_possibly_null_or_undefined : - Diagnostics.Cannot_invoke_an_object_which_is_possibly_undefined : - Diagnostics.Cannot_invoke_an_object_which_is_possibly_null - ); + return result; } - - function checkNonNullTypeWithReporter( - type: Type, - node: Node, - reportError: (node: Node, kind: TypeFlags) => void - ): Type { - if (strictNullChecks && type.flags & TypeFlags.Unknown) { - error(node, Diagnostics.Object_is_of_type_unknown); - return errorType; + function isIdenticalTo(source: ts.Type, target: ts.Type): Ternary { + let result: Ternary; + const flags = source.flags & target.flags; + if (flags & TypeFlags.Object || flags & TypeFlags.IndexedAccess || flags & TypeFlags.Conditional || flags & TypeFlags.Index || flags & TypeFlags.Substitution) { + return recursiveTypeRelatedTo(source, target, /*reportErrors*/ false, /*isIntersectionConstituent*/ false); } - const kind = (strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable; - if (kind) { - reportError(node, kind); - const t = getNonNullableType(type); - return t.flags & (TypeFlags.Nullable | TypeFlags.Never) ? errorType : t; + if (flags & (TypeFlags.Union | TypeFlags.Intersection)) { + if (result = eachTypeRelatedToSomeType((source), (target))) { + if (result &= eachTypeRelatedToSomeType((target), (source))) { + return result; + } + } } - return type; + return Ternary.False; } - - function checkNonNullType(type: Type, node: Node) { - return checkNonNullTypeWithReporter(type, node, reportObjectPossiblyNullOrUndefinedError); + function getTypeOfPropertyInTypes(types: ts.Type[], name: __String) { + const appendPropType = (propTypes: ts.Type[] | undefined, type: ts.Type) => { + type = getApparentType(type); + const prop = type.flags & TypeFlags.UnionOrIntersection ? getPropertyOfUnionOrIntersectionType((type), name) : getPropertyOfObjectType(type, name); + const propType = prop && getTypeOfSymbol(prop) || isNumericLiteralName(name) && getIndexTypeOfType(type, IndexKind.Number) || getIndexTypeOfType(type, IndexKind.String) || undefinedType; + return append(propTypes, propType); + }; + return getUnionType(reduceLeft(types, appendPropType, /*initial*/ undefined) || emptyArray); } - - function checkNonNullNonVoidType(type: Type, node: Node): Type { - const nonNullType = checkNonNullType(type, node); - if (nonNullType !== errorType && nonNullType.flags & TypeFlags.Void) { - error(node, Diagnostics.Object_is_possibly_undefined); + function hasExcessProperties(source: FreshObjectLiteralType, target: ts.Type, reportErrors: boolean): boolean { + if (!isExcessPropertyCheckTarget(target) || !noImplicitAny && getObjectFlags(target) & ObjectFlags.JSLiteral) { + return false; // Disable excess property checks on JS literals to simulate having an implicit "index signature" - but only outside of noImplicitAny } - return nonNullType; - } - - function checkPropertyAccessExpression(node: PropertyAccessExpression) { - return node.flags & NodeFlags.OptionalChain ? checkPropertyAccessChain(node as PropertyAccessChain) : - checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullExpression(node.expression), node.name); - } - - function checkPropertyAccessChain(node: PropertyAccessChain) { - const leftType = checkExpression(node.expression); - const nonOptionalType = getOptionalExpressionType(leftType, node.expression); - return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name), node, nonOptionalType !== leftType); - } - - function checkQualifiedName(node: QualifiedName) { - return checkPropertyAccessExpressionOrQualifiedName(node, node.left, checkNonNullExpression(node.left), node.right); - } - - function isMethodAccessForCall(node: Node) { - while (node.parent.kind === SyntaxKind.ParenthesizedExpression) { - node = node.parent; + const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); + if ((relation === assignableRelation || relation === comparableRelation) && + (isTypeSubsetOf(globalObjectType, target) || (!isComparingJsxAttributes && isEmptyObjectType(target)))) { + return false; } - return isCallOrNewExpression(node.parent) && node.parent.expression === node; - } - - function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier) { - const parentSymbol = getNodeLinks(left).resolvedSymbol; - const assignmentKind = getAssignmentTargetKind(node); - const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType); - if (isTypeAny(apparentType) || apparentType === silentNeverType) { - if (isIdentifier(left) && parentSymbol) { - markAliasReferenced(parentSymbol, node); - } - return apparentType; - } - const prop = getPropertyOfType(apparentType, right.escapedText); - if (isIdentifier(left) && parentSymbol && !(prop && isConstEnumOrConstEnumOnlyModule(prop))) { - markAliasReferenced(parentSymbol, node); + let reducedTarget = target; + let checkTypes: ts.Type[] | undefined; + if (target.flags & TypeFlags.Union) { + reducedTarget = findMatchingDiscriminantType(source, (target)) || filterPrimitivesIfContainsNonPrimitive((target)); + checkTypes = reducedTarget.flags & TypeFlags.Union ? (reducedTarget).types : [reducedTarget]; } - - let propType: Type; - if (!prop) { - const indexInfo = assignmentKind === AssignmentKind.None || !isGenericObjectType(leftType) || isThisTypeParameter(leftType) ? getIndexInfoOfType(apparentType, IndexKind.String) : undefined; - if (!(indexInfo && indexInfo.type)) { - if (isJSLiteralType(leftType)) { - return anyType; - } - if (leftType.symbol === globalThisSymbol) { - if (globalThisSymbol.exports!.has(right.escapedText) && (globalThisSymbol.exports!.get(right.escapedText)!.flags & SymbolFlags.BlockScoped)) { - error(right, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(right.escapedText), typeToString(leftType)); - } - else if (noImplicitAny) { - error(right, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(leftType)); + for (const prop of getPropertiesOfType(source)) { + if (shouldCheckAsExcessProperty(prop, source.symbol)) { + if (!isKnownProperty(reducedTarget, prop.escapedName, isComparingJsxAttributes)) { + if (reportErrors) { + // Report error in terms of object types in the target as those are the only ones + // we check in isKnownProperty. + const errorTarget = filterType(reducedTarget, isExcessPropertyCheckTarget); + // We know *exactly* where things went wrong when comparing the types. + // Use this property as the error node as this will be more helpful in + // reasoning about what went wrong. + if (!errorNode) + return Debug.fail(); + if (isJsxAttributes(errorNode) || isJsxOpeningLikeElement(errorNode) || isJsxOpeningLikeElement(errorNode.parent)) { + // JsxAttributes has an object-literal flag and undergo same type-assignablity check as normal object-literal. + // However, using an object-literal error message will be very confusing to the users so we give different a message. + // TODO: Spelling suggestions for excess jsx attributes (needs new diagnostic messages) + if (prop.valueDeclaration && isJsxAttribute(prop.valueDeclaration) && getSourceFileOfNode(errorNode) === getSourceFileOfNode(prop.valueDeclaration.name)) { + // Note that extraneous children (as in `extra`) don't pass this check, + // since `children` is a SyntaxKind.PropertySignature instead of a SyntaxKind.JsxAttribute. + errorNode = prop.valueDeclaration.name; + } + reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(prop), typeToString(errorTarget)); + } + else { + // use the property's value declaration if the property is assigned inside the literal itself + const objectLiteralDeclaration = source.symbol && firstOrUndefined(source.symbol.declarations); + let suggestion; + if (prop.valueDeclaration && findAncestor(prop.valueDeclaration, d => d === objectLiteralDeclaration) && getSourceFileOfNode(objectLiteralDeclaration) === getSourceFileOfNode(errorNode)) { + const propDeclaration = (prop.valueDeclaration as ObjectLiteralElementLike); + Debug.assertNode(propDeclaration, isObjectLiteralElementLike); + errorNode = propDeclaration; + const name = propDeclaration.name!; + if (isIdentifier(name)) { + suggestion = getSuggestionForNonexistentProperty(name, errorTarget); + } + } + if (suggestion !== undefined) { + reportError(Diagnostics.Object_literal_may_only_specify_known_properties_but_0_does_not_exist_in_type_1_Did_you_mean_to_write_2, symbolToString(prop), typeToString(errorTarget), suggestion); + } + else { + reportError(Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, symbolToString(prop), typeToString(errorTarget)); + } + } } - return anyType; + return true; } - if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) { - reportNonexistentProperty(right, isThisTypeParameter(leftType) ? apparentType : leftType); + if (checkTypes && !isRelatedTo(getTypeOfSymbol(prop), getTypeOfPropertyInTypes(checkTypes, prop.escapedName), reportErrors)) { + if (reportErrors) { + reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(prop)); + } + return true; } - return errorType; - } - if (indexInfo.isReadonly && (isAssignmentTarget(node) || isDeleteTarget(node))) { - error(node, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(apparentType)); } - propType = indexInfo.type; } - else { - checkPropertyNotUsedBeforeDeclaration(prop, node, right); - markPropertyAsReferenced(prop, node, left.kind === SyntaxKind.ThisKeyword); - getNodeLinks(node).resolvedSymbol = prop; - checkPropertyAccessibility(node, left.kind === SyntaxKind.SuperKeyword, apparentType, prop); - if (assignmentKind) { - if (isReferenceToReadonlyEntity(node, prop) || isReferenceThroughNamespaceImport(node)) { - error(right, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, idText(right)); - return errorType; - } + return false; + } + function shouldCheckAsExcessProperty(prop: ts.Symbol, container: ts.Symbol) { + return prop.valueDeclaration && container.valueDeclaration && prop.valueDeclaration.parent === container.valueDeclaration; + } + function eachTypeRelatedToSomeType(source: UnionOrIntersectionType, target: UnionOrIntersectionType): Ternary { + let result = Ternary.True; + const sourceTypes = source.types; + for (const sourceType of sourceTypes) { + const related = typeRelatedToSomeType(sourceType, target, /*reportErrors*/ false); + if (!related) { + return Ternary.False; } - propType = getConstraintForLocation(getTypeOfSymbol(prop), node); + result &= related; } - return getFlowTypeOfAccessExpression(node, prop, propType, right); - } - - function getFlowTypeOfAccessExpression(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, propType: Type, errorNode: Node) { - // Only compute control flow type if this is a property access expression that isn't an - // assignment target, and the referenced property was declared as a variable, property, - // accessor, or optional method. - const assignmentKind = getAssignmentTargetKind(node); - if (node.kind !== SyntaxKind.ElementAccessExpression && node.kind !== SyntaxKind.PropertyAccessExpression || - assignmentKind === AssignmentKind.Definite || - prop && !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) && !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) { - return propType; - } - // If strict null checks and strict property initialization checks are enabled, if we have - // a this.xxx property access, if the property is an instance property without an initializer, - // and if we are in a constructor of the same class as the property declaration, assume that - // the property is uninitialized at the top of the control flow. - let assumeUninitialized = false; - if (strictNullChecks && strictPropertyInitialization && node.expression.kind === SyntaxKind.ThisKeyword) { - const declaration = prop && prop.valueDeclaration; - if (declaration && isInstancePropertyWithoutInitializer(declaration)) { - const flowContainer = getControlFlowContainer(node); - if (flowContainer.kind === SyntaxKind.Constructor && flowContainer.parent === declaration.parent) { - assumeUninitialized = true; - } - } - } - else if (strictNullChecks && prop && prop.valueDeclaration && - isPropertyAccessExpression(prop.valueDeclaration) && - getAssignmentDeclarationPropertyAccessKind(prop.valueDeclaration) && - getControlFlowContainer(node) === getControlFlowContainer(prop.valueDeclaration)) { - assumeUninitialized = true; - } - const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType); - if (assumeUninitialized && !(getFalsyFlags(propType) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) { - error(errorNode, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217 - // Return the declared type to reduce follow-on errors - return propType; - } - return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; + return result; } - - function checkPropertyNotUsedBeforeDeclaration(prop: Symbol, node: PropertyAccessExpression | QualifiedName, right: Identifier): void { - const { valueDeclaration } = prop; - if (!valueDeclaration || getSourceFileOfNode(node).isDeclarationFile) { - return; + function typeRelatedToSomeType(source: ts.Type, target: UnionOrIntersectionType, reportErrors: boolean): Ternary { + const targetTypes = target.types; + if (target.flags & TypeFlags.Union && containsType(targetTypes, source)) { + return Ternary.True; } - - let diagnosticMessage; - const declarationName = idText(right); - if (isInPropertyInitializer(node) && - !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right) - && !isPropertyDeclaredInAncestorClass(prop)) { - diagnosticMessage = error(right, Diagnostics.Property_0_is_used_before_its_initialization, declarationName); - } - else if (valueDeclaration.kind === SyntaxKind.ClassDeclaration && - node.parent.kind !== SyntaxKind.TypeReference && - !(valueDeclaration.flags & NodeFlags.Ambient) && - !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right)) { - diagnosticMessage = error(right, Diagnostics.Class_0_used_before_its_declaration, declarationName); + for (const type of targetTypes) { + const related = isRelatedTo(source, type, /*reportErrors*/ false); + if (related) { + return related; + } } - - if (diagnosticMessage) { - addRelatedInfo(diagnosticMessage, - createDiagnosticForNode(valueDeclaration, Diagnostics._0_is_declared_here, declarationName) - ); + if (reportErrors) { + const bestMatchingType = findMatchingDiscriminantType(source, target) || + findMatchingTypeReferenceOrTypeAliasReference(source, target) || + findBestTypeForObjectLiteral(source, target) || + findBestTypeForInvokable(source, target) || + findMostOverlappyType(source, target); + isRelatedTo(source, bestMatchingType || targetTypes[targetTypes.length - 1], /*reportErrors*/ true); } + return Ternary.False; } - - function isInPropertyInitializer(node: Node): boolean { - return !!findAncestor(node, node => { - switch (node.kind) { - case SyntaxKind.PropertyDeclaration: - return true; - case SyntaxKind.PropertyAssignment: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.SpreadAssignment: - case SyntaxKind.ComputedPropertyName: - case SyntaxKind.TemplateSpan: - case SyntaxKind.JsxExpression: - case SyntaxKind.JsxAttribute: - case SyntaxKind.JsxAttributes: - case SyntaxKind.JsxSpreadAttribute: - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.ExpressionWithTypeArguments: - case SyntaxKind.HeritageClause: - return false; - default: - return isExpressionNode(node) ? false : "quit"; - } - }); + function findMatchingTypeReferenceOrTypeAliasReference(source: ts.Type, unionTarget: UnionOrIntersectionType) { + const sourceObjectFlags = getObjectFlags(source); + if (sourceObjectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous) && unionTarget.flags & TypeFlags.Union) { + return find(unionTarget.types, target => { + if (target.flags & TypeFlags.Object) { + const overlapObjFlags = sourceObjectFlags & getObjectFlags(target); + if (overlapObjFlags & ObjectFlags.Reference) { + return (source as TypeReference).target === (target as TypeReference).target; + } + if (overlapObjFlags & ObjectFlags.Anonymous) { + return !!(source as AnonymousType).aliasSymbol && (source as AnonymousType).aliasSymbol === (target as AnonymousType).aliasSymbol; + } + } + return false; + }); + } + } + function findBestTypeForObjectLiteral(source: ts.Type, unionTarget: UnionOrIntersectionType) { + if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && forEachType(unionTarget, isArrayLikeType)) { + return find(unionTarget.types, t => !isArrayLikeType(t)); + } } - - /** - * It's possible that "prop.valueDeclaration" is a local declaration, but the property was also declared in a superclass. - * In that case we won't consider it used before its declaration, because it gets its value from the superclass' declaration. - */ - function isPropertyDeclaredInAncestorClass(prop: Symbol): boolean { - if (!(prop.parent!.flags & SymbolFlags.Class)) { - return false; + function findBestTypeForInvokable(source: ts.Type, unionTarget: UnionOrIntersectionType) { + let signatureKind = SignatureKind.Call; + const hasSignatures = getSignaturesOfType(source, signatureKind).length > 0 || + (signatureKind = SignatureKind.Construct, getSignaturesOfType(source, signatureKind).length > 0); + if (hasSignatures) { + return find(unionTarget.types, t => getSignaturesOfType(t, signatureKind).length > 0); } - let classType: InterfaceType | undefined = getTypeOfSymbol(prop.parent!) as InterfaceType; - while (true) { - classType = classType.symbol && getSuperClass(classType) as InterfaceType | undefined; - if (!classType) { - return false; + } + function findMostOverlappyType(source: ts.Type, unionTarget: UnionOrIntersectionType) { + let bestMatch: ts.Type | undefined; + let matchingCount = 0; + for (const target of unionTarget.types) { + const overlap = getIntersectionType([getIndexType(source), getIndexType(target)]); + if (overlap.flags & TypeFlags.Index) { + // perfect overlap of keys + bestMatch = target; + matchingCount = Infinity; } - const superProperty = getPropertyOfType(classType, prop.escapedName); - if (superProperty && superProperty.valueDeclaration) { - return true; + else if (overlap.flags & TypeFlags.Union) { + // We only want to account for literal types otherwise. + // If we have a union of index types, it seems likely that we + // needed to elaborate between two generic mapped types anyway. + const len = length(filter((overlap as UnionType).types, isUnitType)); + if (len >= matchingCount) { + bestMatch = target; + matchingCount = len; + } + } + else if (isUnitType(overlap) && 1 >= matchingCount) { + bestMatch = target; + matchingCount = 1; } } + return bestMatch; } - - function getSuperClass(classType: InterfaceType): Type | undefined { - const x = getBaseTypes(classType); - if (x.length === 0) { - return undefined; + function filterPrimitivesIfContainsNonPrimitive(type: UnionType) { + if (maybeTypeOfKind(type, TypeFlags.NonPrimitive)) { + const result = filterType(type, t => !(t.flags & TypeFlags.Primitive)); + if (!(result.flags & TypeFlags.Never)) { + return result; + } } - return getIntersectionType(x); + return type; } - - function reportNonexistentProperty(propNode: Identifier, containingType: Type) { - let errorInfo: DiagnosticMessageChain | undefined; - let relatedInfo: Diagnostic | undefined; - if (containingType.flags & TypeFlags.Union && !(containingType.flags & TypeFlags.Primitive)) { - for (const subtype of (containingType as UnionType).types) { - if (!getPropertyOfType(subtype, propNode.escapedText) && !getIndexInfoOfType(subtype, IndexKind.String)) { - errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(subtype)); - break; + // Keep this up-to-date with the same logic within `getApparentTypeOfContextualType`, since they should behave similarly + function findMatchingDiscriminantType(source: ts.Type, target: ts.Type) { + if (target.flags & TypeFlags.Union && source.flags & (TypeFlags.Intersection | TypeFlags.Object)) { + const sourceProperties = getPropertiesOfType(source); + if (sourceProperties) { + const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target); + if (sourcePropertiesFiltered) { + return discriminateTypeByDiscriminableItems((target), map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => ts.Type, __String])), isRelatedTo); } } } - if (typeHasStaticProperty(propNode.escapedText, containingType)) { - errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_is_a_static_member_of_type_1, declarationNameToString(propNode), typeToString(containingType)); + return undefined; + } + function typeRelatedToEachType(source: ts.Type, target: IntersectionType, reportErrors: boolean): Ternary { + let result = Ternary.True; + const targetTypes = target.types; + for (const targetType of targetTypes) { + const related = isRelatedTo(source, targetType, reportErrors, /*headMessage*/ undefined, /*isIntersectionConstituent*/ true); + if (!related) { + return Ternary.False; + } + result &= related; + } + return result; + } + function someTypeRelatedToType(source: UnionOrIntersectionType, target: ts.Type, reportErrors: boolean): Ternary { + const sourceTypes = source.types; + if (source.flags & TypeFlags.Union && containsType(sourceTypes, target)) { + return Ternary.True; } - else { - const promisedType = getPromisedTypeOfPromise(containingType); - if (promisedType && getPropertyOfType(promisedType, propNode.escapedText)) { - errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType)); - relatedInfo = createDiagnosticForNode(propNode, Diagnostics.Did_you_forget_to_use_await); + const len = sourceTypes.length; + for (let i = 0; i < len; i++) { + const related = isRelatedTo(sourceTypes[i], target, reportErrors && i === len - 1); + if (related) { + return related; } - else { - const suggestion = getSuggestedSymbolForNonexistentProperty(propNode, containingType); - if (suggestion !== undefined) { - const suggestedName = symbolName(suggestion); - errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, declarationNameToString(propNode), typeToString(containingType), suggestedName); - relatedInfo = suggestion.valueDeclaration && createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestedName); + } + return Ternary.False; + } + function eachTypeRelatedToType(source: UnionOrIntersectionType, target: ts.Type, reportErrors: boolean): Ternary { + let result = Ternary.True; + const sourceTypes = source.types; + for (const sourceType of sourceTypes) { + const related = isRelatedTo(sourceType, target, reportErrors); + if (!related) { + return Ternary.False; + } + result &= related; + } + return result; + } + function typeArgumentsRelatedTo(sources: readonly ts.Type[] = emptyArray, targets: readonly ts.Type[] = emptyArray, variances: readonly VarianceFlags[] = emptyArray, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary { + if (sources.length !== targets.length && relation === identityRelation) { + return Ternary.False; + } + const length = sources.length <= targets.length ? sources.length : targets.length; + let result = Ternary.True; + for (let i = 0; i < length; i++) { + // When variance information isn't available we default to covariance. This happens + // in the process of computing variance information for recursive types and when + // comparing 'this' type arguments. + const varianceFlags = i < variances.length ? variances[i] : VarianceFlags.Covariant; + const variance = varianceFlags & VarianceFlags.VarianceMask; + // We ignore arguments for independent type parameters (because they're never witnessed). + if (variance !== VarianceFlags.Independent) { + const s = sources[i]; + const t = targets[i]; + let related = Ternary.True; + if (varianceFlags & VarianceFlags.Unmeasurable) { + // Even an `Unmeasurable` variance works out without a structural check if the source and target are _identical_. + // We can't simply assume invariance, because `Unmeasurable` marks nonlinear relations, for example, a relation tained by + // the `-?` modifier in a mapped type (where, no matter how the inputs are related, the outputs still might not be) + related = relation === identityRelation ? isRelatedTo(s, t, /*reportErrors*/ false) : compareTypesIdentical(s, t); + } + else if (variance === VarianceFlags.Covariant) { + related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent); + } + else if (variance === VarianceFlags.Contravariant) { + related = isRelatedTo(t, s, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent); + } + else if (variance === VarianceFlags.Bivariant) { + // In the bivariant case we first compare contravariantly without reporting + // errors. Then, if that doesn't succeed, we compare covariantly with error + // reporting. Thus, error elaboration will be based on the the covariant check, + // which is generally easier to reason about. + related = isRelatedTo(t, s, /*reportErrors*/ false); + if (!related) { + related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent); + } } else { - errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType)); + // In the invariant case we first compare covariantly, and only when that + // succeeds do we proceed to compare contravariantly. Thus, error elaboration + // will typically be based on the covariant check. + related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent); + if (related) { + related &= isRelatedTo(t, s, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent); + } + } + if (!related) { + return Ternary.False; } + result &= related; } } - const resultDiagnostic = createDiagnosticForNodeFromMessageChain(propNode, errorInfo); - if (relatedInfo) { - addRelatedInfo(resultDiagnostic, relatedInfo); - } - diagnostics.add(resultDiagnostic); - } - - function typeHasStaticProperty(propName: __String, containingType: Type): boolean { - const prop = containingType.symbol && getPropertyOfType(getTypeOfSymbol(containingType.symbol), propName); - return prop !== undefined && prop.valueDeclaration && hasModifier(prop.valueDeclaration, ModifierFlags.Static); - } - - function getSuggestedSymbolForNonexistentProperty(name: Identifier | string, containingType: Type): Symbol | undefined { - return getSpellingSuggestionForName(isString(name) ? name : idText(name), getPropertiesOfType(containingType), SymbolFlags.Value); - } - - function getSuggestionForNonexistentProperty(name: Identifier | string, containingType: Type): string | undefined { - const suggestion = getSuggestedSymbolForNonexistentProperty(name, containingType); - return suggestion && symbolName(suggestion); - } - - function getSuggestedSymbolForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): Symbol | undefined { - Debug.assert(outerName !== undefined, "outername should always be defined"); - const result = resolveNameHelper(location, outerName, meaning, /*nameNotFoundMessage*/ undefined, outerName, /*isUse*/ false, /*excludeGlobals*/ false, (symbols, name, meaning) => { - Debug.assertEqual(outerName, name, "name should equal outerName"); - const symbol = getSymbol(symbols, name, meaning); - // Sometimes the symbol is found when location is a return type of a function: `typeof x` and `x` is declared in the body of the function - // So the table *contains* `x` but `x` isn't actually in scope. - // However, resolveNameHelper will continue and call this callback again, so we'll eventually get a correct suggestion. - return symbol || getSpellingSuggestionForName(unescapeLeadingUnderscores(name), arrayFrom(symbols.values()), meaning); - }); return result; } - - function getSuggestionForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): string | undefined { - const symbolResult = getSuggestedSymbolForNonexistentSymbol(location, outerName, meaning); - return symbolResult && symbolName(symbolResult); - } - - function getSuggestedSymbolForNonexistentModule(name: Identifier, targetModule: Symbol): Symbol | undefined { - return targetModule.exports && getSpellingSuggestionForName(idText(name), getExportsOfModuleAsArray(targetModule), SymbolFlags.ModuleMember); - } - - function getSuggestionForNonexistentExport(name: Identifier, targetModule: Symbol): string | undefined { - const suggestion = getSuggestedSymbolForNonexistentModule(name, targetModule); - return suggestion && symbolName(suggestion); - } - - function getSuggestionForNonexistentIndexSignature(objectType: Type, expr: ElementAccessExpression, keyedType: Type): string | undefined { - // check if object type has setter or getter - function hasProp(name: "set" | "get") { - const prop = getPropertyOfObjectType(objectType, <__String>name); - if (prop) { - const s = getSingleCallSignature(getTypeOfSymbol(prop)); - return !!s && getMinArgumentCount(s) >= 1 && isTypeAssignableTo(keyedType, getTypeAtPosition(s, 0)); + // Determine if possibly recursive types are related. First, check if the result is already available in the global cache. + // Second, check if we have already started a comparison of the given two types in which case we assume the result to be true. + // Third, check if both types are part of deeply nested chains of generic type instantiations and if so assume the types are + // equal and infinitely expanding. Fourth, if we have reached a depth of 100 nested comparisons, assume we have runaway recursion + // and issue an error. Otherwise, actually compare the structure of the two types. + function recursiveTypeRelatedTo(source: ts.Type, target: ts.Type, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary { + if (overflow) { + return Ternary.False; + } + const id = getRelationKey(source, target, isIntersectionConstituent, relation); + const entry = relation.get(id); + if (entry !== undefined) { + if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Reported)) { + // We are elaborating errors and the cached result is an unreported failure. The result will be reported + // as a failure, and should be updated as a reported failure by the bottom of this function. + } + else { + if (outofbandVarianceMarkerHandler) { + // We're in the middle of variance checking - integrate any unmeasurable/unreliable flags from this cached component + const saved = entry & RelationComparisonResult.ReportsMask; + if (saved & RelationComparisonResult.ReportsUnmeasurable) { + instantiateType(source, reportUnmeasurableMarkers); + } + if (saved & RelationComparisonResult.ReportsUnreliable) { + instantiateType(source, reportUnreliableMarkers); + } + } + return entry & RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False; } - return false; - }; - - const suggestedMethod = isAssignmentTarget(expr) ? "set" : "get"; - if (!hasProp(suggestedMethod)) { - return undefined; } - - let suggestion = tryGetPropertyAccessOrIdentifierToString(expr.expression); - if (suggestion === undefined) { - suggestion = suggestedMethod; + if (!maybeKeys) { + maybeKeys = []; + sourceStack = []; + targetStack = []; } else { - suggestion += "." + suggestedMethod; - } - - return suggestion; - } - - /** - * Given a name and a list of symbols whose names are *not* equal to the name, return a spelling suggestion if there is one that is close enough. - * Names less than length 3 only check for case-insensitive equality, not levenshtein distance. - * - * If there is a candidate that's the same except for case, return that. - * If there is a candidate that's within one edit of the name, return that. - * Otherwise, return the candidate with the smallest Levenshtein distance, - * except for candidates: - * * With no name - * * Whose meaning doesn't match the `meaning` parameter. - * * Whose length differs from the target name by more than 0.34 of the length of the name. - * * Whose levenshtein distance is more than 0.4 of the length of the name - * (0.4 allows 1 substitution/transposition for every 5 characters, - * and 1 insertion/deletion at 3 characters) - */ - function getSpellingSuggestionForName(name: string, symbols: Symbol[], meaning: SymbolFlags): Symbol | undefined { - return getSpellingSuggestion(name, symbols, getCandidateName); - function getCandidateName(candidate: Symbol) { - const candidateName = symbolName(candidate); - return !startsWith(candidateName, "\"") && candidate.flags & meaning ? candidateName : undefined; + for (let i = 0; i < maybeCount; i++) { + // If source and target are already being compared, consider them related with assumptions + if (id === maybeKeys[i]) { + return Ternary.Maybe; + } + } + if (depth === 100) { + overflow = true; + return Ternary.False; + } } - } - - function markPropertyAsReferenced(prop: Symbol, nodeForCheckWriteOnly: Node | undefined, isThisAccess: boolean) { - if (!prop || !(prop.flags & SymbolFlags.ClassMember) || !prop.valueDeclaration || !hasModifier(prop.valueDeclaration, ModifierFlags.Private)) { - return; + const maybeStart = maybeCount; + maybeKeys[maybeCount] = id; + maybeCount++; + sourceStack[depth] = source; + targetStack[depth] = target; + depth++; + const saveExpandingFlags = expandingFlags; + if (!(expandingFlags & ExpandingFlags.Source) && isDeeplyNestedType(source, sourceStack, depth)) + expandingFlags |= ExpandingFlags.Source; + if (!(expandingFlags & ExpandingFlags.Target) && isDeeplyNestedType(target, targetStack, depth)) + expandingFlags |= ExpandingFlags.Target; + let originalHandler: typeof outofbandVarianceMarkerHandler; + let propagatingVarianceFlags: RelationComparisonResult = 0; + if (outofbandVarianceMarkerHandler) { + originalHandler = outofbandVarianceMarkerHandler; + outofbandVarianceMarkerHandler = onlyUnreliable => { + propagatingVarianceFlags |= onlyUnreliable ? RelationComparisonResult.ReportsUnreliable : RelationComparisonResult.ReportsUnmeasurable; + return originalHandler!(onlyUnreliable); + }; } - if (nodeForCheckWriteOnly && isWriteOnlyAccess(nodeForCheckWriteOnly) && !(prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor))) { - return; + const result = expandingFlags !== ExpandingFlags.Both ? structuredTypeRelatedTo(source, target, reportErrors, isIntersectionConstituent) : Ternary.Maybe; + if (outofbandVarianceMarkerHandler) { + outofbandVarianceMarkerHandler = originalHandler; } - - if (isThisAccess) { - // Find any FunctionLikeDeclaration because those create a new 'this' binding. But this should only matter for methods (or getters/setters). - const containingMethod = findAncestor(nodeForCheckWriteOnly, isFunctionLikeDeclaration); - if (containingMethod && containingMethod.symbol === prop) { - return; + expandingFlags = saveExpandingFlags; + depth--; + if (result) { + if (result === Ternary.True || depth === 0) { + // If result is definitely true, record all maybe keys as having succeeded + for (let i = maybeStart; i < maybeCount; i++) { + relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags); + } + maybeCount = maybeStart; } } - - (getCheckFlags(prop) & CheckFlags.Instantiated ? getSymbolLinks(prop).target : prop)!.isReferenced = SymbolFlags.All; - } - - function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, propertyName: __String): boolean { - switch (node.kind) { - case SyntaxKind.PropertyAccessExpression: - return isValidPropertyAccessWithType(node, node.expression.kind === SyntaxKind.SuperKeyword, propertyName, getWidenedType(checkExpression(node.expression))); - case SyntaxKind.QualifiedName: - return isValidPropertyAccessWithType(node, /*isSuper*/ false, propertyName, getWidenedType(checkExpression(node.left))); - case SyntaxKind.ImportType: - return isValidPropertyAccessWithType(node, /*isSuper*/ false, propertyName, getTypeFromTypeNode(node)); + else { + // A false result goes straight into global cache (when something is false under + // assumptions it will also be false without assumptions) + relation.set(id, (reportErrors ? RelationComparisonResult.Reported : 0) | RelationComparisonResult.Failed | propagatingVarianceFlags); + maybeCount = maybeStart; } + return result; } - - function isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode | QualifiedName, type: Type, property: Symbol): boolean { - return isValidPropertyAccessWithType(node, node.kind === SyntaxKind.PropertyAccessExpression && node.expression.kind === SyntaxKind.SuperKeyword, property.escapedName, type); - // Previously we validated the 'this' type of methods but this adversely affected performance. See #31377 for more context. - } - - function isValidPropertyAccessWithType( - node: PropertyAccessExpression | QualifiedName | ImportTypeNode, - isSuper: boolean, - propertyName: __String, - type: Type): boolean { - - if (type === errorType || isTypeAny(type)) { - return true; + function structuredTypeRelatedTo(source: ts.Type, target: ts.Type, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary { + const flags = source.flags & target.flags; + if (relation === identityRelation && !(flags & TypeFlags.Object)) { + if (flags & TypeFlags.Index) { + return isRelatedTo((source).type, (target).type, /*reportErrors*/ false); + } + let result = Ternary.False; + if (flags & TypeFlags.IndexedAccess) { + if (result = isRelatedTo((source).objectType, (target).objectType, /*reportErrors*/ false)) { + if (result &= isRelatedTo((source).indexType, (target).indexType, /*reportErrors*/ false)) { + return result; + } + } + } + if (flags & TypeFlags.Conditional) { + if ((source).root.isDistributive === (target).root.isDistributive) { + if (result = isRelatedTo((source).checkType, (target).checkType, /*reportErrors*/ false)) { + if (result &= isRelatedTo((source).extendsType, (target).extendsType, /*reportErrors*/ false)) { + if (result &= isRelatedTo(getTrueTypeFromConditionalType((source)), getTrueTypeFromConditionalType((target)), /*reportErrors*/ false)) { + if (result &= isRelatedTo(getFalseTypeFromConditionalType((source)), getFalseTypeFromConditionalType((target)), /*reportErrors*/ false)) { + return result; + } + } + } + } + } + } + if (flags & TypeFlags.Substitution) { + return isRelatedTo((source).substitute, (target).substitute, /*reportErrors*/ false); + } + return Ternary.False; } - const prop = getPropertyOfType(type, propertyName); - return prop ? checkPropertyAccessibility(node, isSuper, type, prop) - // In js files properties of unions are allowed in completion - : isInJSFile(node) && (type.flags & TypeFlags.Union) !== 0 && (type).types.some(elementType => isValidPropertyAccessWithType(node, isSuper, propertyName, elementType)); - } - - /** - * Return the symbol of the for-in variable declared or referenced by the given for-in statement. - */ - function getForInVariableSymbol(node: ForInStatement): Symbol | undefined { - const initializer = node.initializer; - if (initializer.kind === SyntaxKind.VariableDeclarationList) { - const variable = (initializer).declarations[0]; - if (variable && !isBindingPattern(variable.name)) { - return getSymbolOfNode(variable); + let result: Ternary; + let originalErrorInfo: DiagnosticMessageChain | undefined; + let varianceCheckFailed = false; + const saveErrorInfo = captureErrorCalculationState(); + // We limit alias variance probing to only object and conditional types since their alias behavior + // is more predictable than other, interned types, which may or may not have an alias depending on + // the order in which things were checked. + if (source.flags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol && + source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol && + !(source.aliasTypeArgumentsContainsMarker || target.aliasTypeArgumentsContainsMarker)) { + const variances = getAliasVariances(source.aliasSymbol); + const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, variances, isIntersectionConstituent); + if (varianceResult !== undefined) { + return varianceResult; + } + } + if (target.flags & TypeFlags.TypeParameter) { + // A source type { [P in Q]: X } is related to a target type T if keyof T is related to Q and X is related to T[Q]. + if (getObjectFlags(source) & ObjectFlags.Mapped && isRelatedTo(getIndexType(target), getConstraintTypeFromMappedType((source)))) { + if (!(getMappedTypeModifiers((source)) & MappedTypeModifiers.IncludeOptional)) { + const templateType = getTemplateTypeFromMappedType((source)); + const indexedAccessType = getIndexedAccessType(target, getTypeParameterFromMappedType((source))); + if (result = isRelatedTo(templateType, indexedAccessType, reportErrors)) { + return result; + } + } } } - else if (initializer.kind === SyntaxKind.Identifier) { - return getResolvedSymbol(initializer); + else if (target.flags & TypeFlags.Index) { + // A keyof S is related to a keyof T if T is related to S. + if (source.flags & TypeFlags.Index) { + if (result = isRelatedTo((target).type, (source).type, /*reportErrors*/ false)) { + return result; + } + } + // A type S is assignable to keyof T if S is assignable to keyof C, where C is the + // simplified form of T or, if T doesn't simplify, the constraint of T. + const constraint = getSimplifiedTypeOrConstraint((target).type); + if (constraint) { + // We require Ternary.True here such that circular constraints don't cause + // false positives. For example, given 'T extends { [K in keyof T]: string }', + // 'keyof T' has itself as its constraint and produces a Ternary.Maybe when + // related to other types. + if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), reportErrors) === Ternary.True) { + return Ternary.True; + } + } } - return undefined; - } - - /** - * Return true if the given type is considered to have numeric property names. - */ - function hasNumericPropertyNames(type: Type) { - return getIndexTypeOfType(type, IndexKind.Number) && !getIndexTypeOfType(type, IndexKind.String); - } - - /** - * Return true if given node is an expression consisting of an identifier (possibly parenthesized) - * that references a for-in variable for an object with numeric property names. - */ - function isForInVariableForNumericPropertyNames(expr: Expression) { - const e = skipParentheses(expr); - if (e.kind === SyntaxKind.Identifier) { - const symbol = getResolvedSymbol(e); - if (symbol.flags & SymbolFlags.Variable) { - let child: Node = expr; - let node = expr.parent; - while (node) { - if (node.kind === SyntaxKind.ForInStatement && - child === (node).statement && - getForInVariableSymbol(node) === symbol && - hasNumericPropertyNames(getTypeOfExpression((node).expression))) { - return true; + else if (target.flags & TypeFlags.IndexedAccess) { + // A type S is related to a type T[K] if S is related to C, where C is the base + // constraint of T[K] for writing. + if (relation !== identityRelation) { + const objectType = (target).objectType; + const indexType = (target).indexType; + const baseObjectType = getBaseConstraintOfType(objectType) || objectType; + const baseIndexType = getBaseConstraintOfType(indexType) || indexType; + if (!isGenericObjectType(baseObjectType) && !isGenericIndexType(baseIndexType)) { + const accessFlags = AccessFlags.Writing | (baseObjectType !== objectType ? AccessFlags.NoIndexSignatures : 0); + const constraint = getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, /*accessNode*/ undefined, accessFlags); + if (constraint && (result = isRelatedTo(source, constraint, reportErrors))) { + return result; } - child = node; - node = node.parent; } } } - return false; - } - - function checkIndexedAccess(node: ElementAccessExpression): Type { - return node.flags & NodeFlags.OptionalChain ? checkElementAccessChain(node as ElementAccessChain) : - checkElementAccessExpression(node, checkNonNullExpression(node.expression)); - } - - function checkElementAccessChain(node: ElementAccessChain) { - const exprType = checkExpression(node.expression); - const nonOptionalType = getOptionalExpressionType(exprType, node.expression); - return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression)), node, nonOptionalType !== exprType); - } - - function checkElementAccessExpression(node: ElementAccessExpression, exprType: Type): Type { - const objectType = getAssignmentTargetKind(node) !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(exprType) : exprType; - const indexExpression = node.argumentExpression; - const indexType = checkExpression(indexExpression); - - if (objectType === errorType || objectType === silentNeverType) { - return objectType; + else if (isGenericMappedType(target)) { + // A source type T is related to a target type { [P in X]: T[P] } + const template = getTemplateTypeFromMappedType(target); + const modifiers = getMappedTypeModifiers(target); + if (!(modifiers & MappedTypeModifiers.ExcludeOptional)) { + if (template.flags & TypeFlags.IndexedAccess && (template).objectType === source && + (template).indexType === getTypeParameterFromMappedType(target)) { + return Ternary.True; + } + if (!isGenericMappedType(source)) { + const targetConstraint = getConstraintTypeFromMappedType(target); + const sourceKeys = getIndexType(source, /*stringsOnly*/ undefined, /*noIndexSignatures*/ true); + const includeOptional = modifiers & MappedTypeModifiers.IncludeOptional; + const filteredByApplicability = includeOptional ? intersectTypes(targetConstraint, sourceKeys) : undefined; + // A source type T is related to a target type { [P in Q]: X } if Q is related to keyof T and T[Q] is related to X. + // A source type T is related to a target type { [P in Q]?: X } if some constituent Q' of Q is related to keyof T and T[Q'] is related to X. + if (includeOptional + ? !(filteredByApplicability!.flags & TypeFlags.Never) + : isRelatedTo(targetConstraint, sourceKeys)) { + const typeParameter = getTypeParameterFromMappedType(target); + const indexingType = filteredByApplicability ? getIntersectionType([filteredByApplicability, typeParameter]) : typeParameter; + const indexedAccessType = getIndexedAccessType(source, indexingType); + const templateType = getTemplateTypeFromMappedType(target); + if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) { + return result; + } + } + originalErrorInfo = errorInfo; + resetErrorInfo(saveErrorInfo); + } + } } - - if (isConstEnumObjectType(objectType) && !isStringLiteralLike(indexExpression)) { - error(indexExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal); - return errorType; + if (source.flags & TypeFlags.TypeVariable) { + if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) { + // A type S[K] is related to a type T[J] if S is related to T and K is related to J. + if (result = isRelatedTo((source).objectType, (target).objectType, reportErrors)) { + result &= isRelatedTo((source).indexType, (target).indexType, reportErrors); + } + if (result) { + resetErrorInfo(saveErrorInfo); + return result; + } + } + else { + const constraint = getConstraintOfType((source)); + if (!constraint || (source.flags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) { + // A type variable with no constraint is not related to the non-primitive object type. + if (result = isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive))) { + resetErrorInfo(saveErrorInfo); + return result; + } + } + // hi-speed no-this-instantiation check (less accurate, but avoids costly `this`-instantiation when the constraint will suffice), see #28231 for report on why this is needed + else if (result = isRelatedTo(constraint, target, /*reportErrors*/ false, /*headMessage*/ undefined, isIntersectionConstituent)) { + resetErrorInfo(saveErrorInfo); + return result; + } + // slower, fuller, this-instantiated check (necessary when comparing raw `this` types from base classes), see `subclassWithPolymorphicThisIsAssignable.ts` test for example + else if (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent)) { + resetErrorInfo(saveErrorInfo); + return result; + } + } } - - const effectiveIndexType = isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType; - const accessFlags = isAssignmentTarget(node) ? - AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) : - AccessFlags.None; - const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, node, accessFlags) || errorType; - return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node); - } - - function checkThatExpressionIsProperSymbolReference(expression: Expression, expressionType: Type, reportError: boolean): boolean { - if (expressionType === errorType) { - // There is already an error, so no need to report one. - return false; + else if (source.flags & TypeFlags.Index) { + if (result = isRelatedTo(keyofConstraintType, target, reportErrors)) { + resetErrorInfo(saveErrorInfo); + return result; + } } - - if (!isWellKnownSymbolSyntactically(expression)) { - return false; + else if (source.flags & TypeFlags.Conditional) { + if (target.flags & TypeFlags.Conditional) { + // Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if + // one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2, + // and Y1 is related to Y2. + const sourceParams = (source as ConditionalType).root.inferTypeParameters; + let sourceExtends = (source).extendsType; + let mapper: TypeMapper | undefined; + if (sourceParams) { + // If the source has infer type parameters, we instantiate them in the context of the target + const ctx = createInferenceContext(sourceParams, /*signature*/ undefined, InferenceFlags.None, isRelatedTo); + inferTypes(ctx.inferences, (target).extendsType, sourceExtends, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); + sourceExtends = instantiateType(sourceExtends, ctx.mapper); + mapper = ctx.mapper; + } + if (isTypeIdenticalTo(sourceExtends, (target).extendsType) && + (isRelatedTo((source).checkType, (target).checkType) || isRelatedTo((target).checkType, (source).checkType))) { + if (result = isRelatedTo(instantiateType(getTrueTypeFromConditionalType((source)), mapper), getTrueTypeFromConditionalType((target)), reportErrors)) { + result &= isRelatedTo(getFalseTypeFromConditionalType((source)), getFalseTypeFromConditionalType((target)), reportErrors); + } + if (result) { + resetErrorInfo(saveErrorInfo); + return result; + } + } + } + else { + const distributiveConstraint = getConstraintOfDistributiveConditionalType((source)); + if (distributiveConstraint) { + if (result = isRelatedTo(distributiveConstraint, target, reportErrors)) { + resetErrorInfo(saveErrorInfo); + return result; + } + } + const defaultConstraint = getDefaultConstraintOfConditionalType((source)); + if (defaultConstraint) { + if (result = isRelatedTo(defaultConstraint, target, reportErrors)) { + resetErrorInfo(saveErrorInfo); + return result; + } + } + } } - - // Make sure the property type is the primitive symbol type - if ((expressionType.flags & TypeFlags.ESSymbolLike) === 0) { - if (reportError) { - error(expression, Diagnostics.A_computed_property_name_of_the_form_0_must_be_of_type_symbol, getTextOfNode(expression)); + else { + // An empty object type is related to any mapped type that includes a '?' modifier. + if (relation !== subtypeRelation && isPartialMappedType(target) && isEmptyObjectType(source)) { + return Ternary.True; + } + if (isGenericMappedType(target)) { + if (isGenericMappedType(source)) { + if (result = mappedTypeRelatedTo(source, target, reportErrors)) { + resetErrorInfo(saveErrorInfo); + return result; + } + } + return Ternary.False; + } + const sourceIsPrimitive = !!(source.flags & TypeFlags.Primitive); + if (relation !== identityRelation) { + source = getApparentType(source); + } + else if (isGenericMappedType(source)) { + return Ternary.False; + } + if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target && + !(getObjectFlags(source) & ObjectFlags.MarkerType || getObjectFlags(target) & ObjectFlags.MarkerType)) { + // We have type references to the same generic type, and the type references are not marker + // type references (which are intended by be compared structurally). Obtain the variance + // information for the type parameters and relate the type arguments accordingly. + const variances = getVariances((source).target); + const varianceResult = relateVariances(getTypeArguments((source)), getTypeArguments((target)), variances, isIntersectionConstituent); + if (varianceResult !== undefined) { + return varianceResult; + } + } + else if (isReadonlyArrayType(target) ? isArrayType(source) || isTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) { + if (relation !== identityRelation) { + return isRelatedTo(getIndexTypeOfType(source, IndexKind.Number) || anyType, getIndexTypeOfType(target, IndexKind.Number) || anyType, reportErrors); + } + else { + // By flags alone, we know that the `target` is a readonly array while the source is a normal array or tuple + // or `target` is an array and source is a tuple - in both cases the types cannot be identical, by construction + return Ternary.False; + } + } + // Consider a fresh empty object literal type "closed" under the subtype relationship - this way `{} <- {[idx: string]: any} <- fresh({})` + // and not `{} <- fresh({}) <- {[idx: string]: any}` + else if (relation === subtypeRelation && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source)) { + return Ternary.False; + } + // Even if relationship doesn't hold for unions, intersections, or generic type references, + // it may hold in a structural comparison. + // In a check of the form X = A & B, we will have previously checked if A relates to X or B relates + // to X. Failing both of those we want to check if the aggregation of A and B's members structurally + // relates to X. Thus, we include intersection types on the source side here. + if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) { + // Report structural errors only if we haven't reported any errors yet + const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo.errorInfo && !sourceIsPrimitive; + result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, isIntersectionConstituent); + if (result) { + result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors); + if (result) { + result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors); + if (result) { + result &= indexTypesRelatedTo(source, target, IndexKind.String, sourceIsPrimitive, reportStructuralErrors); + if (result) { + result &= indexTypesRelatedTo(source, target, IndexKind.Number, sourceIsPrimitive, reportStructuralErrors); + } + } + } + } + if (varianceCheckFailed && result) { + errorInfo = originalErrorInfo || errorInfo || saveErrorInfo.errorInfo; // Use variance error (there is no structural one) and return false + } + else if (result) { + return result; + } + } + // If S is an object type and T is a discriminated union, S may be related to T if + // there exists a constituent of T for every combination of the discriminants of S + // with respect to T. We do not report errors here, as we will use the existing + // error result from checking each constituent of the union. + if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Union) { + const objectOnlyTarget = extractTypesOfKind(target, TypeFlags.Object); + if (objectOnlyTarget.flags & TypeFlags.Union) { + const result = typeRelatedToDiscriminatedType(source, (objectOnlyTarget as UnionType)); + if (result) { + return result; + } + } } - return false; - } - - // The name is Symbol., so make sure Symbol actually resolves to the - // global Symbol object - const leftHandSide = (expression).expression; - const leftHandSideSymbol = getResolvedSymbol(leftHandSide); - if (!leftHandSideSymbol) { - return false; - } - - const globalESSymbol = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ true); - if (!globalESSymbol) { - // Already errored when we tried to look up the symbol - return false; } - - if (leftHandSideSymbol !== globalESSymbol) { - if (reportError) { - error(leftHandSide, Diagnostics.Symbol_reference_does_not_refer_to_the_global_Symbol_constructor_object); + return Ternary.False; + function relateVariances(sourceTypeArguments: readonly ts.Type[] | undefined, targetTypeArguments: readonly ts.Type[] | undefined, variances: VarianceFlags[], isIntersectionConstituent: boolean) { + if (result = typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors, isIntersectionConstituent)) { + return result; + } + if (some(variances, v => !!(v & VarianceFlags.AllowsStructuralFallback))) { + // If some type parameter was `Unmeasurable` or `Unreliable`, and we couldn't pass by assuming it was identical, then we + // have to allow a structural fallback check + // We elide the variance-based error elaborations, since those might not be too helpful, since we'll potentially + // be assuming identity of the type parameter. + originalErrorInfo = undefined; + resetErrorInfo(saveErrorInfo); + return undefined; + } + const allowStructuralFallback = targetTypeArguments && hasCovariantVoidArgument(targetTypeArguments, variances); + varianceCheckFailed = !allowStructuralFallback; + // The type arguments did not relate appropriately, but it may be because we have no variance + // information (in which case typeArgumentsRelatedTo defaulted to covariance for all type + // arguments). It might also be the case that the target type has a 'void' type argument for + // a covariant type parameter that is only used in return positions within the generic type + // (in which case any type argument is permitted on the source side). In those cases we proceed + // with a structural comparison. Otherwise, we know for certain the instantiations aren't + // related and we can return here. + if (variances !== emptyArray && !allowStructuralFallback) { + // In some cases generic types that are covariant in regular type checking mode become + // invariant in --strictFunctionTypes mode because one or more type parameters are used in + // both co- and contravariant positions. In order to make it easier to diagnose *why* such + // types are invariant, if any of the type parameters are invariant we reset the reported + // errors and instead force a structural comparison (which will include elaborations that + // reveal the reason). + // We can switch on `reportErrors` here, since varianceCheckFailed guarantees we return `False`, + // we can return `False` early here to skip calculating the structural error message we don't need. + if (varianceCheckFailed && !(reportErrors && some(variances, v => (v & VarianceFlags.VarianceMask) === VarianceFlags.Invariant))) { + return Ternary.False; + } + // We remember the original error information so we can restore it in case the structural + // comparison unexpectedly succeeds. This can happen when the structural comparison result + // is a Ternary.Maybe for example caused by the recursion depth limiter. + originalErrorInfo = errorInfo; + resetErrorInfo(saveErrorInfo); } - return false; } - - return true; } - - function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement { - return isCallOrNewExpression(node) || isTaggedTemplateExpression(node) || isJsxOpeningLikeElement(node); + function reportUnmeasurableMarkers(p: TypeParameter) { + if (outofbandVarianceMarkerHandler && (p === markerSuperType || p === markerSubType || p === markerOtherType)) { + outofbandVarianceMarkerHandler(/*onlyUnreliable*/ false); + } + return p; } - - function resolveUntypedCall(node: CallLikeExpression): Signature { - if (callLikeExpressionMayHaveTypeArguments(node)) { - // Check type arguments even though we will give an error that untyped calls may not accept type arguments. - // This gets us diagnostics for the type arguments and marks them as referenced. - forEach(node.typeArguments, checkSourceElement); + function reportUnreliableMarkers(p: TypeParameter) { + if (outofbandVarianceMarkerHandler && (p === markerSuperType || p === markerSubType || p === markerOtherType)) { + outofbandVarianceMarkerHandler(/*onlyUnreliable*/ true); } - - if (node.kind === SyntaxKind.TaggedTemplateExpression) { - checkExpression(node.template); + return p; + } + // A type [P in S]: X is related to a type [Q in T]: Y if T is related to S and X' is + // related to Y, where X' is an instantiation of X in which P is replaced with Q. Notice + // that S and T are contra-variant whereas X and Y are co-variant. + function mappedTypeRelatedTo(source: MappedType, target: MappedType, reportErrors: boolean): Ternary { + const modifiersRelated = relation === comparableRelation || (relation === identityRelation ? getMappedTypeModifiers(source) === getMappedTypeModifiers(target) : + getCombinedMappedTypeOptionality(source) <= getCombinedMappedTypeOptionality(target)); + if (modifiersRelated) { + let result: Ternary; + const targetConstraint = getConstraintTypeFromMappedType(target); + const sourceConstraint = instantiateType(getConstraintTypeFromMappedType(source), getCombinedMappedTypeOptionality(source) < 0 ? reportUnmeasurableMarkers : reportUnreliableMarkers); + if (result = isRelatedTo(targetConstraint, sourceConstraint, reportErrors)) { + const mapper = createTypeMapper([getTypeParameterFromMappedType(source)], [getTypeParameterFromMappedType(target)]); + return result & isRelatedTo(instantiateType(getTemplateTypeFromMappedType(source), mapper), getTemplateTypeFromMappedType(target), reportErrors); + } + } + return Ternary.False; + } + function typeRelatedToDiscriminatedType(source: ts.Type, target: UnionType) { + // 1. Generate the combinations of discriminant properties & types 'source' can satisfy. + // a. If the number of combinations is above a set limit, the comparison is too complex. + // 2. Filter 'target' to the subset of types whose discriminants exist in the matrix. + // a. If 'target' does not satisfy all discriminants in the matrix, 'source' is not related. + // 3. For each type in the filtered 'target', determine if all non-discriminant properties of + // 'target' are related to a property in 'source'. + // + // NOTE: See ~/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts + // for examples. + const sourceProperties = getPropertiesOfObjectType(source); + const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target); + if (!sourcePropertiesFiltered) + return Ternary.False; + // Though we could compute the number of combinations as we generate + // the matrix, this would incur additional memory overhead due to + // array allocations. To reduce this overhead, we first compute + // the number of combinations to ensure we will not surpass our + // fixed limit before incurring the cost of any allocations: + let numCombinations = 1; + for (const sourceProperty of sourcePropertiesFiltered) { + numCombinations *= countTypes(getTypeOfSymbol(sourceProperty)); + if (numCombinations > 25) { + // We've reached the complexity limit. + return Ternary.False; + } } - else if (isJsxOpeningLikeElement(node)) { - checkExpression(node.attributes); + // Compute the set of types for each discriminant property. + const sourceDiscriminantTypes: ts.Type[][] = new Array(sourcePropertiesFiltered.length); + const excludedProperties = createUnderscoreEscapedMap(); + for (let i = 0; i < sourcePropertiesFiltered.length; i++) { + const sourceProperty = sourcePropertiesFiltered[i]; + const sourcePropertyType = getTypeOfSymbol(sourceProperty); + sourceDiscriminantTypes[i] = sourcePropertyType.flags & TypeFlags.Union + ? (sourcePropertyType as UnionType).types + : [sourcePropertyType]; + excludedProperties.set(sourceProperty.escapedName, true); + } + // Match each combination of the cartesian product of discriminant properties to one or more + // constituents of 'target'. If any combination does not have a match then 'source' is not relatable. + const discriminantCombinations = cartesianProduct(sourceDiscriminantTypes); + const matchingTypes: ts.Type[] = []; + for (const combination of discriminantCombinations) { + let hasMatch = false; + outer: for (const type of target.types) { + for (let i = 0; i < sourcePropertiesFiltered.length; i++) { + const sourceProperty = sourcePropertiesFiltered[i]; + const targetProperty = getPropertyOfObjectType(type, sourceProperty.escapedName); + if (!targetProperty) + continue outer; + if (sourceProperty === targetProperty) + continue; + // We compare the source property to the target in the context of a single discriminant type. + const related = propertyRelatedTo(source, target, sourceProperty, targetProperty, _ => combination[i], /*reportErrors*/ false, /*isIntersectionConstituent*/ false); + // If the target property could not be found, or if the properties were not related, + // then this constituent is not a match. + if (!related) { + continue outer; + } + } + pushIfUnique(matchingTypes, type, equateValues); + hasMatch = true; + } + if (!hasMatch) { + // We failed to match any type for this combination. + return Ternary.False; + } } - else if (node.kind !== SyntaxKind.Decorator) { - forEach((node).arguments, argument => { - checkExpression(argument); - }); + // Compare the remaining non-discriminant properties of each match. + let result = Ternary.True; + for (const type of matchingTypes) { + result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties, /*isIntersectionConstituent*/ false); + if (result) { + result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportStructuralErrors*/ false); + if (result) { + result &= signaturesRelatedTo(source, type, SignatureKind.Construct, /*reportStructuralErrors*/ false); + if (result) { + result &= indexTypesRelatedTo(source, type, IndexKind.String, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false); + if (result) { + result &= indexTypesRelatedTo(source, type, IndexKind.Number, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false); + } + } + } + } + if (!result) { + return result; + } } - return anySignature; + return result; } - - function resolveErrorCall(node: CallLikeExpression): Signature { - resolveUntypedCall(node); - return unknownSignature; + function excludeProperties(properties: ts.Symbol[], excludedProperties: UnderscoreEscapedMap | undefined) { + if (!excludedProperties || properties.length === 0) + return properties; + let result: ts.Symbol[] | undefined; + for (let i = 0; i < properties.length; i++) { + if (!excludedProperties.has(properties[i].escapedName)) { + if (result) { + result.push(properties[i]); + } + } + else if (!result) { + result = properties.slice(0, i); + } + } + return result || properties; } - - // Re-order candidate signatures into the result array. Assumes the result array to be empty. - // The candidate list orders groups in reverse, but within a group signatures are kept in declaration order - // A nit here is that we reorder only signatures that belong to the same symbol, - // so order how inherited signatures are processed is still preserved. - // interface A { (x: string): void } - // interface B extends A { (x: 'foo'): string } - // const b: B; - // b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void] - function reorderCandidates(signatures: readonly Signature[], result: Signature[], callChainFlags: SignatureFlags): void { - let lastParent: Node | undefined; - let lastSymbol: Symbol | undefined; - let cutoffIndex = 0; - let index: number | undefined; - let specializedIndex = -1; - let spliceIndex: number; - Debug.assert(!result.length); - for (const signature of signatures) { - const symbol = signature.declaration && getSymbolOfNode(signature.declaration); - const parent = signature.declaration && signature.declaration.parent; - if (!lastSymbol || symbol === lastSymbol) { - if (lastParent && parent === lastParent) { - index = index! + 1; + function isPropertySymbolTypeRelated(sourceProp: ts.Symbol, targetProp: ts.Symbol, getTypeOfSourceProperty: (sym: ts.Symbol) => ts.Type, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary { + const targetIsOptional = strictNullChecks && !!(getCheckFlags(targetProp) & CheckFlags.Partial); + const source = getTypeOfSourceProperty(sourceProp); + if (getCheckFlags(targetProp) & CheckFlags.DeferredType && !getSymbolLinks(targetProp).type) { + // Rather than resolving (and normalizing) the type, relate constituent-by-constituent without performing normalization or seconadary passes + const links = getSymbolLinks(targetProp); + Debug.assertDefined(links.deferralParent); + Debug.assertDefined(links.deferralConstituents); + const unionParent = !!(links.deferralParent!.flags & TypeFlags.Union); + let result = unionParent ? Ternary.False : Ternary.True; + const targetTypes = links.deferralConstituents!; + for (const targetType of targetTypes) { + const related = isRelatedTo(source, targetType, /*reportErrors*/ false, /*headMessage*/ undefined, /*isIntersectionConstituent*/ !unionParent); + if (!unionParent) { + if (!related) { + // Can't assign to a target individually - have to fallback to assigning to the _whole_ intersection (which forces normalization) + return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors); + } + result &= related; } else { - lastParent = parent; - index = cutoffIndex; + if (related) { + return related; + } } } - else { - // current declaration belongs to a different symbol - // set cutoffIndex so re-orderings in the future won't change result set from 0 to cutoffIndex - index = cutoffIndex = result.length; - lastParent = parent; - } - lastSymbol = symbol; - - // specialized signatures always need to be placed before non-specialized signatures regardless - // of the cutoff position; see GH#1133 - if (signatureHasLiteralTypes(signature)) { - specializedIndex++; - spliceIndex = specializedIndex; - // The cutoff index always needs to be greater than or equal to the specialized signature index - // in order to prevent non-specialized signatures from being added before a specialized - // signature. - cutoffIndex++; + if (unionParent && !result && targetIsOptional) { + result = isRelatedTo(source, undefinedType); } - else { - spliceIndex = index; + if (unionParent && !result && reportErrors) { + // The easiest way to get the right errors here is to un-defer (which may be costly) + // If it turns out this is too costly too often, we can replicate the error handling logic within + // typeRelatedToSomeType without the discriminatable type branch (as that requires a manifest union + // type on which to hand discriminable properties, which we are expressly trying to avoid here) + return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors); } - - result.splice(spliceIndex, 0, callChainFlags ? getOptionalCallSignature(signature, callChainFlags) : signature); + return result; + } + else { + return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors, /*headMessage*/ undefined, isIntersectionConstituent); } } - - function isSpreadArgument(arg: Expression | undefined): arg is Expression { - return !!arg && (arg.kind === SyntaxKind.SpreadElement || arg.kind === SyntaxKind.SyntheticExpression && (arg).isSpread); - } - - function getSpreadArgumentIndex(args: readonly Expression[]): number { - return findIndex(args, isSpreadArgument); - } - - function acceptsVoid(t: Type): boolean { - return !!(t.flags & TypeFlags.Void); - } - - function hasCorrectArity(node: CallLikeExpression, args: readonly Expression[], signature: Signature, signatureHelpTrailingComma = false) { - let argCount: number; - let callIsIncomplete = false; // In incomplete call we want to be lenient when we have too few arguments - let effectiveParameterCount = getParameterCount(signature); - let effectiveMinimumArguments = getMinArgumentCount(signature); - - if (node.kind === SyntaxKind.TaggedTemplateExpression) { - argCount = args.length; - if (node.template.kind === SyntaxKind.TemplateExpression) { - // If a tagged template expression lacks a tail literal, the call is incomplete. - // Specifically, a template only can end in a TemplateTail or a Missing literal. - const lastSpan = last(node.template.templateSpans); // we should always have at least one span. - callIsIncomplete = nodeIsMissing(lastSpan.literal) || !!lastSpan.literal.isUnterminated; + function propertyRelatedTo(source: ts.Type, target: ts.Type, sourceProp: ts.Symbol, targetProp: ts.Symbol, getTypeOfSourceProperty: (sym: ts.Symbol) => ts.Type, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary { + const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp); + const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp); + if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) { + const hasDifferingDeclarations = sourceProp.valueDeclaration !== targetProp.valueDeclaration; + if (getCheckFlags(sourceProp) & CheckFlags.ContainsPrivate && hasDifferingDeclarations) { + if (reportErrors) { + reportError(Diagnostics.Property_0_has_conflicting_declarations_and_is_inaccessible_in_type_1, symbolToString(sourceProp), typeToString(source)); + } + return Ternary.False; } - else { - // If the template didn't end in a backtick, or its beginning occurred right prior to EOF, - // then this might actually turn out to be a TemplateHead in the future; - // so we consider the call to be incomplete. - const templateLiteral = node.template; - Debug.assert(templateLiteral.kind === SyntaxKind.NoSubstitutionTemplateLiteral); - callIsIncomplete = !!templateLiteral.isUnterminated; + if (hasDifferingDeclarations) { + if (reportErrors) { + if (sourcePropFlags & ModifierFlags.Private && targetPropFlags & ModifierFlags.Private) { + reportError(Diagnostics.Types_have_separate_declarations_of_a_private_property_0, symbolToString(targetProp)); + } + else { + reportError(Diagnostics.Property_0_is_private_in_type_1_but_not_in_type_2, symbolToString(targetProp), typeToString(sourcePropFlags & ModifierFlags.Private ? source : target), typeToString(sourcePropFlags & ModifierFlags.Private ? target : source)); + } + } + return Ternary.False; } } - else if (node.kind === SyntaxKind.Decorator) { - argCount = getDecoratorArgumentCount(node, signature); + else if (targetPropFlags & ModifierFlags.Protected) { + if (!isValidOverrideOf(sourceProp, targetProp)) { + if (reportErrors) { + reportError(Diagnostics.Property_0_is_protected_but_type_1_is_not_a_class_derived_from_2, symbolToString(targetProp), typeToString(getDeclaringClass(sourceProp) || source), typeToString(getDeclaringClass(targetProp) || target)); + } + return Ternary.False; + } } - else if (isJsxOpeningLikeElement(node)) { - callIsIncomplete = node.attributes.end === node.end; - if (callIsIncomplete) { - return true; + else if (sourcePropFlags & ModifierFlags.Protected) { + if (reportErrors) { + reportError(Diagnostics.Property_0_is_protected_in_type_1_but_public_in_type_2, symbolToString(targetProp), typeToString(source), typeToString(target)); } - argCount = effectiveMinimumArguments === 0 ? args.length : 1; - effectiveParameterCount = args.length === 0 ? effectiveParameterCount : 1; // class may have argumentless ctor functions - still resolve ctor and compare vs props member type - effectiveMinimumArguments = Math.min(effectiveMinimumArguments, 1); // sfc may specify context argument - handled by framework and not typechecked + return Ternary.False; } - else { - if (!node.arguments) { - // This only happens when we have something of the form: 'new C' - Debug.assert(node.kind === SyntaxKind.NewExpression); - return getMinArgumentCount(signature) === 0; + // If the target comes from a partial union prop, allow `undefined` in the target type + const related = isPropertySymbolTypeRelated(sourceProp, targetProp, getTypeOfSourceProperty, reportErrors, isIntersectionConstituent); + if (!related) { + if (reportErrors) { + reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp)); } - - argCount = signatureHelpTrailingComma ? args.length + 1 : args.length; - - // If we are missing the close parenthesis, the call is incomplete. - callIsIncomplete = node.arguments.end === node.end; - - // If a spread argument is present, check that it corresponds to a rest parameter or at least that it's in the valid range. - const spreadArgIndex = getSpreadArgumentIndex(args); - if (spreadArgIndex >= 0) { - return spreadArgIndex >= getMinArgumentCount(signature) && (hasEffectiveRestParameter(signature) || spreadArgIndex < getParameterCount(signature)); + return Ternary.False; + } + // When checking for comparability, be more lenient with optional properties. + if (relation !== comparableRelation && sourceProp.flags & SymbolFlags.Optional && !(targetProp.flags & SymbolFlags.Optional)) { + // TypeScript 1.0 spec (April 2014): 3.8.3 + // S is a subtype of a type T, and T is a supertype of S if ... + // S' and T are object types and, for each member M in T.. + // M is a property and S' contains a property N where + // if M is a required property, N is also a required property + // (M - property in T) + // (N - property in S) + if (reportErrors) { + reportError(Diagnostics.Property_0_is_optional_in_type_1_but_required_in_type_2, symbolToString(targetProp), typeToString(source), typeToString(target)); } + return Ternary.False; } - - // Too many arguments implies incorrect arity. - if (!hasEffectiveRestParameter(signature) && argCount > effectiveParameterCount) { - return false; + return related; + } + function propertiesRelatedTo(source: ts.Type, target: ts.Type, reportErrors: boolean, excludedProperties: UnderscoreEscapedMap | undefined, isIntersectionConstituent: boolean): Ternary { + if (relation === identityRelation) { + return propertiesIdenticalTo(source, target, excludedProperties); } - - // If the call is incomplete, we should skip the lower bound check. - // JSX signatures can have extra parameters provided by the library which we don't check - if (callIsIncomplete || argCount >= effectiveMinimumArguments) { - return true; + const requireOptionalProperties = relation === subtypeRelation && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source) && !isTupleType(source); + const unmatchedProperty = getUnmatchedProperty(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false); + if (unmatchedProperty) { + if (reportErrors) { + const props = arrayFrom(getUnmatchedProperties(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false)); + let shouldSkipElaboration = false; + if (!headMessage || (headMessage.code !== Diagnostics.Class_0_incorrectly_implements_interface_1.code && + headMessage.code !== Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass.code)) { + shouldSkipElaboration = true; // Retain top-level error for interface implementing issues, otherwise omit it + } + if (props.length === 1) { + const propName = symbolToString(unmatchedProperty); + reportError(Diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2, propName, ...getTypeNamesForErrorDisplay(source, target)); + if (length(unmatchedProperty.declarations)) { + associateRelatedInfo(createDiagnosticForNode(unmatchedProperty.declarations[0], Diagnostics._0_is_declared_here, propName)); + } + if (shouldSkipElaboration && errorInfo) { + overrideNextErrorInfo++; + } + } + else if (tryElaborateArrayLikeErrors(source, target, /*reportErrors*/ false)) { + if (props.length > 5) { // arbitrary cutoff for too-long list form + reportError(Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2_and_3_more, typeToString(source), typeToString(target), map(props.slice(0, 4), p => symbolToString(p)).join(", "), props.length - 4); + } + else { + reportError(Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2, typeToString(source), typeToString(target), map(props, p => symbolToString(p)).join(", ")); + } + if (shouldSkipElaboration && errorInfo) { + overrideNextErrorInfo++; + } + } + // ELSE: No array like or unmatched property error - just issue top level error (errorInfo = undefined) + } + return Ternary.False; } - for (let i = argCount; i < effectiveMinimumArguments; i++) { - const type = getTypeAtPosition(signature, i); - if (filterType(type, acceptsVoid).flags & TypeFlags.Never) { - return false; + if (isObjectLiteralType(target)) { + for (const sourceProp of excludeProperties(getPropertiesOfType(source), excludedProperties)) { + if (!getPropertyOfObjectType(target, sourceProp.escapedName)) { + const sourceType = getTypeOfSymbol(sourceProp); + if (!(sourceType === undefinedType || sourceType === undefinedWideningType || sourceType === optionalType)) { + if (reportErrors) { + reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(sourceProp), typeToString(target)); + } + return Ternary.False; + } + } } } - return true; - } - - function hasCorrectTypeArgumentArity(signature: Signature, typeArguments: NodeArray | undefined) { - // If the user supplied type arguments, but the number of type arguments does not match - // the declared number of type parameters, the call has an incorrect arity. - const numTypeParameters = length(signature.typeParameters); - const minTypeArgumentCount = getMinTypeArgumentCount(signature.typeParameters); - return !some(typeArguments) || - (typeArguments.length >= minTypeArgumentCount && typeArguments.length <= numTypeParameters); - } - - // If type has a single call signature and no other members, return that signature. Otherwise, return undefined. - function getSingleCallSignature(type: Type): Signature | undefined { - return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false); - } - - function getSingleCallOrConstructSignature(type: Type): Signature | undefined { - return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false) || - getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ false); - } - - function getSingleSignature(type: Type, kind: SignatureKind, allowMembers: boolean): Signature | undefined { - if (type.flags & TypeFlags.Object) { - const resolved = resolveStructuredTypeMembers(type); - if (allowMembers || resolved.properties.length === 0 && !resolved.stringIndexInfo && !resolved.numberIndexInfo) { - if (kind === SignatureKind.Call && resolved.callSignatures.length === 1 && resolved.constructSignatures.length === 0) { - return resolved.callSignatures[0]; + let result = Ternary.True; + if (isTupleType(target)) { + const targetRestType = getRestTypeOfTupleType(target); + if (targetRestType) { + if (!isTupleType(source)) { + return Ternary.False; + } + const sourceRestType = getRestTypeOfTupleType(source); + if (sourceRestType && !isRelatedTo(sourceRestType, targetRestType, reportErrors)) { + if (reportErrors) { + reportError(Diagnostics.Rest_signatures_are_incompatible); + } + return Ternary.False; + } + const targetCount = getTypeReferenceArity(target) - 1; + const sourceCount = getTypeReferenceArity(source) - (sourceRestType ? 1 : 0); + const sourceTypeArguments = getTypeArguments((source)); + for (let i = targetCount; i < sourceCount; i++) { + const related = isRelatedTo(sourceTypeArguments[i], targetRestType, reportErrors); + if (!related) { + if (reportErrors) { + reportError(Diagnostics.Property_0_is_incompatible_with_rest_element_type, "" + i); + } + return Ternary.False; + } + result &= related; } - if (kind === SignatureKind.Construct && resolved.constructSignatures.length === 1 && resolved.callSignatures.length === 0) { - return resolved.constructSignatures[0]; + } + } + // We only call this for union target types when we're attempting to do excess property checking - in those cases, we want to get _all possible props_ + // from the target union, across all members + const properties = getPropertiesOfType(target); + const numericNamesOnly = isTupleType(source) && isTupleType(target); + for (const targetProp of excludeProperties(properties, excludedProperties)) { + const name = targetProp.escapedName; + if (!(targetProp.flags & SymbolFlags.Prototype) && (!numericNamesOnly || isNumericLiteralName(name) || name === "length")) { + const sourceProp = getPropertyOfType(source, name); + if (sourceProp && sourceProp !== targetProp) { + const related = propertyRelatedTo(source, target, sourceProp, targetProp, getTypeOfSymbol, reportErrors, isIntersectionConstituent); + if (!related) { + return Ternary.False; + } + result &= related; } } } - return undefined; + return result; } - - // Instantiate a generic signature in the context of a non-generic signature (section 3.8.5 in TypeScript spec) - function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, inferenceContext?: InferenceContext, compareTypes?: TypeComparer): Signature { - const context = createInferenceContext(signature.typeParameters!, signature, InferenceFlags.None, compareTypes); - // We clone the inferenceContext to avoid fixing. For example, when the source signature is (x: T) => T[] and - // the contextual signature is (...args: A) => B, we want to infer the element type of A's constraint (say 'any') - // for T but leave it possible to later infer '[any]' back to A. - const restType = getEffectiveRestType(contextualSignature); - const mapper = inferenceContext && (restType && restType.flags & TypeFlags.TypeParameter ? inferenceContext.nonFixingMapper : inferenceContext.mapper); - const sourceSignature = mapper ? instantiateSignature(contextualSignature, mapper) : contextualSignature; - applyToParameterTypes(sourceSignature, signature, (source, target) => { - // Type parameters from outer context referenced by source type are fixed by instantiation of the source type - inferTypes(context.inferences, source, target); - }); - if (!inferenceContext) { - applyToReturnTypes(contextualSignature, signature, (source, target) => { - inferTypes(context.inferences, source, target, InferencePriority.ReturnType); - }); + function propertiesIdenticalTo(source: ts.Type, target: ts.Type, excludedProperties: UnderscoreEscapedMap | undefined): Ternary { + if (!(source.flags & TypeFlags.Object && target.flags & TypeFlags.Object)) { + return Ternary.False; } - return getSignatureInstantiation(signature, getInferredTypes(context), isInJSFile(contextualSignature.declaration)); - } - - function inferJsxTypeArguments(node: JsxOpeningLikeElement, signature: Signature, checkMode: CheckMode, context: InferenceContext): Type[] { - const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node); - const checkAttrType = checkExpressionWithContextualType(node.attributes, paramType, context, checkMode); - inferTypes(context.inferences, checkAttrType, paramType); - return getInferredTypes(context); - } - - function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: readonly Expression[], checkMode: CheckMode, context: InferenceContext): Type[] { - if (isJsxOpeningLikeElement(node)) { - return inferJsxTypeArguments(node, signature, checkMode, context); + const sourceProperties = excludeProperties(getPropertiesOfObjectType(source), excludedProperties); + const targetProperties = excludeProperties(getPropertiesOfObjectType(target), excludedProperties); + if (sourceProperties.length !== targetProperties.length) { + return Ternary.False; } - - // If a contextual type is available, infer from that type to the return type of the call expression. For - // example, given a 'function wrap(cb: (x: T) => U): (x: T) => U' and a call expression - // 'let f: (x: string) => number = wrap(s => s.length)', we infer from the declared type of 'f' to the - // return type of 'wrap'. - if (node.kind !== SyntaxKind.Decorator) { - const contextualType = getContextualType(node); - if (contextualType) { - // We clone the inference context to avoid disturbing a resolution in progress for an - // outer call expression. Effectively we just want a snapshot of whatever has been - // inferred for any outer call expression so far. - const outerContext = getInferenceContext(node); - const outerMapper = getMapperFromContext(cloneInferenceContext(outerContext, InferenceFlags.NoDefault)); - const instantiatedType = instantiateType(contextualType, outerMapper); - // If the contextual type is a generic function type with a single call signature, we - // instantiate the type with its own type parameters and type arguments. This ensures that - // the type parameters are not erased to type any during type inference such that they can - // be inferred as actual types from the contextual type. For example: - // declare function arrayMap(f: (x: T) => U): (a: T[]) => U[]; - // const boxElements: (a: A[]) => { value: A }[] = arrayMap(value => ({ value })); - // Above, the type of the 'value' parameter is inferred to be 'A'. - const contextualSignature = getSingleCallSignature(instantiatedType); - const inferenceSourceType = contextualSignature && contextualSignature.typeParameters ? - getOrCreateTypeFromSignature(getSignatureInstantiationWithoutFillingInTypeArguments(contextualSignature, contextualSignature.typeParameters)) : - instantiatedType; - const inferenceTargetType = getReturnTypeOfSignature(signature); - // Inferences made from return types have lower priority than all other inferences. - inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType); - // Create a type mapper for instantiating generic contextual types using the inferences made - // from the return type. We need a separate inference pass here because (a) instantiation of - // the source type uses the outer context's return mapper (which excludes inferences made from - // outer arguments), and (b) we don't want any further inferences going into this context. - const returnContext = createInferenceContext(signature.typeParameters!, signature, context.flags); - const returnSourceType = instantiateType(contextualType, outerContext && outerContext.returnMapper); - inferTypes(returnContext.inferences, returnSourceType, inferenceTargetType); - context.returnMapper = some(returnContext.inferences, hasInferenceCandidates) ? getMapperFromContext(cloneInferredPartOfContext(returnContext)) : undefined; + let result = Ternary.True; + for (const sourceProp of sourceProperties) { + const targetProp = getPropertyOfObjectType(target, sourceProp.escapedName); + if (!targetProp) { + return Ternary.False; + } + const related = compareProperties(sourceProp, targetProp, isRelatedTo); + if (!related) { + return Ternary.False; } + result &= related; } - - const thisType = getThisTypeOfSignature(signature); - if (thisType) { - const thisArgumentNode = getThisArgumentOfCall(node); - const thisArgumentType = thisArgumentNode ? checkExpression(thisArgumentNode) : voidType; - inferTypes(context.inferences, thisArgumentType, thisType); + return result; + } + function signaturesRelatedTo(source: ts.Type, target: ts.Type, kind: SignatureKind, reportErrors: boolean): Ternary { + if (relation === identityRelation) { + return signaturesIdenticalTo(source, target, kind); } - - const restType = getNonArrayRestType(signature); - const argCount = restType ? Math.min(getParameterCount(signature) - 1, args.length) : args.length; - for (let i = 0; i < argCount; i++) { - const arg = args[i]; - if (arg.kind !== SyntaxKind.OmittedExpression) { - const paramType = getTypeAtPosition(signature, i); - const argType = checkExpressionWithContextualType(arg, paramType, context, checkMode); - inferTypes(context.inferences, argType, paramType); + if (target === anyFunctionType || source === anyFunctionType) { + return Ternary.True; + } + const sourceIsJSConstructor = source.symbol && isJSConstructor(source.symbol.valueDeclaration); + const targetIsJSConstructor = target.symbol && isJSConstructor(target.symbol.valueDeclaration); + const sourceSignatures = getSignaturesOfType(source, (sourceIsJSConstructor && kind === SignatureKind.Construct) ? + SignatureKind.Call : kind); + const targetSignatures = getSignaturesOfType(target, (targetIsJSConstructor && kind === SignatureKind.Construct) ? + SignatureKind.Call : kind); + if (kind === SignatureKind.Construct && sourceSignatures.length && targetSignatures.length) { + if (isAbstractConstructorType(source) && !isAbstractConstructorType(target)) { + // An abstract constructor type is not assignable to a non-abstract constructor type + // as it would otherwise be possible to new an abstract class. Note that the assignability + // check we perform for an extends clause excludes construct signatures from the target, + // so this check never proceeds. + if (reportErrors) { + reportError(Diagnostics.Cannot_assign_an_abstract_constructor_type_to_a_non_abstract_constructor_type); + } + return Ternary.False; + } + if (!constructorVisibilitiesAreCompatible(sourceSignatures[0], targetSignatures[0], reportErrors)) { + return Ternary.False; } } - - if (restType) { - const spreadType = getSpreadArgumentType(args, argCount, args.length, restType, context); - inferTypes(context.inferences, spreadType, restType); + let result = Ternary.True; + const saveErrorInfo = captureErrorCalculationState(); + const incompatibleReporter = kind === SignatureKind.Construct ? reportIncompatibleConstructSignatureReturn : reportIncompatibleCallSignatureReturn; + if (getObjectFlags(source) & ObjectFlags.Instantiated && getObjectFlags(target) & ObjectFlags.Instantiated && source.symbol === target.symbol) { + // We have instantiations of the same anonymous type (which typically will be the type of a + // method). Simply do a pairwise comparison of the signatures in the two signature lists instead + // of the much more expensive N * M comparison matrix we explore below. We erase type parameters + // as they are known to always be the same. + for (let i = 0; i < targetSignatures.length; i++) { + const related = signatureRelatedTo(sourceSignatures[i], targetSignatures[i], /*erase*/ true, reportErrors, incompatibleReporter(sourceSignatures[i], targetSignatures[i])); + if (!related) { + return Ternary.False; + } + result &= related; + } } - - return getInferredTypes(context); - } - - function getArrayifiedType(type: Type) { - return type.flags & TypeFlags.Union ? mapType(type, getArrayifiedType) : - type.flags & (TypeFlags.Any | TypeFlags.Instantiable) || isMutableArrayOrTuple(type) ? type : - isTupleType(type) ? createTupleType(getTypeArguments(type), type.target.minLength, type.target.hasRestElement, /*readonly*/ false, type.target.associatedNames) : - createArrayType(getIndexedAccessType(type, numberType)); - } - - function getSpreadArgumentType(args: readonly Expression[], index: number, argCount: number, restType: Type, context: InferenceContext | undefined) { - if (index >= argCount - 1) { - const arg = args[argCount - 1]; - if (isSpreadArgument(arg)) { - // We are inferring from a spread expression in the last argument position, i.e. both the parameter - // and the argument are ...x forms. - return arg.kind === SyntaxKind.SyntheticExpression ? - createArrayType((arg).type) : - getArrayifiedType(checkExpressionWithContextualType((arg).expression, restType, context, CheckMode.Normal)); - } - } - const types = []; - let spreadIndex = -1; - for (let i = index; i < argCount; i++) { - const contextualType = getIndexedAccessType(restType, getLiteralType(i - index)); - const argType = checkExpressionWithContextualType(args[i], contextualType, context, CheckMode.Normal); - if (spreadIndex < 0 && isSpreadArgument(args[i])) { - spreadIndex = i - index; - } - const hasPrimitiveContextualType = maybeTypeOfKind(contextualType, TypeFlags.Primitive | TypeFlags.Index); - types.push(hasPrimitiveContextualType ? getRegularTypeOfLiteralType(argType) : getWidenedLiteralType(argType)); - } - return spreadIndex < 0 ? - createTupleType(types) : - createTupleType(append(types.slice(0, spreadIndex), getUnionType(types.slice(spreadIndex))), spreadIndex, /*hasRestElement*/ true); - } - - function checkTypeArguments(signature: Signature, typeArgumentNodes: readonly TypeNode[], reportErrors: boolean, headMessage?: DiagnosticMessage): Type[] | undefined { - const isJavascript = isInJSFile(signature.declaration); - const typeParameters = signature.typeParameters!; - const typeArgumentTypes = fillMissingTypeArguments(map(typeArgumentNodes, getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isJavascript); - let mapper: TypeMapper | undefined; - for (let i = 0; i < typeArgumentNodes.length; i++) { - Debug.assert(typeParameters[i] !== undefined, "Should not call checkTypeArguments with too many type arguments"); - const constraint = getConstraintOfTypeParameter(typeParameters[i]); - if (constraint) { - const errorInfo = reportErrors && headMessage ? (() => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Type_0_does_not_satisfy_the_constraint_1)) : undefined; - const typeArgumentHeadMessage = headMessage || Diagnostics.Type_0_does_not_satisfy_the_constraint_1; - if (!mapper) { - mapper = createTypeMapper(typeParameters, typeArgumentTypes); - } - const typeArgument = typeArgumentTypes[i]; - if (!checkTypeAssignableTo( - typeArgument, - getTypeWithThisArgument(instantiateType(constraint, mapper), typeArgument), - reportErrors ? typeArgumentNodes[i] : undefined, - typeArgumentHeadMessage, - errorInfo)) { - return undefined; + else if (sourceSignatures.length === 1 && targetSignatures.length === 1) { + // For simple functions (functions with a single signature) we only erase type parameters for + // the comparable relation. Otherwise, if the source signature is generic, we instantiate it + // in the context of the target signature before checking the relationship. Ideally we'd do + // this regardless of the number of signatures, but the potential costs are prohibitive due + // to the quadratic nature of the logic below. + const eraseGenerics = relation === comparableRelation || !!compilerOptions.noStrictGenericChecks; + result = signatureRelatedTo(sourceSignatures[0], targetSignatures[0], eraseGenerics, reportErrors, incompatibleReporter(sourceSignatures[0], targetSignatures[0])); + } + else { + outer: for (const t of targetSignatures) { + // Only elaborate errors from the first failure + let shouldElaborateErrors = reportErrors; + for (const s of sourceSignatures) { + const related = signatureRelatedTo(s, t, /*erase*/ true, shouldElaborateErrors, incompatibleReporter(s, t)); + if (related) { + result &= related; + resetErrorInfo(saveErrorInfo); + continue outer; + } + shouldElaborateErrors = false; + } + if (shouldElaborateErrors) { + reportError(Diagnostics.Type_0_provides_no_match_for_the_signature_1, typeToString(source), signatureToString(t, /*enclosingDeclaration*/ undefined, /*flags*/ undefined, kind)); } + return Ternary.False; } } - return typeArgumentTypes; + return result; } - - function getJsxReferenceKind(node: JsxOpeningLikeElement): JsxReferenceKind { - if (isJsxIntrinsicIdentifier(node.tagName)) { - return JsxReferenceKind.Mixed; - } - const tagType = getApparentType(checkExpression(node.tagName)); - if (length(getSignaturesOfType(tagType, SignatureKind.Construct))) { - return JsxReferenceKind.Component; + function reportIncompatibleCallSignatureReturn(siga: ts.Signature, sigb: ts.Signature) { + if (siga.parameters.length === 0 && sigb.parameters.length === 0) { + return (source: ts.Type, target: ts.Type) => reportIncompatibleError(Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, typeToString(source), typeToString(target)); } - if (length(getSignaturesOfType(tagType, SignatureKind.Call))) { - return JsxReferenceKind.Function; + return (source: ts.Type, target: ts.Type) => reportIncompatibleError(Diagnostics.Call_signature_return_types_0_and_1_are_incompatible, typeToString(source), typeToString(target)); + } + function reportIncompatibleConstructSignatureReturn(siga: ts.Signature, sigb: ts.Signature) { + if (siga.parameters.length === 0 && sigb.parameters.length === 0) { + return (source: ts.Type, target: ts.Type) => reportIncompatibleError(Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, typeToString(source), typeToString(target)); } - return JsxReferenceKind.Mixed; + return (source: ts.Type, target: ts.Type) => reportIncompatibleError(Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible, typeToString(source), typeToString(target)); } - /** - * Check if the given signature can possibly be a signature called by the JSX opening-like element. - * @param node a JSX opening-like element we are trying to figure its call signature - * @param signature a candidate signature we are trying whether it is a call signature - * @param relation a relationship to check parameter and argument type + * See signatureAssignableTo, compareSignaturesIdentical */ - function checkApplicableSignatureForJsxOpeningLikeElement( - node: JsxOpeningLikeElement, - signature: Signature, - relation: Map, - checkMode: CheckMode, - reportErrors: boolean, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } - ) { - // Stateless function components can have maximum of three arguments: "props", "context", and "updater". - // However "context" and "updater" are implicit and can't be specify by users. Only the first parameter, props, - // can be specified by users through attributes property. - const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node); - const attributesType = checkExpressionWithContextualType(node.attributes, paramType, /*inferenceContext*/ undefined, checkMode); - return checkTypeRelatedToAndOptionallyElaborate( - attributesType, - paramType, - relation, - reportErrors ? node.tagName : undefined, - node.attributes, - /*headMessage*/ undefined, - containingMessageChain, - errorOutputContainer); + function signatureRelatedTo(source: ts.Signature, target: ts.Signature, erase: boolean, reportErrors: boolean, incompatibleReporter: (source: ts.Type, target: ts.Type) => void): Ternary { + return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target, CallbackCheck.None, /*ignoreReturnTypes*/ false, reportErrors, reportError, incompatibleReporter, isRelatedTo, reportUnreliableMarkers); } - - function getSignatureApplicabilityError( - node: CallLikeExpression, - args: readonly Expression[], - signature: Signature, - relation: Map, - checkMode: CheckMode, - reportErrors: boolean, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - ): readonly Diagnostic[] | undefined { - - const errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } = { errors: undefined, skipLogging: true }; - if (isJsxOpeningLikeElement(node)) { - if (!checkApplicableSignatureForJsxOpeningLikeElement(node, signature, relation, checkMode, reportErrors, containingMessageChain, errorOutputContainer)) { - Debug.assert(!reportErrors || !!errorOutputContainer.errors, "jsx should have errors when reporting errors"); - return errorOutputContainer.errors || emptyArray; - } - return undefined; + function signaturesIdenticalTo(source: ts.Type, target: ts.Type, kind: SignatureKind): Ternary { + const sourceSignatures = getSignaturesOfType(source, kind); + const targetSignatures = getSignaturesOfType(target, kind); + if (sourceSignatures.length !== targetSignatures.length) { + return Ternary.False; } - const thisType = getThisTypeOfSignature(signature); - if (thisType && thisType !== voidType && node.kind !== SyntaxKind.NewExpression) { - // If the called expression is not of the form `x.f` or `x["f"]`, then sourceType = voidType - // If the signature's 'this' type is voidType, then the check is skipped -- anything is compatible. - // If the expression is a new expression, then the check is skipped. - const thisArgumentNode = getThisArgumentOfCall(node); - let thisArgumentType: Type; - if (thisArgumentNode) { - thisArgumentType = checkExpression(thisArgumentNode); - if (isOptionalChainRoot(thisArgumentNode.parent)) { - thisArgumentType = getNonNullableType(thisArgumentType); - } - else if (isOptionalChain(thisArgumentNode.parent)) { - thisArgumentType = removeOptionalTypeMarker(thisArgumentType); - } - } - else { - thisArgumentType = voidType; - } - - const errorNode = reportErrors ? (thisArgumentNode || node) : undefined; - const headMessage = Diagnostics.The_this_context_of_type_0_is_not_assignable_to_method_s_this_of_type_1; - if (!checkTypeRelatedTo(thisArgumentType, thisType, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer)) { - Debug.assert(!reportErrors || !!errorOutputContainer.errors, "this parameter should have errors when reporting errors"); - return errorOutputContainer.errors || emptyArray; + let result = Ternary.True; + for (let i = 0; i < sourceSignatures.length; i++) { + const related = compareSignaturesIdentical(sourceSignatures[i], targetSignatures[i], /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false, isRelatedTo); + if (!related) { + return Ternary.False; } + result &= related; } - const headMessage = Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1; - const restType = getNonArrayRestType(signature); - const argCount = restType ? Math.min(getParameterCount(signature) - 1, args.length) : args.length; - for (let i = 0; i < argCount; i++) { - const arg = args[i]; - if (arg.kind !== SyntaxKind.OmittedExpression) { - const paramType = getTypeAtPosition(signature, i); - const argType = checkExpressionWithContextualType(arg, paramType, /*inferenceContext*/ undefined, checkMode); - // If one or more arguments are still excluded (as indicated by CheckMode.SkipContextSensitive), - // we obtain the regular type of any object literal arguments because we may not have inferred complete - // parameter types yet and therefore excess property checks may yield false positives (see #17041). - const checkArgType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(argType) : argType; - if (!checkTypeRelatedToAndOptionallyElaborate(checkArgType, paramType, relation, reportErrors ? arg : undefined, arg, headMessage, containingMessageChain, errorOutputContainer)) { - Debug.assert(!reportErrors || !!errorOutputContainer.errors, "parameter should have errors when reporting errors"); - maybeAddMissingAwaitInfo(arg, checkArgType, paramType); - return errorOutputContainer.errors || emptyArray; - } - } - } - if (restType) { - const spreadType = getSpreadArgumentType(args, argCount, args.length, restType, /*context*/ undefined); - const errorNode = reportErrors ? argCount < args.length ? args[argCount] : node : undefined; - if (!checkTypeRelatedTo(spreadType, restType, relation, errorNode, headMessage, /*containingMessageChain*/ undefined, errorOutputContainer)) { - Debug.assert(!reportErrors || !!errorOutputContainer.errors, "rest parameter should have errors when reporting errors"); - maybeAddMissingAwaitInfo(errorNode, spreadType, restType); - return errorOutputContainer.errors || emptyArray; + return result; + } + function eachPropertyRelatedTo(source: ts.Type, target: ts.Type, kind: IndexKind, reportErrors: boolean): Ternary { + let result = Ternary.True; + for (const prop of getPropertiesOfObjectType(source)) { + if (isIgnoredJsxProperty(source, prop)) { + continue; } - } - return undefined; - - function maybeAddMissingAwaitInfo(errorNode: Node | undefined, source: Type, target: Type) { - if (errorNode && reportErrors && errorOutputContainer.errors && errorOutputContainer.errors.length) { - // Bail if target is Promise-like---something else is wrong - if (getAwaitedTypeOfPromise(target)) { - return; - } - const awaitedTypeOfSource = getAwaitedTypeOfPromise(source); - if (awaitedTypeOfSource && isTypeRelatedTo(awaitedTypeOfSource, target, relation)) { - addRelatedInfo(errorOutputContainer.errors[0], createDiagnosticForNode(errorNode, Diagnostics.Did_you_forget_to_use_await)); + // Skip over symbol-named members + if (prop.nameType && prop.nameType.flags & TypeFlags.UniqueESSymbol) { + continue; + } + if (kind === IndexKind.String || isNumericLiteralName(prop.escapedName)) { + const related = isRelatedTo(getTypeOfSymbol(prop), target, reportErrors); + if (!related) { + if (reportErrors) { + reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, symbolToString(prop)); + } + return Ternary.False; } + result &= related; } } + return result; } - - /** - * Returns the this argument in calls like x.f(...) and x[f](...). Undefined otherwise. - */ - function getThisArgumentOfCall(node: CallLikeExpression): LeftHandSideExpression | undefined { - if (node.kind === SyntaxKind.CallExpression) { - const callee = skipOuterExpressions(node.expression); - if (callee.kind === SyntaxKind.PropertyAccessExpression || callee.kind === SyntaxKind.ElementAccessExpression) { - return (callee as AccessExpression).expression; - } + function indexInfoRelatedTo(sourceInfo: IndexInfo, targetInfo: IndexInfo, reportErrors: boolean) { + const related = isRelatedTo(sourceInfo.type, targetInfo.type, reportErrors); + if (!related && reportErrors) { + reportError(Diagnostics.Index_signatures_are_incompatible); } + return related; } - - function createSyntheticExpression(parent: Node, type: Type, isSpread?: boolean) { - const result = createNode(SyntaxKind.SyntheticExpression, parent.pos, parent.end); - result.parent = parent; - result.type = type; - result.isSpread = isSpread || false; - return result; - } - - /** - * Returns the effective arguments for an expression that works like a function invocation. - */ - function getEffectiveCallArguments(node: CallLikeExpression): readonly Expression[] { - if (node.kind === SyntaxKind.TaggedTemplateExpression) { - const template = node.template; - const args: Expression[] = [createSyntheticExpression(template, getGlobalTemplateStringsArrayType())]; - if (template.kind === SyntaxKind.TemplateExpression) { - forEach(template.templateSpans, span => { - args.push(span.expression); - }); - } - return args; + function indexTypesRelatedTo(source: ts.Type, target: ts.Type, kind: IndexKind, sourceIsPrimitive: boolean, reportErrors: boolean): Ternary { + if (relation === identityRelation) { + return indexTypesIdenticalTo(source, target, kind); } - if (node.kind === SyntaxKind.Decorator) { - return getEffectiveDecoratorArguments(node); + const targetInfo = getIndexInfoOfType(target, kind); + if (!targetInfo || targetInfo.type.flags & TypeFlags.Any && !sourceIsPrimitive) { + // Index signature of type any permits assignment from everything but primitives + return Ternary.True; } - if (isJsxOpeningLikeElement(node)) { - return node.attributes.properties.length > 0 || (isJsxOpeningElement(node) && node.parent.children.length > 0) ? [node.attributes] : emptyArray; + const sourceInfo = getIndexInfoOfType(source, kind) || + kind === IndexKind.Number && getIndexInfoOfType(source, IndexKind.String); + if (sourceInfo) { + return indexInfoRelatedTo(sourceInfo, targetInfo, reportErrors); } - const args = node.arguments || emptyArray; - const length = args.length; - if (length && isSpreadArgument(args[length - 1]) && getSpreadArgumentIndex(args) === length - 1) { - // We have a spread argument in the last position and no other spread arguments. If the type - // of the argument is a tuple type, spread the tuple elements into the argument list. We can - // call checkExpressionCached because spread expressions never have a contextual type. - const spreadArgument = args[length - 1]; - const type = flowLoopCount ? checkExpression(spreadArgument.expression) : checkExpressionCached(spreadArgument.expression); - if (isTupleType(type)) { - const typeArguments = getTypeArguments(type); - const restIndex = type.target.hasRestElement ? typeArguments.length - 1 : -1; - const syntheticArgs = map(typeArguments, (t, i) => createSyntheticExpression(spreadArgument, t, /*isSpread*/ i === restIndex)); - return concatenate(args.slice(0, length - 1), syntheticArgs); - } + if (isGenericMappedType(source)) { + // A generic mapped type { [P in K]: T } is related to an index signature { [x: string]: U } + // if T is related to U. + return kind === IndexKind.String ? isRelatedTo(getTemplateTypeFromMappedType(source), targetInfo.type, reportErrors) : Ternary.False; } - return args; - } - - /** - * Returns the synthetic argument list for a decorator invocation. - */ - function getEffectiveDecoratorArguments(node: Decorator): readonly Expression[] { - const parent = node.parent; - const expr = node.expression; - switch (parent.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - // For a class decorator, the `target` is the type of the class (e.g. the - // "static" or "constructor" side of the class). - return [ - createSyntheticExpression(expr, getTypeOfSymbol(getSymbolOfNode(parent))) - ]; - case SyntaxKind.Parameter: - // A parameter declaration decorator will have three arguments (see - // `ParameterDecorator` in core.d.ts). - const func = parent.parent; - return [ - createSyntheticExpression(expr, parent.parent.kind === SyntaxKind.Constructor ? getTypeOfSymbol(getSymbolOfNode(func)) : errorType), - createSyntheticExpression(expr, anyType), - createSyntheticExpression(expr, numberType) - ]; - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - // A method or accessor declaration decorator will have two or three arguments (see - // `PropertyDecorator` and `MethodDecorator` in core.d.ts). If we are emitting decorators - // for ES3, we will only pass two arguments. - const hasPropDesc = parent.kind !== SyntaxKind.PropertyDeclaration && languageVersion !== ScriptTarget.ES3; - return [ - createSyntheticExpression(expr, getParentTypeOfClassElement(parent)), - createSyntheticExpression(expr, getClassElementPropertyKeyType(parent)), - createSyntheticExpression(expr, hasPropDesc ? createTypedPropertyDescriptorType(getTypeOfNode(parent)) : anyType) - ]; + if (isObjectTypeWithInferableIndex(source)) { + let related = Ternary.True; + if (kind === IndexKind.String) { + const sourceNumberInfo = getIndexInfoOfType(source, IndexKind.Number); + if (sourceNumberInfo) { + related = indexInfoRelatedTo(sourceNumberInfo, targetInfo, reportErrors); + } + } + if (related) { + related &= eachPropertyRelatedTo(source, targetInfo.type, kind, reportErrors); + } + return related; } - return Debug.fail(); - } - - /** - * Returns the argument count for a decorator node that works like a function invocation. - */ - function getDecoratorArgumentCount(node: Decorator, signature: Signature) { - switch (node.parent.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - return 1; - case SyntaxKind.PropertyDeclaration: - return 2; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - // For ES3 or decorators with only two parameters we supply only two arguments - return languageVersion === ScriptTarget.ES3 || signature.parameters.length <= 2 ? 2 : 3; - case SyntaxKind.Parameter: - return 3; - default: - return Debug.fail(); + if (reportErrors) { + reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source)); } + return Ternary.False; } - function getDiagnosticSpanForCallNode(node: CallExpression, doNotIncludeArguments?: boolean) { - let start: number; - let length: number; - const sourceFile = getSourceFileOfNode(node); - - if (isPropertyAccessExpression(node.expression)) { - const nameSpan = getErrorSpanForNode(sourceFile, node.expression.name); - start = nameSpan.start; - length = doNotIncludeArguments ? nameSpan.length : node.end - start; + function indexTypesIdenticalTo(source: ts.Type, target: ts.Type, indexKind: IndexKind): Ternary { + const targetInfo = getIndexInfoOfType(target, indexKind); + const sourceInfo = getIndexInfoOfType(source, indexKind); + if (!sourceInfo && !targetInfo) { + return Ternary.True; } - else { - const expressionSpan = getErrorSpanForNode(sourceFile, node.expression); - start = expressionSpan.start; - length = doNotIncludeArguments ? expressionSpan.length : node.end - start; + if (sourceInfo && targetInfo && sourceInfo.isReadonly === targetInfo.isReadonly) { + return isRelatedTo(sourceInfo.type, targetInfo.type); } - return { start, length, sourceFile }; + return Ternary.False; } - function getDiagnosticForCallNode(node: CallLikeExpression, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): DiagnosticWithLocation { - if (isCallExpression(node)) { - const { sourceFile, start, length } = getDiagnosticSpanForCallNode(node); - return createFileDiagnostic(sourceFile, start, length, message, arg0, arg1, arg2, arg3); + function constructorVisibilitiesAreCompatible(sourceSignature: ts.Signature, targetSignature: ts.Signature, reportErrors: boolean) { + if (!sourceSignature.declaration || !targetSignature.declaration) { + return true; } - else { - return createDiagnosticForNode(node, message, arg0, arg1, arg2, arg3); + const sourceAccessibility = getSelectedModifierFlags(sourceSignature.declaration, ModifierFlags.NonPublicAccessibilityModifier); + const targetAccessibility = getSelectedModifierFlags(targetSignature.declaration, ModifierFlags.NonPublicAccessibilityModifier); + // A public, protected and private signature is assignable to a private signature. + if (targetAccessibility === ModifierFlags.Private) { + return true; } - } - - function getArgumentArityError(node: CallLikeExpression, signatures: readonly Signature[], args: readonly Expression[]) { - let min = Number.POSITIVE_INFINITY; - let max = Number.NEGATIVE_INFINITY; - let belowArgCount = Number.NEGATIVE_INFINITY; - let aboveArgCount = Number.POSITIVE_INFINITY; - - let argCount = args.length; - let closestSignature: Signature | undefined; - for (const sig of signatures) { - const minCount = getMinArgumentCount(sig); - const maxCount = getParameterCount(sig); - if (minCount < argCount && minCount > belowArgCount) belowArgCount = minCount; - if (argCount < maxCount && maxCount < aboveArgCount) aboveArgCount = maxCount; - if (minCount < min) { - min = minCount; - closestSignature = sig; - } - max = Math.max(max, maxCount); + // A public and protected signature is assignable to a protected signature. + if (targetAccessibility === ModifierFlags.Protected && sourceAccessibility !== ModifierFlags.Private) { + return true; } - - const hasRestParameter = some(signatures, hasEffectiveRestParameter); - const paramRange = hasRestParameter ? min : - min < max ? min + "-" + max : - min; - const hasSpreadArgument = getSpreadArgumentIndex(args) > -1; - if (argCount <= max && hasSpreadArgument) { - argCount--; + // Only a public signature is assignable to public signature. + if (targetAccessibility !== ModifierFlags.Protected && !sourceAccessibility) { + return true; } - - let spanArray: NodeArray; - let related: DiagnosticWithLocation | undefined; - - const error = hasRestParameter || hasSpreadArgument ? hasRestParameter && hasSpreadArgument ? Diagnostics.Expected_at_least_0_arguments_but_got_1_or_more : - hasRestParameter ? Diagnostics.Expected_at_least_0_arguments_but_got_1 : - Diagnostics.Expected_0_arguments_but_got_1_or_more : Diagnostics.Expected_0_arguments_but_got_1; - - if (closestSignature && getMinArgumentCount(closestSignature) > argCount && closestSignature.declaration) { - const paramDecl = closestSignature.declaration.parameters[closestSignature.thisParameter ? argCount + 1 : argCount]; - if (paramDecl) { - related = createDiagnosticForNode( - paramDecl, - isBindingPattern(paramDecl.name) ? Diagnostics.An_argument_matching_this_binding_pattern_was_not_provided : Diagnostics.An_argument_for_0_was_not_provided, - !paramDecl.name ? argCount : !isBindingPattern(paramDecl.name) ? idText(getFirstIdentifier(paramDecl.name)) : undefined - ); + if (reportErrors) { + reportError(Diagnostics.Cannot_assign_a_0_constructor_type_to_a_1_constructor_type, visibilityToString(sourceAccessibility), visibilityToString(targetAccessibility)); + } + return false; + } + } + function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => ts.Type, __String][], related: (source: ts.Type, target: ts.Type) => boolean | Ternary): ts.Type | undefined; + function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => ts.Type, __String][], related: (source: ts.Type, target: ts.Type) => boolean | Ternary, defaultValue: ts.Type): ts.Type; + function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => ts.Type, __String][], related: (source: ts.Type, target: ts.Type) => boolean | Ternary, defaultValue?: ts.Type) { + // undefined=unknown, true=discriminated, false=not discriminated + // The state of each type progresses from left to right. Discriminated types stop at 'true'. + const discriminable = target.types.map(_ => undefined) as (boolean | undefined)[]; + for (const [getDiscriminatingType, propertyName] of discriminators) { + let i = 0; + for (const type of target.types) { + const targetType = getTypeOfPropertyOfType(type, propertyName); + if (targetType && related(getDiscriminatingType(), targetType)) { + discriminable[i] = discriminable[i] === undefined ? true : discriminable[i]; } + else { + discriminable[i] = false; + } + i++; } - if (min < argCount && argCount < max) { - return getDiagnosticForCallNode(node, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, argCount, belowArgCount, aboveArgCount); + } + const match = discriminable.indexOf(/*searchElement*/ true); + // make sure exactly 1 matches before returning it + return match === -1 || discriminable.indexOf(/*searchElement*/ true, match + 1) !== -1 ? defaultValue : target.types[match]; + } + /** + * A type is 'weak' if it is an object type with at least one optional property + * and no required properties, call/construct signatures or index signatures + */ + function isWeakType(type: ts.Type): boolean { + if (type.flags & TypeFlags.Object) { + const resolved = resolveStructuredTypeMembers((type)); + return resolved.callSignatures.length === 0 && resolved.constructSignatures.length === 0 && + !resolved.stringIndexInfo && !resolved.numberIndexInfo && + resolved.properties.length > 0 && + every(resolved.properties, p => !!(p.flags & SymbolFlags.Optional)); + } + if (type.flags & TypeFlags.Intersection) { + return every((type).types, isWeakType); + } + return false; + } + function hasCommonProperties(source: ts.Type, target: ts.Type, isComparingJsxAttributes: boolean) { + for (const prop of getPropertiesOfType(source)) { + if (isKnownProperty(target, prop.escapedName, isComparingJsxAttributes)) { + return true; } - - if (!hasSpreadArgument && argCount < min) { - const diagnostic = getDiagnosticForCallNode(node, error, paramRange, argCount); - return related ? addRelatedInfo(diagnostic, related) : diagnostic; + } + return false; + } + // Return a type reference where the source type parameter is replaced with the target marker + // type, and flag the result as a marker type reference. + function getMarkerTypeReference(type: GenericType, source: TypeParameter, target: ts.Type) { + const result = createTypeReference(type, map(type.typeParameters, t => t === source ? target : t)); + result.objectFlags |= ObjectFlags.MarkerType; + return result; + } + function getAliasVariances(symbol: ts.Symbol) { + const links = getSymbolLinks(symbol); + return getVariancesWorker(links.typeParameters, links, (_links, param, marker) => { + const type = getTypeAliasInstantiation(symbol, instantiateTypes(links.typeParameters!, makeUnaryTypeMapper(param, marker))); + type.aliasTypeArgumentsContainsMarker = true; + return type; + }); + } + // Return an array containing the variance of each type parameter. The variance is effectively + // a digest of the type comparisons that occur for each type argument when instantiations of the + // generic type are structurally compared. We infer the variance information by comparing + // instantiations of the generic type for type arguments with known relations. The function + // returns the emptyArray singleton if we're not in strictFunctionTypes mode or if the function + // has been invoked recursively for the given generic type. + function getVariancesWorker(typeParameters: readonly TypeParameter[] = emptyArray, cache: TCache, createMarkerType: (input: TCache, param: TypeParameter, marker: ts.Type) => ts.Type): VarianceFlags[] { + let variances = cache.variances; + if (!variances) { + // The emptyArray singleton is used to signal a recursive invocation. + cache.variances = emptyArray; + variances = []; + for (const tp of typeParameters) { + let unmeasurable = false; + let unreliable = false; + const oldHandler = outofbandVarianceMarkerHandler; + outofbandVarianceMarkerHandler = (onlyUnreliable) => onlyUnreliable ? unreliable = true : unmeasurable = true; + // We first compare instantiations where the type parameter is replaced with + // marker types that have a known subtype relationship. From this we can infer + // invariance, covariance, contravariance or bivariance. + const typeWithSuper = createMarkerType(cache, tp, markerSuperType); + const typeWithSub = createMarkerType(cache, tp, markerSubType); + let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? VarianceFlags.Covariant : 0) | + (isTypeAssignableTo(typeWithSuper, typeWithSub) ? VarianceFlags.Contravariant : 0); + // If the instantiations appear to be related bivariantly it may be because the + // type parameter is independent (i.e. it isn't witnessed anywhere in the generic + // type). To determine this we compare instantiations where the type parameter is + // replaced with marker types that are known to be unrelated. + if (variance === VarianceFlags.Bivariant && isTypeAssignableTo(createMarkerType(cache, tp, markerOtherType), typeWithSuper)) { + variance = VarianceFlags.Independent; + } + outofbandVarianceMarkerHandler = oldHandler; + if (unmeasurable || unreliable) { + if (unmeasurable) { + variance |= VarianceFlags.Unmeasurable; + } + if (unreliable) { + variance |= VarianceFlags.Unreliable; + } + } + variances.push(variance); + } + cache.variances = variances; + } + return variances; + } + function getVariances(type: GenericType): VarianceFlags[] { + // Arrays and tuples are known to be covariant, no need to spend time computing this (emptyArray implies covariance for all parameters) + if (type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple) { + return emptyArray; + } + return getVariancesWorker(type.typeParameters, type, getMarkerTypeReference); + } + // Return true if the given type reference has a 'void' type argument for a covariant type parameter. + // See comment at call in recursiveTypeRelatedTo for when this case matters. + function hasCovariantVoidArgument(typeArguments: readonly ts.Type[], variances: VarianceFlags[]): boolean { + for (let i = 0; i < variances.length; i++) { + if ((variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Covariant && typeArguments[i].flags & TypeFlags.Void) { + return true; } - - if (hasRestParameter || hasSpreadArgument) { - spanArray = createNodeArray(args); - if (hasSpreadArgument && argCount) { - const nextArg = elementAt(args, getSpreadArgumentIndex(args) + 1) || undefined; - spanArray = createNodeArray(args.slice(max > argCount && nextArg ? args.indexOf(nextArg) : Math.min(max, args.length - 1))); - } + } + return false; + } + function isUnconstrainedTypeParameter(type: ts.Type) { + return type.flags & TypeFlags.TypeParameter && !getConstraintOfTypeParameter((type)); + } + function isNonDeferredTypeReference(type: ts.Type): type is TypeReference { + return !!(getObjectFlags(type) & ObjectFlags.Reference) && !(type).node; + } + function isTypeReferenceWithGenericArguments(type: ts.Type): boolean { + return isNonDeferredTypeReference(type) && some(getTypeArguments(type), t => isUnconstrainedTypeParameter(t) || isTypeReferenceWithGenericArguments(t)); + } + /** + * getTypeReferenceId(A) returns "111=0-12=1" + * where A.id=111 and number.id=12 + */ + function getTypeReferenceId(type: TypeReference, typeParameters: ts.Type[], depth = 0) { + let result = "" + type.target.id; + for (const t of getTypeArguments(type)) { + if (isUnconstrainedTypeParameter(t)) { + let index = typeParameters.indexOf(t); + if (index < 0) { + index = typeParameters.length; + typeParameters.push(t); + } + result += "=" + index; + } + else if (depth < 4 && isTypeReferenceWithGenericArguments(t)) { + result += "<" + getTypeReferenceId((t as TypeReference), typeParameters, depth + 1) + ">"; } else { - spanArray = createNodeArray(args.slice(max)); - } - - spanArray.pos = first(spanArray).pos; - spanArray.end = last(spanArray).end; - if (spanArray.end === spanArray.pos) { - spanArray.end++; + result += "-" + t.id; } - const diagnostic = createDiagnosticForNodeArray( - getSourceFileOfNode(node), spanArray, error, paramRange, argCount); - return related ? addRelatedInfo(diagnostic, related) : diagnostic; } - - function getTypeArgumentArityError(node: Node, signatures: readonly Signature[], typeArguments: NodeArray) { - const argCount = typeArguments.length; - // No overloads exist - if (signatures.length === 1) { - const sig = signatures[0]; - const min = getMinTypeArgumentCount(sig.typeParameters); - const max = length(sig.typeParameters); - return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, min < max ? min + "-" + max : min , argCount); - } - // Overloads exist - let belowArgCount = -Infinity; - let aboveArgCount = Infinity; - for (const sig of signatures) { - const min = getMinTypeArgumentCount(sig.typeParameters); - const max = length(sig.typeParameters); - if (min > argCount) { - aboveArgCount = Math.min(aboveArgCount, min); - } - else if (max < argCount) { - belowArgCount = Math.max(belowArgCount, max); + return result; + } + /** + * To improve caching, the relation key for two generic types uses the target's id plus ids of the type parameters. + * For other cases, the types ids are used. + */ + function getRelationKey(source: ts.Type, target: ts.Type, isIntersectionConstituent: boolean, relation: ts.Map) { + if (relation === identityRelation && source.id > target.id) { + const temp = source; + source = target; + target = temp; + } + const intersection = isIntersectionConstituent ? "&" : ""; + if (isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target)) { + const typeParameters: ts.Type[] = []; + return getTypeReferenceId((source), typeParameters) + "," + getTypeReferenceId((target), typeParameters) + intersection; + } + return source.id + "," + target.id + intersection; + } + // Invoke the callback for each underlying property symbol of the given symbol and return the first + // value that isn't undefined. + function forEachProperty(prop: ts.Symbol, callback: (p: ts.Symbol) => T): T | undefined { + if (getCheckFlags(prop) & CheckFlags.Synthetic) { + for (const t of (prop).containingType!.types) { + const p = getPropertyOfType(t, prop.escapedName); + const result = p && forEachProperty(p, callback); + if (result) { + return result; } } - if (belowArgCount !== -Infinity && aboveArgCount !== Infinity) { - return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.No_overload_expects_0_type_arguments_but_overloads_do_exist_that_expect_either_1_or_2_type_arguments, argCount, belowArgCount, aboveArgCount); - } - return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount); + return undefined; } - - function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, callChainFlags: SignatureFlags, fallbackError?: DiagnosticMessage): Signature { - const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; - const isDecorator = node.kind === SyntaxKind.Decorator; - const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node); - const reportErrors = !candidatesOutArray; - - let typeArguments: NodeArray | undefined; - - if (!isDecorator) { - typeArguments = (node).typeArguments; - - // We already perform checking on the type arguments on the class declaration itself. - if (isTaggedTemplate || isJsxOpeningOrSelfClosingElement || (node).expression.kind !== SyntaxKind.SuperKeyword) { - forEach(typeArguments, checkSourceElement); + return callback(prop); + } + // Return the declaring class type of a property or undefined if property not declared in class + function getDeclaringClass(prop: ts.Symbol) { + return prop.parent && prop.parent.flags & SymbolFlags.Class ? getDeclaredTypeOfSymbol(getParentOfSymbol(prop)!) : undefined; + } + // Return true if some underlying source property is declared in a class that derives + // from the given base class. + function isPropertyInClassDerivedFrom(prop: ts.Symbol, baseClass: ts.Type | undefined) { + return forEachProperty(prop, sp => { + const sourceClass = getDeclaringClass(sp); + return sourceClass ? hasBaseType(sourceClass, baseClass) : false; + }); + } + // Return true if source property is a valid override of protected parts of target property. + function isValidOverrideOf(sourceProp: ts.Symbol, targetProp: ts.Symbol) { + return !forEachProperty(targetProp, tp => getDeclarationModifierFlagsFromSymbol(tp) & ModifierFlags.Protected ? + !isPropertyInClassDerivedFrom(sourceProp, getDeclaringClass(tp)) : false); + } + // Return true if the given class derives from each of the declaring classes of the protected + // constituents of the given property. + function isClassDerivedFromDeclaringClasses(checkClass: ts.Type, prop: ts.Symbol) { + return forEachProperty(prop, p => getDeclarationModifierFlagsFromSymbol(p) & ModifierFlags.Protected ? + !hasBaseType(checkClass, getDeclaringClass(p)) : false) ? undefined : checkClass; + } + // Return true if the given type is deeply nested. We consider this to be the case when structural type comparisons + // for 5 or more occurrences or instantiations of the type have been recorded on the given stack. It is possible, + // though highly unlikely, for this test to be true in a situation where a chain of instantiations is not infinitely + // expanding. Effectively, we will generate a false positive when two types are structurally equal to at least 5 + // levels, but unequal at some level beyond that. + // In addition, this will also detect when an indexed access has been chained off of 5 or more times (which is essentially + // the dual of the structural comparison), and likewise mark the type as deeply nested, potentially adding false positives + // for finite but deeply expanding indexed accesses (eg, for `Q[P1][P2][P3][P4][P5]`). + function isDeeplyNestedType(type: ts.Type, stack: ts.Type[], depth: number): boolean { + // We track all object types that have an associated symbol (representing the origin of the type) + if (depth >= 5 && type.flags & TypeFlags.Object && !isObjectOrArrayLiteralType(type)) { + const symbol = type.symbol; + if (symbol) { + let count = 0; + for (let i = 0; i < depth; i++) { + const t = stack[i]; + if (t.flags & TypeFlags.Object && t.symbol === symbol) { + count++; + if (count >= 5) + return true; + } } } - - const candidates = candidatesOutArray || []; - // reorderCandidates fills up the candidates array directly - reorderCandidates(signatures, candidates, callChainFlags); - if (!candidates.length) { - if (reportErrors) { - diagnostics.add(getDiagnosticForCallNode(node, Diagnostics.Call_target_does_not_contain_any_signatures)); + } + if (depth >= 5 && type.flags & TypeFlags.IndexedAccess) { + const root = getRootObjectTypeFromIndexedAccessChain(type); + let count = 0; + for (let i = 0; i < depth; i++) { + const t = stack[i]; + if (getRootObjectTypeFromIndexedAccessChain(t) === root) { + count++; + if (count >= 5) + return true; } - return resolveErrorCall(node); - } - - const args = getEffectiveCallArguments(node); - - // The excludeArgument array contains true for each context sensitive argument (an argument - // is context sensitive it is susceptible to a one-time permanent contextual typing). - // - // The idea is that we will perform type argument inference & assignability checking once - // without using the susceptible parameters that are functions, and once more for those - // parameters, contextually typing each as we go along. - // - // For a tagged template, then the first argument be 'undefined' if necessary because it - // represents a TemplateStringsArray. - // - // For a decorator, no arguments are susceptible to contextual typing due to the fact - // decorators are applied to a declaration by the emitter, and not to an expression. - const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters; - let argCheckMode = !isDecorator && !isSingleNonGenericCandidate && some(args, isContextSensitive) ? CheckMode.SkipContextSensitive : CheckMode.Normal; - - // The following variables are captured and modified by calls to chooseOverload. - // If overload resolution or type argument inference fails, we want to report the - // best error possible. The best error is one which says that an argument was not - // assignable to a parameter. This implies that everything else about the overload - // was fine. So if there is any overload that is only incorrect because of an - // argument, we will report an error on that one. - // - // function foo(s: string): void; - // function foo(n: number): void; // Report argument error on this overload - // function foo(): void; - // foo(true); - // - // If none of the overloads even made it that far, there are two possibilities. - // There was a problem with type arguments for some overload, in which case - // report an error on that. Or none of the overloads even had correct arity, - // in which case give an arity error. - // - // function foo(x: T): void; // Report type argument error - // function foo(): void; - // foo(0); - // - let candidatesForArgumentError: Signature[] | undefined; - let candidateForArgumentArityError: Signature | undefined; - let candidateForTypeArgumentError: Signature | undefined; - let result: Signature | undefined; - - // If we are in signature help, a trailing comma indicates that we intend to provide another argument, - // so we will only accept overloads with arity at least 1 higher than the current number of provided arguments. - const signatureHelpTrailingComma = - !!(checkMode & CheckMode.IsForSignatureHelp) && node.kind === SyntaxKind.CallExpression && node.arguments.hasTrailingComma; - - // Section 4.12.1: - // if the candidate list contains one or more signatures for which the type of each argument - // expression is a subtype of each corresponding parameter type, the return type of the first - // of those signatures becomes the return type of the function call. - // Otherwise, the return type of the first signature in the candidate list becomes the return - // type of the function call. - // - // Whether the call is an error is determined by assignability of the arguments. The subtype pass - // is just important for choosing the best signature. So in the case where there is only one - // signature, the subtype pass is useless. So skipping it is an optimization. - if (candidates.length > 1) { - result = chooseOverload(candidates, subtypeRelation, signatureHelpTrailingComma); } - if (!result) { - result = chooseOverload(candidates, assignableRelation, signatureHelpTrailingComma); + } + return false; + } + /** + * Gets the leftmost object type in a chain of indexed accesses, eg, in A[P][Q], returns A + */ + function getRootObjectTypeFromIndexedAccessChain(type: ts.Type) { + let t = type; + while (t.flags & TypeFlags.IndexedAccess) { + t = (t as IndexedAccessType).objectType; + } + return t; + } + function isPropertyIdenticalTo(sourceProp: ts.Symbol, targetProp: ts.Symbol): boolean { + return compareProperties(sourceProp, targetProp, compareTypesIdentical) !== Ternary.False; + } + function compareProperties(sourceProp: ts.Symbol, targetProp: ts.Symbol, compareTypes: (source: ts.Type, target: ts.Type) => Ternary): Ternary { + // Two members are considered identical when + // - they are public properties with identical names, optionality, and types, + // - they are private or protected properties originating in the same declaration and having identical types + if (sourceProp === targetProp) { + return Ternary.True; + } + const sourcePropAccessibility = getDeclarationModifierFlagsFromSymbol(sourceProp) & ModifierFlags.NonPublicAccessibilityModifier; + const targetPropAccessibility = getDeclarationModifierFlagsFromSymbol(targetProp) & ModifierFlags.NonPublicAccessibilityModifier; + if (sourcePropAccessibility !== targetPropAccessibility) { + return Ternary.False; + } + if (sourcePropAccessibility) { + if (getTargetSymbol(sourceProp) !== getTargetSymbol(targetProp)) { + return Ternary.False; } - if (result) { - return result; + } + else { + if ((sourceProp.flags & SymbolFlags.Optional) !== (targetProp.flags & SymbolFlags.Optional)) { + return Ternary.False; } - - // No signatures were applicable. Now report errors based on the last applicable signature with - // no arguments excluded from assignability checks. - // If candidate is undefined, it means that no candidates had a suitable arity. In that case, - // skip the checkApplicableSignature check. - if (reportErrors) { - if (candidatesForArgumentError) { - if (candidatesForArgumentError.length === 1 || candidatesForArgumentError.length > 3) { - const last = candidatesForArgumentError[candidatesForArgumentError.length - 1]; - let chain: DiagnosticMessageChain | undefined; - if (candidatesForArgumentError.length > 3) { - chain = chainDiagnosticMessages(chain, Diagnostics.The_last_overload_gave_the_following_error); - chain = chainDiagnosticMessages(chain, Diagnostics.No_overload_matches_this_call); - } - const diags = getSignatureApplicabilityError(node, args, last, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, () => chain); - if (diags) { - for (const d of diags) { - if (last.declaration && candidatesForArgumentError.length > 3) { - addRelatedInfo(d, createDiagnosticForNode(last.declaration, Diagnostics.The_last_overload_is_declared_here)); - } - diagnostics.add(d); - } - } - else { - Debug.fail("No error for last overload signature"); - } - } - else { - const allDiagnostics: (readonly DiagnosticRelatedInformation[])[] = []; - let max = 0; - let min = Number.MAX_VALUE; - let minIndex = 0; - let i = 0; - for (const c of candidatesForArgumentError) { - const chain = () => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Overload_0_of_1_2_gave_the_following_error, i + 1, candidates.length, signatureToString(c)); - const diags = getSignatureApplicabilityError(node, args, c, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, chain); - if (diags) { - if (diags.length <= min) { - min = diags.length; - minIndex = i; - } - max = Math.max(max, diags.length); - allDiagnostics.push(diags); - } - else { - Debug.fail("No error for 3 or fewer overload signatures"); - } - i++; - } - - const diags = max > 1 ? allDiagnostics[minIndex] : flatten(allDiagnostics); - Debug.assert(diags.length > 0, "No errors reported for 3 or fewer overload signatures"); - const chain = chainDiagnosticMessages( - map(diags, d => typeof d.messageText === "string" ? (d as DiagnosticMessageChain) : d.messageText), - Diagnostics.No_overload_matches_this_call); - const related = flatMap(diags, d => (d as Diagnostic).relatedInformation) as DiagnosticRelatedInformation[]; - if (every(diags, d => d.start === diags[0].start && d.length === diags[0].length && d.file === diags[0].file)) { - const { file, start, length } = diags[0]; - diagnostics.add({ file, start, length, code: chain.code, category: chain.category, messageText: chain, relatedInformation: related }); - } - else { - diagnostics.add(createDiagnosticForNodeFromMessageChain(node, chain, related)); - } - } - } - else if (candidateForArgumentArityError) { - diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args)); - } - else if (candidateForTypeArgumentError) { - checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!, /*reportErrors*/ true, fallbackError); - } - else { - const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments)); - if (signaturesWithCorrectTypeArgumentArity.length === 0) { - diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments!)); - } - else if (!isDecorator) { - diagnostics.add(getArgumentArityError(node, signaturesWithCorrectTypeArgumentArity, args)); - } - else if (fallbackError) { - diagnostics.add(getDiagnosticForCallNode(node, fallbackError)); - } + } + if (isReadonlySymbol(sourceProp) !== isReadonlySymbol(targetProp)) { + return Ternary.False; + } + return compareTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp)); + } + function isMatchingSignature(source: ts.Signature, target: ts.Signature, partialMatch: boolean) { + const sourceParameterCount = getParameterCount(source); + const targetParameterCount = getParameterCount(target); + const sourceMinArgumentCount = getMinArgumentCount(source); + const targetMinArgumentCount = getMinArgumentCount(target); + const sourceHasRestParameter = hasEffectiveRestParameter(source); + const targetHasRestParameter = hasEffectiveRestParameter(target); + // A source signature matches a target signature if the two signatures have the same number of required, + // optional, and rest parameters. + if (sourceParameterCount === targetParameterCount && + sourceMinArgumentCount === targetMinArgumentCount && + sourceHasRestParameter === targetHasRestParameter) { + return true; + } + // A source signature partially matches a target signature if the target signature has no fewer required + // parameters + if (partialMatch && sourceMinArgumentCount <= targetMinArgumentCount) { + return true; + } + return false; + } + /** + * See signatureRelatedTo, compareSignaturesIdentical + */ + function compareSignaturesIdentical(source: ts.Signature, target: ts.Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean, compareTypes: (s: ts.Type, t: ts.Type) => Ternary): Ternary { + // TODO (drosen): De-duplicate code between related functions. + if (source === target) { + return Ternary.True; + } + if (!(isMatchingSignature(source, target, partialMatch))) { + return Ternary.False; + } + // Check that the two signatures have the same number of type parameters. + if (length(source.typeParameters) !== length(target.typeParameters)) { + return Ternary.False; + } + // Check that type parameter constraints and defaults match. If they do, instantiate the source + // signature with the type parameters of the target signature and continue the comparison. + if (target.typeParameters) { + const mapper = createTypeMapper(source.typeParameters!, target.typeParameters); + for (let i = 0; i < target.typeParameters.length; i++) { + const s = source.typeParameters![i]; + const t = target.typeParameters[i]; + if (!(s === t || compareTypes(instantiateType(getConstraintFromTypeParameter(s), mapper) || unknownType, getConstraintFromTypeParameter(t) || unknownType) && + compareTypes(instantiateType(getDefaultFromTypeParameter(s), mapper) || unknownType, getDefaultFromTypeParameter(t) || unknownType))) { + return Ternary.False; } } - - return produceDiagnostics || !args ? resolveErrorCall(node) : getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray); - - function chooseOverload(candidates: Signature[], relation: Map, signatureHelpTrailingComma = false) { - candidatesForArgumentError = undefined; - candidateForArgumentArityError = undefined; - candidateForTypeArgumentError = undefined; - - if (isSingleNonGenericCandidate) { - const candidate = candidates[0]; - if (some(typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { - return undefined; - } - if (getSignatureApplicabilityError(node, args, candidate, relation, CheckMode.Normal, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { - candidatesForArgumentError = [candidate]; - return undefined; - } - return candidate; - } - - for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) { - const candidate = candidates[candidateIndex]; - if (!hasCorrectTypeArgumentArity(candidate, typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { - continue; - } - - let checkCandidate: Signature; - let inferenceContext: InferenceContext | undefined; - - if (candidate.typeParameters) { - let typeArgumentTypes: Type[] | undefined; - if (some(typeArguments)) { - typeArgumentTypes = checkTypeArguments(candidate, typeArguments, /*reportErrors*/ false); - if (!typeArgumentTypes) { - candidateForTypeArgumentError = candidate; - continue; - } - } - else { - inferenceContext = createInferenceContext(candidate.typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None); - typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext); - argCheckMode |= inferenceContext.flags & InferenceFlags.SkippedGenericFunction ? CheckMode.SkipGenericFunctions : CheckMode.Normal; - } - checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters); - // If the original signature has a generic rest type, instantiation may produce a - // signature with different arity and we need to perform another arity check. - if (getNonArrayRestType(candidate) && !hasCorrectArity(node, args, checkCandidate, signatureHelpTrailingComma)) { - candidateForArgumentArityError = checkCandidate; - continue; - } - } - else { - checkCandidate = candidate; - } - if (getSignatureApplicabilityError(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { - // Give preference to error candidates that have no rest parameters (as they are more specific) - (candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate); - continue; - } - if (argCheckMode) { - // If one or more context sensitive arguments were excluded, we start including - // them now (and keeping do so for any subsequent candidates) and perform a second - // round of type inference and applicability checking for this particular candidate. - argCheckMode = CheckMode.Normal; - if (inferenceContext) { - const typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode, inferenceContext); - checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters); - // If the original signature has a generic rest type, instantiation may produce a - // signature with different arity and we need to perform another arity check. - if (getNonArrayRestType(candidate) && !hasCorrectArity(node, args, checkCandidate, signatureHelpTrailingComma)) { - candidateForArgumentArityError = checkCandidate; - continue; - } - } - if (getSignatureApplicabilityError(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { - // Give preference to error candidates that have no rest parameters (as they are more specific) - (candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate); - continue; - } + source = instantiateSignature(source, mapper, /*eraseTypeParameters*/ true); + } + let result = Ternary.True; + if (!ignoreThisTypes) { + const sourceThisType = getThisTypeOfSignature(source); + if (sourceThisType) { + const targetThisType = getThisTypeOfSignature(target); + if (targetThisType) { + const related = compareTypes(sourceThisType, targetThisType); + if (!related) { + return Ternary.False; } - candidates[candidateIndex] = checkCandidate; - return checkCandidate; + result &= related; } - - return undefined; } } - - // No signature was applicable. We have already reported the errors for the invalid signature. - // If this is a type resolution session, e.g. Language Service, try to get better information than anySignature. - function getCandidateForOverloadFailure( - node: CallLikeExpression, - candidates: Signature[], - args: readonly Expression[], - hasCandidatesOutArray: boolean, - ): Signature { - Debug.assert(candidates.length > 0); // Else should not have called this. - // Normally we will combine overloads. Skip this if they have type parameters since that's hard to combine. - // Don't do this if there is a `candidatesOutArray`, - // because then we want the chosen best candidate to be one of the overloads, not a combination. - return hasCandidatesOutArray || candidates.length === 1 || candidates.some(c => !!c.typeParameters) - ? pickLongestCandidateSignature(node, candidates, args) - : createUnionOfSignaturesForOverloadFailure(candidates); + const targetLen = getParameterCount(target); + for (let i = 0; i < targetLen; i++) { + const s = getTypeAtPosition(source, i); + const t = getTypeAtPosition(target, i); + const related = compareTypes(t, s); + if (!related) { + return Ternary.False; + } + result &= related; + } + if (!ignoreReturnTypes) { + const sourceTypePredicate = getTypePredicateOfSignature(source); + const targetTypePredicate = getTypePredicateOfSignature(target); + result &= sourceTypePredicate || targetTypePredicate ? + compareTypePredicatesIdentical(sourceTypePredicate, targetTypePredicate, compareTypes) : + compareTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); + } + return result; + } + function compareTypePredicatesIdentical(source: TypePredicate | undefined, target: TypePredicate | undefined, compareTypes: (s: ts.Type, t: ts.Type) => Ternary): Ternary { + return !(source && target && typePredicateKindsMatch(source, target)) ? Ternary.False : + source.type === target.type ? Ternary.True : + source.type && target.type ? compareTypes(source.type, target.type) : + Ternary.False; + } + function literalTypesWithSameBaseType(types: ts.Type[]): boolean { + let commonBaseType: ts.Type | undefined; + for (const t of types) { + const baseType = getBaseTypeOfLiteralType(t); + if (!commonBaseType) { + commonBaseType = baseType; + } + if (baseType === t || baseType !== commonBaseType) { + return false; + } + } + return true; + } + // When the candidate types are all literal types with the same base type, return a union + // of those literal types. Otherwise, return the leftmost type for which no type to the + // right is a supertype. + function getSupertypeOrUnion(types: ts.Type[]): ts.Type { + return literalTypesWithSameBaseType(types) ? + getUnionType(types) : + reduceLeft(types, (s, t) => isTypeSubtypeOf(s, t) ? t : s)!; + } + function getCommonSupertype(types: ts.Type[]): ts.Type { + if (!strictNullChecks) { + return getSupertypeOrUnion(types); + } + const primaryTypes = filter(types, t => !(t.flags & TypeFlags.Nullable)); + return primaryTypes.length ? + getNullableType(getSupertypeOrUnion(primaryTypes), getFalsyFlagsOfTypes(types) & TypeFlags.Nullable) : + getUnionType(types, UnionReduction.Subtype); + } + // Return the leftmost type for which no type to the right is a subtype. + function getCommonSubtype(types: ts.Type[]) { + return reduceLeft(types, (s, t) => isTypeSubtypeOf(t, s) ? t : s)!; + } + function isArrayType(type: ts.Type): boolean { + return !!(getObjectFlags(type) & ObjectFlags.Reference) && ((type).target === globalArrayType || (type).target === globalReadonlyArrayType); + } + function isReadonlyArrayType(type: ts.Type): boolean { + return !!(getObjectFlags(type) & ObjectFlags.Reference) && (type).target === globalReadonlyArrayType; + } + function isMutableArrayOrTuple(type: ts.Type): boolean { + return isArrayType(type) && !isReadonlyArrayType(type) || isTupleType(type) && !type.target.readonly; + } + function getElementTypeOfArrayType(type: ts.Type): ts.Type | undefined { + return isArrayType(type) ? getTypeArguments((type as TypeReference))[0] : undefined; + } + function isArrayLikeType(type: ts.Type): boolean { + // A type is array-like if it is a reference to the global Array or global ReadonlyArray type, + // or if it is not the undefined or null type and if it is assignable to ReadonlyArray + return isArrayType(type) || !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType); + } + function isEmptyArrayLiteralType(type: ts.Type): boolean { + const elementType = isArrayType(type) ? getTypeArguments((type))[0] : undefined; + return elementType === undefinedWideningType || elementType === implicitNeverType; + } + function isTupleLikeType(type: ts.Type): boolean { + return isTupleType(type) || !!getPropertyOfType(type, ("0" as __String)); + } + function isArrayOrTupleLikeType(type: ts.Type): boolean { + return isArrayLikeType(type) || isTupleLikeType(type); + } + function getTupleElementType(type: ts.Type, index: number) { + const propType = getTypeOfPropertyOfType(type, ("" + index as __String)); + if (propType) { + return propType; } - - function createUnionOfSignaturesForOverloadFailure(candidates: readonly Signature[]): Signature { - const thisParameters = mapDefined(candidates, c => c.thisParameter); - let thisParameter: Symbol | undefined; - if (thisParameters.length) { - thisParameter = createCombinedSymbolFromTypes(thisParameters, thisParameters.map(getTypeOfParameter)); - } - const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters); - const parameters: Symbol[] = []; - for (let i = 0; i < maxNonRestParam; i++) { - const symbols = mapDefined(candidates, s => signatureHasRestParameter(s) ? - i < s.parameters.length - 1 ? s.parameters[i] : last(s.parameters) : - i < s.parameters.length ? s.parameters[i] : undefined); - Debug.assert(symbols.length !== 0); - parameters.push(createCombinedSymbolFromTypes(symbols, mapDefined(candidates, candidate => tryGetTypeAtPosition(candidate, i)))); - } - const restParameterSymbols = mapDefined(candidates, c => signatureHasRestParameter(c) ? last(c.parameters) : undefined); - let flags = SignatureFlags.None; - if (restParameterSymbols.length !== 0) { - const type = createArrayType(getUnionType(mapDefined(candidates, tryGetRestTypeOfSignature), UnionReduction.Subtype)); - parameters.push(createCombinedSymbolForOverloadFailure(restParameterSymbols, type)); - flags |= SignatureFlags.HasRestParameter; - } - if (candidates.some(signatureHasLiteralTypes)) { - flags |= SignatureFlags.HasLiteralTypes; - } - return createSignature( - candidates[0].declaration, - /*typeParameters*/ undefined, // Before calling this we tested for `!candidates.some(c => !!c.typeParameters)`. - thisParameter, - parameters, - /*resolvedReturnType*/ getIntersectionType(candidates.map(getReturnTypeOfSignature)), - /*typePredicate*/ undefined, - minArgumentCount, - flags); + if (everyType(type, isTupleType)) { + return mapType(type, t => getRestTypeOfTupleType((t)) || undefinedType); } - - function getNumNonRestParameters(signature: Signature): number { - const numParams = signature.parameters.length; - return signatureHasRestParameter(signature) ? numParams - 1 : numParams; + return undefined; + } + function isNeitherUnitTypeNorNever(type: ts.Type): boolean { + return !(type.flags & (TypeFlags.Unit | TypeFlags.Never)); + } + function isUnitType(type: ts.Type): boolean { + return !!(type.flags & TypeFlags.Unit); + } + function isLiteralType(type: ts.Type): boolean { + return type.flags & TypeFlags.Boolean ? true : + type.flags & TypeFlags.Union ? type.flags & TypeFlags.EnumLiteral ? true : every((type).types, isUnitType) : + isUnitType(type); + } + function getBaseTypeOfLiteralType(type: ts.Type): ts.Type { + return type.flags & TypeFlags.EnumLiteral ? getBaseTypeOfEnumLiteralType((type)) : + type.flags & TypeFlags.StringLiteral ? stringType : + type.flags & TypeFlags.NumberLiteral ? numberType : + type.flags & TypeFlags.BigIntLiteral ? bigintType : + type.flags & TypeFlags.BooleanLiteral ? booleanType : + type.flags & TypeFlags.Union ? getUnionType(sameMap((type).types, getBaseTypeOfLiteralType)) : + type; + } + function getWidenedLiteralType(type: ts.Type): ts.Type { + return type.flags & TypeFlags.EnumLiteral && isFreshLiteralType(type) ? getBaseTypeOfEnumLiteralType((type)) : + type.flags & TypeFlags.StringLiteral && isFreshLiteralType(type) ? stringType : + type.flags & TypeFlags.NumberLiteral && isFreshLiteralType(type) ? numberType : + type.flags & TypeFlags.BigIntLiteral && isFreshLiteralType(type) ? bigintType : + type.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(type) ? booleanType : + type.flags & TypeFlags.Union ? getUnionType(sameMap((type).types, getWidenedLiteralType)) : + type; + } + function getWidenedUniqueESSymbolType(type: ts.Type): ts.Type { + return type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : + type.flags & TypeFlags.Union ? getUnionType(sameMap((type).types, getWidenedUniqueESSymbolType)) : + type; + } + function getWidenedLiteralLikeTypeForContextualType(type: ts.Type, contextualType: ts.Type | undefined) { + if (!isLiteralOfContextualType(type, contextualType)) { + type = getWidenedUniqueESSymbolType(getWidenedLiteralType(type)); } - - function createCombinedSymbolFromTypes(sources: readonly Symbol[], types: Type[]): Symbol { - return createCombinedSymbolForOverloadFailure(sources, getUnionType(types, UnionReduction.Subtype)); + return type; + } + function getWidenedLiteralLikeTypeForContextualReturnTypeIfNeeded(type: ts.Type | undefined, contextualSignatureReturnType: ts.Type | undefined, isAsync: boolean) { + if (type && isUnitType(type)) { + const contextualType = !contextualSignatureReturnType ? undefined : + isAsync ? getPromisedTypeOfPromise(contextualSignatureReturnType) : + contextualSignatureReturnType; + type = getWidenedLiteralLikeTypeForContextualType(type, contextualType); } - - function createCombinedSymbolForOverloadFailure(sources: readonly Symbol[], type: Type): Symbol { - // This function is currently only used for erroneous overloads, so it's good enough to just use the first source. - return createSymbolWithType(first(sources), type); + return type; + } + function getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(type: ts.Type | undefined, contextualSignatureReturnType: ts.Type | undefined, kind: IterationTypeKind, isAsyncGenerator: boolean) { + if (type && isUnitType(type)) { + const contextualType = !contextualSignatureReturnType ? undefined : + getIterationTypeOfGeneratorFunctionReturnType(kind, contextualSignatureReturnType, isAsyncGenerator); + type = getWidenedLiteralLikeTypeForContextualType(type, contextualType); } - - function pickLongestCandidateSignature(node: CallLikeExpression, candidates: Signature[], args: readonly Expression[]): Signature { - // Pick the longest signature. This way we can get a contextual type for cases like: - // declare function f(a: { xa: number; xb: number; }, b: number); - // f({ | - // Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like: - // declare function f(k: keyof T); - // f(" - const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args.length : apparentArgumentCount); - const candidate = candidates[bestIndex]; - const { typeParameters } = candidate; - if (!typeParameters) { - return candidate; - } - - const typeArgumentNodes: readonly TypeNode[] | undefined = callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments : undefined; - const instantiated = typeArgumentNodes - ? createSignatureInstantiation(candidate, getTypeArgumentsFromNodes(typeArgumentNodes, typeParameters, isInJSFile(node))) - : inferSignatureInstantiationForOverloadFailure(node, typeParameters, candidate, args); - candidates[bestIndex] = instantiated; - return instantiated; + return type; + } + /** + * Check if a Type was written as a tuple type literal. + * Prefer using isTupleLikeType() unless the use of `elementTypes` is required. + */ + function isTupleType(type: ts.Type): type is TupleTypeReference { + return !!(getObjectFlags(type) & ObjectFlags.Reference && (type).target.objectFlags & ObjectFlags.Tuple); + } + function getRestTypeOfTupleType(type: TupleTypeReference) { + return type.target.hasRestElement ? getTypeArguments(type)[type.target.typeParameters!.length - 1] : undefined; + } + function getRestArrayTypeOfTupleType(type: TupleTypeReference) { + const restType = getRestTypeOfTupleType(type); + return restType && createArrayType(restType); + } + function getLengthOfTupleType(type: TupleTypeReference) { + return getTypeReferenceArity(type) - (type.target.hasRestElement ? 1 : 0); + } + function isZeroBigInt({ value }: BigIntLiteralType) { + return value.base10Value === "0"; + } + function getFalsyFlagsOfTypes(types: ts.Type[]): TypeFlags { + let result: TypeFlags = 0; + for (const t of types) { + result |= getFalsyFlags(t); } - - function getTypeArgumentsFromNodes(typeArgumentNodes: readonly TypeNode[], typeParameters: readonly TypeParameter[], isJs: boolean): readonly Type[] { - const typeArguments = typeArgumentNodes.map(getTypeOfNode); - while (typeArguments.length > typeParameters.length) { - typeArguments.pop(); - } - while (typeArguments.length < typeParameters.length) { - typeArguments.push(getConstraintOfTypeParameter(typeParameters[typeArguments.length]) || getDefaultTypeArgumentType(isJs)); - } - return typeArguments; + return result; + } + // Returns the String, Number, Boolean, StringLiteral, NumberLiteral, BooleanLiteral, Void, Undefined, or Null + // flags for the string, number, boolean, "", 0, false, void, undefined, or null types respectively. Returns + // no flags for all other types (including non-falsy literal types). + function getFalsyFlags(type: ts.Type): TypeFlags { + return type.flags & TypeFlags.Union ? getFalsyFlagsOfTypes((type).types) : + type.flags & TypeFlags.StringLiteral ? (type).value === "" ? TypeFlags.StringLiteral : 0 : + type.flags & TypeFlags.NumberLiteral ? (type).value === 0 ? TypeFlags.NumberLiteral : 0 : + type.flags & TypeFlags.BigIntLiteral ? isZeroBigInt((type)) ? TypeFlags.BigIntLiteral : 0 : + type.flags & TypeFlags.BooleanLiteral ? (type === falseType || type === regularFalseType) ? TypeFlags.BooleanLiteral : 0 : + type.flags & TypeFlags.PossiblyFalsy; + } + function removeDefinitelyFalsyTypes(type: ts.Type): ts.Type { + return getFalsyFlags(type) & TypeFlags.DefinitelyFalsy ? + filterType(type, t => !(getFalsyFlags(t) & TypeFlags.DefinitelyFalsy)) : + type; + } + function extractDefinitelyFalsyTypes(type: ts.Type): ts.Type { + return mapType(type, getDefinitelyFalsyPartOfType); + } + function getDefinitelyFalsyPartOfType(type: ts.Type): ts.Type { + return type.flags & TypeFlags.String ? emptyStringType : + type.flags & TypeFlags.Number ? zeroType : + type.flags & TypeFlags.BigInt ? zeroBigIntType : + type === regularFalseType || + type === falseType || + type.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null) || + type.flags & TypeFlags.StringLiteral && (type).value === "" || + type.flags & TypeFlags.NumberLiteral && (type).value === 0 || + type.flags & TypeFlags.BigIntLiteral && isZeroBigInt((type)) ? type : + neverType; + } + /** + * Add undefined or null or both to a type if they are missing. + * @param type - type to add undefined and/or null to if not present + * @param flags - Either TypeFlags.Undefined or TypeFlags.Null, or both + */ + function getNullableType(type: ts.Type, flags: TypeFlags): ts.Type { + const missing = (flags & ~type.flags) & (TypeFlags.Undefined | TypeFlags.Null); + return missing === 0 ? type : + missing === TypeFlags.Undefined ? getUnionType([type, undefinedType]) : + missing === TypeFlags.Null ? getUnionType([type, nullType]) : + getUnionType([type, undefinedType, nullType]); + } + function getOptionalType(type: ts.Type): ts.Type { + Debug.assert(strictNullChecks); + return type.flags & TypeFlags.Undefined ? type : getUnionType([type, undefinedType]); + } + function getGlobalNonNullableTypeInstantiation(type: ts.Type) { + if (!deferredGlobalNonNullableTypeAlias) { + deferredGlobalNonNullableTypeAlias = getGlobalSymbol(("NonNullable" as __String), SymbolFlags.TypeAlias, /*diagnostic*/ undefined) || unknownSymbol; } - - function inferSignatureInstantiationForOverloadFailure(node: CallLikeExpression, typeParameters: readonly TypeParameter[], candidate: Signature, args: readonly Expression[]): Signature { - const inferenceContext = createInferenceContext(typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None); - const typeArgumentTypes = inferTypeArguments(node, candidate, args, CheckMode.SkipContextSensitive | CheckMode.SkipGenericFunctions, inferenceContext); - return createSignatureInstantiation(candidate, typeArgumentTypes); + // Use NonNullable global type alias if available to improve quick info/declaration emit + if (deferredGlobalNonNullableTypeAlias !== unknownSymbol) { + return getTypeAliasInstantiation(deferredGlobalNonNullableTypeAlias, [type]); } - - function getLongestCandidateIndex(candidates: Signature[], argsCount: number): number { - let maxParamsIndex = -1; - let maxParams = -1; - - for (let i = 0; i < candidates.length; i++) { - const candidate = candidates[i]; - const paramCount = getParameterCount(candidate); - if (hasEffectiveRestParameter(candidate) || paramCount >= argsCount) { - return i; - } - if (paramCount > maxParams) { - maxParams = paramCount; - maxParamsIndex = i; - } - } - - return maxParamsIndex; + return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); // Type alias unavailable, fall back to non-higher-order behavior + } + function getNonNullableType(type: ts.Type): ts.Type { + return strictNullChecks ? getGlobalNonNullableTypeInstantiation(type) : type; + } + function addOptionalTypeMarker(type: ts.Type) { + return strictNullChecks ? getUnionType([type, optionalType]) : type; + } + function isNotOptionalTypeMarker(type: ts.Type) { + return type !== optionalType; + } + function removeOptionalTypeMarker(type: ts.Type): ts.Type { + return strictNullChecks ? filterType(type, isNotOptionalTypeMarker) : type; + } + function propagateOptionalTypeMarker(type: ts.Type, node: OptionalChain, wasOptional: boolean) { + return wasOptional ? isOutermostOptionalChain(node) ? getOptionalType(type) : addOptionalTypeMarker(type) : type; + } + function getOptionalExpressionType(exprType: ts.Type, expression: Expression) { + return isExpressionOfOptionalChainRoot(expression) ? getNonNullableType(exprType) : + isOptionalChain(expression) ? removeOptionalTypeMarker(exprType) : + exprType; + } + /** + * Is source potentially coercible to target type under `==`. + * Assumes that `source` is a constituent of a union, hence + * the boolean literal flag on the LHS, but not on the RHS. + * + * This does not fully replicate the semantics of `==`. The + * intention is to catch cases that are clearly not right. + * + * Comparing (string | number) to number should not remove the + * string element. + * + * Comparing (string | number) to 1 will remove the string + * element, though this is not sound. This is a pragmatic + * choice. + * + * @see narrowTypeByEquality + * + * @param source + * @param target + */ + function isCoercibleUnderDoubleEquals(source: ts.Type, target: ts.Type): boolean { + return ((source.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.BooleanLiteral)) !== 0) + && ((target.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.Boolean)) !== 0); + } + /** + * Return true if type was inferred from an object literal, written as an object type literal, or is the shape of a module + * with no call or construct signatures. + */ + function isObjectTypeWithInferableIndex(type: ts.Type): boolean { + return !!(type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum | SymbolFlags.ValueModule)) !== 0 && + !typeHasCallOrConstructSignatures(type)) || !!(getObjectFlags(type) & ObjectFlags.ReverseMapped && isObjectTypeWithInferableIndex((type as ReverseMappedType).source)); + } + function createSymbolWithType(source: ts.Symbol, type: ts.Type | undefined) { + const symbol = createSymbol(source.flags, source.escapedName, getCheckFlags(source) & CheckFlags.Readonly); + symbol.declarations = source.declarations; + symbol.parent = source.parent; + symbol.type = type; + symbol.target = source; + if (source.valueDeclaration) { + symbol.valueDeclaration = source.valueDeclaration; + } + if (source.nameType) { + symbol.nameType = source.nameType; + } + return symbol; + } + function transformTypeOfMembers(type: ts.Type, f: (propertyType: ts.Type) => ts.Type) { + const members = createSymbolTable(); + for (const property of getPropertiesOfObjectType(type)) { + const original = getTypeOfSymbol(property); + const updated = f(original); + members.set(property.escapedName, updated === original ? property : createSymbolWithType(property, updated)); + } + return members; + } + /** + * If the the provided object literal is subject to the excess properties check, + * create a new that is exempt. Recursively mark object literal members as exempt. + * Leave signatures alone since they are not subject to the check. + */ + function getRegularTypeOfObjectLiteral(type: ts.Type): ts.Type { + if (!(isObjectLiteralType(type) && getObjectFlags(type) & ObjectFlags.FreshLiteral)) { + return type; } - - function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { - if (node.expression.kind === SyntaxKind.SuperKeyword) { - const superType = checkSuperExpression(node.expression); - if (isTypeAny(superType)) { - for (const arg of node.arguments) { - checkExpression(arg); // Still visit arguments so they get marked for visibility, etc - } - return anySignature; - } - if (superType !== errorType) { - // In super call, the candidate signatures are the matching arity signatures of the base constructor function instantiated - // with the type arguments specified in the extends clause. - const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!); - if (baseTypeNode) { - const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode); - return resolveCall(node, baseConstructors, candidatesOutArray, checkMode, SignatureFlags.None); + const regularType = (type).regularType; + if (regularType) { + return regularType; + } + const resolved = (type); + const members = transformTypeOfMembers(type, getRegularTypeOfObjectLiteral); + const regularNew = createAnonymousType(resolved.symbol, members, resolved.callSignatures, resolved.constructSignatures, resolved.stringIndexInfo, resolved.numberIndexInfo); + regularNew.flags = resolved.flags; + regularNew.objectFlags |= resolved.objectFlags & ~ObjectFlags.FreshLiteral; + (type).regularType = regularNew; + return regularNew; + } + function createWideningContext(parent: WideningContext | undefined, propertyName: __String | undefined, siblings: ts.Type[] | undefined): WideningContext { + return { parent, propertyName, siblings, resolvedProperties: undefined }; + } + function getSiblingsOfContext(context: WideningContext): ts.Type[] { + if (!context.siblings) { + const siblings: ts.Type[] = []; + for (const type of getSiblingsOfContext(context.parent!)) { + if (isObjectLiteralType(type)) { + const prop = getPropertyOfObjectType(type, context.propertyName!); + if (prop) { + forEachType(getTypeOfSymbol(prop), t => { + siblings.push(t); + }); } } - return resolveUntypedCall(node); - } - - let callChainFlags: SignatureFlags; - let funcType = checkExpression(node.expression); - if (isCallChain(node)) { - const nonOptionalType = getOptionalExpressionType(funcType, node.expression); - callChainFlags = nonOptionalType === funcType ? SignatureFlags.None : - isOutermostOptionalChain(node) ? SignatureFlags.IsOuterCallChain : - SignatureFlags.IsInnerCallChain; - funcType = nonOptionalType; - } - else { - callChainFlags = SignatureFlags.None; - } - - funcType = checkNonNullTypeWithReporter( - funcType, - node.expression, - reportCannotInvokePossiblyNullOrUndefinedError - ); - - if (funcType === silentNeverType) { - return silentNeverSignature; - } - - const apparentType = getApparentType(funcType); - if (apparentType === errorType) { - // Another error has already been reported - return resolveErrorCall(node); } - - // Technically, this signatures list may be incomplete. We are taking the apparent type, - // but we are not including call signatures that may have been added to the Object or - // Function interface, since they have none by default. This is a bit of a leap of faith - // that the user will not add any. - const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); - const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; - - // TS 1.0 Spec: 4.12 - // In an untyped function call no TypeArgs are permitted, Args can be any argument list, no contextual - // types are provided for the argument expressions, and the result is always of type Any. - if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) { - // The unknownType indicates that an error already occurred (and was reported). No - // need to report another error in this case. - if (funcType !== errorType && node.typeArguments) { - error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments); - } - return resolveUntypedCall(node); - } - // If FuncExpr's apparent type(section 3.8.1) is a function type, the call is a typed function call. - // TypeScript employs overload resolution in typed function calls in order to support functions - // with multiple call signatures. - if (!callSignatures.length) { - if (numConstructSignatures) { - error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType)); - } - else { - let relatedInformation: DiagnosticRelatedInformation | undefined; - if (node.arguments.length === 1) { - const text = getSourceFileOfNode(node).text; - if (isLineBreak(text.charCodeAt(skipTrivia(text, node.expression.end, /* stopAfterLineBreak */ true) - 1))) { - relatedInformation = createDiagnosticForNode(node.expression, Diagnostics.It_is_highly_likely_that_you_are_missing_a_semicolon); - } + context.siblings = siblings; + } + return context.siblings; + } + function getPropertiesOfContext(context: WideningContext): ts.Symbol[] { + if (!context.resolvedProperties) { + const names = (createMap() as UnderscoreEscapedMap); + for (const t of getSiblingsOfContext(context)) { + if (isObjectLiteralType(t) && !(getObjectFlags(t) & ObjectFlags.ContainsSpread)) { + for (const prop of getPropertiesOfType(t)) { + names.set(prop.escapedName, prop); } - invocationError(node.expression, apparentType, SignatureKind.Call, relatedInformation); } - return resolveErrorCall(node); } - // When a call to a generic function is an argument to an outer call to a generic function for which - // inference is in process, we have a choice to make. If the inner call relies on inferences made from - // its contextual type to its return type, deferring the inner call processing allows the best possible - // contextual type to accumulate. But if the outer call relies on inferences made from the return type of - // the inner call, the inner call should be processed early. There's no sure way to know which choice is - // right (only a full unification algorithm can determine that), so we resort to the following heuristic: - // If no type arguments are specified in the inner call and at least one call signature is generic and - // returns a function type, we choose to defer processing. This narrowly permits function composition - // operators to flow inferences through return types, but otherwise processes calls right away. We - // use the resolvingSignature singleton to indicate that we deferred processing. This result will be - // propagated out and eventually turned into nonInferrableType (a type that is assignable to anything and - // from which we never make inferences). - if (checkMode & CheckMode.SkipGenericFunctions && !node.typeArguments && callSignatures.some(isGenericFunctionReturningFunction)) { - skippedGenericFunction(node, checkMode); - return resolvingSignature; - } - // If the function is explicitly marked with `@class`, then it must be constructed. - if (callSignatures.some(sig => isInJSFile(sig.declaration) && !!getJSDocClassTag(sig.declaration!))) { - error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType)); - return resolveErrorCall(node); - } - - return resolveCall(node, callSignatures, candidatesOutArray, checkMode, callChainFlags); + context.resolvedProperties = arrayFrom(names.values()); } - - function isGenericFunctionReturningFunction(signature: Signature) { - return !!(signature.typeParameters && isFunctionType(getReturnTypeOfSignature(signature))); - } - - /** - * TS 1.0 spec: 4.12 - * If FuncExpr is of type Any, or of an object type that has no call or construct signatures - * but is a subtype of the Function interface, the call is an untyped function call. - */ - function isUntypedFunctionCall(funcType: Type, apparentFuncType: Type, numCallSignatures: number, numConstructSignatures: number): boolean { - // We exclude union types because we may have a union of function types that happen to have no common signatures. - return isTypeAny(funcType) || isTypeAny(apparentFuncType) && !!(funcType.flags & TypeFlags.TypeParameter) || - !numCallSignatures && !numConstructSignatures && !(apparentFuncType.flags & (TypeFlags.Union | TypeFlags.Never)) && isTypeAssignableTo(funcType, globalFunctionType); + return context.resolvedProperties; + } + function getWidenedProperty(prop: ts.Symbol, context: WideningContext | undefined): ts.Symbol { + if (!(prop.flags & SymbolFlags.Property)) { + // Since get accessors already widen their return value there is no need to + // widen accessor based properties here. + return prop; + } + const original = getTypeOfSymbol(prop); + const propContext = context && createWideningContext(context, prop.escapedName, /*siblings*/ undefined); + const widened = getWidenedTypeWithContext(original, propContext); + return widened === original ? prop : createSymbolWithType(prop, widened); + } + function getUndefinedProperty(prop: ts.Symbol) { + const cached = undefinedProperties.get(prop.escapedName); + if (cached) { + return cached; + } + const result = createSymbolWithType(prop, undefinedType); + result.flags |= SymbolFlags.Optional; + undefinedProperties.set(prop.escapedName, result); + return result; + } + function getWidenedTypeOfObjectLiteral(type: ts.Type, context: WideningContext | undefined): ts.Type { + const members = createSymbolTable(); + for (const prop of getPropertiesOfObjectType(type)) { + members.set(prop.escapedName, getWidenedProperty(prop, context)); } - - function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { - if (node.arguments && languageVersion < ScriptTarget.ES5) { - const spreadIndex = getSpreadArgumentIndex(node.arguments); - if (spreadIndex >= 0) { - error(node.arguments[spreadIndex], Diagnostics.Spread_operator_in_new_expressions_is_only_available_when_targeting_ECMAScript_5_and_higher); + if (context) { + for (const prop of getPropertiesOfContext(context)) { + if (!members.has(prop.escapedName)) { + members.set(prop.escapedName, getUndefinedProperty(prop)); } } - - let expressionType = checkNonNullExpression(node.expression); - if (expressionType === silentNeverType) { - return silentNeverSignature; - } - - // If expressionType's apparent type(section 3.8.1) is an object type with one or - // more construct signatures, the expression is processed in the same manner as a - // function call, but using the construct signatures as the initial set of candidate - // signatures for overload resolution. The result type of the function call becomes - // the result type of the operation. - expressionType = getApparentType(expressionType); - if (expressionType === errorType) { - // Another error has already been reported - return resolveErrorCall(node); + } + const stringIndexInfo = getIndexInfoOfType(type, IndexKind.String); + const numberIndexInfo = getIndexInfoOfType(type, IndexKind.Number); + const result = createAnonymousType(type.symbol, members, emptyArray, emptyArray, stringIndexInfo && createIndexInfo(getWidenedType(stringIndexInfo.type), stringIndexInfo.isReadonly), numberIndexInfo && createIndexInfo(getWidenedType(numberIndexInfo.type), numberIndexInfo.isReadonly)); + result.objectFlags |= (getObjectFlags(type) & (ObjectFlags.JSLiteral | ObjectFlags.NonInferrableType)); // Retain js literal flag through widening + return result; + } + function getWidenedType(type: ts.Type) { + return getWidenedTypeWithContext(type, /*context*/ undefined); + } + function getWidenedTypeWithContext(type: ts.Type, context: WideningContext | undefined): ts.Type { + if (getObjectFlags(type) & ObjectFlags.RequiresWidening) { + if (context === undefined && type.widened) { + return type.widened; + } + let result: ts.Type | undefined; + if (type.flags & (TypeFlags.Any | TypeFlags.Nullable)) { + result = anyType; + } + else if (isObjectLiteralType(type)) { + result = getWidenedTypeOfObjectLiteral(type, context); + } + else if (type.flags & TypeFlags.Union) { + const unionContext = context || createWideningContext(/*parent*/ undefined, /*propertyName*/ undefined, (type).types); + const widenedTypes = sameMap((type).types, t => t.flags & TypeFlags.Nullable ? t : getWidenedTypeWithContext(t, unionContext)); + // Widening an empty object literal transitions from a highly restrictive type to + // a highly inclusive one. For that reason we perform subtype reduction here if the + // union includes empty object types (e.g. reducing {} | string to just {}). + result = getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType) ? UnionReduction.Subtype : UnionReduction.Literal); } - - // TS 1.0 spec: 4.11 - // If expressionType is of type Any, Args can be any argument - // list and the result of the operation is of type Any. - if (isTypeAny(expressionType)) { - if (node.typeArguments) { - error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments); - } - return resolveUntypedCall(node); + else if (type.flags & TypeFlags.Intersection) { + result = getIntersectionType(sameMap((type).types, getWidenedType)); } - - // Technically, this signatures list may be incomplete. We are taking the apparent type, - // but we are not including construct signatures that may have been added to the Object or - // Function interface, since they have none by default. This is a bit of a leap of faith - // that the user will not add any. - const constructSignatures = getSignaturesOfType(expressionType, SignatureKind.Construct); - if (constructSignatures.length) { - if (!isConstructorAccessible(node, constructSignatures[0])) { - return resolveErrorCall(node); - } - // If the expression is a class of abstract type, then it cannot be instantiated. - // Note, only class declarations can be declared abstract. - // In the case of a merged class-module or class-interface declaration, - // only the class declaration node will have the Abstract flag set. - const valueDecl = expressionType.symbol && getClassLikeDeclarationOfSymbol(expressionType.symbol); - if (valueDecl && hasModifier(valueDecl, ModifierFlags.Abstract)) { - error(node, Diagnostics.Cannot_create_an_instance_of_an_abstract_class); - return resolveErrorCall(node); - } - - return resolveCall(node, constructSignatures, candidatesOutArray, checkMode, SignatureFlags.None); + else if (isArrayType(type) || isTupleType(type)) { + result = createTypeReference((type).target, sameMap(getTypeArguments((type)), getWidenedType)); } - - // If expressionType's apparent type is an object type with no construct signatures but - // one or more call signatures, the expression is processed as a function call. A compile-time - // error occurs if the result of the function call is not Void. The type of the result of the - // operation is Any. It is an error to have a Void this type. - const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call); - if (callSignatures.length) { - const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); - if (!noImplicitAny) { - if (signature.declaration && !isJSConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) { - error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword); - } - if (getThisTypeOfSignature(signature) === voidType) { - error(node, Diagnostics.A_function_that_is_called_with_the_new_keyword_cannot_have_a_this_type_that_is_void); - } - } - return signature; + if (result && context === undefined) { + type.widened = result; } - - invocationError(node.expression, expressionType, SignatureKind.Construct); - return resolveErrorCall(node); + return result || type; } - - function typeHasProtectedAccessibleBase(target: Symbol, type: InterfaceType): boolean { - const baseTypes = getBaseTypes(type); - if (!length(baseTypes)) { - return false; - } - const firstBase = baseTypes[0]; - if (firstBase.flags & TypeFlags.Intersection) { - const types = (firstBase as IntersectionType).types; - const mixinFlags = findMixins(types); - let i = 0; - for (const intersectionMember of (firstBase as IntersectionType).types) { - // We want to ignore mixin ctors - if (!mixinFlags[i]) { - if (getObjectFlags(intersectionMember) & (ObjectFlags.Class | ObjectFlags.Interface)) { - if (intersectionMember.symbol === target) { - return true; - } - if (typeHasProtectedAccessibleBase(target, intersectionMember as InterfaceType)) { - return true; - } + return type; + } + /** + * Reports implicit any errors that occur as a result of widening 'null' and 'undefined' + * to 'any'. A call to reportWideningErrorsInType is normally accompanied by a call to + * getWidenedType. But in some cases getWidenedType is called without reporting errors + * (type argument inference is an example). + * + * The return value indicates whether an error was in fact reported. The particular circumstances + * are on a best effort basis. Currently, if the null or undefined that causes widening is inside + * an object literal property (arbitrarily deeply), this function reports an error. If no error is + * reported, reportImplicitAnyError is a suitable fallback to report a general error. + */ + function reportWideningErrorsInType(type: ts.Type): boolean { + let errorReported = false; + if (getObjectFlags(type) & ObjectFlags.ContainsWideningType) { + if (type.flags & TypeFlags.Union) { + if (some((type).types, isEmptyObjectType)) { + errorReported = true; + } + else { + for (const t of (type).types) { + if (reportWideningErrorsInType(t)) { + errorReported = true; } } - i++; } - return false; - } - if (firstBase.symbol === target) { - return true; - } - return typeHasProtectedAccessibleBase(target, firstBase as InterfaceType); - } - - function isConstructorAccessible(node: NewExpression, signature: Signature) { - if (!signature || !signature.declaration) { - return true; - } - - const declaration = signature.declaration; - const modifiers = getSelectedModifierFlags(declaration, ModifierFlags.NonPublicAccessibilityModifier); - - // Public constructor is accessible. - if (!modifiers) { - return true; } - - const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(declaration.parent.symbol)!; - const declaringClass = getDeclaredTypeOfSymbol(declaration.parent.symbol); - - // A private or protected constructor can only be instantiated within its own class (or a subclass, for protected) - if (!isNodeWithinClass(node, declaringClassDeclaration)) { - const containingClass = getContainingClass(node); - if (containingClass && modifiers & ModifierFlags.Protected) { - const containingType = getTypeOfNode(containingClass); - if (typeHasProtectedAccessibleBase(declaration.parent.symbol, containingType as InterfaceType)) { - return true; + if (isArrayType(type) || isTupleType(type)) { + for (const t of getTypeArguments((type))) { + if (reportWideningErrorsInType(t)) { + errorReported = true; } } - if (modifiers & ModifierFlags.Private) { - error(node, Diagnostics.Constructor_of_class_0_is_private_and_only_accessible_within_the_class_declaration, typeToString(declaringClass)); - } - if (modifiers & ModifierFlags.Protected) { - error(node, Diagnostics.Constructor_of_class_0_is_protected_and_only_accessible_within_the_class_declaration, typeToString(declaringClass)); - } - return false; } - - return true; - } - - function invocationErrorDetails(apparentType: Type, kind: SignatureKind): { messageChain: DiagnosticMessageChain, relatedMessage: DiagnosticMessage | undefined } { - let errorInfo: DiagnosticMessageChain | undefined; - const isCall = kind === SignatureKind.Call; - const awaitedType = getAwaitedType(apparentType); - const maybeMissingAwait = awaitedType && getSignaturesOfType(awaitedType, kind).length > 0; - if (apparentType.flags & TypeFlags.Union) { - const types = (apparentType as UnionType).types; - let hasSignatures = false; - for (const constituent of types) { - const signatures = getSignaturesOfType(constituent, kind); - if (signatures.length !== 0) { - hasSignatures = true; - if (errorInfo) { - // Bail early if we already have an error, no chance of "No constituent of type is callable" - break; - } - } - else { - // Error on the first non callable constituent only - if (!errorInfo) { - errorInfo = chainDiagnosticMessages( - errorInfo, - isCall ? - Diagnostics.Type_0_has_no_call_signatures : - Diagnostics.Type_0_has_no_construct_signatures, - typeToString(constituent) - ); - errorInfo = chainDiagnosticMessages( - errorInfo, - isCall ? - Diagnostics.Not_all_constituents_of_type_0_are_callable : - Diagnostics.Not_all_constituents_of_type_0_are_constructable, - typeToString(apparentType) - ); - } - if (hasSignatures) { - // Bail early if we already found a siganture, no chance of "No constituent of type is callable" - break; + if (isObjectLiteralType(type)) { + for (const p of getPropertiesOfObjectType(type)) { + const t = getTypeOfSymbol(p); + if (getObjectFlags(t) & ObjectFlags.ContainsWideningType) { + if (!reportWideningErrorsInType(t)) { + error(p.valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, symbolToString(p), typeToString(getWidenedType(t))); } + errorReported = true; } } - if (!hasSignatures) { - errorInfo = chainDiagnosticMessages( - /* detials */ undefined, - isCall ? - Diagnostics.No_constituent_of_type_0_is_callable : - Diagnostics.No_constituent_of_type_0_is_constructable, - typeToString(apparentType) - ); - } - if (!errorInfo) { - errorInfo = chainDiagnosticMessages( - errorInfo, - isCall ? - Diagnostics.Each_member_of_the_union_type_0_has_signatures_but_none_of_those_signatures_are_compatible_with_each_other : - Diagnostics.Each_member_of_the_union_type_0_has_construct_signatures_but_none_of_those_signatures_are_compatible_with_each_other, - typeToString(apparentType) - ); - } - } - else { - errorInfo = chainDiagnosticMessages( - errorInfo, - isCall ? - Diagnostics.Type_0_has_no_call_signatures : - Diagnostics.Type_0_has_no_construct_signatures, - typeToString(apparentType) - ); - } - return { - messageChain: chainDiagnosticMessages( - errorInfo, - isCall ? Diagnostics.This_expression_is_not_callable : Diagnostics.This_expression_is_not_constructable - ), - relatedMessage: maybeMissingAwait ? Diagnostics.Did_you_forget_to_use_await : undefined, - }; - } - function invocationError(errorTarget: Node, apparentType: Type, kind: SignatureKind, relatedInformation?: DiagnosticRelatedInformation) { - const { messageChain, relatedMessage: relatedInfo } = invocationErrorDetails(apparentType, kind); - const diagnostic = createDiagnosticForNodeFromMessageChain(errorTarget, messageChain); - if (relatedInfo) { - addRelatedInfo(diagnostic, createDiagnosticForNode(errorTarget, relatedInfo)); - } - if (isCallExpression(errorTarget.parent)) { - const { start, length } = getDiagnosticSpanForCallNode(errorTarget.parent, /* doNotIncludeArguments */ true); - diagnostic.start = start; - diagnostic.length = length; - } - diagnostics.add(diagnostic); - invocationErrorRecovery(apparentType, kind, relatedInformation ? addRelatedInfo(diagnostic, relatedInformation) : diagnostic); - } - - function invocationErrorRecovery(apparentType: Type, kind: SignatureKind, diagnostic: Diagnostic) { - if (!apparentType.symbol) { - return; - } - const importNode = getSymbolLinks(apparentType.symbol).originatingImport; - // Create a diagnostic on the originating import if possible onto which we can attach a quickfix - // An import call expression cannot be rewritten into another form to correct the error - the only solution is to use `.default` at the use-site - if (importNode && !isImportCall(importNode)) { - const sigs = getSignaturesOfType(getTypeOfSymbol(getSymbolLinks(apparentType.symbol).target!), kind); - if (!sigs || !sigs.length) return; - - addRelatedInfo(diagnostic, - createDiagnosticForNode(importNode, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead) - ); - } - } - - function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { - const tagType = checkExpression(node.tag); - const apparentType = getApparentType(tagType); - - if (apparentType === errorType) { - // Another error has already been reported - return resolveErrorCall(node); - } - - const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); - const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; - - if (isUntypedFunctionCall(tagType, apparentType, callSignatures.length, numConstructSignatures)) { - return resolveUntypedCall(node); - } - - if (!callSignatures.length) { - invocationError(node.tag, apparentType, SignatureKind.Call); - return resolveErrorCall(node); - } - - return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); - } - - /** - * Gets the localized diagnostic head message to use for errors when resolving a decorator as a call expression. - */ - function getDiagnosticHeadMessageForDecoratorResolution(node: Decorator) { - switch (node.parent.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - return Diagnostics.Unable_to_resolve_signature_of_class_decorator_when_called_as_an_expression; - - case SyntaxKind.Parameter: - return Diagnostics.Unable_to_resolve_signature_of_parameter_decorator_when_called_as_an_expression; - - case SyntaxKind.PropertyDeclaration: - return Diagnostics.Unable_to_resolve_signature_of_property_decorator_when_called_as_an_expression; - - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return Diagnostics.Unable_to_resolve_signature_of_method_decorator_when_called_as_an_expression; - - default: - return Debug.fail(); } } - - /** - * Resolves a decorator as if it were a call expression. - */ - function resolveDecorator(node: Decorator, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { - const funcType = checkExpression(node.expression); - const apparentType = getApparentType(funcType); - if (apparentType === errorType) { - return resolveErrorCall(node); - } - - const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); - const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; - if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) { - return resolveUntypedCall(node); - } - - if (isPotentiallyUncalledDecorator(node, callSignatures)) { - const nodeStr = getTextOfNode(node.expression, /*includeTrivia*/ false); - error(node, Diagnostics._0_accepts_too_few_arguments_to_be_used_as_a_decorator_here_Did_you_mean_to_call_it_first_and_write_0, nodeStr); - return resolveErrorCall(node); - } - - const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node); - if (!callSignatures.length) { - const errorDetails = invocationErrorDetails(apparentType, SignatureKind.Call); - const messageChain = chainDiagnosticMessages(errorDetails.messageChain, headMessage); - const diag = createDiagnosticForNodeFromMessageChain(node.expression, messageChain); - if (errorDetails.relatedMessage) { - addRelatedInfo(diag, createDiagnosticForNode(node.expression, errorDetails.relatedMessage)); + return errorReported; + } + function reportImplicitAny(declaration: Declaration, type: ts.Type, wideningKind?: WideningKind) { + const typeAsString = typeToString(getWidenedType(type)); + if (isInJSFile(declaration) && !isCheckJsEnabledForFile(getSourceFileOfNode(declaration), compilerOptions)) { + // Only report implicit any errors/suggestions in TS and ts-check JS files + return; + } + let diagnostic: DiagnosticMessage; + switch (declaration.kind) { + case SyntaxKind.BinaryExpression: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + diagnostic = noImplicitAny ? Diagnostics.Member_0_implicitly_has_an_1_type : Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; + break; + case SyntaxKind.Parameter: + const param = (declaration as ParameterDeclaration); + if (isIdentifier(param.name) && + (isCallSignatureDeclaration(param.parent) || isMethodSignature(param.parent) || isFunctionTypeNode(param.parent)) && + param.parent.parameters.indexOf(param) > -1 && + (resolveName(param, param.name.escapedText, SymbolFlags.Type, undefined, param.name.escapedText, /*isUse*/ true) || + param.name.originalKeywordKind && isTypeNodeKind(param.name.originalKeywordKind))) { + const newName = "arg" + param.parent.parameters.indexOf(param); + errorOrSuggestion(noImplicitAny, declaration, Diagnostics.Parameter_has_a_name_but_no_type_Did_you_mean_0_Colon_1, newName, declarationNameToString(param.name)); + return; + } + diagnostic = (declaration).dotDotDotToken ? + noImplicitAny ? Diagnostics.Rest_parameter_0_implicitly_has_an_any_type : Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage : + noImplicitAny ? Diagnostics.Parameter_0_implicitly_has_an_1_type : Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; + break; + case SyntaxKind.BindingElement: + diagnostic = Diagnostics.Binding_element_0_implicitly_has_an_1_type; + if (!noImplicitAny) { + // Don't issue a suggestion for binding elements since the codefix doesn't yet support them. + return; } - diagnostics.add(diag); - invocationErrorRecovery(apparentType, SignatureKind.Call, diag); - return resolveErrorCall(node); + break; + case SyntaxKind.JSDocFunctionType: + error(declaration, Diagnostics.Function_type_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString); + return; + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + if (noImplicitAny && !(declaration as NamedDeclaration).name) { + if (wideningKind === WideningKind.GeneratorYield) { + error(declaration, Diagnostics.Generator_implicitly_has_yield_type_0_because_it_does_not_yield_any_values_Consider_supplying_a_return_type_annotation, typeAsString); + } + else { + error(declaration, Diagnostics.Function_expression_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString); + } + return; + } + diagnostic = !noImplicitAny ? Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage : + wideningKind === WideningKind.GeneratorYield ? Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_yield_type : + Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type; + break; + case SyntaxKind.MappedType: + if (noImplicitAny) { + error(declaration, Diagnostics.Mapped_object_type_implicitly_has_an_any_template_type); + } + return; + default: + diagnostic = noImplicitAny ? Diagnostics.Variable_0_implicitly_has_an_1_type : Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; + } + errorOrSuggestion(noImplicitAny, declaration, diagnostic, declarationNameToString(getNameOfDeclaration(declaration)), typeAsString); + } + function reportErrorsFromWidening(declaration: Declaration, type: ts.Type, wideningKind?: WideningKind) { + if (produceDiagnostics && noImplicitAny && getObjectFlags(type) & ObjectFlags.ContainsWideningType) { + // Report implicit any error within type if possible, otherwise report error on declaration + if (!reportWideningErrorsInType(type)) { + reportImplicitAny(declaration, type, wideningKind); } - - return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None, headMessage); } - - function createSignatureForJSXIntrinsic(node: JsxOpeningLikeElement, result: Type): Signature { - const namespace = getJsxNamespaceAt(node); - const exports = namespace && getExportsOfSymbol(namespace); - // We fake up a SFC signature for each intrinsic, however a more specific per-element signature drawn from the JSX declaration - // file would probably be preferable. - const typeSymbol = exports && getSymbol(exports, JsxNames.Element, SymbolFlags.Type); - const returnNode = typeSymbol && nodeBuilder.symbolToEntityName(typeSymbol, SymbolFlags.Type, node); - const declaration = createFunctionTypeNode(/*typeParameters*/ undefined, - [createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotdotdot*/ undefined, "props", /*questionMark*/ undefined, nodeBuilder.typeToTypeNode(result, node))], - returnNode ? createTypeReferenceNode(returnNode, /*typeArguments*/ undefined) : createKeywordTypeNode(SyntaxKind.AnyKeyword) - ); - const parameterSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "props" as __String); - parameterSymbol.type = result; - return createSignature( - declaration, - /*typeParameters*/ undefined, - /*thisParameter*/ undefined, - [parameterSymbol], - typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType, - /*returnTypePredicate*/ undefined, - 1, - SignatureFlags.None - ); + } + function applyToParameterTypes(source: ts.Signature, target: ts.Signature, callback: (s: ts.Type, t: ts.Type) => void) { + const sourceCount = getParameterCount(source); + const targetCount = getParameterCount(target); + const sourceRestType = getEffectiveRestType(source); + const targetRestType = getEffectiveRestType(target); + const targetNonRestCount = targetRestType ? targetCount - 1 : targetCount; + const paramCount = sourceRestType ? targetNonRestCount : Math.min(sourceCount, targetNonRestCount); + const sourceThisType = getThisTypeOfSignature(source); + if (sourceThisType) { + const targetThisType = getThisTypeOfSignature(target); + if (targetThisType) { + callback(sourceThisType, targetThisType); + } + } + for (let i = 0; i < paramCount; i++) { + callback(getTypeAtPosition(source, i), getTypeAtPosition(target, i)); + } + if (targetRestType) { + callback(getRestTypeAtPosition(source, paramCount), targetRestType); } - - function resolveJsxOpeningLikeElement(node: JsxOpeningLikeElement, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { - if (isJsxIntrinsicIdentifier(node.tagName)) { - const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node); - const fakeSignature = createSignatureForJSXIntrinsic(node, result); - checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*mapper*/ undefined, CheckMode.Normal), result, node.tagName, node.attributes); - return fakeSignature; - } - const exprTypes = checkExpression(node.tagName); - const apparentType = getApparentType(exprTypes); - if (apparentType === errorType) { - return resolveErrorCall(node); - } - - const signatures = getUninstantiatedJsxSignaturesOfType(exprTypes, node); - if (isUntypedFunctionCall(exprTypes, apparentType, signatures.length, /*constructSignatures*/ 0)) { - return resolveUntypedCall(node); - } - - if (signatures.length === 0) { - // We found no signatures at all, which is an error - error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName)); - return resolveErrorCall(node); - } - - return resolveCall(node, signatures, candidatesOutArray, checkMode, SignatureFlags.None); + } + function applyToReturnTypes(source: ts.Signature, target: ts.Signature, callback: (s: ts.Type, t: ts.Type) => void) { + const sourceTypePredicate = getTypePredicateOfSignature(source); + const targetTypePredicate = getTypePredicateOfSignature(target); + if (sourceTypePredicate && targetTypePredicate && typePredicateKindsMatch(sourceTypePredicate, targetTypePredicate) && sourceTypePredicate.type && targetTypePredicate.type) { + callback(sourceTypePredicate.type, targetTypePredicate.type); } - - /** - * Sometimes, we have a decorator that could accept zero arguments, - * but is receiving too many arguments as part of the decorator invocation. - * In those cases, a user may have meant to *call* the expression before using it as a decorator. - */ - function isPotentiallyUncalledDecorator(decorator: Decorator, signatures: readonly Signature[]) { - return signatures.length && every(signatures, signature => - signature.minArgumentCount === 0 && - !signatureHasRestParameter(signature) && - signature.parameters.length < getDecoratorArgumentCount(decorator, signature)); + else { + callback(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); } - - function resolveSignature(node: CallLikeExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { - switch (node.kind) { - case SyntaxKind.CallExpression: - return resolveCallExpression(node, candidatesOutArray, checkMode); - case SyntaxKind.NewExpression: - return resolveNewExpression(node, candidatesOutArray, checkMode); - case SyntaxKind.TaggedTemplateExpression: - return resolveTaggedTemplateExpression(node, candidatesOutArray, checkMode); - case SyntaxKind.Decorator: - return resolveDecorator(node, candidatesOutArray, checkMode); - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxSelfClosingElement: - return resolveJsxOpeningLikeElement(node, candidatesOutArray, checkMode); + } + function createInferenceContext(typeParameters: readonly TypeParameter[], signature: ts.Signature | undefined, flags: InferenceFlags, compareTypes?: TypeComparer): InferenceContext { + return createInferenceContextWorker(typeParameters.map(createInferenceInfo), signature, flags, compareTypes || compareTypesAssignable); + } + function cloneInferenceContext(context: T, extraFlags: InferenceFlags = 0): InferenceContext | (T & undefined) { + return context && createInferenceContextWorker(map(context.inferences, cloneInferenceInfo), context.signature, context.flags | extraFlags, context.compareTypes); + } + function createInferenceContextWorker(inferences: InferenceInfo[], signature: ts.Signature | undefined, flags: InferenceFlags, compareTypes: TypeComparer): InferenceContext { + const context: InferenceContext = { + inferences, + signature, + flags, + compareTypes, + mapper: t => mapToInferredType(context, t, /*fix*/ true), + nonFixingMapper: t => mapToInferredType(context, t, /*fix*/ false), + }; + return context; + } + function mapToInferredType(context: InferenceContext, t: ts.Type, fix: boolean): ts.Type { + const inferences = context.inferences; + for (let i = 0; i < inferences.length; i++) { + const inference = inferences[i]; + if (t === inference.typeParameter) { + if (fix && !inference.isFixed) { + clearCachedInferences(inferences); + inference.isFixed = true; + } + return getInferredType(context, i); } - throw Debug.assertNever(node, "Branch in 'resolveSignature' should be unreachable."); } - - /** - * Resolve a signature of a given call-like expression. - * @param node a call-like expression to try resolve a signature for - * @param candidatesOutArray an array of signature to be filled in by the function. It is passed by signature help in the language service; - * the function will fill it up with appropriate candidate signatures - * @return a signature of the call-like expression or undefined if one can't be found - */ - function getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[] | undefined, checkMode?: CheckMode): Signature { - const links = getNodeLinks(node); - // If getResolvedSignature has already been called, we will have cached the resolvedSignature. - // However, it is possible that either candidatesOutArray was not passed in the first time, - // or that a different candidatesOutArray was passed in. Therefore, we need to redo the work - // to correctly fill the candidatesOutArray. - const cached = links.resolvedSignature; - if (cached && cached !== resolvingSignature && !candidatesOutArray) { - return cached; - } - links.resolvedSignature = resolvingSignature; - const result = resolveSignature(node, candidatesOutArray, checkMode || CheckMode.Normal); - // When CheckMode.SkipGenericFunctions is set we use resolvingSignature to indicate that call - // resolution should be deferred. - if (result !== resolvingSignature) { - // If signature resolution originated in control flow type analysis (for example to compute the - // assigned type in a flow assignment) we don't cache the result as it may be based on temporary - // types from the control flow analysis. - links.resolvedSignature = flowLoopStart === flowLoopCount ? result : cached; + return t; + } + function clearCachedInferences(inferences: InferenceInfo[]) { + for (const inference of inferences) { + if (!inference.isFixed) { + inference.inferredType = undefined; } - return result; } - - /** - * Indicates whether a declaration can be treated as a constructor in a JavaScript - * file. - */ - function isJSConstructor(node: Node | undefined): node is FunctionDeclaration | FunctionExpression { - if (!node || !isInJSFile(node)) { - return false; + } + function createInferenceInfo(typeParameter: TypeParameter): InferenceInfo { + return { + typeParameter, + candidates: undefined, + contraCandidates: undefined, + inferredType: undefined, + priority: undefined, + topLevel: true, + isFixed: false + }; + } + function cloneInferenceInfo(inference: InferenceInfo): InferenceInfo { + return { + typeParameter: inference.typeParameter, + candidates: inference.candidates && inference.candidates.slice(), + contraCandidates: inference.contraCandidates && inference.contraCandidates.slice(), + inferredType: inference.inferredType, + priority: inference.priority, + topLevel: inference.topLevel, + isFixed: inference.isFixed + }; + } + function cloneInferredPartOfContext(context: InferenceContext): InferenceContext | undefined { + const inferences = filter(context.inferences, hasInferenceCandidates); + return inferences.length ? + createInferenceContextWorker(map(inferences, cloneInferenceInfo), context.signature, context.flags, context.compareTypes) : + undefined; + } + function getMapperFromContext(context: T): TypeMapper | (T & undefined) { + return context && context.mapper; + } + // Return true if the given type could possibly reference a type parameter for which + // we perform type inference (i.e. a type parameter of a generic function). We cache + // results for union and intersection types for performance reasons. + function couldContainTypeVariables(type: ts.Type): boolean { + const objectFlags = getObjectFlags(type); + return !!(type.flags & TypeFlags.Instantiable || + objectFlags & ObjectFlags.Reference && ((type).node || forEach(getTypeArguments((type)), couldContainTypeVariables)) || + objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations || + objectFlags & (ObjectFlags.Mapped | ObjectFlags.ObjectRestType) || + type.flags & TypeFlags.UnionOrIntersection && !(type.flags & TypeFlags.EnumLiteral) && couldUnionOrIntersectionContainTypeVariables((type))); + } + function couldUnionOrIntersectionContainTypeVariables(type: UnionOrIntersectionType): boolean { + if (type.couldContainTypeVariables === undefined) { + type.couldContainTypeVariables = some(type.types, couldContainTypeVariables); + } + return type.couldContainTypeVariables; + } + function isTypeParameterAtTopLevel(type: ts.Type, typeParameter: TypeParameter): boolean { + return !!(type === typeParameter || + type.flags & TypeFlags.UnionOrIntersection && some((type).types, t => isTypeParameterAtTopLevel(t, typeParameter)) || + type.flags & TypeFlags.Conditional && (isTypeParameterAtTopLevel(getTrueTypeFromConditionalType((type)), typeParameter) || + isTypeParameterAtTopLevel(getFalseTypeFromConditionalType((type)), typeParameter))); + } + /** Create an object with properties named in the string literal type. Every property has type `any` */ + function createEmptyObjectTypeFromStringLiteral(type: ts.Type) { + const members = createSymbolTable(); + forEachType(type, t => { + if (!(t.flags & TypeFlags.StringLiteral)) { + return; } - const func = isFunctionDeclaration(node) || isFunctionExpression(node) ? node : - isVariableDeclaration(node) && node.initializer && isFunctionExpression(node.initializer) ? node.initializer : - undefined; - if (func) { - // If the node has a @class tag, treat it like a constructor. - if (getJSDocClassTag(node)) return true; - - // If the symbol of the node has members, treat it like a constructor. - const symbol = getSymbolOfNode(func); - return !!symbol && hasEntries(symbol.members); + const name = escapeLeadingUnderscores((t as StringLiteralType).value); + const literalProp = createSymbol(SymbolFlags.Property, name); + literalProp.type = anyType; + if (t.symbol) { + literalProp.declarations = t.symbol.declarations; + literalProp.valueDeclaration = t.symbol.valueDeclaration; } - return false; + members.set(name, literalProp); + }); + const indexInfo = type.flags & TypeFlags.String ? createIndexInfo(emptyObjectType, /*isReadonly*/ false) : undefined; + return createAnonymousType(undefined, members, emptyArray, emptyArray, indexInfo, undefined); + } + /** + * Infer a suitable input type for a homomorphic mapped type { [P in keyof T]: X }. We construct + * an object type with the same set of properties as the source type, where the type of each + * property is computed by inferring from the source property type to X for the type + * variable T[P] (i.e. we treat the type T[P] as the type variable we're inferring for). + */ + function inferTypeForHomomorphicMappedType(source: ts.Type, target: MappedType, constraint: IndexType): ts.Type | undefined { + const key = source.id + "," + target.id + "," + constraint.id; + if (reverseMappedCache.has(key)) { + return reverseMappedCache.get(key); + } + reverseMappedCache.set(key, undefined); + const type = createReverseMappedType(source, target, constraint); + reverseMappedCache.set(key, type); + return type; + } + // We consider a type to be partially inferable if it isn't marked non-inferable or if it is + // an object literal type with at least one property of an inferable type. For example, an object + // literal { a: 123, b: x => true } is marked non-inferable because it contains a context sensitive + // arrow function, but is considered partially inferable because property 'a' has an inferable type. + function isPartiallyInferableType(type: ts.Type): boolean { + return !(getObjectFlags(type) & ObjectFlags.NonInferrableType) || + isObjectLiteralType(type) && some(getPropertiesOfType(type), prop => isPartiallyInferableType(getTypeOfSymbol(prop))); + } + function createReverseMappedType(source: ts.Type, target: MappedType, constraint: IndexType) { + // We consider a source type reverse mappable if it has a string index signature or if + // it has one or more properties and is of a partially inferable type. + if (!(getIndexInfoOfType(source, IndexKind.String) || getPropertiesOfType(source).length !== 0 && isPartiallyInferableType(source))) { + return undefined; } - - function mergeJSSymbols(target: Symbol, source: Symbol | undefined) { - if (source) { - const links = getSymbolLinks(source); - if (!links.inferredClassSymbol || !links.inferredClassSymbol.has("" + getSymbolId(target))) { - const inferred = isTransientSymbol(target) ? target : cloneSymbol(target) as TransientSymbol; - inferred.exports = inferred.exports || createSymbolTable(); - inferred.members = inferred.members || createSymbolTable(); - inferred.flags |= source.flags & SymbolFlags.Class; - if (hasEntries(source.exports)) { - mergeSymbolTable(inferred.exports, source.exports); - } - if (hasEntries(source.members)) { - mergeSymbolTable(inferred.members, source.members); + // For arrays and tuples we infer new arrays and tuples where the reverse mapping has been + // applied to the element type(s). + if (isArrayType(source)) { + return createArrayType(inferReverseMappedType(getTypeArguments((source))[0], target, constraint), isReadonlyArrayType(source)); + } + if (isTupleType(source)) { + const elementTypes = map(getTypeArguments(source), t => inferReverseMappedType(t, target, constraint)); + const minLength = getMappedTypeModifiers(target) & MappedTypeModifiers.IncludeOptional ? + getTypeReferenceArity(source) - (source.target.hasRestElement ? 1 : 0) : source.target.minLength; + return createTupleType(elementTypes, minLength, source.target.hasRestElement, source.target.readonly, source.target.associatedNames); + } + // For all other object types we infer a new object type where the reverse mapping has been + // applied to the type of each property. + const reversed = (createObjectType(ObjectFlags.ReverseMapped | ObjectFlags.Anonymous, /*symbol*/ undefined) as ReverseMappedType); + reversed.source = source; + reversed.mappedType = target; + reversed.constraintType = constraint; + return reversed; + } + function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol) { + return inferReverseMappedType(symbol.propertyType, symbol.mappedType, symbol.constraintType); + } + function inferReverseMappedType(sourceType: ts.Type, target: MappedType, constraint: IndexType): ts.Type { + const typeParameter = (getIndexedAccessType(constraint.type, getTypeParameterFromMappedType(target))); + const templateType = getTemplateTypeFromMappedType(target); + const inference = createInferenceInfo(typeParameter); + inferTypes([inference], sourceType, templateType); + return getTypeFromInference(inference) || unknownType; + } + function* getUnmatchedProperties(source: ts.Type, target: ts.Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): IterableIterator { + const properties = getPropertiesOfType(target); + for (const targetProp of properties) { + if (requireOptionalProperties || !(targetProp.flags & SymbolFlags.Optional || getCheckFlags(targetProp) & CheckFlags.Partial)) { + const sourceProp = getPropertyOfType(source, targetProp.escapedName); + if (!sourceProp) { + yield targetProp; + } + else if (matchDiscriminantProperties) { + const targetType = getTypeOfSymbol(targetProp); + if (targetType.flags & TypeFlags.Unit) { + const sourceType = getTypeOfSymbol(sourceProp); + if (!(sourceType.flags & TypeFlags.Any || getRegularTypeOfLiteralType(sourceType) === getRegularTypeOfLiteralType(targetType))) { + yield targetProp; + } } - (links.inferredClassSymbol || (links.inferredClassSymbol = createMap())).set("" + getSymbolId(inferred), inferred); - return inferred; } - return links.inferredClassSymbol.get("" + getSymbolId(target)); } } - - function getAssignedClassSymbol(decl: Declaration): Symbol | undefined { - const assignmentSymbol = decl && decl.parent && - (isFunctionDeclaration(decl) && getSymbolOfNode(decl) || - isBinaryExpression(decl.parent) && getSymbolOfNode(decl.parent.left) || - isVariableDeclaration(decl.parent) && getSymbolOfNode(decl.parent)); - const prototype = assignmentSymbol && assignmentSymbol.exports && assignmentSymbol.exports.get("prototype" as __String); - const init = prototype && prototype.valueDeclaration && getAssignedJSPrototype(prototype.valueDeclaration); - return init ? getSymbolOfNode(init) : undefined; - } - - function getAssignedJSPrototype(node: Node) { - if (!node.parent) { - return false; - } - let parent: Node = node.parent; - while (parent && parent.kind === SyntaxKind.PropertyAccessExpression) { - parent = parent.parent; + } + function getUnmatchedProperty(source: ts.Type, target: ts.Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): ts.Symbol | undefined { + const result = getUnmatchedProperties(source, target, requireOptionalProperties, matchDiscriminantProperties).next(); + if (!result.done) + return result.value; + } + function tupleTypesDefinitelyUnrelated(source: TupleTypeReference, target: TupleTypeReference) { + return target.target.minLength > source.target.minLength || + !getRestTypeOfTupleType(target) && (!!getRestTypeOfTupleType(source) || getLengthOfTupleType(target) < getLengthOfTupleType(source)); + } + function typesDefinitelyUnrelated(source: ts.Type, target: ts.Type) { + // Two tuple types with incompatible arities are definitely unrelated. + // Two object types that each have a property that is unmatched in the other are definitely unrelated. + return isTupleType(source) && isTupleType(target) && tupleTypesDefinitelyUnrelated(source, target) || + !!getUnmatchedProperty(source, target, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ true) && + !!getUnmatchedProperty(target, source, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ true); + } + function getTypeFromInference(inference: InferenceInfo) { + return inference.candidates ? getUnionType(inference.candidates, UnionReduction.Subtype) : + inference.contraCandidates ? getIntersectionType(inference.contraCandidates) : + undefined; + } + function inferTypes(inferences: InferenceInfo[], originalSource: ts.Type, originalTarget: ts.Type, priority: InferencePriority = 0, contravariant = false) { + let symbolStack: ts.Symbol[]; + let visited: ts.Map; + let bivariant = false; + let propagationType: ts.Type; + let inferencePriority = InferencePriority.MaxValue; + let allowComplexConstraintInference = true; + inferFromTypes(originalSource, originalTarget); + function inferFromTypes(source: ts.Type, target: ts.Type): void { + if (!couldContainTypeVariables(target)) { + return; } - if (parent && isBinaryExpression(parent) && isPrototypeAccess(parent.left) && parent.operatorToken.kind === SyntaxKind.EqualsToken) { - const right = getInitializerOfBinaryExpression(parent); - return isObjectLiteralExpression(right) && right; + if (source === wildcardType) { + // We are inferring from an 'any' type. We want to infer this type for every type parameter + // referenced in the target type, so we record it as the propagation type and infer from the + // target to itself. Then, as we find candidates we substitute the propagation type. + const savePropagationType = propagationType; + propagationType = source; + inferFromTypes(target, target); + propagationType = savePropagationType; + return; } - } - - /** - * Syntactically and semantically checks a call or new expression. - * @param node The call/new expression to be checked. - * @returns On success, the expression's signature's return type. On failure, anyType. - */ - function checkCallExpression(node: CallExpression | NewExpression, checkMode?: CheckMode): Type { - if (!checkGrammarTypeArguments(node, node.typeArguments)) checkGrammarArguments(node.arguments); - - const signature = getResolvedSignature(node, /*candidatesOutArray*/ undefined, checkMode); - if (signature === resolvingSignature) { - // CheckMode.SkipGenericFunctions is enabled and this is a call to a generic function that - // returns a function type. We defer checking and return nonInferrableType. - return nonInferrableType; + if (source.aliasSymbol && source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol) { + // Source and target are types originating in the same generic type alias declaration. + // Simply infer from source type arguments to target type arguments. + inferFromTypeArguments(source.aliasTypeArguments, target.aliasTypeArguments!, getAliasVariances(source.aliasSymbol)); + return; } - - if (node.expression.kind === SyntaxKind.SuperKeyword) { - return voidType; + if (source === target && source.flags & TypeFlags.UnionOrIntersection) { + // When source and target are the same union or intersection type, just relate each constituent + // type to itself. + for (const t of (source).types) { + inferFromTypes(t, t); + } + return; } - - if (node.kind === SyntaxKind.NewExpression) { - const declaration = signature.declaration; - - if (declaration && - declaration.kind !== SyntaxKind.Constructor && - declaration.kind !== SyntaxKind.ConstructSignature && - declaration.kind !== SyntaxKind.ConstructorType && - !isJSDocConstructSignature(declaration) && - !isJSConstructor(declaration)) { - - // When resolved signature is a call signature (and not a construct signature) the result type is any - if (noImplicitAny) { - error(node, Diagnostics.new_expression_whose_target_lacks_a_construct_signature_implicitly_has_an_any_type); + if (target.flags & TypeFlags.Union) { + // First, infer between identically matching source and target constituents and remove the + // matching types. + const [tempSources, tempTargets] = inferFromMatchingTypes(source.flags & TypeFlags.Union ? (source).types : [source], (target).types, isTypeOrBaseIdenticalTo); + // Next, infer between closely matching source and target constituents and remove + // the matching types. Types closely match when they are instantiations of the same + // object type or instantiations of the same type alias. + const [sources, targets] = inferFromMatchingTypes(tempSources, tempTargets, isTypeCloselyMatchedBy); + if (targets.length === 0) { + return; + } + target = getUnionType(targets); + if (sources.length === 0) { + // All source constituents have been matched and there is nothing further to infer from. + // However, simply making no inferences is undesirable because it could ultimately mean + // inferring a type parameter constraint. Instead, make a lower priority inference from + // the full source to whatever remains in the target. For example, when inferring from + // string to 'string | T', make a lower priority inference of string for T. + inferWithPriority(source, target, InferencePriority.NakedTypeVariable); + return; + } + source = getUnionType(sources); + } + else if (target.flags & TypeFlags.Intersection && some((target).types, t => !!getInferenceInfoForType(t) || (isGenericMappedType(t) && !!getInferenceInfoForType(getHomomorphicTypeVariable(t) || neverType)))) { + // We reduce intersection types only when they contain naked type parameters. For example, when + // inferring from 'string[] & { extra: any }' to 'string[] & T' we want to remove string[] and + // infer { extra: any } for T. But when inferring to 'string[] & Iterable' we want to keep the + // string[] on the source side and infer string for T. + // Likewise, we consider a homomorphic mapped type constrainted to the target type parameter as similar to a "naked type variable" + // in such scenarios. + if (!(source.flags & TypeFlags.Union)) { + // Infer between identically matching source and target constituents and remove the matching types. + const [sources, targets] = inferFromMatchingTypes(source.flags & TypeFlags.Intersection ? (source).types : [source], (target).types, isTypeIdenticalTo); + if (sources.length === 0 || targets.length === 0) { + return; } - return anyType; + source = getIntersectionType(sources); + target = getIntersectionType(targets); } } - - // In JavaScript files, calls to any identifier 'require' are treated as external module imports - if (isInJSFile(node) && isCommonJsRequire(node)) { - return resolveExternalModuleTypeByLiteral(node.arguments![0] as StringLiteral); - } - - const returnType = getReturnTypeOfSignature(signature); - // Treat any call to the global 'Symbol' function that is part of a const variable or readonly property - // as a fresh unique symbol literal type. - if (returnType.flags & TypeFlags.ESSymbolLike && isSymbolOrSymbolForCall(node)) { - return getESSymbolLikeTypeForNode(walkUpParenthesizedExpressions(node.parent)); + else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) { + target = getActualTypeVariable(target); } - if (node.kind === SyntaxKind.CallExpression && node.parent.kind === SyntaxKind.ExpressionStatement && - returnType.flags & TypeFlags.Void && getTypePredicateOfSignature(signature)) { - if (!isDottedName(node.expression)) { - error(node.expression, Diagnostics.Assertions_require_the_call_target_to_be_an_identifier_or_qualified_name); + if (target.flags & TypeFlags.TypeVariable) { + // If target is a type parameter, make an inference, unless the source type contains + // the anyFunctionType (the wildcard type that's used to avoid contextually typing functions). + // Because the anyFunctionType is internal, it should not be exposed to the user by adding + // it as an inference candidate. Hopefully, a better candidate will come along that does + // not contain anyFunctionType when we come back to this argument for its second round + // of inference. Also, we exclude inferences for silentNeverType (which is used as a wildcard + // when constructing types from type parameters that had no inference candidates). + if (getObjectFlags(source) & ObjectFlags.NonInferrableType || source === nonInferrableAnyType || source === silentNeverType || + (priority & InferencePriority.ReturnType && (source === autoType || source === autoArrayType))) { + return; } - else if (!getEffectsSignature(node)) { - const diagnostic = error(node.expression, Diagnostics.Assertions_require_every_name_in_the_call_target_to_be_declared_with_an_explicit_type_annotation); - getTypeOfDottedName(node.expression, diagnostic); + const inference = getInferenceInfoForType(target); + if (inference) { + if (!inference.isFixed) { + if (inference.priority === undefined || priority < inference.priority) { + inference.candidates = undefined; + inference.contraCandidates = undefined; + inference.topLevel = true; + inference.priority = priority; + } + if (priority === inference.priority) { + const candidate = propagationType || source; + // We make contravariant inferences only if we are in a pure contravariant position, + // i.e. only if we have not descended into a bivariant position. + if (contravariant && !bivariant) { + if (!contains(inference.contraCandidates, candidate)) { + inference.contraCandidates = append(inference.contraCandidates, candidate); + clearCachedInferences(inferences); + } + } + else if (!contains(inference.candidates, candidate)) { + inference.candidates = append(inference.candidates, candidate); + clearCachedInferences(inferences); + } + } + if (!(priority & InferencePriority.ReturnType) && target.flags & TypeFlags.TypeParameter && inference.topLevel && !isTypeParameterAtTopLevel(originalTarget, (target))) { + inference.topLevel = false; + clearCachedInferences(inferences); + } + } + inferencePriority = Math.min(inferencePriority, priority); + return; } - } - - if (isInJSFile(node)) { - const decl = getDeclarationOfExpando(node); - if (decl) { - const jsSymbol = getSymbolOfNode(decl); - if (jsSymbol && hasEntries(jsSymbol.exports)) { - const jsAssignmentType = createAnonymousType(jsSymbol, jsSymbol.exports, emptyArray, emptyArray, undefined, undefined); - jsAssignmentType.objectFlags |= ObjectFlags.JSLiteral; - return getIntersectionType([returnType, jsAssignmentType]); + else { + // Infer to the simplified version of an indexed access, if possible, to (hopefully) expose more bare type parameters to the inference engine + const simplified = getSimplifiedType(target, /*writing*/ false); + if (simplified !== target) { + invokeOnce(source, simplified, inferFromTypes); + } + else if (target.flags & TypeFlags.IndexedAccess) { + const indexType = getSimplifiedType((target as IndexedAccessType).indexType, /*writing*/ false); + // Generally simplifications of instantiable indexes are avoided to keep relationship checking correct, however if our target is an access, we can consider + // that key of that access to be "instantiated", since we're looking to find the infernce goal in any way we can. + if (indexType.flags & TypeFlags.Instantiable) { + const simplified = distributeIndexOverObjectType(getSimplifiedType((target as IndexedAccessType).objectType, /*writing*/ false), indexType, /*writing*/ false); + if (simplified && simplified !== target) { + invokeOnce(source, simplified, inferFromTypes); + } + } } } } - - return returnType; - } - - function isSymbolOrSymbolForCall(node: Node) { - if (!isCallExpression(node)) return false; - let left = node.expression; - if (isPropertyAccessExpression(left) && left.name.escapedText === "for") { - left = left.expression; + if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && ((source).target === (target).target || isArrayType(source) && isArrayType(target)) && + !((source).node && (target).node)) { + // If source and target are references to the same generic type, infer from type arguments + inferFromTypeArguments(getTypeArguments((source)), getTypeArguments((target)), getVariances((source).target)); } - if (!isIdentifier(left) || left.escapedText !== "Symbol") { - return false; + else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) { + contravariant = !contravariant; + inferFromTypes((source).type, (target).type); + contravariant = !contravariant; } - - // make sure `Symbol` is the global symbol - const globalESSymbol = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false); - if (!globalESSymbol) { - return false; + else if ((isLiteralType(source) || source.flags & TypeFlags.String) && target.flags & TypeFlags.Index) { + const empty = createEmptyObjectTypeFromStringLiteral(source); + contravariant = !contravariant; + inferWithPriority(empty, (target as IndexType).type, InferencePriority.LiteralKeyof); + contravariant = !contravariant; } - - return globalESSymbol === resolveName(left, "Symbol" as __String, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); - } - - function checkImportCallExpression(node: ImportCall): Type { - // Check grammar of dynamic import - if (!checkGrammarArguments(node.arguments)) checkGrammarImportCallExpression(node); - - if (node.arguments.length === 0) { - return createPromiseReturnType(node, anyType); + else if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) { + inferFromTypes((source).objectType, (target).objectType); + inferFromTypes((source).indexType, (target).indexType); } - const specifier = node.arguments[0]; - const specifierType = checkExpressionCached(specifier); - // Even though multiple arguments is grammatically incorrect, type-check extra arguments for completion - for (let i = 1; i < node.arguments.length; ++i) { - checkExpressionCached(node.arguments[i]); + else if (source.flags & TypeFlags.Conditional && target.flags & TypeFlags.Conditional) { + inferFromTypes((source).checkType, (target).checkType); + inferFromTypes((source).extendsType, (target).extendsType); + inferFromTypes(getTrueTypeFromConditionalType((source)), getTrueTypeFromConditionalType((target))); + inferFromTypes(getFalseTypeFromConditionalType((source)), getFalseTypeFromConditionalType((target))); } - - if (specifierType.flags & TypeFlags.Undefined || specifierType.flags & TypeFlags.Null || !isTypeAssignableTo(specifierType, stringType)) { - error(specifier, Diagnostics.Dynamic_import_s_specifier_must_be_of_type_string_but_here_has_type_0, typeToString(specifierType)); + else if (target.flags & TypeFlags.Conditional) { + const savePriority = priority; + priority |= contravariant ? InferencePriority.ContravariantConditional : 0; + const targetTypes = [getTrueTypeFromConditionalType((target)), getFalseTypeFromConditionalType((target))]; + inferToMultipleTypes(source, targetTypes, target.flags); + priority = savePriority; } - - // resolveExternalModuleName will return undefined if the moduleReferenceExpression is not a string literal - const moduleSymbol = resolveExternalModuleName(node, specifier); - if (moduleSymbol) { - const esModuleSymbol = resolveESModuleSymbol(moduleSymbol, specifier, /*dontRecursivelyResolve*/ true, /*suppressUsageError*/ false); - if (esModuleSymbol) { - return createPromiseReturnType(node, getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol)); + else if (target.flags & TypeFlags.UnionOrIntersection) { + inferToMultipleTypes(source, (target).types, target.flags); + } + else if (source.flags & TypeFlags.Union) { + // Source is a union or intersection type, infer from each constituent type + const sourceTypes = (source).types; + for (const sourceType of sourceTypes) { + inferFromTypes(sourceType, target); } } - return createPromiseReturnType(node, anyType); + else { + if (!(priority & InferencePriority.NoConstraints && source.flags & (TypeFlags.Intersection | TypeFlags.Instantiable))) { + const apparentSource = getApparentType(source); + // getApparentType can return _any_ type, since an indexed access or conditional may simplify to any other type. + // If that occurs and it doesn't simplify to an object or intersection, we'll need to restart `inferFromTypes` + // with the simplified source. + if (apparentSource !== source && allowComplexConstraintInference && !(apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection))) { + // TODO: The `allowComplexConstraintInference` flag is a hack! This forbids inference from complex constraints within constraints! + // This isn't required algorithmically, but rather is used to lower the memory burden caused by performing inference + // that is _too good_ in projects with complicated constraints (eg, fp-ts). In such cases, if we did not limit ourselves + // here, we might produce more valid inferences for types, causing us to do more checks and perform more instantiations + // (in addition to the extra stack depth here) which, in turn, can push the already close process over its limit. + // TL;DR: If we ever become generally more memory efficient (or our resource budget ever increases), we should just + // remove this `allowComplexConstraintInference` flag. + allowComplexConstraintInference = false; + return inferFromTypes(apparentSource, target); + } + source = apparentSource; + } + if (source.flags & (TypeFlags.Object | TypeFlags.Intersection)) { + invokeOnce(source, target, inferFromObjectTypes); + } + } + } + function inferWithPriority(source: ts.Type, target: ts.Type, newPriority: InferencePriority) { + const savePriority = priority; + priority |= newPriority; + inferFromTypes(source, target); + priority = savePriority; + } + function invokeOnce(source: ts.Type, target: ts.Type, action: (source: ts.Type, target: ts.Type) => void) { + const key = source.id + "," + target.id; + const status = visited && visited.get(key); + if (status !== undefined) { + inferencePriority = Math.min(inferencePriority, status); + return; + } + (visited || (visited = createMap())).set(key, InferencePriority.Circularity); + const saveInferencePriority = inferencePriority; + inferencePriority = InferencePriority.MaxValue; + action(source, target); + visited.set(key, inferencePriority); + inferencePriority = Math.min(inferencePriority, saveInferencePriority); } - - function getTypeWithSyntheticDefaultImportType(type: Type, symbol: Symbol, originalSymbol: Symbol): Type { - if (allowSyntheticDefaultImports && type && type !== errorType) { - const synthType = type as SyntheticDefaultModuleType; - if (!synthType.syntheticType) { - const file = find(originalSymbol.declarations, isSourceFile); - const hasSyntheticDefault = canHaveSyntheticDefault(file, originalSymbol, /*dontResolveAlias*/ false); - if (hasSyntheticDefault) { - const memberTable = createSymbolTable(); - const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default); - newSymbol.nameType = getLiteralType("default"); - newSymbol.target = resolveSymbol(symbol); - memberTable.set(InternalSymbolName.Default, newSymbol); - const anonymousSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); - const defaultContainingObject = createAnonymousType(anonymousSymbol, memberTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined); - anonymousSymbol.type = defaultContainingObject; - synthType.syntheticType = isValidSpreadType(type) ? getSpreadType(type, defaultContainingObject, anonymousSymbol, /*objectFlags*/ 0, /*readonly*/ false) : defaultContainingObject; - } - else { - synthType.syntheticType = type; + function inferFromMatchingTypes(sources: ts.Type[], targets: ts.Type[], matches: (s: ts.Type, t: ts.Type) => boolean): [ts.Type[], ts.Type[]] { + let matchedSources: ts.Type[] | undefined; + let matchedTargets: ts.Type[] | undefined; + for (const t of targets) { + for (const s of sources) { + if (matches(s, t)) { + inferFromTypes(s, t); + matchedSources = appendIfUnique(matchedSources, s); + matchedTargets = appendIfUnique(matchedTargets, t); } } - return synthType.syntheticType; } - return type; + return [ + matchedSources ? filter(sources, t => !contains(matchedSources, t)) : sources, + matchedTargets ? filter(targets, t => !contains(matchedTargets, t)) : targets, + ]; } - - function isCommonJsRequire(node: Node): boolean { - if (!isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) { - return false; - } - - // Make sure require is not a local function - if (!isIdentifier(node.expression)) return Debug.fail(); - const resolvedRequire = resolveName(node.expression, node.expression.escapedText, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true)!; // TODO: GH#18217 - if (resolvedRequire === requireSymbol) { - return true; + function inferFromTypeArguments(sourceTypes: readonly ts.Type[], targetTypes: readonly ts.Type[], variances: readonly VarianceFlags[]) { + const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length; + for (let i = 0; i < count; i++) { + if (i < variances.length && (variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Contravariant) { + inferFromContravariantTypes(sourceTypes[i], targetTypes[i]); + } + else { + inferFromTypes(sourceTypes[i], targetTypes[i]); + } } - // project includes symbol named 'require' - make sure that it is ambient and local non-alias - if (resolvedRequire.flags & SymbolFlags.Alias) { - return false; + } + function inferFromContravariantTypes(source: ts.Type, target: ts.Type) { + if (strictFunctionTypes || priority & InferencePriority.AlwaysStrict) { + contravariant = !contravariant; + inferFromTypes(source, target); + contravariant = !contravariant; } - - const targetDeclarationKind = resolvedRequire.flags & SymbolFlags.Function - ? SyntaxKind.FunctionDeclaration - : resolvedRequire.flags & SymbolFlags.Variable - ? SyntaxKind.VariableDeclaration - : SyntaxKind.Unknown; - if (targetDeclarationKind !== SyntaxKind.Unknown) { - const decl = getDeclarationOfKind(resolvedRequire, targetDeclarationKind)!; - // function/variable declaration should be ambient - return !!decl && !!(decl.flags & NodeFlags.Ambient); + else { + inferFromTypes(source, target); } - return false; } - - function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type { - if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, node.typeArguments); - if (languageVersion < ScriptTarget.ES2015) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject); + function getInferenceInfoForType(type: ts.Type) { + if (type.flags & TypeFlags.TypeVariable) { + for (const inference of inferences) { + if (type === inference.typeParameter) { + return inference; + } + } } - return getReturnTypeOfSignature(getResolvedSignature(node)); - } - - function checkAssertion(node: AssertionExpression) { - return checkAssertionWorker(node, node.type, node.expression); + return undefined; } - - function isValidConstAssertionArgument(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.ArrayLiteralExpression: - case SyntaxKind.ObjectLiteralExpression: - return true; - case SyntaxKind.ParenthesizedExpression: - return isValidConstAssertionArgument((node).expression); - case SyntaxKind.PrefixUnaryExpression: - const op = (node).operator; - const arg = (node).operand; - return op === SyntaxKind.MinusToken && (arg.kind === SyntaxKind.NumericLiteral || arg.kind === SyntaxKind.BigIntLiteral) || - op === SyntaxKind.PlusToken && arg.kind === SyntaxKind.NumericLiteral; - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - const expr = (node).expression; - if (isIdentifier(expr)) { - let symbol = getSymbolAtLocation(expr); - if (symbol && symbol.flags & SymbolFlags.Alias) { - symbol = resolveAlias(symbol); + function getSingleTypeVariableFromIntersectionTypes(types: ts.Type[]) { + let typeVariable: ts.Type | undefined; + for (const type of types) { + const t = type.flags & TypeFlags.Intersection && find((type).types, t => !!getInferenceInfoForType(t)); + if (!t || typeVariable && t !== typeVariable) { + return undefined; + } + typeVariable = t; + } + return typeVariable; + } + function inferToMultipleTypes(source: ts.Type, targets: ts.Type[], targetFlags: TypeFlags) { + let typeVariableCount = 0; + if (targetFlags & TypeFlags.Union) { + let nakedTypeVariable: ts.Type | undefined; + const sources = source.flags & TypeFlags.Union ? (source).types : [source]; + const matched = new Array(sources.length); + let inferenceCircularity = false; + // First infer to types that are not naked type variables. For each source type we + // track whether inferences were made from that particular type to some target with + // equal priority (i.e. of equal quality) to what we would infer for a naked type + // parameter. + for (const t of targets) { + if (getInferenceInfoForType(t)) { + nakedTypeVariable = t; + typeVariableCount++; + } + else { + for (let i = 0; i < sources.length; i++) { + const saveInferencePriority = inferencePriority; + inferencePriority = InferencePriority.MaxValue; + inferFromTypes(sources[i], t); + if (inferencePriority === priority) + matched[i] = true; + inferenceCircularity = inferenceCircularity || inferencePriority === InferencePriority.Circularity; + inferencePriority = Math.min(inferencePriority, saveInferencePriority); } - return !!(symbol && (symbol.flags & SymbolFlags.Enum) && getEnumKind(symbol) === EnumKind.Literal); } - } - return false; - } - - function checkAssertionWorker(errNode: Node, type: TypeNode, expression: UnaryExpression | Expression, checkMode?: CheckMode) { - let exprType = checkExpression(expression, checkMode); - if (isConstTypeReference(type)) { - if (!isValidConstAssertionArgument(expression)) { - error(expression, Diagnostics.A_const_assertions_can_only_be_applied_to_references_to_enum_members_or_string_number_boolean_array_or_object_literals); } - return getRegularTypeOfLiteralType(exprType); - } - checkSourceElement(type); - exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(exprType)); - const targetType = getTypeFromTypeNode(type); - if (produceDiagnostics && targetType !== errorType) { - const widenedType = getWidenedType(exprType); - if (!isTypeComparableTo(targetType, widenedType)) { - checkTypeComparableTo(exprType, targetType, errNode, - Diagnostics.Conversion_of_type_0_to_type_1_may_be_a_mistake_because_neither_type_sufficiently_overlaps_with_the_other_If_this_was_intentional_convert_the_expression_to_unknown_first); + if (typeVariableCount === 0) { + // If every target is an intersection of types containing a single naked type variable, + // make a lower priority inference to that type variable. This handles inferring from + // 'A | B' to 'T & (X | Y)' where we want to infer 'A | B' for T. + const intersectionTypeVariable = getSingleTypeVariableFromIntersectionTypes(targets); + if (intersectionTypeVariable) { + inferWithPriority(source, intersectionTypeVariable, InferencePriority.NakedTypeVariable); + } + return; + } + // If the target has a single naked type variable and no inference circularities were + // encountered above (meaning we explored the types fully), create a union of the source + // types from which no inferences have been made so far and infer from that union to the + // naked type variable. + if (typeVariableCount === 1 && !inferenceCircularity) { + const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s); + if (unmatched.length) { + inferFromTypes(getUnionType(unmatched), nakedTypeVariable!); + return; + } } } - return targetType; - } - - function checkNonNullAssertion(node: NonNullExpression) { - return getNonNullableType(checkExpression(node.expression)); - } - - function checkMetaProperty(node: MetaProperty): Type { - checkGrammarMetaProperty(node); - - if (node.keywordToken === SyntaxKind.NewKeyword) { - return checkNewTargetMetaProperty(node); + else { + // We infer from types that are not naked type variables first so that inferences we + // make from nested naked type variables and given slightly higher priority by virtue + // of being first in the candidates array. + for (const t of targets) { + if (getInferenceInfoForType(t)) { + typeVariableCount++; + } + else { + inferFromTypes(source, t); + } + } } - - if (node.keywordToken === SyntaxKind.ImportKeyword) { - return checkImportMetaProperty(node); + // Inferences directly to naked type variables are given lower priority as they are + // less specific. For example, when inferring from Promise to T | Promise, + // we want to infer string for T, not Promise | string. For intersection types + // we only infer to single naked type variables. + if (targetFlags & TypeFlags.Intersection ? typeVariableCount === 1 : typeVariableCount > 0) { + for (const t of targets) { + if (getInferenceInfoForType(t)) { + inferWithPriority(source, t, InferencePriority.NakedTypeVariable); + } + } } - - return Debug.assertNever(node.keywordToken); } - - function checkNewTargetMetaProperty(node: MetaProperty) { - const container = getNewTargetContainer(node); - if (!container) { - error(node, Diagnostics.Meta_property_0_is_only_allowed_in_the_body_of_a_function_declaration_function_expression_or_constructor, "new.target"); - return errorType; - } - else if (container.kind === SyntaxKind.Constructor) { - const symbol = getSymbolOfNode(container.parent as ClassLikeDeclaration); - return getTypeOfSymbol(symbol); - } - else { - const symbol = getSymbolOfNode(container)!; - return getTypeOfSymbol(symbol); + function inferToMappedType(source: ts.Type, target: MappedType, constraintType: ts.Type): boolean { + if (constraintType.flags & TypeFlags.Union) { + let result = false; + for (const type of (constraintType as UnionType).types) { + result = inferToMappedType(source, target, type) || result; + } + return result; } - } - - function checkImportMetaProperty(node: MetaProperty) { - if (moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.System) { - error(node, Diagnostics.The_import_meta_meta_property_is_only_allowed_when_the_module_option_is_esnext_or_system); + if (constraintType.flags & TypeFlags.Index) { + // We're inferring from some source type S to a homomorphic mapped type { [P in keyof T]: X }, + // where T is a type variable. Use inferTypeForHomomorphicMappedType to infer a suitable source + // type and then make a secondary inference from that type to T. We make a secondary inference + // such that direct inferences to T get priority over inferences to Partial, for example. + const inference = getInferenceInfoForType((constraintType).type); + if (inference && !inference.isFixed) { + const inferredType = inferTypeForHomomorphicMappedType(source, target, (constraintType)); + if (inferredType) { + // We assign a lower priority to inferences made from types containing non-inferrable + // types because we may only have a partial result (i.e. we may have failed to make + // reverse inferences for some properties). + inferWithPriority(inferredType, inference.typeParameter, getObjectFlags(source) & ObjectFlags.NonInferrableType ? + InferencePriority.PartialHomomorphicMappedType : + InferencePriority.HomomorphicMappedType); + } + } + return true; } - const file = getSourceFileOfNode(node); - Debug.assert(!!(file.flags & NodeFlags.PossiblyContainsImportMeta), "Containing file is missing import meta node flag."); - Debug.assert(!!file.externalModuleIndicator, "Containing file should be a module."); - return node.name.escapedText === "meta" ? getGlobalImportMetaType() : errorType; - } - - function getTypeOfParameter(symbol: Symbol) { - const type = getTypeOfSymbol(symbol); - if (strictNullChecks) { - const declaration = symbol.valueDeclaration; - if (declaration && hasInitializer(declaration)) { - return getOptionalType(type); + if (constraintType.flags & TypeFlags.TypeParameter) { + // We're inferring from some source type S to a mapped type { [P in K]: X }, where K is a type + // parameter. First infer from 'keyof S' to K. + inferWithPriority(getIndexType(source), constraintType, InferencePriority.MappedTypeConstraint); + // If K is constrained to a type C, also infer to C. Thus, for a mapped type { [P in K]: X }, + // where K extends keyof T, we make the same inferences as for a homomorphic mapped type + // { [P in keyof T]: X }. This enables us to make meaningful inferences when the target is a + // Pick. + const extendedConstraint = getConstraintOfType(constraintType); + if (extendedConstraint && inferToMappedType(source, target, extendedConstraint)) { + return true; } + // If no inferences can be made to K's constraint, infer from a union of the property types + // in the source to the template type X. + const propTypes = map(getPropertiesOfType(source), getTypeOfSymbol); + const stringIndexType = getIndexTypeOfType(source, IndexKind.String); + const numberIndexInfo = getNonEnumNumberIndexInfo(source); + const numberIndexType = numberIndexInfo && numberIndexInfo.type; + inferFromTypes(getUnionType(append(append(propTypes, stringIndexType), numberIndexType)), getTemplateTypeFromMappedType(target)); + return true; } - return type; + return false; } - - function getParameterNameAtPosition(signature: Signature, pos: number) { - const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); - if (pos < paramCount) { - return signature.parameters[pos].escapedName; + function inferFromObjectTypes(source: ts.Type, target: ts.Type) { + // If we are already processing another target type with the same associated symbol (such as + // an instantiation of the same generic type), we do not explore this target as it would yield + // no further inferences. We exclude the static side of classes from this check since it shares + // its symbol with the instance side which would lead to false positives. + const isNonConstructorObject = target.flags & TypeFlags.Object && + !(getObjectFlags(target) & ObjectFlags.Anonymous && target.symbol && target.symbol.flags & SymbolFlags.Class); + const symbol = isNonConstructorObject ? target.symbol : undefined; + if (symbol) { + if (contains(symbolStack, symbol)) { + inferencePriority = InferencePriority.Circularity; + return; + } + (symbolStack || (symbolStack = [])).push(symbol); + inferFromObjectTypesWorker(source, target); + symbolStack.pop(); } - const restParameter = signature.parameters[paramCount] || unknownSymbol; - const restType = getTypeOfSymbol(restParameter); - if (isTupleType(restType)) { - const associatedNames = ((restType).target).associatedNames; - const index = pos - paramCount; - return associatedNames && associatedNames[index] || restParameter.escapedName + "_" + index as __String; + else { + inferFromObjectTypesWorker(source, target); } - return restParameter.escapedName; } - - function getTypeAtPosition(signature: Signature, pos: number): Type { - return tryGetTypeAtPosition(signature, pos) || anyType; - } - - function tryGetTypeAtPosition(signature: Signature, pos: number): Type | undefined { - const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); - if (pos < paramCount) { - return getTypeOfParameter(signature.parameters[pos]); + function inferFromObjectTypesWorker(source: ts.Type, target: ts.Type) { + if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && ((source).target === (target).target || isArrayType(source) && isArrayType(target))) { + // If source and target are references to the same generic type, infer from type arguments + inferFromTypeArguments(getTypeArguments((source)), getTypeArguments((target)), getVariances((source).target)); + return; } - if (signatureHasRestParameter(signature)) { - // We want to return the value undefined for an out of bounds parameter position, - // so we need to check bounds here before calling getIndexedAccessType (which - // otherwise would return the type 'undefined'). - const restType = getTypeOfSymbol(signature.parameters[paramCount]); - const index = pos - paramCount; - if (!isTupleType(restType) || restType.target.hasRestElement || index < getTypeArguments(restType).length) { - return getIndexedAccessType(restType, getLiteralType(index)); - } + if (isGenericMappedType(source) && isGenericMappedType(target)) { + // The source and target types are generic types { [P in S]: X } and { [P in T]: Y }, so we infer + // from S to T and from X to Y. + inferFromTypes(getConstraintTypeFromMappedType(source), getConstraintTypeFromMappedType(target)); + inferFromTypes(getTemplateTypeFromMappedType(source), getTemplateTypeFromMappedType(target)); } - return undefined; - } - - function getRestTypeAtPosition(source: Signature, pos: number): Type { - const paramCount = getParameterCount(source); - const restType = getEffectiveRestType(source); - const nonRestCount = paramCount - (restType ? 1 : 0); - if (restType && pos === nonRestCount) { - return restType; - } - const types = []; - const names = []; - for (let i = pos; i < nonRestCount; i++) { - types.push(getTypeAtPosition(source, i)); - names.push(getParameterNameAtPosition(source, i)); - } - if (restType) { - types.push(getIndexedAccessType(restType, numberType)); - names.push(getParameterNameAtPosition(source, nonRestCount)); - } - const minArgumentCount = getMinArgumentCount(source); - const minLength = minArgumentCount < pos ? 0 : minArgumentCount - pos; - return createTupleType(types, minLength, !!restType, /*readonly*/ false, names); - } - - function getParameterCount(signature: Signature) { - const length = signature.parameters.length; - if (signatureHasRestParameter(signature)) { - const restType = getTypeOfSymbol(signature.parameters[length - 1]); - if (isTupleType(restType)) { - return length + getTypeArguments(restType).length - 1; + if (getObjectFlags(target) & ObjectFlags.Mapped) { + const constraintType = getConstraintTypeFromMappedType((target)); + if (inferToMappedType(source, (target), constraintType)) { + return; } } - return length; + // Infer from the members of source and target only if the two types are possibly related + if (!typesDefinitelyUnrelated(source, target)) { + inferFromProperties(source, target); + inferFromSignatures(source, target, SignatureKind.Call); + inferFromSignatures(source, target, SignatureKind.Construct); + inferFromIndexTypes(source, target); + } } - - function getMinArgumentCount(signature: Signature) { - if (signatureHasRestParameter(signature)) { - const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); - if (isTupleType(restType)) { - const minLength = restType.target.minLength; - if (minLength > 0) { - return signature.parameters.length - 1 + minLength; + function inferFromProperties(source: ts.Type, target: ts.Type) { + if (isArrayType(source) || isTupleType(source)) { + if (isTupleType(target)) { + const sourceLength = isTupleType(source) ? getLengthOfTupleType(source) : 0; + const targetLength = getLengthOfTupleType(target); + const sourceRestType = isTupleType(source) ? getRestTypeOfTupleType(source) : getElementTypeOfArrayType(source); + const targetRestType = getRestTypeOfTupleType(target); + const fixedLength = targetLength < sourceLength || sourceRestType ? targetLength : sourceLength; + for (let i = 0; i < fixedLength; i++) { + inferFromTypes(i < sourceLength ? getTypeArguments((source))[i] : sourceRestType!, getTypeArguments(target)[i]); + } + if (targetRestType) { + const types = fixedLength < sourceLength ? getTypeArguments((source)).slice(fixedLength, sourceLength) : []; + if (sourceRestType) { + types.push(sourceRestType); + } + if (types.length) { + inferFromTypes(getUnionType(types), targetRestType); + } } + return; + } + if (isArrayType(target)) { + inferFromIndexTypes(source, target); + return; } } - return signature.minArgumentCount; - } - - function hasEffectiveRestParameter(signature: Signature) { - if (signatureHasRestParameter(signature)) { - const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); - return !isTupleType(restType) || restType.target.hasRestElement; + const properties = getPropertiesOfObjectType(target); + for (const targetProp of properties) { + const sourceProp = getPropertyOfType(source, targetProp.escapedName); + if (sourceProp) { + inferFromTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp)); + } } - return false; } - - function getEffectiveRestType(signature: Signature) { - if (signatureHasRestParameter(signature)) { - const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); - return isTupleType(restType) ? getRestArrayTypeOfTupleType(restType) : restType; + function inferFromSignatures(source: ts.Type, target: ts.Type, kind: SignatureKind) { + const sourceSignatures = getSignaturesOfType(source, kind); + const targetSignatures = getSignaturesOfType(target, kind); + const sourceLen = sourceSignatures.length; + const targetLen = targetSignatures.length; + const len = sourceLen < targetLen ? sourceLen : targetLen; + const skipParameters = !!(getObjectFlags(source) & ObjectFlags.NonInferrableType); + for (let i = 0; i < len; i++) { + inferFromSignature(getBaseSignature(sourceSignatures[sourceLen - len + i]), getBaseSignature(targetSignatures[targetLen - len + i]), skipParameters); } - return undefined; - } - - function getNonArrayRestType(signature: Signature) { - const restType = getEffectiveRestType(signature); - return restType && !isArrayType(restType) && !isTypeAny(restType) ? restType : undefined; - } - - function getTypeOfFirstParameterOfSignature(signature: Signature) { - return getTypeOfFirstParameterOfSignatureWithFallback(signature, neverType); } - - function getTypeOfFirstParameterOfSignatureWithFallback(signature: Signature, fallbackType: Type) { - return signature.parameters.length > 0 ? getTypeAtPosition(signature, 0) : fallbackType; + function inferFromSignature(source: ts.Signature, target: ts.Signature, skipParameters: boolean) { + if (!skipParameters) { + const saveBivariant = bivariant; + const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown; + // Once we descend into a bivariant signature we remain bivariant for all nested inferences + bivariant = bivariant || kind === SyntaxKind.MethodDeclaration || kind === SyntaxKind.MethodSignature || kind === SyntaxKind.Constructor; + applyToParameterTypes(source, target, inferFromContravariantTypes); + bivariant = saveBivariant; + } + applyToReturnTypes(source, target, inferFromTypes); } - - function inferFromAnnotatedParameters(signature: Signature, context: Signature, inferenceContext: InferenceContext) { - const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); - for (let i = 0; i < len; i++) { - const declaration = signature.parameters[i].valueDeclaration; - if (declaration.type) { - const typeNode = getEffectiveTypeAnnotationNode(declaration); - if (typeNode) { - inferTypes(inferenceContext.inferences, getTypeFromTypeNode(typeNode), getTypeAtPosition(context, i)); - } + function inferFromIndexTypes(source: ts.Type, target: ts.Type) { + const targetStringIndexType = getIndexTypeOfType(target, IndexKind.String); + if (targetStringIndexType) { + const sourceIndexType = getIndexTypeOfType(source, IndexKind.String) || + getImplicitIndexTypeOfType(source, IndexKind.String); + if (sourceIndexType) { + inferFromTypes(sourceIndexType, targetStringIndexType); } } - const restType = getEffectiveRestType(context); - if (restType && restType.flags & TypeFlags.TypeParameter) { - // The contextual signature has a generic rest parameter. We first instantiate the contextual - // signature (without fixing type parameters) and assign types to contextually typed parameters. - const instantiatedContext = instantiateSignature(context, inferenceContext.nonFixingMapper); - assignContextualParameterTypes(signature, instantiatedContext); - // We then infer from a tuple type representing the parameters that correspond to the contextual - // rest parameter. - const restPos = getParameterCount(context) - 1; - inferTypes(inferenceContext.inferences, getRestTypeAtPosition(signature, restPos), restType); + const targetNumberIndexType = getIndexTypeOfType(target, IndexKind.Number); + if (targetNumberIndexType) { + const sourceIndexType = getIndexTypeOfType(source, IndexKind.Number) || + getIndexTypeOfType(source, IndexKind.String) || + getImplicitIndexTypeOfType(source, IndexKind.Number); + if (sourceIndexType) { + inferFromTypes(sourceIndexType, targetNumberIndexType); + } } } - - function assignContextualParameterTypes(signature: Signature, context: Signature) { - signature.typeParameters = context.typeParameters; - if (context.thisParameter) { - const parameter = signature.thisParameter; - if (!parameter || parameter.valueDeclaration && !(parameter.valueDeclaration).type) { - if (!parameter) { - signature.thisParameter = createSymbolWithType(context.thisParameter, /*type*/ undefined); + } + function isTypeOrBaseIdenticalTo(s: ts.Type, t: ts.Type) { + return isTypeIdenticalTo(s, t) || !!(s.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) && isTypeIdenticalTo(getBaseTypeOfLiteralType(s), t); + } + function isTypeCloselyMatchedBy(s: ts.Type, t: ts.Type) { + return !!(s.flags & TypeFlags.Object && t.flags & TypeFlags.Object && s.symbol && s.symbol === t.symbol || + s.aliasSymbol && s.aliasTypeArguments && s.aliasSymbol === t.aliasSymbol); + } + function hasPrimitiveConstraint(type: TypeParameter): boolean { + const constraint = getConstraintOfTypeParameter(type); + return !!constraint && maybeTypeOfKind(constraint.flags & TypeFlags.Conditional ? getDefaultConstraintOfConditionalType((constraint as ConditionalType)) : constraint, TypeFlags.Primitive | TypeFlags.Index); + } + function isObjectLiteralType(type: ts.Type) { + return !!(getObjectFlags(type) & ObjectFlags.ObjectLiteral); + } + function isObjectOrArrayLiteralType(type: ts.Type) { + return !!(getObjectFlags(type) & (ObjectFlags.ObjectLiteral | ObjectFlags.ArrayLiteral)); + } + function unionObjectAndArrayLiteralCandidates(candidates: ts.Type[]): ts.Type[] { + if (candidates.length > 1) { + const objectLiterals = filter(candidates, isObjectOrArrayLiteralType); + if (objectLiterals.length) { + const literalsType = getUnionType(objectLiterals, UnionReduction.Subtype); + return concatenate(filter(candidates, t => !isObjectOrArrayLiteralType(t)), [literalsType]); + } + } + return candidates; + } + function getContravariantInference(inference: InferenceInfo) { + return (inference.priority!) & InferencePriority.PriorityImpliesCombination ? getIntersectionType(inference.contraCandidates!) : getCommonSubtype(inference.contraCandidates!); + } + function getCovariantInference(inference: InferenceInfo, signature: ts.Signature) { + // Extract all object and array literal types and replace them with a single widened and normalized type. + const candidates = unionObjectAndArrayLiteralCandidates(inference.candidates!); + // We widen inferred literal types if + // all inferences were made to top-level occurrences of the type parameter, and + // the type parameter has no constraint or its constraint includes no primitive or literal types, and + // the type parameter was fixed during inference or does not occur at top-level in the return type. + const primitiveConstraint = hasPrimitiveConstraint(inference.typeParameter); + const widenLiteralTypes = !primitiveConstraint && inference.topLevel && + (inference.isFixed || !isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), inference.typeParameter)); + const baseCandidates = primitiveConstraint ? sameMap(candidates, getRegularTypeOfLiteralType) : + widenLiteralTypes ? sameMap(candidates, getWidenedLiteralType) : + candidates; + // If all inferences were made from a position that implies a combined result, infer a union type. + // Otherwise, infer a common supertype. + const unwidenedType = (inference.priority!) & InferencePriority.PriorityImpliesCombination ? + getUnionType(baseCandidates, UnionReduction.Subtype) : + getCommonSupertype(baseCandidates); + return getWidenedType(unwidenedType); + } + function getInferredType(context: InferenceContext, index: number): ts.Type { + const inference = context.inferences[index]; + if (!inference.inferredType) { + let inferredType: ts.Type | undefined; + const signature = context.signature; + if (signature) { + const inferredCovariantType = inference.candidates ? getCovariantInference(inference, signature) : undefined; + if (inference.contraCandidates) { + const inferredContravariantType = getContravariantInference(inference); + // If we have both co- and contra-variant inferences, we prefer the contra-variant inference + // unless the co-variant inference is a subtype and not 'never'. + inferredType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) && + isTypeSubtypeOf(inferredCovariantType, inferredContravariantType) ? + inferredCovariantType : inferredContravariantType; + } + else if (inferredCovariantType) { + inferredType = inferredCovariantType; + } + else if (context.flags & InferenceFlags.NoDefault) { + // We use silentNeverType as the wildcard that signals no inferences. + inferredType = silentNeverType; + } + else { + // Infer either the default or the empty object type when no inferences were + // made. It is important to remember that in this case, inference still + // succeeds, meaning there is no error for not having inference candidates. An + // inference error only occurs when there are *conflicting* candidates, i.e. + // candidates with no common supertype. + const defaultType = getDefaultFromTypeParameter(inference.typeParameter); + if (defaultType) { + // Instantiate the default type. Any forward reference to a type + // parameter should be instantiated to the empty object type. + inferredType = instantiateType(defaultType, combineTypeMappers(createBackreferenceMapper(context, index), context.nonFixingMapper)); } - assignTypeToParameterAndFixTypeParameters(signature.thisParameter!, getTypeOfSymbol(context.thisParameter)); } } - const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); - for (let i = 0; i < len; i++) { - const parameter = signature.parameters[i]; - if (!getEffectiveTypeAnnotationNode(parameter.valueDeclaration)) { - const contextualParameterType = getTypeAtPosition(context, i); - assignTypeToParameterAndFixTypeParameters(parameter, contextualParameterType); - } + else { + inferredType = getTypeFromInference(inference); } - if (signatureHasRestParameter(signature)) { - // parameter might be a transient symbol generated by use of `arguments` in the function body. - const parameter = last(signature.parameters); - if (isTransientSymbol(parameter) || !getEffectiveTypeAnnotationNode(parameter.valueDeclaration)) { - const contextualParameterType = getRestTypeAtPosition(context, len); - assignTypeToParameterAndFixTypeParameters(parameter, contextualParameterType); + inference.inferredType = inferredType || getDefaultTypeArgumentType(!!(context.flags & InferenceFlags.AnyDefault)); + const constraint = getConstraintOfTypeParameter(inference.typeParameter); + if (constraint) { + const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper); + if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) { + inference.inferredType = inferredType = instantiatedConstraint; } } } - - // When contextual typing assigns a type to a parameter that contains a binding pattern, we also need to push - // the destructured type into the contained binding elements. - function assignBindingElementTypes(pattern: BindingPattern) { - for (const element of pattern.elements) { - if (!isOmittedExpression(element)) { - if (element.name.kind === SyntaxKind.Identifier) { - getSymbolLinks(getSymbolOfNode(element)).type = getTypeForBindingElement(element); - } - else { - assignBindingElementTypes(element.name); - } - } - } + return inference.inferredType; + } + function getDefaultTypeArgumentType(isInJavaScriptFile: boolean): ts.Type { + return isInJavaScriptFile ? anyType : unknownType; + } + function getInferredTypes(context: InferenceContext): ts.Type[] { + const result: ts.Type[] = []; + for (let i = 0; i < context.inferences.length; i++) { + result.push(getInferredType(context, i)); } - - function assignTypeToParameterAndFixTypeParameters(parameter: Symbol, contextualType: Type) { - const links = getSymbolLinks(parameter); - if (!links.type) { - links.type = contextualType; - const decl = parameter.valueDeclaration as ParameterDeclaration; - if (decl.name.kind !== SyntaxKind.Identifier) { - // if inference didn't come up with anything but unknown, fall back to the binding pattern if present. - if (links.type === unknownType) { - links.type = getTypeFromBindingPattern(decl.name); - } - assignBindingElementTypes(decl.name); + return result; + } + // EXPRESSION TYPE CHECKING + function getCannotFindNameDiagnosticForName(node: Identifier): DiagnosticMessage { + switch (node.escapedText) { + case "document": + case "console": + return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_include_dom; + case "$": + return compilerOptions.types + ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_types_Slashjquery_and_then_add_jquery_to_the_types_field_in_your_tsconfig + : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_types_Slashjquery; + case "describe": + case "suite": + case "it": + case "test": + return compilerOptions.types + ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_a_test_runner_Try_npm_i_types_Slashjest_or_npm_i_types_Slashmocha_and_then_add_jest_or_mocha_to_the_types_field_in_your_tsconfig + : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_a_test_runner_Try_npm_i_types_Slashjest_or_npm_i_types_Slashmocha; + case "process": + case "require": + case "Buffer": + case "module": + return compilerOptions.types + ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_types_Slashnode_and_then_add_node_to_the_types_field_in_your_tsconfig + : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_types_Slashnode; + case "Map": + case "Set": + case "Promise": + case "Symbol": + case "WeakMap": + case "WeakSet": + case "Iterator": + case "AsyncIterator": + return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_es2015_or_later; + default: + if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { + return Diagnostics.No_value_exists_in_scope_for_the_shorthand_property_0_Either_declare_one_or_provide_an_initializer; + } + else { + return Diagnostics.Cannot_find_name_0; } - } } - - function createPromiseType(promisedType: Type): Type { - // creates a `Promise` type where `T` is the promisedType argument - const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true); - if (globalPromiseType !== emptyGenericType) { - // if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type - promisedType = getAwaitedType(promisedType) || unknownType; - return createTypeReference(globalPromiseType, [promisedType]); - } - - return unknownType; + } + function getResolvedSymbol(node: Identifier): ts.Symbol { + const links = getNodeLinks(node); + if (!links.resolvedSymbol) { + links.resolvedSymbol = !nodeIsMissing(node) && + resolveName(node, node.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, getCannotFindNameDiagnosticForName(node), node, !isWriteOnlyAccess(node), + /*excludeGlobals*/ false, Diagnostics.Cannot_find_name_0_Did_you_mean_1) || unknownSymbol; + } + return links.resolvedSymbol; + } + function isInTypeQuery(node: Node): boolean { + // TypeScript 1.0 spec (April 2014): 3.6.3 + // A type query consists of the keyword typeof followed by an expression. + // The expression is restricted to a single identifier or a sequence of identifiers separated by periods + return !!findAncestor(node, n => n.kind === SyntaxKind.TypeQuery ? true : n.kind === SyntaxKind.Identifier || n.kind === SyntaxKind.QualifiedName ? false : "quit"); + } + // Return the flow cache key for a "dotted name" (i.e. a sequence of identifiers + // separated by dots). The key consists of the id of the symbol referenced by the + // leftmost identifier followed by zero or more property names separated by dots. + // The result is undefined if the reference isn't a dotted name. We prefix nodes + // occurring in an apparent type position with '@' because the control flow type + // of such nodes may be based on the apparent type instead of the declared type. + function getFlowCacheKey(node: Node, declaredType: ts.Type, initialType: ts.Type, flowContainer: Node | undefined): string | undefined { + switch (node.kind) { + case SyntaxKind.Identifier: + const symbol = getResolvedSymbol((node)); + return symbol !== unknownSymbol ? `${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}|${isConstraintPosition(node) ? "@" : ""}${getSymbolId(symbol)}` : undefined; + case SyntaxKind.ThisKeyword: + return "0"; + case SyntaxKind.NonNullExpression: + case SyntaxKind.ParenthesizedExpression: + return getFlowCacheKey((node).expression, declaredType, initialType, flowContainer); + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + const propName = getAccessedPropertyName((node)); + if (propName !== undefined) { + const key = getFlowCacheKey((node).expression, declaredType, initialType, flowContainer); + return key && key + "." + propName; + } + } + return undefined; + } + function isMatchingReference(source: Node, target: Node): boolean { + switch (target.kind) { + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.NonNullExpression: + return isMatchingReference(source, (target as NonNullExpression | ParenthesizedExpression).expression); } - - function createPromiseLikeType(promisedType: Type): Type { - // creates a `PromiseLike` type where `T` is the promisedType argument - const globalPromiseLikeType = getGlobalPromiseLikeType(/*reportErrors*/ true); - if (globalPromiseLikeType !== emptyGenericType) { - // if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type - promisedType = getAwaitedType(promisedType) || unknownType; - return createTypeReference(globalPromiseLikeType, [promisedType]); + switch (source.kind) { + case SyntaxKind.Identifier: + return target.kind === SyntaxKind.Identifier && getResolvedSymbol((source)) === getResolvedSymbol((target)) || + (target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) && + getExportSymbolOfValueSymbolIfExported(getResolvedSymbol((source))) === getSymbolOfNode(target); + case SyntaxKind.ThisKeyword: + return target.kind === SyntaxKind.ThisKeyword; + case SyntaxKind.SuperKeyword: + return target.kind === SyntaxKind.SuperKeyword; + case SyntaxKind.NonNullExpression: + case SyntaxKind.ParenthesizedExpression: + return isMatchingReference((source as NonNullExpression | ParenthesizedExpression).expression, target); + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return isAccessExpression(target) && + getAccessedPropertyName((source)) === getAccessedPropertyName(target) && + isMatchingReference((source).expression, target.expression); + } + return false; + } + function getAccessedPropertyName(access: AccessExpression): __String | undefined { + return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText : + isStringOrNumericLiteralLike(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) : + undefined; + } + function containsMatchingReference(source: Node, target: Node) { + while (isAccessExpression(source)) { + source = source.expression; + if (isMatchingReference(source, target)) { + return true; } - - return unknownType; } - - function createPromiseReturnType(func: FunctionLikeDeclaration | ImportCall, promisedType: Type) { - const promiseType = createPromiseType(promisedType); - if (promiseType === unknownType) { - error(func, isImportCall(func) ? - Diagnostics.A_dynamic_import_call_returns_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option : - Diagnostics.An_async_function_or_method_must_return_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option); - return errorType; - } - else if (!getGlobalPromiseConstructorSymbol(/*reportErrors*/ true)) { - error(func, isImportCall(func) ? - Diagnostics.A_dynamic_import_call_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option : - Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option); + return false; + } + function optionalChainContainsReference(source: Node, target: Node) { + while (isOptionalChain(source)) { + source = source.expression; + if (isMatchingReference(source, target)) { + return true; } - - return promiseType; } - - function getReturnTypeFromBody(func: FunctionLikeDeclaration, checkMode?: CheckMode): Type { - if (!func.body) { - return errorType; + return false; + } + // Return true if target is a property access xxx.yyy, source is a property access xxx.zzz, the declared + // type of xxx is a union type, and yyy is a property that is possibly a discriminant. We consider a property + // a possible discriminant if its type differs in the constituents of containing union type, and if every + // choice is a unit type or a union of unit types. + function containsMatchingReferenceDiscriminant(source: Node, target: Node) { + let name; + return isAccessExpression(target) && + containsMatchingReference(source, target.expression) && + (name = getAccessedPropertyName(target)) !== undefined && + isDiscriminantProperty(getDeclaredTypeOfReference(target.expression), name); + } + function getDeclaredTypeOfReference(expr: Node): ts.Type | undefined { + if (expr.kind === SyntaxKind.Identifier) { + return getTypeOfSymbol(getResolvedSymbol((expr))); + } + if (isAccessExpression(expr)) { + const type = getDeclaredTypeOfReference(expr.expression); + if (type) { + const propName = getAccessedPropertyName(expr); + return propName !== undefined ? getTypeOfPropertyOfType(type, propName) : undefined; } - - const functionFlags = getFunctionFlags(func); - const isAsync = (functionFlags & FunctionFlags.Async) !== 0; - const isGenerator = (functionFlags & FunctionFlags.Generator) !== 0; - - let returnType: Type | undefined; - let yieldType: Type | undefined; - let nextType: Type | undefined; - let fallbackReturnType: Type = voidType; - if (func.body.kind !== SyntaxKind.Block) { // Async or normal arrow function - returnType = checkExpressionCached(func.body, checkMode && checkMode & ~CheckMode.SkipGenericFunctions); - if (isAsync) { - // From within an async function you can return either a non-promise value or a promise. Any - // Promise/A+ compatible implementation will always assimilate any foreign promise, so the - // return type of the body should be unwrapped to its awaited type, which we will wrap in - // the native Promise type later in this function. - returnType = checkAwaitedType(returnType, /*errorNode*/ func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + } + return undefined; + } + function isDiscriminantProperty(type: ts.Type | undefined, name: __String) { + if (type && type.flags & TypeFlags.Union) { + const prop = getUnionOrIntersectionProperty((type), name); + if (prop && getCheckFlags(prop) & CheckFlags.SyntheticProperty) { + if ((prop).isDiscriminantProperty === undefined) { + (prop).isDiscriminantProperty = + ((prop).checkFlags & CheckFlags.Discriminant) === CheckFlags.Discriminant && + !maybeTypeOfKind(getTypeOfSymbol(prop), TypeFlags.Instantiable); } + return !!(prop).isDiscriminantProperty; } - else if (isGenerator) { // Generator or AsyncGenerator function - const returnTypes = checkAndAggregateReturnExpressionTypes(func, checkMode); - if (!returnTypes) { - fallbackReturnType = neverType; - } - else if (returnTypes.length > 0) { - returnType = getUnionType(returnTypes, UnionReduction.Subtype); + } + return false; + } + function isSyntheticThisPropertyAccess(expr: Node) { + return isAccessExpression(expr) && expr.expression.kind === SyntaxKind.ThisKeyword && !!(expr.expression.flags & NodeFlags.Synthesized); + } + function findDiscriminantProperties(sourceProperties: ts.Symbol[], target: ts.Type): ts.Symbol[] | undefined { + let result: ts.Symbol[] | undefined; + for (const sourceProperty of sourceProperties) { + if (isDiscriminantProperty(target, sourceProperty.escapedName)) { + if (result) { + result.push(sourceProperty); + continue; } - const { yieldTypes, nextTypes } = checkAndAggregateYieldOperandTypes(func, checkMode); - yieldType = some(yieldTypes) ? getUnionType(yieldTypes, UnionReduction.Subtype) : undefined; - nextType = some(nextTypes) ? getIntersectionType(nextTypes) : undefined; + result = [sourceProperty]; } - else { // Async or normal function - const types = checkAndAggregateReturnExpressionTypes(func, checkMode); - if (!types) { - // For an async function, the return type will not be never, but rather a Promise for never. - return functionFlags & FunctionFlags.Async - ? createPromiseReturnType(func, neverType) // Async function - : neverType; // Normal function - } - if (types.length === 0) { - // For an async function, the return type will not be void, but rather a Promise for void. - return functionFlags & FunctionFlags.Async - ? createPromiseReturnType(func, voidType) // Async function - : voidType; // Normal function + } + return result; + } + function isOrContainsMatchingReference(source: Node, target: Node) { + return isMatchingReference(source, target) || containsMatchingReference(source, target); + } + function hasMatchingArgument(callExpression: CallExpression, reference: Node) { + if (callExpression.arguments) { + for (const argument of callExpression.arguments) { + if (isOrContainsMatchingReference(reference, argument)) { + return true; } - - // Return a union of the return expression types. - returnType = getUnionType(types, UnionReduction.Subtype); } - - if (returnType || yieldType || nextType) { - const contextualSignature = getContextualSignatureForFunctionLikeDeclaration(func); - if (!contextualSignature) { - if (yieldType) reportErrorsFromWidening(func, yieldType, WideningKind.GeneratorYield); - if (returnType) reportErrorsFromWidening(func, returnType); - if (nextType) reportErrorsFromWidening(func, nextType); - } - if (returnType && isUnitType(returnType) || - yieldType && isUnitType(yieldType) || - nextType && isUnitType(nextType)) { - const contextualType = !contextualSignature ? undefined : - contextualSignature === getSignatureFromDeclaration(func) ? isGenerator ? undefined : returnType : - instantiateContextualType(getReturnTypeOfSignature(contextualSignature), func); - if (isGenerator) { - yieldType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(yieldType, contextualType, IterationTypeKind.Yield, isAsync); - returnType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(returnType, contextualType, IterationTypeKind.Return, isAsync); - nextType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(nextType, contextualType, IterationTypeKind.Next, isAsync); - } - else { - returnType = getWidenedLiteralLikeTypeForContextualReturnTypeIfNeeded(returnType, contextualType, isAsync); - } - } - - if (yieldType) yieldType = getWidenedType(yieldType); - if (returnType) returnType = getWidenedType(returnType); - if (nextType) nextType = getWidenedType(nextType); + } + if (callExpression.expression.kind === SyntaxKind.PropertyAccessExpression && + isOrContainsMatchingReference(reference, (callExpression.expression).expression)) { + return true; + } + return false; + } + function getFlowNodeId(flow: FlowNode): number { + if (!flow.id || flow.id < 0) { + flow.id = nextFlowId; + nextFlowId++; + } + return flow.id; + } + function typeMaybeAssignableTo(source: ts.Type, target: ts.Type) { + if (!(source.flags & TypeFlags.Union)) { + return isTypeAssignableTo(source, target); + } + for (const t of (source).types) { + if (isTypeAssignableTo(t, target)) { + return true; } - - if (isGenerator) { - return createGeneratorReturnType( - yieldType || neverType, - returnType || fallbackReturnType, - nextType || getContextualIterationType(IterationTypeKind.Next, func) || unknownType, - isAsync); + } + return false; + } + // Remove those constituent types of declaredType to which no constituent type of assignedType is assignable. + // For example, when a variable of type number | string | boolean is assigned a value of type number | boolean, + // we remove type string. + function getAssignmentReducedType(declaredType: UnionType, assignedType: ts.Type) { + if (declaredType !== assignedType) { + if (assignedType.flags & TypeFlags.Never) { + return assignedType; + } + let reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t)); + if (assignedType.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(assignedType)) { + reducedType = mapType(reducedType, getFreshTypeOfLiteralType); // Ensure that if the assignment is a fresh type, that we narrow to fresh types + } + // Our crude heuristic produces an invalid result in some cases: see GH#26130. + // For now, when that happens, we give up and don't narrow at all. (This also + // means we'll never narrow for erroneous assignments where the assigned type + // is not assignable to the declared type.) + if (isTypeAssignableTo(assignedType, reducedType)) { + return reducedType; + } + } + return declaredType; + } + function getTypeFactsOfTypes(types: ts.Type[]): TypeFacts { + let result: TypeFacts = TypeFacts.None; + for (const t of types) { + result |= getTypeFacts(t); + } + return result; + } + function isFunctionObjectType(type: ObjectType): boolean { + // We do a quick check for a "bind" property before performing the more expensive subtype + // check. This gives us a quicker out in the common case where an object type is not a function. + const resolved = resolveStructuredTypeMembers(type); + return !!(resolved.callSignatures.length || resolved.constructSignatures.length || + resolved.members.get(("bind" as __String)) && isTypeSubtypeOf(type, globalFunctionType)); + } + function getTypeFacts(type: ts.Type): TypeFacts { + const flags = type.flags; + if (flags & TypeFlags.String) { + return strictNullChecks ? TypeFacts.StringStrictFacts : TypeFacts.StringFacts; + } + if (flags & TypeFlags.StringLiteral) { + const isEmpty = (type).value === ""; + return strictNullChecks ? + isEmpty ? TypeFacts.EmptyStringStrictFacts : TypeFacts.NonEmptyStringStrictFacts : + isEmpty ? TypeFacts.EmptyStringFacts : TypeFacts.NonEmptyStringFacts; + } + if (flags & (TypeFlags.Number | TypeFlags.Enum)) { + return strictNullChecks ? TypeFacts.NumberStrictFacts : TypeFacts.NumberFacts; + } + if (flags & TypeFlags.NumberLiteral) { + const isZero = (type).value === 0; + return strictNullChecks ? + isZero ? TypeFacts.ZeroNumberStrictFacts : TypeFacts.NonZeroNumberStrictFacts : + isZero ? TypeFacts.ZeroNumberFacts : TypeFacts.NonZeroNumberFacts; + } + if (flags & TypeFlags.BigInt) { + return strictNullChecks ? TypeFacts.BigIntStrictFacts : TypeFacts.BigIntFacts; + } + if (flags & TypeFlags.BigIntLiteral) { + const isZero = isZeroBigInt((type)); + return strictNullChecks ? + isZero ? TypeFacts.ZeroBigIntStrictFacts : TypeFacts.NonZeroBigIntStrictFacts : + isZero ? TypeFacts.ZeroBigIntFacts : TypeFacts.NonZeroBigIntFacts; + } + if (flags & TypeFlags.Boolean) { + return strictNullChecks ? TypeFacts.BooleanStrictFacts : TypeFacts.BooleanFacts; + } + if (flags & TypeFlags.BooleanLike) { + return strictNullChecks ? + (type === falseType || type === regularFalseType) ? TypeFacts.FalseStrictFacts : TypeFacts.TrueStrictFacts : + (type === falseType || type === regularFalseType) ? TypeFacts.FalseFacts : TypeFacts.TrueFacts; + } + if (flags & TypeFlags.Object) { + return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType((type)) ? + strictNullChecks ? TypeFacts.EmptyObjectStrictFacts : TypeFacts.EmptyObjectFacts : + isFunctionObjectType((type)) ? + strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts : + strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; + } + if (flags & (TypeFlags.Void | TypeFlags.Undefined)) { + return TypeFacts.UndefinedFacts; + } + if (flags & TypeFlags.Null) { + return TypeFacts.NullFacts; + } + if (flags & TypeFlags.ESSymbolLike) { + return strictNullChecks ? TypeFacts.SymbolStrictFacts : TypeFacts.SymbolFacts; + } + if (flags & TypeFlags.NonPrimitive) { + return strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; + } + if (flags & TypeFlags.Instantiable) { + return getTypeFacts(getBaseConstraintOfType(type) || unknownType); + } + if (flags & TypeFlags.UnionOrIntersection) { + return getTypeFactsOfTypes((type).types); + } + return TypeFacts.All; + } + function getTypeWithFacts(type: ts.Type, include: TypeFacts) { + return filterType(type, t => (getTypeFacts(t) & include) !== 0); + } + function getTypeWithDefault(type: ts.Type, defaultExpression: Expression) { + if (defaultExpression) { + const defaultType = getTypeOfExpression(defaultExpression); + return getUnionType([getTypeWithFacts(type, TypeFacts.NEUndefined), defaultType]); + } + return type; + } + function getTypeOfDestructuredProperty(type: ts.Type, name: PropertyName) { + const nameType = getLiteralTypeFromPropertyName(name); + if (!isTypeUsableAsPropertyName(nameType)) + return errorType; + const text = getPropertyNameFromType(nameType); + return getConstraintForLocation(getTypeOfPropertyOfType(type, text), name) || + isNumericLiteralName(text) && getIndexTypeOfType(type, IndexKind.Number) || + getIndexTypeOfType(type, IndexKind.String) || + errorType; + } + function getTypeOfDestructuredArrayElement(type: ts.Type, index: number) { + return everyType(type, isTupleLikeType) && getTupleElementType(type, index) || + checkIteratedTypeOrElementType(IterationUse.Destructuring, type, undefinedType, /*errorNode*/ undefined) || + errorType; + } + function getTypeOfDestructuredSpreadExpression(type: ts.Type) { + return createArrayType(checkIteratedTypeOrElementType(IterationUse.Destructuring, type, undefinedType, /*errorNode*/ undefined) || errorType); + } + function getAssignedTypeOfBinaryExpression(node: BinaryExpression): ts.Type { + const isDestructuringDefaultAssignment = node.parent.kind === SyntaxKind.ArrayLiteralExpression && isDestructuringAssignmentTarget(node.parent) || + node.parent.kind === SyntaxKind.PropertyAssignment && isDestructuringAssignmentTarget(node.parent.parent); + return isDestructuringDefaultAssignment ? + getTypeWithDefault(getAssignedType(node), node.right) : + getTypeOfExpression(node.right); + } + function isDestructuringAssignmentTarget(parent: Node) { + return parent.parent.kind === SyntaxKind.BinaryExpression && (parent.parent as BinaryExpression).left === parent || + parent.parent.kind === SyntaxKind.ForOfStatement && (parent.parent as ForOfStatement).initializer === parent; + } + function getAssignedTypeOfArrayLiteralElement(node: ArrayLiteralExpression, element: Expression): ts.Type { + return getTypeOfDestructuredArrayElement(getAssignedType(node), node.elements.indexOf(element)); + } + function getAssignedTypeOfSpreadExpression(node: SpreadElement): ts.Type { + return getTypeOfDestructuredSpreadExpression(getAssignedType((node.parent))); + } + function getAssignedTypeOfPropertyAssignment(node: PropertyAssignment | ShorthandPropertyAssignment): ts.Type { + return getTypeOfDestructuredProperty(getAssignedType(node.parent), node.name); + } + function getAssignedTypeOfShorthandPropertyAssignment(node: ShorthandPropertyAssignment): ts.Type { + return getTypeWithDefault(getAssignedTypeOfPropertyAssignment(node), node.objectAssignmentInitializer!); + } + function getAssignedType(node: Expression): ts.Type { + const { parent } = node; + switch (parent.kind) { + case SyntaxKind.ForInStatement: + return stringType; + case SyntaxKind.ForOfStatement: + return checkRightHandSideOfForOf((parent).expression, (parent).awaitModifier) || errorType; + case SyntaxKind.BinaryExpression: + return getAssignedTypeOfBinaryExpression((parent)); + case SyntaxKind.DeleteExpression: + return undefinedType; + case SyntaxKind.ArrayLiteralExpression: + return getAssignedTypeOfArrayLiteralElement((parent), node); + case SyntaxKind.SpreadElement: + return getAssignedTypeOfSpreadExpression((parent)); + case SyntaxKind.PropertyAssignment: + return getAssignedTypeOfPropertyAssignment((parent)); + case SyntaxKind.ShorthandPropertyAssignment: + return getAssignedTypeOfShorthandPropertyAssignment((parent)); + } + return errorType; + } + function getInitialTypeOfBindingElement(node: BindingElement): ts.Type { + const pattern = node.parent; + const parentType = getInitialType((pattern.parent)); + const type = pattern.kind === SyntaxKind.ObjectBindingPattern ? + getTypeOfDestructuredProperty(parentType, node.propertyName || (node.name)) : + !node.dotDotDotToken ? + getTypeOfDestructuredArrayElement(parentType, pattern.elements.indexOf(node)) : + getTypeOfDestructuredSpreadExpression(parentType); + return getTypeWithDefault(type, node.initializer!); + } + function getTypeOfInitializer(node: Expression) { + // Return the cached type if one is available. If the type of the variable was inferred + // from its initializer, we'll already have cached the type. Otherwise we compute it now + // without caching such that transient types are reflected. + const links = getNodeLinks(node); + return links.resolvedType || getTypeOfExpression(node); + } + function getInitialTypeOfVariableDeclaration(node: VariableDeclaration) { + if (node.initializer) { + return getTypeOfInitializer(node.initializer); + } + if (node.parent.parent.kind === SyntaxKind.ForInStatement) { + return stringType; + } + if (node.parent.parent.kind === SyntaxKind.ForOfStatement) { + return checkRightHandSideOfForOf(node.parent.parent.expression, node.parent.parent.awaitModifier) || errorType; + } + return errorType; + } + function getInitialType(node: VariableDeclaration | BindingElement) { + return node.kind === SyntaxKind.VariableDeclaration ? + getInitialTypeOfVariableDeclaration(node) : + getInitialTypeOfBindingElement(node); + } + function isEmptyArrayAssignment(node: VariableDeclaration | BindingElement | Expression) { + return node.kind === SyntaxKind.VariableDeclaration && (node).initializer && + isEmptyArrayLiteral(((node).initializer!)) || + node.kind !== SyntaxKind.BindingElement && node.parent.kind === SyntaxKind.BinaryExpression && + isEmptyArrayLiteral((node.parent).right); + } + function getReferenceCandidate(node: Expression): Expression { + switch (node.kind) { + case SyntaxKind.ParenthesizedExpression: + return getReferenceCandidate((node).expression); + case SyntaxKind.BinaryExpression: + switch ((node).operatorToken.kind) { + case SyntaxKind.EqualsToken: + return getReferenceCandidate((node).left); + case SyntaxKind.CommaToken: + return getReferenceCandidate((node).right); + } + } + return node; + } + function getReferenceRoot(node: Node): Node { + const { parent } = node; + return parent.kind === SyntaxKind.ParenthesizedExpression || + parent.kind === SyntaxKind.BinaryExpression && (parent).operatorToken.kind === SyntaxKind.EqualsToken && (parent).left === node || + parent.kind === SyntaxKind.BinaryExpression && (parent).operatorToken.kind === SyntaxKind.CommaToken && (parent).right === node ? + getReferenceRoot(parent) : node; + } + function getTypeOfSwitchClause(clause: CaseClause | DefaultClause) { + if (clause.kind === SyntaxKind.CaseClause) { + return getRegularTypeOfLiteralType(getTypeOfExpression(clause.expression)); + } + return neverType; + } + function getSwitchClauseTypes(switchStatement: SwitchStatement): ts.Type[] { + const links = getNodeLinks(switchStatement); + if (!links.switchTypes) { + links.switchTypes = []; + for (const clause of switchStatement.caseBlock.clauses) { + links.switchTypes.push(getTypeOfSwitchClause(clause)); } - else { - // From within an async function you can return either a non-promise value or a promise. Any - // Promise/A+ compatible implementation will always assimilate any foreign promise, so the - // return type of the body is awaited type of the body, wrapped in a native Promise type. - return isAsync - ? createPromiseType(returnType || fallbackReturnType) - : returnType || fallbackReturnType; + } + return links.switchTypes; + } + // Get the types from all cases in a switch on `typeof`. An + // `undefined` element denotes an explicit `default` clause. + function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement): (string | undefined)[] { + const witnesses: (string | undefined)[] = []; + for (const clause of switchStatement.caseBlock.clauses) { + if (clause.kind === SyntaxKind.CaseClause) { + if (isStringLiteralLike(clause.expression)) { + witnesses.push(clause.expression.text); + continue; + } + return emptyArray; } + witnesses.push(/*explicitDefaultStatement*/ undefined); } - - function createGeneratorReturnType(yieldType: Type, returnType: Type, nextType: Type, isAsyncGenerator: boolean) { - const resolver = isAsyncGenerator ? asyncIterationTypesResolver : syncIterationTypesResolver; - const globalGeneratorType = resolver.getGlobalGeneratorType(/*reportErrors*/ false); - yieldType = resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || unknownType; - returnType = resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || unknownType; - nextType = resolver.resolveIterationType(nextType, /*errorNode*/ undefined) || unknownType; - if (globalGeneratorType === emptyGenericType) { - // Fall back to the global IterableIterator if returnType is assignable to the expected return iteration - // type of IterableIterator, and the expected next iteration type of IterableIterator is assignable to - // nextType. - const globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false); - const iterationTypes = globalType !== emptyGenericType ? getIterationTypesOfGlobalIterableType(globalType, resolver) : undefined; - const iterableIteratorReturnType = iterationTypes ? iterationTypes.returnType : anyType; - const iterableIteratorNextType = iterationTypes ? iterationTypes.nextType : undefinedType; - if (isTypeAssignableTo(returnType, iterableIteratorReturnType) && - isTypeAssignableTo(iterableIteratorNextType, nextType)) { - if (globalType !== emptyGenericType) { - return createTypeFromGenericGlobalType(globalType, [yieldType]); - } - - // The global IterableIterator type doesn't exist, so report an error - resolver.getGlobalIterableIteratorType(/*reportErrors*/ true); - return emptyObjectType; + return witnesses; + } + function eachTypeContainedIn(source: ts.Type, types: ts.Type[]) { + return source.flags & TypeFlags.Union ? !forEach((source).types, t => !contains(types, t)) : contains(types, source); + } + function isTypeSubsetOf(source: ts.Type, target: ts.Type) { + return source === target || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, (target)); + } + function isTypeSubsetOfUnion(source: ts.Type, target: UnionType) { + if (source.flags & TypeFlags.Union) { + for (const t of (source).types) { + if (!containsType(target.types, t)) { + return false; } - - // The global Generator type doesn't exist, so report an error - resolver.getGlobalGeneratorType(/*reportErrors*/ true); - return emptyObjectType; } - - return createTypeFromGenericGlobalType(globalGeneratorType, [yieldType, returnType, nextType]); + return true; } - - function checkAndAggregateYieldOperandTypes(func: FunctionLikeDeclaration, checkMode: CheckMode | undefined) { - const yieldTypes: Type[] = []; - const nextTypes: Type[] = []; - const isAsync = (getFunctionFlags(func) & FunctionFlags.Async) !== 0; - forEachYieldExpression(func.body, yieldExpression => { - const yieldExpressionType = yieldExpression.expression ? checkExpression(yieldExpression.expression, checkMode) : undefinedWideningType; - pushIfUnique(yieldTypes, getYieldedTypeOfYieldExpression(yieldExpression, yieldExpressionType, anyType, isAsync)); - let nextType: Type | undefined; - if (yieldExpression.asteriskToken) { - const iterationTypes = getIterationTypesOfIterable( - yieldExpressionType, - isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar, - yieldExpression.expression); - nextType = iterationTypes && iterationTypes.nextType; + if (source.flags & TypeFlags.EnumLiteral && getBaseTypeOfEnumLiteralType((source)) === target) { + return true; + } + return containsType(target.types, source); + } + function forEachType(type: ts.Type, f: (t: ts.Type) => T | undefined): T | undefined { + return type.flags & TypeFlags.Union ? forEach((type).types, f) : f(type); + } + function everyType(type: ts.Type, f: (t: ts.Type) => boolean): boolean { + return type.flags & TypeFlags.Union ? every((type).types, f) : f(type); + } + function filterType(type: ts.Type, f: (t: ts.Type) => boolean): ts.Type { + if (type.flags & TypeFlags.Union) { + const types = (type).types; + const filtered = filter(types, f); + return filtered === types ? type : getUnionTypeFromSortedList(filtered, (type).objectFlags); + } + return f(type) ? type : neverType; + } + function countTypes(type: ts.Type) { + return type.flags & TypeFlags.Union ? (type as UnionType).types.length : 1; + } + // Apply a mapping function to a type and return the resulting type. If the source type + // is a union type, the mapping function is applied to each constituent type and a union + // of the resulting types is returned. + function mapType(type: ts.Type, mapper: (t: ts.Type) => ts.Type, noReductions?: boolean): ts.Type; + function mapType(type: ts.Type, mapper: (t: ts.Type) => ts.Type | undefined, noReductions?: boolean): ts.Type | undefined; + function mapType(type: ts.Type, mapper: (t: ts.Type) => ts.Type | undefined, noReductions?: boolean): ts.Type | undefined { + if (type.flags & TypeFlags.Never) { + return type; + } + if (!(type.flags & TypeFlags.Union)) { + return mapper(type); + } + let mappedTypes: ts.Type[] | undefined; + for (const t of (type).types) { + const mapped = mapper(t); + if (mapped) { + if (!mappedTypes) { + mappedTypes = [mapped]; } else { - nextType = getContextualType(yieldExpression); + mappedTypes.push(mapped); } - if (nextType) pushIfUnique(nextTypes, nextType); - }); - return { yieldTypes, nextTypes }; + } } - - function getYieldedTypeOfYieldExpression(node: YieldExpression, expressionType: Type, sentType: Type, isAsync: boolean): Type | undefined { - const errorNode = node.expression || node; - // A `yield*` expression effectively yields everything that its operand yields - const yieldedType = node.asteriskToken ? checkIteratedTypeOrElementType(isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar, expressionType, sentType, errorNode) : expressionType; - return !isAsync ? yieldedType : getAwaitedType(yieldedType, errorNode, node.asteriskToken - ? Diagnostics.Type_of_iterated_elements_of_a_yield_Asterisk_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member - : Diagnostics.Type_of_yield_operand_in_an_async_generator_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + return mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal); + } + function extractTypesOfKind(type: ts.Type, kind: TypeFlags) { + return filterType(type, t => (t.flags & kind) !== 0); + } + // Return a new type in which occurrences of the string and number primitive types in + // typeWithPrimitives have been replaced with occurrences of string literals and numeric + // literals in typeWithLiterals, respectively. + function replacePrimitivesWithLiterals(typeWithPrimitives: ts.Type, typeWithLiterals: ts.Type) { + if (isTypeSubsetOf(stringType, typeWithPrimitives) && maybeTypeOfKind(typeWithLiterals, TypeFlags.StringLiteral) || + isTypeSubsetOf(numberType, typeWithPrimitives) && maybeTypeOfKind(typeWithLiterals, TypeFlags.NumberLiteral) || + isTypeSubsetOf(bigintType, typeWithPrimitives) && maybeTypeOfKind(typeWithLiterals, TypeFlags.BigIntLiteral)) { + return mapType(typeWithPrimitives, t => t.flags & TypeFlags.String ? extractTypesOfKind(typeWithLiterals, TypeFlags.String | TypeFlags.StringLiteral) : + t.flags & TypeFlags.Number ? extractTypesOfKind(typeWithLiterals, TypeFlags.Number | TypeFlags.NumberLiteral) : + t.flags & TypeFlags.BigInt ? extractTypesOfKind(typeWithLiterals, TypeFlags.BigInt | TypeFlags.BigIntLiteral) : t); } - - /** - * Collect the TypeFacts learned from a typeof switch with - * total clauses `witnesses`, and the active clause ranging - * from `start` to `end`. Parameter `hasDefault` denotes - * whether the active clause contains a default clause. - */ - function getFactsFromTypeofSwitch(start: number, end: number, witnesses: string[], hasDefault: boolean): TypeFacts { - let facts: TypeFacts = TypeFacts.None; - // When in the default we only collect inequality facts - // because default is 'in theory' a set of infinite - // equalities. - if (hasDefault) { - // Value is not equal to any types after the active clause. - for (let i = end; i < witnesses.length; i++) { - facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject; - } - // Remove inequalities for types that appear in the - // active clause because they appear before other - // types collected so far. - for (let i = start; i < end; i++) { - facts &= ~(typeofNEFacts.get(witnesses[i]) || 0); - } - // Add inequalities for types before the active clause unconditionally. - for (let i = 0; i < start; i++) { - facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject; - } - } - // When in an active clause without default the set of - // equalities is finite. - else { - // Add equalities for all types in the active clause. - for (let i = start; i < end; i++) { - facts |= typeofEQFacts.get(witnesses[i]) || TypeFacts.TypeofEQHostObject; - } - // Remove equalities for types that appear before the - // active clause. - for (let i = 0; i < start; i++) { - facts &= ~(typeofEQFacts.get(witnesses[i]) || 0); + return typeWithPrimitives; + } + function isIncomplete(flowType: FlowType) { + return flowType.flags === 0; + } + function getTypeFromFlowType(flowType: FlowType) { + return flowType.flags === 0 ? (flowType).type : flowType; + } + function createFlowType(type: ts.Type, incomplete: boolean): FlowType { + return incomplete ? { flags: 0, type } : type; + } + // An evolving array type tracks the element types that have so far been seen in an + // 'x.push(value)' or 'x[n] = value' operation along the control flow graph. Evolving + // array types are ultimately converted into manifest array types (using getFinalArrayType) + // and never escape the getFlowTypeOfReference function. + function createEvolvingArrayType(elementType: ts.Type): EvolvingArrayType { + const result = (createObjectType(ObjectFlags.EvolvingArray)); + result.elementType = elementType; + return result; + } + function getEvolvingArrayType(elementType: ts.Type): EvolvingArrayType { + return evolvingArrayTypes[elementType.id] || (evolvingArrayTypes[elementType.id] = createEvolvingArrayType(elementType)); + } + // When adding evolving array element types we do not perform subtype reduction. Instead, + // we defer subtype reduction until the evolving array type is finalized into a manifest + // array type. + function addEvolvingArrayElementType(evolvingArrayType: EvolvingArrayType, node: Expression): EvolvingArrayType { + const elementType = getBaseTypeOfLiteralType(getContextFreeTypeOfExpression(node)); + return isTypeSubsetOf(elementType, evolvingArrayType.elementType) ? evolvingArrayType : getEvolvingArrayType(getUnionType([evolvingArrayType.elementType, elementType])); + } + function createFinalArrayType(elementType: ts.Type) { + return elementType.flags & TypeFlags.Never ? + autoArrayType : + createArrayType(elementType.flags & TypeFlags.Union ? + getUnionType((elementType).types, UnionReduction.Subtype) : + elementType); + } + // We perform subtype reduction upon obtaining the final array type from an evolving array type. + function getFinalArrayType(evolvingArrayType: EvolvingArrayType): ts.Type { + return evolvingArrayType.finalArrayType || (evolvingArrayType.finalArrayType = createFinalArrayType(evolvingArrayType.elementType)); + } + function finalizeEvolvingArrayType(type: ts.Type): ts.Type { + return getObjectFlags(type) & ObjectFlags.EvolvingArray ? getFinalArrayType((type)) : type; + } + function getElementTypeOfEvolvingArrayType(type: ts.Type) { + return getObjectFlags(type) & ObjectFlags.EvolvingArray ? (type).elementType : neverType; + } + function isEvolvingArrayTypeList(types: ts.Type[]) { + let hasEvolvingArrayType = false; + for (const t of types) { + if (!(t.flags & TypeFlags.Never)) { + if (!(getObjectFlags(t) & ObjectFlags.EvolvingArray)) { + return false; } + hasEvolvingArrayType = true; } - return facts; } - - function isExhaustiveSwitchStatement(node: SwitchStatement): boolean { - const links = getNodeLinks(node); - return links.isExhaustive !== undefined ? links.isExhaustive : (links.isExhaustive = computeExhaustiveSwitchStatement(node)); + return hasEvolvingArrayType; + } + // At flow control branch or loop junctions, if the type along every antecedent code path + // is an evolving array type, we construct a combined evolving array type. Otherwise we + // finalize all evolving array types. + function getUnionOrEvolvingArrayType(types: ts.Type[], subtypeReduction: UnionReduction) { + return isEvolvingArrayTypeList(types) ? + getEvolvingArrayType(getUnionType(map(types, getElementTypeOfEvolvingArrayType))) : + getUnionType(sameMap(types, finalizeEvolvingArrayType), subtypeReduction); + } + // Return true if the given node is 'x' in an 'x.length', x.push(value)', 'x.unshift(value)' or + // 'x[n] = value' operation, where 'n' is an expression of type any, undefined, or a number-like type. + function isEvolvingArrayOperationTarget(node: Node) { + const root = getReferenceRoot(node); + const parent = root.parent; + const isLengthPushOrUnshift = parent.kind === SyntaxKind.PropertyAccessExpression && ((parent).name.escapedText === "length" || + parent.parent.kind === SyntaxKind.CallExpression && isPushOrUnshiftIdentifier((parent).name)); + const isElementAssignment = parent.kind === SyntaxKind.ElementAccessExpression && + (parent).expression === root && + parent.parent.kind === SyntaxKind.BinaryExpression && + (parent.parent).operatorToken.kind === SyntaxKind.EqualsToken && + (parent.parent).left === parent && + !isAssignmentTarget(parent.parent) && + isTypeAssignableToKind(getTypeOfExpression((parent).argumentExpression), TypeFlags.NumberLike); + return isLengthPushOrUnshift || isElementAssignment; + } + function isDeclarationWithExplicitTypeAnnotation(declaration: Declaration | undefined) { + return !!(declaration && (declaration.kind === SyntaxKind.VariableDeclaration || declaration.kind === SyntaxKind.Parameter || + declaration.kind === SyntaxKind.PropertyDeclaration || declaration.kind === SyntaxKind.PropertySignature) && + getEffectiveTypeAnnotationNode((declaration as VariableDeclaration | ParameterDeclaration | PropertyDeclaration | PropertySignature))); + } + function getExplicitTypeOfSymbol(symbol: ts.Symbol, diagnostic?: Diagnostic) { + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.ValueModule)) { + return getTypeOfSymbol(symbol); } - - function computeExhaustiveSwitchStatement(node: SwitchStatement): boolean { - if (node.expression.kind === SyntaxKind.TypeOfExpression) { - const operandType = getTypeOfExpression((node.expression as TypeOfExpression).expression); - // This cast is safe because the switch is possibly exhaustive and does not contain a default case, so there can be no undefined. - const witnesses = getSwitchClauseTypeOfWitnesses(node); - // notEqualFacts states that the type of the switched value is not equal to every type in the switch. - const notEqualFacts = getFactsFromTypeofSwitch(0, 0, witnesses, /*hasDefault*/ true); - const type = getBaseConstraintOfType(operandType) || operandType; - return !!(filterType(type, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts).flags & TypeFlags.Never); - } - const type = getTypeOfExpression(node.expression); - if (!isLiteralType(type)) { - return false; + if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { + if (isDeclarationWithExplicitTypeAnnotation(symbol.valueDeclaration)) { + return getTypeOfSymbol(symbol); } - const switchTypes = getSwitchClauseTypes(node); - if (!switchTypes.length || some(switchTypes, isNeitherUnitTypeNorNever)) { - return false; + if (diagnostic && symbol.valueDeclaration) { + addRelatedInfo(diagnostic, createDiagnosticForNode(symbol.valueDeclaration, Diagnostics._0_is_declared_here, symbolToString(symbol))); } - return eachTypeContainedIn(mapType(type, getRegularTypeOfLiteralType), switchTypes); } - - function functionHasImplicitReturn(func: FunctionLikeDeclaration) { - return func.endFlowNode && isReachableFlowNode(func.endFlowNode); + } + // We require the dotted function name in an assertion expression to be comprised of identifiers + // that reference function, method, class or value module symbols; or variable, property or + // parameter symbols with declarations that have explicit type annotations. Such references are + // resolvable with no possibility of triggering circularities in control flow analysis. + function getTypeOfDottedName(node: Expression, diagnostic: Diagnostic | undefined): ts.Type | undefined { + if (!(node.flags & NodeFlags.InWithStatement)) { + switch (node.kind) { + case SyntaxKind.Identifier: + const symbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol((node))); + return getExplicitTypeOfSymbol(symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol, diagnostic); + case SyntaxKind.ThisKeyword: + return getExplicitThisType(node); + case SyntaxKind.PropertyAccessExpression: + const type = getTypeOfDottedName((node).expression, diagnostic); + const prop = type && getPropertyOfType(type, (node).name.escapedText); + return prop && getExplicitTypeOfSymbol(prop, diagnostic); + case SyntaxKind.ParenthesizedExpression: + return getTypeOfDottedName((node).expression, diagnostic); + } } - - /** NOTE: Return value of `[]` means a different thing than `undefined`. `[]` means func returns `void`, `undefined` means it returns `never`. */ - function checkAndAggregateReturnExpressionTypes(func: FunctionLikeDeclaration, checkMode: CheckMode | undefined): Type[] | undefined { - const functionFlags = getFunctionFlags(func); - const aggregatedTypes: Type[] = []; - let hasReturnWithNoExpression = functionHasImplicitReturn(func); - let hasReturnOfTypeNever = false; - forEachReturnStatement(func.body, returnStatement => { - const expr = returnStatement.expression; - if (expr) { - let type = checkExpressionCached(expr, checkMode && checkMode & ~CheckMode.SkipGenericFunctions); - if (functionFlags & FunctionFlags.Async) { - // From within an async function you can return either a non-promise value or a promise. Any - // Promise/A+ compatible implementation will always assimilate any foreign promise, so the - // return type of the body should be unwrapped to its awaited type, which should be wrapped in - // the native Promise type by the caller. - type = checkAwaitedType(type, func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); - } - if (type.flags & TypeFlags.Never) { - hasReturnOfTypeNever = true; - } - pushIfUnique(aggregatedTypes, type); + } + function getEffectsSignature(node: CallExpression) { + const links = getNodeLinks(node); + let signature = links.effectsSignature; + if (signature === undefined) { + // A call expression parented by an expression statement is a potential assertion. Other call + // expressions are potential type predicate function calls. In order to avoid triggering + // circularities in control flow analysis, we use getTypeOfDottedName when resolving the call + // target expression of an assertion. + let funcType: ts.Type | undefined; + if (node.parent.kind === SyntaxKind.ExpressionStatement) { + funcType = getTypeOfDottedName(node.expression, /*diagnostic*/ undefined); + } + else if (node.expression.kind !== SyntaxKind.SuperKeyword) { + if (isOptionalChain(node)) { + funcType = checkNonNullType(getOptionalExpressionType(checkExpression(node.expression), node.expression), node.expression); } else { - hasReturnWithNoExpression = true; + funcType = checkNonNullExpression(node.expression); } - }); - if (aggregatedTypes.length === 0 && !hasReturnWithNoExpression && (hasReturnOfTypeNever || mayReturnNever(func))) { - return undefined; } - if (strictNullChecks && aggregatedTypes.length && hasReturnWithNoExpression && - !(isJSConstructor(func) && aggregatedTypes.some(t => t.symbol === func.symbol))) { - // Javascript "callable constructors", containing eg `if (!(this instanceof A)) return new A()` should not add undefined - pushIfUnique(aggregatedTypes, undefinedType); - } - return aggregatedTypes; + const signatures = getSignaturesOfType(funcType && getApparentType(funcType) || unknownType, SignatureKind.Call); + const candidate = signatures.length === 1 && !signatures[0].typeParameters ? signatures[0] : + some(signatures, hasTypePredicateOrNeverReturnType) ? getResolvedSignature(node) : + undefined; + signature = links.effectsSignature = candidate && hasTypePredicateOrNeverReturnType(candidate) ? candidate : unknownSignature; } - function mayReturnNever(func: FunctionLikeDeclaration): boolean { - switch (func.kind) { - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return true; - case SyntaxKind.MethodDeclaration: - return func.parent.kind === SyntaxKind.ObjectLiteralExpression; - default: - return false; - } + return signature === unknownSignature ? undefined : signature; + } + function hasTypePredicateOrNeverReturnType(signature: ts.Signature) { + return !!(getTypePredicateOfSignature(signature) || + signature.declaration && (getReturnTypeFromAnnotation(signature.declaration) || unknownType).flags & TypeFlags.Never); + } + function getTypePredicateArgument(predicate: TypePredicate, callExpression: CallExpression) { + if (predicate.kind === TypePredicateKind.Identifier || predicate.kind === TypePredicateKind.AssertsIdentifier) { + return callExpression.arguments[predicate.parameterIndex]; } - - /** - * TypeScript Specification 1.0 (6.3) - July 2014 - * An explicitly typed function whose return type isn't the Void type, - * the Any type, or a union type containing the Void or Any type as a constituent - * must have at least one return statement somewhere in its body. - * An exception to this rule is if the function implementation consists of a single 'throw' statement. - * - * @param returnType - return type of the function, can be undefined if return type is not explicitly specified - */ - function checkAllCodePathsInNonVoidFunctionReturnOrThrow(func: FunctionLikeDeclaration | MethodSignature, returnType: Type | undefined): void { - if (!produceDiagnostics) { - return; - } - - const functionFlags = getFunctionFlags(func); - const type = returnType && getReturnOrPromisedType(returnType, functionFlags); - - // Functions with with an explicitly specified 'void' or 'any' return type don't need any return expressions. - if (type && maybeTypeOfKind(type, TypeFlags.Any | TypeFlags.Void)) { - return; - } - - // If all we have is a function signature, or an arrow function with an expression body, then there is nothing to check. - // also if HasImplicitReturn flag is not set this means that all codepaths in function body end with return or throw - if (func.kind === SyntaxKind.MethodSignature || nodeIsMissing(func.body) || func.body!.kind !== SyntaxKind.Block || !functionHasImplicitReturn(func)) { - return; - } - - const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn; - - if (type && type.flags & TypeFlags.Never) { - error(getEffectiveReturnTypeNode(func), Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point); + const invokedExpression = skipParentheses(callExpression.expression); + return isAccessExpression(invokedExpression) ? skipParentheses(invokedExpression.expression) : undefined; + } + function reportFlowControlError(node: Node) { + const block = (findAncestor(node, isFunctionOrModuleBlock)); + const sourceFile = getSourceFileOfNode(node); + const span = getSpanOfTokenAtPosition(sourceFile, block.statements.pos); + diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.The_containing_function_or_module_body_is_too_large_for_control_flow_analysis)); + } + function isReachableFlowNode(flow: FlowNode) { + const result = isReachableFlowNodeWorker(flow, /*skipCacheCheck*/ false); + lastFlowNode = flow; + lastFlowNodeReachable = result; + return result; + } + function isUnlockedReachableFlowNode(flow: FlowNode) { + return !(flow.flags & FlowFlags.PreFinally && (flow).lock.locked) && isReachableFlowNodeWorker(flow, /*skipCacheCheck*/ false); + } + function isFalseExpression(expr: Expression): boolean { + const node = skipParentheses(expr); + return node.kind === SyntaxKind.FalseKeyword || node.kind === SyntaxKind.BinaryExpression && ((node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken && (isFalseExpression((node).left) || isFalseExpression((node).right)) || + (node).operatorToken.kind === SyntaxKind.BarBarToken && isFalseExpression((node).left) && isFalseExpression((node).right)); + } + function isReachableFlowNodeWorker(flow: FlowNode, noCacheCheck: boolean): boolean { + while (true) { + if (flow === lastFlowNode) { + return lastFlowNodeReachable; } - else if (type && !hasExplicitReturn) { - // minimal check: function has syntactic return type annotation and no explicit return statements in the body - // this function does not conform to the specification. - // NOTE: having returnType !== undefined is a precondition for entering this branch so func.type will always be present - error(getEffectiveReturnTypeNode(func), Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value); + const flags = flow.flags; + if (flags & FlowFlags.Shared) { + if (!noCacheCheck) { + const id = getFlowNodeId(flow); + const reachable = flowNodeReachable[id]; + return reachable !== undefined ? reachable : (flowNodeReachable[id] = isReachableFlowNodeWorker(flow, /*skipCacheCheck*/ true)); + } + noCacheCheck = false; } - else if (type && strictNullChecks && !isTypeAssignableTo(undefinedType, type)) { - error(getEffectiveReturnTypeNode(func) || func, Diagnostics.Function_lacks_ending_return_statement_and_return_type_does_not_include_undefined); + if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation | FlowFlags.PreFinally)) { + flow = (flow).antecedent; } - else if (compilerOptions.noImplicitReturns) { - if (!type) { - // If return type annotation is omitted check if function has any explicit return statements. - // If it does not have any - its inferred return type is void - don't do any checks. - // Otherwise get inferred return type from function body and report error only if it is not void / anytype - if (!hasExplicitReturn) { - return; + else if (flags & FlowFlags.Call) { + const signature = getEffectsSignature((flow).node); + if (signature) { + const predicate = getTypePredicateOfSignature(signature); + if (predicate && predicate.kind === TypePredicateKind.AssertsIdentifier) { + const predicateArgument = (flow).node.arguments[predicate.parameterIndex]; + if (predicateArgument && isFalseExpression(predicateArgument)) { + return false; + } } - const inferredReturnType = getReturnTypeOfSignature(getSignatureFromDeclaration(func)); - if (isUnwrappedReturnTypeVoidOrAny(func, inferredReturnType)) { - return; + if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) { + return false; } } - error(getEffectiveReturnTypeNode(func) || func, Diagnostics.Not_all_code_paths_return_a_value); + flow = (flow).antecedent; } - } - - function checkFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | MethodDeclaration, checkMode?: CheckMode): Type { - Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); - checkNodeDeferred(node); - - // The identityMapper object is used to indicate that function expressions are wildcards - if (checkMode && checkMode & CheckMode.SkipContextSensitive && isContextSensitive(node)) { - // Skip parameters, return signature with return type that retains noncontextual parts so inferences can still be drawn in an early stage - if (!getEffectiveReturnTypeNode(node) && hasContextSensitiveReturnExpression(node)) { - // Return plain anyFunctionType if there is no possibility we'll make inferences from the return type - const contextualSignature = getContextualSignature(node); - if (contextualSignature && couldContainTypeVariables(getReturnTypeOfSignature(contextualSignature))) { - const links = getNodeLinks(node); - if (links.contextFreeType) { - return links.contextFreeType; - } - const returnType = getReturnTypeFromBody(node, checkMode); - const returnOnlySignature = createSignature(undefined, undefined, undefined, emptyArray, returnType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); - const returnOnlyType = createAnonymousType(node.symbol, emptySymbols, [returnOnlySignature], emptyArray, undefined, undefined); - returnOnlyType.objectFlags |= ObjectFlags.NonInferrableType; - return links.contextFreeType = returnOnlyType; - } + else if (flags & FlowFlags.BranchLabel) { + // A branching point is reachable if any branch is reachable. + return some((flow).antecedents, isUnlockedReachableFlowNode); + } + else if (flags & FlowFlags.LoopLabel) { + // A loop is reachable if the control flow path that leads to the top is reachable. + flow = (flow).antecedents![0]; + } + else if (flags & FlowFlags.SwitchClause) { + // The control flow path representing an unmatched value in a switch statement with + // no default clause is unreachable if the switch statement is exhaustive. + if ((flow).clauseStart === (flow).clauseEnd && isExhaustiveSwitchStatement((flow).switchStatement)) { + return false; } - return anyFunctionType; + flow = (flow).antecedent; } - - // Grammar checking - const hasGrammarError = checkGrammarFunctionLikeDeclaration(node); - if (!hasGrammarError && node.kind === SyntaxKind.FunctionExpression) { - checkGrammarForGenerator(node); + else if (flags & FlowFlags.AfterFinally) { + // Cache is unreliable once we start locking nodes + lastFlowNode = undefined; + (flow).locked = true; + const result = isReachableFlowNodeWorker((flow).antecedent, /*skipCacheCheck*/ false); + (flow).locked = false; + return result; } - - const type = getTypeOfSymbol(getMergedSymbol(node.symbol)); - if (isTypeAny(type)) { - return type; + else { + return !(flags & FlowFlags.Unreachable); } - - contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node, checkMode); - - return type; } - - function contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | ArrowFunction | MethodDeclaration, checkMode?: CheckMode) { - const links = getNodeLinks(node); - // Check if function expression is contextually typed and assign parameter types if so. - if (!(links.flags & NodeCheckFlags.ContextChecked)) { - const contextualSignature = getContextualSignature(node); - // If a type check is started at a function expression that is an argument of a function call, obtaining the - // contextual type may recursively get back to here during overload resolution of the call. If so, we will have - // already assigned contextual types. - if (!(links.flags & NodeCheckFlags.ContextChecked)) { - links.flags |= NodeCheckFlags.ContextChecked; - if (contextualSignature) { - const type = getTypeOfSymbol(getMergedSymbol(node.symbol)); - if (isTypeAny(type)) { - return; - } - const signature = getSignaturesOfType(type, SignatureKind.Call)[0]; - if (isContextSensitive(node)) { - const inferenceContext = getInferenceContext(node); - if (checkMode && checkMode & CheckMode.Inferential) { - inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!); - } - const instantiatedContextualSignature = inferenceContext ? - instantiateSignature(contextualSignature, inferenceContext.mapper) : contextualSignature; - assignContextualParameterTypes(signature, instantiatedContextualSignature); - } - if (!getReturnTypeFromAnnotation(node) && !signature.resolvedReturnType) { - const returnType = getReturnTypeFromBody(node, checkMode); - if (!signature.resolvedReturnType) { - signature.resolvedReturnType = returnType; - } + } + function getFlowTypeOfReference(reference: Node, declaredType: ts.Type, initialType = declaredType, flowContainer?: Node, couldBeUninitialized?: boolean) { + let key: string | undefined; + let keySet = false; + let flowDepth = 0; + if (flowAnalysisDisabled) { + return errorType; + } + if (!reference.flowNode || !couldBeUninitialized && !(declaredType.flags & TypeFlags.Narrowable)) { + return declaredType; + } + flowInvocationCount++; + const sharedFlowStart = sharedFlowCount; + const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode)); + sharedFlowCount = sharedFlowStart; + // When the reference is 'x' in an 'x.length', 'x.push(value)', 'x.unshift(value)' or x[n] = value' operation, + // we give type 'any[]' to 'x' instead of using the type determined by control flow analysis such that operations + // on empty arrays are possible without implicit any errors and new element types can be inferred without + // type mismatch errors. + const resultType = getObjectFlags(evolvedType) & ObjectFlags.EvolvingArray && isEvolvingArrayOperationTarget(reference) ? autoArrayType : finalizeEvolvingArrayType(evolvedType); + if (resultType === unreachableNeverType || reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) { + return declaredType; + } + return resultType; + function getOrSetCacheKey() { + if (keySet) { + return key; + } + keySet = true; + return key = getFlowCacheKey(reference, declaredType, initialType, flowContainer); + } + function getTypeAtFlowNode(flow: FlowNode): FlowType { + if (flowDepth === 2000) { + // We have made 2000 recursive invocations. To avoid overflowing the call stack we report an error + // and disable further control flow analysis in the containing function or module body. + flowAnalysisDisabled = true; + reportFlowControlError(reference); + return errorType; + } + flowDepth++; + while (true) { + const flags = flow.flags; + if (flags & FlowFlags.Shared) { + // We cache results of flow type resolution for shared nodes that were previously visited in + // the same getFlowTypeOfReference invocation. A node is considered shared when it is the + // antecedent of more than one node. + for (let i = sharedFlowStart; i < sharedFlowCount; i++) { + if (sharedFlowNodes[i] === flow) { + flowDepth--; + return sharedFlowTypes[i]; } } - checkSignatureDeclaration(node); } - } - } - - function getReturnOrPromisedType(type: Type | undefined, functionFlags: FunctionFlags) { - const isGenerator = !!(functionFlags & FunctionFlags.Generator); - const isAsync = !!(functionFlags & FunctionFlags.Async); - return type && isGenerator ? getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, type, isAsync) || errorType : - type && isAsync ? getAwaitedType(type) || errorType : - type; - } - - function checkFunctionExpressionOrObjectLiteralMethodDeferred(node: ArrowFunction | FunctionExpression | MethodDeclaration) { - Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); - - const functionFlags = getFunctionFlags(node); - const returnType = getReturnTypeFromAnnotation(node); - checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); - - if (node.body) { - if (!getEffectiveReturnTypeNode(node)) { - // There are some checks that are only performed in getReturnTypeFromBody, that may produce errors - // we need. An example is the noImplicitAny errors resulting from widening the return expression - // of a function. Because checking of function expression bodies is deferred, there was never an - // appropriate time to do this during the main walk of the file (see the comment at the top of - // checkFunctionExpressionBodies). So it must be done now. - getReturnTypeOfSignature(getSignatureFromDeclaration(node)); + let type: FlowType | undefined; + if (flags & FlowFlags.AfterFinally) { + // block flow edge: finally -> pre-try (for larger explanation check comment in binder.ts - bindTryStatement + (flow).locked = true; + type = getTypeAtFlowNode((flow).antecedent); + (flow).locked = false; } - - if (node.body.kind === SyntaxKind.Block) { - checkSourceElement(node.body); + else if (flags & FlowFlags.PreFinally) { + // locked pre-finally flows are filtered out in getTypeAtFlowBranchLabel + // so here just redirect to antecedent + flow = (flow).antecedent; + continue; } - else { - // From within an async function you can return either a non-promise value or a promise. Any - // Promise/A+ compatible implementation will always assimilate any foreign promise, so we - // should not be checking assignability of a promise to the return type. Instead, we need to - // check assignability of the awaited type of the expression body against the promised type of - // its return type annotation. - const exprType = checkExpression(node.body); - const returnOrPromisedType = getReturnOrPromisedType(returnType, functionFlags); - if (returnOrPromisedType) { - if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { // Async function - const awaitedType = checkAwaitedType(exprType, node.body, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); - checkTypeAssignableToAndOptionallyElaborate(awaitedType, returnOrPromisedType, node.body, node.body); - } - else { // Normal function - checkTypeAssignableToAndOptionallyElaborate(exprType, returnOrPromisedType, node.body, node.body); - } + else if (flags & FlowFlags.Assignment) { + type = getTypeAtFlowAssignment((flow)); + if (!type) { + flow = (flow).antecedent; + continue; } } - } - } - - function checkArithmeticOperandType(operand: Node, type: Type, diagnostic: DiagnosticMessage, isAwaitValid = false): boolean { - if (!isTypeAssignableTo(type, numberOrBigIntType)) { - const awaitedType = isAwaitValid && getAwaitedTypeOfPromise(type); - errorAndMaybeSuggestAwait( - operand, - !!awaitedType && isTypeAssignableTo(awaitedType, numberOrBigIntType), - diagnostic); - return false; - } - return true; - } - - function isReadonlyAssignmentDeclaration(d: Declaration) { - if (!isCallExpression(d)) { - return false; - } - if (!isBindableObjectDefinePropertyCall(d)) { - return false; - } - const objectLitType = checkExpressionCached(d.arguments[2]); - const valueType = getTypeOfPropertyOfType(objectLitType, "value" as __String); - if (valueType) { - const writableProp = getPropertyOfType(objectLitType, "writable" as __String); - const writableType = writableProp && getTypeOfSymbol(writableProp); - if (!writableType || writableType === falseType || writableType === regularFalseType) { - return true; + else if (flags & FlowFlags.Call) { + type = getTypeAtFlowCall((flow)); + if (!type) { + flow = (flow).antecedent; + continue; + } } - // We include this definition whereupon we walk back and check the type at the declaration because - // The usual definition of `Object.defineProperty` will _not_ cause literal types to be preserved in the - // argument types, should the type be contextualized by the call itself. - if (writableProp && writableProp.valueDeclaration && isPropertyAssignment(writableProp.valueDeclaration)) { - const initializer = writableProp.valueDeclaration.initializer; - const rawOriginalType = checkExpression(initializer); - if (rawOriginalType === falseType || rawOriginalType === regularFalseType) { - return true; + else if (flags & FlowFlags.Condition) { + type = getTypeAtFlowCondition((flow)); + } + else if (flags & FlowFlags.SwitchClause) { + type = getTypeAtSwitchClause((flow)); + } + else if (flags & FlowFlags.Label) { + if ((flow).antecedents!.length === 1) { + flow = (flow).antecedents![0]; + continue; } + type = flags & FlowFlags.BranchLabel ? + getTypeAtFlowBranchLabel((flow)) : + getTypeAtFlowLoopLabel((flow)); } - return false; - } - const setProp = getPropertyOfType(objectLitType, "set" as __String); - return !setProp; - } - - function isReadonlySymbol(symbol: Symbol): boolean { - // The following symbols are considered read-only: - // Properties with a 'readonly' modifier - // Variables declared with 'const' - // Get accessors without matching set accessors - // Enum members - // Object.defineProperty assignments with writable false or no setter - // Unions and intersections of the above (unions and intersections eagerly set isReadonly on creation) - return !!(getCheckFlags(symbol) & CheckFlags.Readonly || - symbol.flags & SymbolFlags.Property && getDeclarationModifierFlagsFromSymbol(symbol) & ModifierFlags.Readonly || - symbol.flags & SymbolFlags.Variable && getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const || - symbol.flags & SymbolFlags.Accessor && !(symbol.flags & SymbolFlags.SetAccessor) || - symbol.flags & SymbolFlags.EnumMember || - some(symbol.declarations, isReadonlyAssignmentDeclaration) - ); - } - - function isReferenceToReadonlyEntity(expr: Expression, symbol: Symbol): boolean { - if (isReadonlySymbol(symbol)) { - // Allow assignments to readonly properties within constructors of the same class declaration. - if (symbol.flags & SymbolFlags.Property && - (expr.kind === SyntaxKind.PropertyAccessExpression || expr.kind === SyntaxKind.ElementAccessExpression) && - (expr as AccessExpression).expression.kind === SyntaxKind.ThisKeyword) { - // Look for if this is the constructor for the class that `symbol` is a property of. - const func = getContainingFunction(expr); - if (!(func && func.kind === SyntaxKind.Constructor)) { - return true; + else if (flags & FlowFlags.ArrayMutation) { + type = getTypeAtFlowArrayMutation((flow)); + if (!type) { + flow = (flow).antecedent; + continue; } - // If func.parent is a class and symbol is a (readonly) property of that class, or - // if func is a constructor and symbol is a (readonly) parameter property declared in it, - // then symbol is writeable here. - return !symbol.valueDeclaration || !(func.parent === symbol.valueDeclaration.parent || func === symbol.valueDeclaration.parent); } - return true; - } - return false; - } - - function isReferenceThroughNamespaceImport(expr: Expression): boolean { - if (expr.kind === SyntaxKind.PropertyAccessExpression || expr.kind === SyntaxKind.ElementAccessExpression) { - const node = skipParentheses((expr as AccessExpression).expression); - if (node.kind === SyntaxKind.Identifier) { - const symbol = getNodeLinks(node).resolvedSymbol!; - if (symbol.flags & SymbolFlags.Alias) { - const declaration = getDeclarationOfAliasSymbol(symbol); - return !!declaration && declaration.kind === SyntaxKind.NamespaceImport; + else if (flags & FlowFlags.Start) { + // Check if we should continue with the control flow of the containing function. + const container = (flow).node; + if (container && container !== flowContainer && + reference.kind !== SyntaxKind.PropertyAccessExpression && + reference.kind !== SyntaxKind.ElementAccessExpression && + reference.kind !== SyntaxKind.ThisKeyword) { + flow = container.flowNode!; + continue; } + // At the top of the flow we have the initial type. + type = initialType; } + else { + // Unreachable code errors are reported in the binding phase. Here we + // simply return the non-auto declared type to reduce follow-on errors. + type = convertAutoToAny(declaredType); + } + if (flags & FlowFlags.Shared) { + // Record visited node and the associated type in the cache. + sharedFlowNodes[sharedFlowCount] = flow; + sharedFlowTypes[sharedFlowCount] = type; + sharedFlowCount++; + } + flowDepth--; + return type; } - return false; - } - - function checkReferenceExpression(expr: Expression, invalidReferenceMessage: DiagnosticMessage, invalidOptionalChainMessage: DiagnosticMessage): boolean { - // References are combinations of identifiers, parentheses, and property accesses. - const node = skipOuterExpressions(expr, OuterExpressionKinds.Assertions | OuterExpressionKinds.Parentheses); - if (node.kind !== SyntaxKind.Identifier && node.kind !== SyntaxKind.PropertyAccessExpression && node.kind !== SyntaxKind.ElementAccessExpression) { - error(expr, invalidReferenceMessage); - return false; - } - if (node.flags & NodeFlags.OptionalChain) { - error(expr, invalidOptionalChainMessage); - return false; - } - return true; - } - - function checkDeleteExpression(node: DeleteExpression): Type { - checkExpression(node.expression); - const expr = skipParentheses(node.expression); - if (expr.kind !== SyntaxKind.PropertyAccessExpression && expr.kind !== SyntaxKind.ElementAccessExpression) { - error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_a_property_reference); - return booleanType; - } - const links = getNodeLinks(expr); - const symbol = getExportSymbolOfValueSymbolIfExported(links.resolvedSymbol); - if (symbol && isReadonlySymbol(symbol)) { - error(expr, Diagnostics.The_operand_of_a_delete_operator_cannot_be_a_read_only_property); - } - return booleanType; - } - - function checkTypeOfExpression(node: TypeOfExpression): Type { - checkExpression(node.expression); - return typeofType; } - - function checkVoidExpression(node: VoidExpression): Type { - checkExpression(node.expression); - return undefinedWideningType; + function getInitialOrAssignedType(flow: FlowAssignment) { + const node = flow.node; + return getConstraintForLocation(node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ? + getInitialType((node)) : + getAssignedType(node), reference); } - - function checkAwaitExpression(node: AwaitExpression): Type { - // Grammar checking - if (produceDiagnostics) { - if (!(node.flags & NodeFlags.AwaitContext)) { - // use of 'await' in non-async function - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - const span = getSpanOfTokenAtPosition(sourceFile, node.pos); - const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.await_expression_is_only_allowed_within_an_async_function); - const func = getContainingFunction(node); - if (func && func.kind !== SyntaxKind.Constructor && (getFunctionFlags(func) & FunctionFlags.Async) === 0) { - const relatedInfo = createDiagnosticForNode(func, Diagnostics.Did_you_mean_to_mark_this_function_as_async); - addRelatedInfo(diagnostic, relatedInfo); - } - diagnostics.add(diagnostic); - } + function getTypeAtFlowAssignment(flow: FlowAssignment) { + const node = flow.node; + // Assignments only narrow the computed type if the declared type is a union type. Thus, we + // only need to evaluate the assigned type if the declared type is a union type. + if (isMatchingReference(reference, node)) { + if (!isReachableFlowNode(flow)) { + return unreachableNeverType; } - - if (isInParameterInitializerBeforeContainingFunction(node)) { - error(node, Diagnostics.await_expressions_cannot_be_used_in_a_parameter_initializer); + if (getAssignmentTargetKind(node) === AssignmentKind.Compound) { + const flowType = getTypeAtFlowNode(flow.antecedent); + return createFlowType(getBaseTypeOfLiteralType(getTypeFromFlowType(flowType)), isIncomplete(flowType)); } - } - - const operandType = checkExpression(node.expression); - const awaitedType = checkAwaitedType(operandType, node, Diagnostics.Type_of_await_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); - if (awaitedType === operandType && awaitedType !== errorType && !(operandType.flags & TypeFlags.AnyOrUnknown)) { - addErrorOrSuggestion(/*isError*/ false, createDiagnosticForNode(node, Diagnostics.await_has_no_effect_on_the_type_of_this_expression)); - } - return awaitedType; - } - - function checkPrefixUnaryExpression(node: PrefixUnaryExpression): Type { - const operandType = checkExpression(node.operand); - if (operandType === silentNeverType) { - return silentNeverType; - } - switch (node.operand.kind) { - case SyntaxKind.NumericLiteral: - switch (node.operator) { - case SyntaxKind.MinusToken: - return getFreshTypeOfLiteralType(getLiteralType(-(node.operand as NumericLiteral).text)); - case SyntaxKind.PlusToken: - return getFreshTypeOfLiteralType(getLiteralType(+(node.operand as NumericLiteral).text)); - } - break; - case SyntaxKind.BigIntLiteral: - if (node.operator === SyntaxKind.MinusToken) { - return getFreshTypeOfLiteralType(getLiteralType({ - negative: true, - base10Value: parsePseudoBigInt((node.operand as BigIntLiteral).text) - })); + if (declaredType === autoType || declaredType === autoArrayType) { + if (isEmptyArrayAssignment(node)) { + return getEvolvingArrayType(neverType); } + const assignedType = getBaseTypeOfLiteralType(getInitialOrAssignedType(flow)); + return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType; + } + if (declaredType.flags & TypeFlags.Union) { + return getAssignmentReducedType((declaredType), getInitialOrAssignedType(flow)); + } + return declaredType; } - switch (node.operator) { - case SyntaxKind.PlusToken: - case SyntaxKind.MinusToken: - case SyntaxKind.TildeToken: - checkNonNullType(operandType, node.operand); - if (maybeTypeOfKind(operandType, TypeFlags.ESSymbolLike)) { - error(node.operand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(node.operator)); - } - if (node.operator === SyntaxKind.PlusToken) { - if (maybeTypeOfKind(operandType, TypeFlags.BigIntLike)) { - error(node.operand, Diagnostics.Operator_0_cannot_be_applied_to_type_1, tokenToString(node.operator), typeToString(getBaseTypeOfLiteralType(operandType))); - } - return numberType; - } - return getUnaryResultType(operandType); - case SyntaxKind.ExclamationToken: - checkTruthinessExpression(node.operand); - const facts = getTypeFacts(operandType) & (TypeFacts.Truthy | TypeFacts.Falsy); - return facts === TypeFacts.Truthy ? falseType : - facts === TypeFacts.Falsy ? trueType : - booleanType; - case SyntaxKind.PlusPlusToken: - case SyntaxKind.MinusMinusToken: - const ok = checkArithmeticOperandType(node.operand, checkNonNullType(operandType, node.operand), - Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type); - if (ok) { - // run check only if former checks succeeded to avoid reporting cascading errors - checkReferenceExpression( - node.operand, - Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, - Diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access); - } - return getUnaryResultType(operandType); - } - return errorType; - } - - function checkPostfixUnaryExpression(node: PostfixUnaryExpression): Type { - const operandType = checkExpression(node.operand); - if (operandType === silentNeverType) { - return silentNeverType; - } - const ok = checkArithmeticOperandType( - node.operand, - checkNonNullType(operandType, node.operand), - Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type); - if (ok) { - // run check only if former checks succeeded to avoid reporting cascading errors - checkReferenceExpression( - node.operand, - Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, - Diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access); - } - return getUnaryResultType(operandType); - } - - function getUnaryResultType(operandType: Type): Type { - if (maybeTypeOfKind(operandType, TypeFlags.BigIntLike)) { - return isTypeAssignableToKind(operandType, TypeFlags.AnyOrUnknown) || maybeTypeOfKind(operandType, TypeFlags.NumberLike) - ? numberOrBigIntType - : bigintType; - } - // If it's not a bigint type, implicit coercion will result in a number - return numberType; - } - - // Return true if type might be of the given kind. A union or intersection type might be of a given - // kind if at least one constituent type is of the given kind. - function maybeTypeOfKind(type: Type, kind: TypeFlags): boolean { - if (type.flags & kind & ~TypeFlags.GenericMappedType || kind & TypeFlags.GenericMappedType && isGenericMappedType(type)) { - return true; - } - if (type.flags & TypeFlags.UnionOrIntersection) { - const types = (type).types; - for (const t of types) { - if (maybeTypeOfKind(t, kind)) { - return true; + // We didn't have a direct match. However, if the reference is a dotted name, this + // may be an assignment to a left hand part of the reference. For example, for a + // reference 'x.y.z', we may be at an assignment to 'x.y' or 'x'. In that case, + // return the declared type. + if (containsMatchingReference(reference, node)) { + if (!isReachableFlowNode(flow)) { + return unreachableNeverType; + } + // A matching dotted name might also be an expando property on a function *expression*, + // in which case we continue control flow analysis back to the function's declaration + if (isVariableDeclaration(node) && (isInJSFile(node) || isVarConst(node))) { + const init = getDeclaredExpandoInitializer(node); + if (init && (init.kind === SyntaxKind.FunctionExpression || init.kind === SyntaxKind.ArrowFunction)) { + return getTypeAtFlowNode(flow.antecedent); } } + return declaredType; } - return false; - } - - function isTypeAssignableToKind(source: Type, kind: TypeFlags, strict?: boolean): boolean { - if (source.flags & kind) { - return true; - } - if (strict && source.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null)) { - return false; - } - return !!(kind & TypeFlags.NumberLike) && isTypeAssignableTo(source, numberType) || - !!(kind & TypeFlags.BigIntLike) && isTypeAssignableTo(source, bigintType) || - !!(kind & TypeFlags.StringLike) && isTypeAssignableTo(source, stringType) || - !!(kind & TypeFlags.BooleanLike) && isTypeAssignableTo(source, booleanType) || - !!(kind & TypeFlags.Void) && isTypeAssignableTo(source, voidType) || - !!(kind & TypeFlags.Never) && isTypeAssignableTo(source, neverType) || - !!(kind & TypeFlags.Null) && isTypeAssignableTo(source, nullType) || - !!(kind & TypeFlags.Undefined) && isTypeAssignableTo(source, undefinedType) || - !!(kind & TypeFlags.ESSymbol) && isTypeAssignableTo(source, esSymbolType) || - !!(kind & TypeFlags.NonPrimitive) && isTypeAssignableTo(source, nonPrimitiveType); - } - - function allTypesAssignableToKind(source: Type, kind: TypeFlags, strict?: boolean): boolean { - return source.flags & TypeFlags.Union ? - every((source as UnionType).types, subType => allTypesAssignableToKind(subType, kind, strict)) : - isTypeAssignableToKind(source, kind, strict); - } - - function isConstEnumObjectType(type: Type): boolean { - return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && !!type.symbol && isConstEnumSymbol(type.symbol); - } - - function isConstEnumSymbol(symbol: Symbol): boolean { - return (symbol.flags & SymbolFlags.ConstEnum) !== 0; - } - - function checkInstanceOfExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type { - if (leftType === silentNeverType || rightType === silentNeverType) { - return silentNeverType; - } - // TypeScript 1.0 spec (April 2014): 4.15.4 - // The instanceof operator requires the left operand to be of type Any, an object type, or a type parameter type, - // and the right operand to be of type Any, a subtype of the 'Function' interface type, or have a call or construct signature. - // The result is always of the Boolean primitive type. - // NOTE: do not raise error if leftType is unknown as related error was already reported - if (!isTypeAny(leftType) && - allTypesAssignableToKind(leftType, TypeFlags.Primitive)) { - error(left, Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter); - } - // NOTE: do not raise error if right is unknown as related error was already reported - if (!(isTypeAny(rightType) || typeHasCallOrConstructSignatures(rightType) || isTypeSubtypeOf(rightType, globalFunctionType))) { - error(right, Diagnostics.The_right_hand_side_of_an_instanceof_expression_must_be_of_type_any_or_of_a_type_assignable_to_the_Function_interface_type); - } - return booleanType; - } - - function checkInExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type { - if (leftType === silentNeverType || rightType === silentNeverType) { - return silentNeverType; - } - leftType = checkNonNullType(leftType, left); - rightType = checkNonNullType(rightType, right); - // TypeScript 1.0 spec (April 2014): 4.15.5 - // The in operator requires the left operand to be of type Any, the String primitive type, or the Number primitive type, - // and the right operand to be of type Any, an object type, or a type parameter type. - // The result is always of the Boolean primitive type. - if (!(isTypeComparableTo(leftType, stringType) || isTypeAssignableToKind(leftType, TypeFlags.NumberLike | TypeFlags.ESSymbolLike))) { - error(left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_of_type_any_string_number_or_symbol); - } - if (!allTypesAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive)) { - error(right, Diagnostics.The_right_hand_side_of_an_in_expression_must_be_of_type_any_an_object_type_or_a_type_parameter); + // for (const _ in ref) acts as a nonnull on ref + if (isVariableDeclaration(node) && node.parent.parent.kind === SyntaxKind.ForInStatement && isMatchingReference(reference, node.parent.parent.expression)) { + return getNonNullableTypeIfNeeded(getTypeFromFlowType(getTypeAtFlowNode(flow.antecedent))); } - return booleanType; + // Assignment doesn't affect reference + return undefined; } - - function checkObjectLiteralAssignment(node: ObjectLiteralExpression, sourceType: Type, rightIsThis?: boolean): Type { - const properties = node.properties; - if (strictNullChecks && properties.length === 0) { - return checkNonNullType(sourceType, node); - } - for (let i = 0; i < properties.length; i++) { - checkObjectLiteralDestructuringPropertyAssignment(node, sourceType, i, properties, rightIsThis); + function narrowTypeByAssertion(type: ts.Type, expr: Expression): ts.Type { + const node = skipParentheses(expr); + if (node.kind === SyntaxKind.FalseKeyword) { + return unreachableNeverType; } - return sourceType; - } - - /** Note: If property cannot be a SpreadAssignment, then allProperties does not need to be provided */ - function checkObjectLiteralDestructuringPropertyAssignment(node: ObjectLiteralExpression, objectLiteralType: Type, propertyIndex: number, allProperties?: NodeArray, rightIsThis = false) { - const properties = node.properties; - const property = properties[propertyIndex]; - if (property.kind === SyntaxKind.PropertyAssignment || property.kind === SyntaxKind.ShorthandPropertyAssignment) { - const name = property.name; - const exprType = getLiteralTypeFromPropertyName(name); - if (isTypeUsableAsPropertyName(exprType)) { - const text = getPropertyNameFromType(exprType); - const prop = getPropertyOfType(objectLiteralType, text); - if (prop) { - markPropertyAsReferenced(prop, property, rightIsThis); - checkPropertyAccessibility(property, /*isSuper*/ false, objectLiteralType, prop); - } + if (node.kind === SyntaxKind.BinaryExpression) { + if ((node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { + return narrowTypeByAssertion(narrowTypeByAssertion(type, (node).left), (node).right); + } + if ((node).operatorToken.kind === SyntaxKind.BarBarToken) { + return getUnionType([narrowTypeByAssertion(type, (node).left), narrowTypeByAssertion(type, (node).right)]); } - const elementType = getIndexedAccessType(objectLiteralType, exprType, name); - const type = getFlowTypeOfDestructuring(property, elementType); - return checkDestructuringAssignment(property.kind === SyntaxKind.ShorthandPropertyAssignment ? property : property.initializer, type); } - else if (property.kind === SyntaxKind.SpreadAssignment) { - if (propertyIndex < properties.length - 1) { - error(property, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); + return narrowType(type, node, /*assumeTrue*/ true); + } + function getTypeAtFlowCall(flow: FlowCall): FlowType | undefined { + const signature = getEffectsSignature(flow.node); + if (signature) { + const predicate = getTypePredicateOfSignature(signature); + if (predicate && (predicate.kind === TypePredicateKind.AssertsThis || predicate.kind === TypePredicateKind.AssertsIdentifier)) { + const flowType = getTypeAtFlowNode(flow.antecedent); + const type = getTypeFromFlowType(flowType); + const narrowedType = predicate.type ? narrowTypeByTypePredicate(type, predicate, flow.node, /*assumeTrue*/ true) : + predicate.kind === TypePredicateKind.AssertsIdentifier && predicate.parameterIndex >= 0 && predicate.parameterIndex < flow.node.arguments.length ? narrowTypeByAssertion(type, flow.node.arguments[predicate.parameterIndex]) : + type; + return narrowedType === type ? flowType : createFlowType(narrowedType, isIncomplete(flowType)); } - else { - if (languageVersion < ScriptTarget.ESNext) { - checkExternalEmitHelpers(property, ExternalEmitHelpers.Rest); - } - const nonRestNames: PropertyName[] = []; - if (allProperties) { - for (const otherProperty of allProperties) { - if (!isSpreadAssignment(otherProperty)) { - nonRestNames.push(otherProperty.name); + if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) { + return unreachableNeverType; + } + } + return undefined; + } + function getTypeAtFlowArrayMutation(flow: FlowArrayMutation): FlowType | undefined { + if (declaredType === autoType || declaredType === autoArrayType) { + const node = flow.node; + const expr = node.kind === SyntaxKind.CallExpression ? + (node.expression).expression : + (node.left).expression; + if (isMatchingReference(reference, getReferenceCandidate(expr))) { + const flowType = getTypeAtFlowNode(flow.antecedent); + const type = getTypeFromFlowType(flowType); + if (getObjectFlags(type) & ObjectFlags.EvolvingArray) { + let evolvedType = (type); + if (node.kind === SyntaxKind.CallExpression) { + for (const arg of node.arguments) { + evolvedType = addEvolvingArrayElementType(evolvedType, arg); } } + else { + // We must get the context free expression type so as to not recur in an uncached fashion on the LHS (which causes exponential blowup in compile time) + const indexType = getContextFreeTypeOfExpression((node.left).argumentExpression); + if (isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) { + evolvedType = addEvolvingArrayElementType(evolvedType, node.right); + } + } + return evolvedType === type ? flowType : createFlowType(evolvedType, isIncomplete(flowType)); } - const type = getRestType(objectLiteralType, nonRestNames, objectLiteralType.symbol); - checkGrammarForDisallowedTrailingComma(allProperties, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); - return checkDestructuringAssignment(property.expression, type); + return flowType; } } - else { - error(property, Diagnostics.Property_assignment_expected); - } + return undefined; } - - function checkArrayLiteralAssignment(node: ArrayLiteralExpression, sourceType: Type, checkMode?: CheckMode): Type { - const elements = node.elements; - if (languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Read); + function getTypeAtFlowCondition(flow: FlowCondition): FlowType { + const flowType = getTypeAtFlowNode(flow.antecedent); + const type = getTypeFromFlowType(flowType); + if (type.flags & TypeFlags.Never) { + return flowType; + } + // If we have an antecedent type (meaning we're reachable in some way), we first + // attempt to narrow the antecedent type. If that produces the never type, and if + // the antecedent type is incomplete (i.e. a transient type in a loop), then we + // take the type guard as an indication that control *could* reach here once we + // have the complete type. We proceed by switching to the silent never type which + // doesn't report errors when operators are applied to it. Note that this is the + // *only* place a silent never type is ever generated. + const assumeTrue = (flow.flags & FlowFlags.TrueCondition) !== 0; + const nonEvolvingType = finalizeEvolvingArrayType(type); + const narrowedType = narrowType(nonEvolvingType, flow.node, assumeTrue); + if (narrowedType === nonEvolvingType) { + return flowType; + } + const incomplete = isIncomplete(flowType); + const resultType = incomplete && narrowedType.flags & TypeFlags.Never ? silentNeverType : narrowedType; + return createFlowType(resultType, incomplete); + } + function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType { + const expr = flow.switchStatement.expression; + const flowType = getTypeAtFlowNode(flow.antecedent); + let type = getTypeFromFlowType(flowType); + if (isMatchingReference(reference, expr)) { + type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); + } + else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) { + type = narrowBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); } - // This elementType will be used if the specific property corresponding to this index is not - // present (aka the tuple element property). This call also checks that the parentType is in - // fact an iterable or array (depending on target language). - const elementType = checkIteratedTypeOrElementType(IterationUse.Destructuring, sourceType, undefinedType, node) || errorType; - for (let i = 0; i < elements.length; i++) { - checkArrayLiteralDestructuringElementAssignment(node, sourceType, i, elementType, checkMode); + else { + if (strictNullChecks) { + if (optionalChainContainsReference(expr, reference)) { + type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd, t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never))); + } + else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) { + type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd, t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (t).value === "undefined")); + } + } + if (isMatchingReferenceDiscriminant(expr, type)) { + type = narrowTypeByDiscriminant(type, (expr as AccessExpression), t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd)); + } + else if (containsMatchingReferenceDiscriminant(reference, expr)) { + type = declaredType; + } } - return sourceType; + return createFlowType(type, isIncomplete(flowType)); } - - function checkArrayLiteralDestructuringElementAssignment(node: ArrayLiteralExpression, sourceType: Type, - elementIndex: number, elementType: Type, checkMode?: CheckMode) { - const elements = node.elements; - const element = elements[elementIndex]; - if (element.kind !== SyntaxKind.OmittedExpression) { - if (element.kind !== SyntaxKind.SpreadElement) { - const indexType = getLiteralType(elementIndex); - if (isArrayLikeType(sourceType)) { - // We create a synthetic expression so that getIndexedAccessType doesn't get confused - // when the element is a SyntaxKind.ElementAccessExpression. - const accessFlags = hasDefaultValue(element) ? AccessFlags.NoTupleBoundsCheck : 0; - const elementType = getIndexedAccessTypeOrUndefined(sourceType, indexType, createSyntheticExpression(element, indexType), accessFlags) || errorType; - const assignedType = hasDefaultValue(element) ? getTypeWithFacts(elementType, TypeFacts.NEUndefined) : elementType; - const type = getFlowTypeOfDestructuring(element, assignedType); - return checkDestructuringAssignment(element, type, checkMode); - } - return checkDestructuringAssignment(element, elementType, checkMode); - } - if (elementIndex < elements.length - 1) { - error(element, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); + function getTypeAtFlowBranchLabel(flow: FlowLabel): FlowType { + const antecedentTypes: ts.Type[] = []; + let subtypeReduction = false; + let seenIncomplete = false; + let bypassFlow: FlowSwitchClause | undefined; + for (const antecedent of flow.antecedents!) { + if (antecedent.flags & FlowFlags.PreFinally && (antecedent).lock.locked) { + // if flow correspond to branch from pre-try to finally and this branch is locked - this means that + // we initially have started following the flow outside the finally block. + // in this case we should ignore this branch. + continue; } - else { - const restExpression = (element).expression; - if (restExpression.kind === SyntaxKind.BinaryExpression && (restExpression).operatorToken.kind === SyntaxKind.EqualsToken) { - error((restExpression).operatorToken, Diagnostics.A_rest_element_cannot_have_an_initializer); + if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (antecedent).clauseStart === (antecedent).clauseEnd) { + // The antecedent is the bypass branch of a potentially exhaustive switch statement. + bypassFlow = (antecedent); + continue; + } + const flowType = getTypeAtFlowNode(antecedent); + const type = getTypeFromFlowType(flowType); + // If the type at a particular antecedent path is the declared type and the + // reference is known to always be assigned (i.e. when declared and initial types + // are the same), there is no reason to process more antecedents since the only + // possible outcome is subtypes that will be removed in the final union type anyway. + if (type === declaredType && declaredType === initialType) { + return type; + } + pushIfUnique(antecedentTypes, type); + // If an antecedent type is not a subset of the declared type, we need to perform + // subtype reduction. This happens when a "foreign" type is injected into the control + // flow using the instanceof operator or a user defined type predicate. + if (!isTypeSubsetOf(type, declaredType)) { + subtypeReduction = true; + } + if (isIncomplete(flowType)) { + seenIncomplete = true; + } + } + if (bypassFlow) { + const flowType = getTypeAtFlowNode(bypassFlow); + const type = getTypeFromFlowType(flowType); + // If the bypass flow contributes a type we haven't seen yet and the switch statement + // isn't exhaustive, process the bypass flow type. Since exhaustiveness checks increase + // the risk of circularities, we only want to perform them when they make a difference. + if (!contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.switchStatement)) { + if (type === declaredType && declaredType === initialType) { + return type; } - else { - checkGrammarForDisallowedTrailingComma(node.elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); - const type = everyType(sourceType, isTupleType) ? - mapType(sourceType, t => sliceTupleType(t, elementIndex)) : - createArrayType(elementType); - return checkDestructuringAssignment(restExpression, type, checkMode); + antecedentTypes.push(type); + if (!isTypeSubsetOf(type, declaredType)) { + subtypeReduction = true; + } + if (isIncomplete(flowType)) { + seenIncomplete = true; } } } - return undefined; + return createFlowType(getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal), seenIncomplete); } - - function checkDestructuringAssignment(exprOrAssignment: Expression | ShorthandPropertyAssignment, sourceType: Type, checkMode?: CheckMode, rightIsThis?: boolean): Type { - let target: Expression; - if (exprOrAssignment.kind === SyntaxKind.ShorthandPropertyAssignment) { - const prop = exprOrAssignment; - if (prop.objectAssignmentInitializer) { - // In strict null checking mode, if a default value of a non-undefined type is specified, remove - // undefined from the final type. - if (strictNullChecks && - !(getFalsyFlags(checkExpression(prop.objectAssignmentInitializer)) & TypeFlags.Undefined)) { - sourceType = getTypeWithFacts(sourceType, TypeFacts.NEUndefined); + function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType { + // If we have previously computed the control flow type for the reference at + // this flow loop junction, return the cached type. + const id = getFlowNodeId(flow); + const cache = flowLoopCaches[id] || (flowLoopCaches[id] = createMap()); + const key = getOrSetCacheKey(); + if (!key) { + // No cache key is generated when binding patterns are in unnarrowable situations + return declaredType; + } + const cached = cache.get(key); + if (cached) { + return cached; + } + // If this flow loop junction and reference are already being processed, return + // the union of the types computed for each branch so far, marked as incomplete. + // It is possible to see an empty array in cases where loops are nested and the + // back edge of the outer loop reaches an inner loop that is already being analyzed. + // In such cases we restart the analysis of the inner loop, which will then see + // a non-empty in-process array for the outer loop and eventually terminate because + // the first antecedent of a loop junction is always the non-looping control flow + // path that leads to the top. + for (let i = flowLoopStart; i < flowLoopCount; i++) { + if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key && flowLoopTypes[i].length) { + return createFlowType(getUnionOrEvolvingArrayType(flowLoopTypes[i], UnionReduction.Literal), /*incomplete*/ true); + } + } + // Add the flow loop junction and reference to the in-process stack and analyze + // each antecedent code path. + const antecedentTypes: ts.Type[] = []; + let subtypeReduction = false; + let firstAntecedentType: FlowType | undefined; + for (const antecedent of flow.antecedents!) { + let flowType; + if (!firstAntecedentType) { + // The first antecedent of a loop junction is always the non-looping control + // flow path that leads to the top. + flowType = firstAntecedentType = getTypeAtFlowNode(antecedent); + } + else { + // All but the first antecedent are the looping control flow paths that lead + // back to the loop junction. We track these on the flow loop stack. + flowLoopNodes[flowLoopCount] = flow; + flowLoopKeys[flowLoopCount] = key; + flowLoopTypes[flowLoopCount] = antecedentTypes; + flowLoopCount++; + const saveFlowTypeCache = flowTypeCache; + flowTypeCache = undefined; + flowType = getTypeAtFlowNode(antecedent); + flowTypeCache = saveFlowTypeCache; + flowLoopCount--; + // If we see a value appear in the cache it is a sign that control flow analysis + // was restarted and completed by checkExpressionCached. We can simply pick up + // the resulting type and bail out. + const cached = cache.get(key); + if (cached) { + return cached; } - checkBinaryLikeExpression(prop.name, prop.equalsToken!, prop.objectAssignmentInitializer, checkMode); } - target = (exprOrAssignment).name; - } - else { - target = exprOrAssignment; + const type = getTypeFromFlowType(flowType); + pushIfUnique(antecedentTypes, type); + // If an antecedent type is not a subset of the declared type, we need to perform + // subtype reduction. This happens when a "foreign" type is injected into the control + // flow using the instanceof operator or a user defined type predicate. + if (!isTypeSubsetOf(type, declaredType)) { + subtypeReduction = true; + } + // If the type at a particular antecedent path is the declared type there is no + // reason to process more antecedents since the only possible outcome is subtypes + // that will be removed in the final union type anyway. + if (type === declaredType) { + break; + } } - - if (target.kind === SyntaxKind.BinaryExpression && (target).operatorToken.kind === SyntaxKind.EqualsToken) { - checkBinaryExpression(target, checkMode); - target = (target).left; + // The result is incomplete if the first antecedent (the non-looping control flow path) + // is incomplete. + const result = getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal); + if (isIncomplete(firstAntecedentType!)) { + return createFlowType(result, /*incomplete*/ true); } - if (target.kind === SyntaxKind.ObjectLiteralExpression) { - return checkObjectLiteralAssignment(target, sourceType, rightIsThis); + cache.set(key, result); + return result; + } + function isMatchingReferenceDiscriminant(expr: Expression, computedType: ts.Type) { + if (!(computedType.flags & TypeFlags.Union) || !isAccessExpression(expr)) { + return false; } - if (target.kind === SyntaxKind.ArrayLiteralExpression) { - return checkArrayLiteralAssignment(target, sourceType, checkMode); + const name = getAccessedPropertyName(expr); + if (name === undefined) { + return false; } - return checkReferenceAssignment(target, sourceType, checkMode); + return isMatchingReference(reference, expr.expression) && isDiscriminantProperty(computedType, name); } - - function checkReferenceAssignment(target: Expression, sourceType: Type, checkMode?: CheckMode): Type { - const targetType = checkExpression(target, checkMode); - const error = target.parent.kind === SyntaxKind.SpreadAssignment ? - Diagnostics.The_target_of_an_object_rest_assignment_must_be_a_variable_or_a_property_access : - Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access; - const optionalError = target.parent.kind === SyntaxKind.SpreadAssignment ? - Diagnostics.The_target_of_an_object_rest_assignment_may_not_be_an_optional_property_access : - Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access; - if (checkReferenceExpression(target, error, optionalError)) { - checkTypeAssignableToAndOptionallyElaborate(sourceType, targetType, target, target); - } - return sourceType; - } - - /** - * This is a *shallow* check: An expression is side-effect-free if the - * evaluation of the expression *itself* cannot produce side effects. - * For example, x++ / 3 is side-effect free because the / operator - * does not have side effects. - * The intent is to "smell test" an expression for correctness in positions where - * its value is discarded (e.g. the left side of the comma operator). - */ - function isSideEffectFree(node: Node): boolean { - node = skipParentheses(node); - switch (node.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.StringLiteral: - case SyntaxKind.RegularExpressionLiteral: - case SyntaxKind.TaggedTemplateExpression: - case SyntaxKind.TemplateExpression: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.NullKeyword: - case SyntaxKind.UndefinedKeyword: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ClassExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.ArrayLiteralExpression: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.TypeOfExpression: - case SyntaxKind.NonNullExpression: - case SyntaxKind.JsxSelfClosingElement: - case SyntaxKind.JsxElement: - return true; - - case SyntaxKind.ConditionalExpression: - return isSideEffectFree((node as ConditionalExpression).whenTrue) && - isSideEffectFree((node as ConditionalExpression).whenFalse); - - case SyntaxKind.BinaryExpression: - if (isAssignmentOperator((node as BinaryExpression).operatorToken.kind)) { - return false; - } - return isSideEffectFree((node as BinaryExpression).left) && - isSideEffectFree((node as BinaryExpression).right); - - case SyntaxKind.PrefixUnaryExpression: - case SyntaxKind.PostfixUnaryExpression: - // Unary operators ~, !, +, and - have no side effects. - // The rest do. - switch ((node as PrefixUnaryExpression).operator) { - case SyntaxKind.ExclamationToken: - case SyntaxKind.PlusToken: - case SyntaxKind.MinusToken: - case SyntaxKind.TildeToken: - return true; - } - return false; - - // Some forms listed here for clarity - case SyntaxKind.VoidExpression: // Explicit opt-out - case SyntaxKind.TypeAssertionExpression: // Not SEF, but can produce useful type warnings - case SyntaxKind.AsExpression: // Not SEF, but can produce useful type warnings - default: - return false; + function narrowTypeByDiscriminant(type: ts.Type, access: AccessExpression, narrowType: (t: ts.Type) => ts.Type): ts.Type { + const propName = getAccessedPropertyName(access); + if (propName === undefined) { + return type; } - } - - function isTypeEqualityComparableTo(source: Type, target: Type) { - return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target); - } - - function checkBinaryExpression(node: BinaryExpression, checkMode?: CheckMode) { - if (isInJSFile(node) && getAssignedExpandoInitializer(node)) { - return checkExpression(node.right, checkMode); + const propType = getTypeOfPropertyOfType(type, propName); + if (!propType) { + return type; } - checkGrammarNullishCoalesceWithLogicalExpression(node); - return checkBinaryLikeExpression(node.left, node.operatorToken, node.right, checkMode, node); + const narrowedPropType = narrowType(propType); + return filterType(type, t => { + const discriminantType = getTypeOfPropertyOrIndexSignature(t, propName); + return !(discriminantType.flags & TypeFlags.Never) && isTypeComparableTo(discriminantType, narrowedPropType); + }); } - - function checkGrammarNullishCoalesceWithLogicalExpression(node: BinaryExpression) { - const { left, operatorToken, right } = node; - if (operatorToken.kind === SyntaxKind.QuestionQuestionToken) { - if (isBinaryExpression(left) && (left.operatorToken.kind === SyntaxKind.BarBarToken || left.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) { - grammarErrorOnNode(left, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(left.operatorToken.kind), tokenToString(operatorToken.kind)); - } - if (isBinaryExpression(right) && (right.operatorToken.kind === SyntaxKind.BarBarToken || right.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) { - grammarErrorOnNode(right, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(right.operatorToken.kind), tokenToString(operatorToken.kind)); - } + function narrowTypeByTruthiness(type: ts.Type, expr: Expression, assumeTrue: boolean): ts.Type { + if (isMatchingReference(reference, expr)) { + return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy); + } + if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) { + type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); + } + if (isMatchingReferenceDiscriminant(expr, declaredType)) { + return narrowTypeByDiscriminant(type, (expr), t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy)); + } + if (containsMatchingReferenceDiscriminant(reference, expr)) { + return declaredType; } + return type; } - - function checkBinaryLikeExpression(left: Expression, operatorToken: Node, right: Expression, checkMode?: CheckMode, errorNode?: Node): Type { - const operator = operatorToken.kind; - if (operator === SyntaxKind.EqualsToken && (left.kind === SyntaxKind.ObjectLiteralExpression || left.kind === SyntaxKind.ArrayLiteralExpression)) { - return checkDestructuringAssignment(left, checkExpression(right, checkMode), checkMode, right.kind === SyntaxKind.ThisKeyword); + function isTypePresencePossible(type: ts.Type, propName: __String, assumeTrue: boolean) { + if (getIndexInfoOfType(type, IndexKind.String)) { + return true; } - let leftType: Type; - if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) { - leftType = checkTruthinessExpression(left, checkMode); + const prop = getPropertyOfType(type, propName); + if (prop) { + return prop.flags & SymbolFlags.Optional ? true : assumeTrue; } - else { - leftType = checkExpression(left, checkMode); + return !assumeTrue; + } + function narrowByInKeyword(type: ts.Type, literal: LiteralExpression, assumeTrue: boolean) { + if (type.flags & (TypeFlags.Union | TypeFlags.Object) || isThisTypeParameter(type)) { + const propName = escapeLeadingUnderscores(literal.text); + return filterType(type, t => isTypePresencePossible(t, propName, assumeTrue)); } - - let rightType = checkExpression(right, checkMode); - switch (operator) { - case SyntaxKind.AsteriskToken: - case SyntaxKind.AsteriskAsteriskToken: - case SyntaxKind.AsteriskEqualsToken: - case SyntaxKind.AsteriskAsteriskEqualsToken: - case SyntaxKind.SlashToken: - case SyntaxKind.SlashEqualsToken: - case SyntaxKind.PercentToken: - case SyntaxKind.PercentEqualsToken: - case SyntaxKind.MinusToken: - case SyntaxKind.MinusEqualsToken: - case SyntaxKind.LessThanLessThanToken: - case SyntaxKind.LessThanLessThanEqualsToken: - case SyntaxKind.GreaterThanGreaterThanToken: - case SyntaxKind.GreaterThanGreaterThanEqualsToken: - case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: - case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: - case SyntaxKind.BarToken: - case SyntaxKind.BarEqualsToken: - case SyntaxKind.CaretToken: - case SyntaxKind.CaretEqualsToken: - case SyntaxKind.AmpersandToken: - case SyntaxKind.AmpersandEqualsToken: - if (leftType === silentNeverType || rightType === silentNeverType) { - return silentNeverType; - } - - leftType = checkNonNullType(leftType, left); - rightType = checkNonNullType(rightType, right); - - let suggestedOperator: SyntaxKind | undefined; - // if a user tries to apply a bitwise operator to 2 boolean operands - // try and return them a helpful suggestion - if ((leftType.flags & TypeFlags.BooleanLike) && - (rightType.flags & TypeFlags.BooleanLike) && - (suggestedOperator = getSuggestedBooleanOperator(operatorToken.kind)) !== undefined) { - error(errorNode || operatorToken, Diagnostics.The_0_operator_is_not_allowed_for_boolean_types_Consider_using_1_instead, tokenToString(operatorToken.kind), tokenToString(suggestedOperator)); - return numberType; - } - else { - // otherwise just check each operand separately and report errors as normal - const leftOk = checkArithmeticOperandType(left, leftType, Diagnostics.The_left_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type, /*isAwaitValid*/ true); - const rightOk = checkArithmeticOperandType(right, rightType, Diagnostics.The_right_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type, /*isAwaitValid*/ true); - let resultType: Type; - // If both are any or unknown, allow operation; assume it will resolve to number - if ((isTypeAssignableToKind(leftType, TypeFlags.AnyOrUnknown) && isTypeAssignableToKind(rightType, TypeFlags.AnyOrUnknown)) || - // Or, if neither could be bigint, implicit coercion results in a number result - !(maybeTypeOfKind(leftType, TypeFlags.BigIntLike) || maybeTypeOfKind(rightType, TypeFlags.BigIntLike)) - ) { - resultType = numberType; - } - // At least one is assignable to bigint, so check that both are - else if (bothAreBigIntLike(leftType, rightType)) { - switch (operator) { - case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: - case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: - reportOperatorError(); - } - resultType = bigintType; - } - // Exactly one of leftType/rightType is assignable to bigint - else { - reportOperatorError(bothAreBigIntLike); - resultType = errorType; - } - if (leftOk && rightOk) { - checkAssignmentOperator(resultType); - } - return resultType; - } - case SyntaxKind.PlusToken: - case SyntaxKind.PlusEqualsToken: - if (leftType === silentNeverType || rightType === silentNeverType) { - return silentNeverType; - } - - if (!isTypeAssignableToKind(leftType, TypeFlags.StringLike) && !isTypeAssignableToKind(rightType, TypeFlags.StringLike)) { - leftType = checkNonNullType(leftType, left); - rightType = checkNonNullType(rightType, right); - } - - let resultType: Type | undefined; - if (isTypeAssignableToKind(leftType, TypeFlags.NumberLike, /*strict*/ true) && isTypeAssignableToKind(rightType, TypeFlags.NumberLike, /*strict*/ true)) { - // Operands of an enum type are treated as having the primitive type Number. - // If both operands are of the Number primitive type, the result is of the Number primitive type. - resultType = numberType; + return type; + } + function narrowTypeByBinaryExpression(type: ts.Type, expr: BinaryExpression, assumeTrue: boolean): ts.Type { + switch (expr.operatorToken.kind) { + case SyntaxKind.EqualsToken: + return narrowTypeByTruthiness(narrowType(type, expr.right, assumeTrue), expr.left, assumeTrue); + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + const operator = expr.operatorToken.kind; + const left = getReferenceCandidate(expr.left); + const right = getReferenceCandidate(expr.right); + if (left.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(right)) { + return narrowTypeByTypeof(type, (left), operator, right, assumeTrue); } - else if (isTypeAssignableToKind(leftType, TypeFlags.BigIntLike, /*strict*/ true) && isTypeAssignableToKind(rightType, TypeFlags.BigIntLike, /*strict*/ true)) { - // If both operands are of the BigInt primitive type, the result is of the BigInt primitive type. - resultType = bigintType; + if (right.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(left)) { + return narrowTypeByTypeof(type, (right), operator, left, assumeTrue); } - else if (isTypeAssignableToKind(leftType, TypeFlags.StringLike, /*strict*/ true) || isTypeAssignableToKind(rightType, TypeFlags.StringLike, /*strict*/ true)) { - // If one or both operands are of the String primitive type, the result is of the String primitive type. - resultType = stringType; + if (isMatchingReference(reference, left)) { + return narrowTypeByEquality(type, operator, right, assumeTrue); } - else if (isTypeAny(leftType) || isTypeAny(rightType)) { - // Otherwise, the result is of type Any. - // NOTE: unknown type here denotes error type. Old compiler treated this case as any type so do we. - resultType = leftType === errorType || rightType === errorType ? errorType : anyType; + if (isMatchingReference(reference, right)) { + return narrowTypeByEquality(type, operator, left, assumeTrue); } - - // Symbols are not allowed at all in arithmetic expressions - if (resultType && !checkForDisallowedESSymbolOperand(operator)) { - return resultType; + if (strictNullChecks) { + if (optionalChainContainsReference(left, reference)) { + type = narrowTypeByOptionalChainContainment(type, operator, right, assumeTrue); + } + else if (optionalChainContainsReference(right, reference)) { + type = narrowTypeByOptionalChainContainment(type, operator, left, assumeTrue); + } } - - if (!resultType) { - // Types that have a reasonably good chance of being a valid operand type. - // If both types have an awaited type of one of these, we'll assume the user - // might be missing an await without doing an exhaustive check that inserting - // await(s) will actually be a completely valid binary expression. - const closeEnoughKind = TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.AnyOrUnknown; - reportOperatorError((left, right) => - isTypeAssignableToKind(left, closeEnoughKind) && - isTypeAssignableToKind(right, closeEnoughKind)); - return anyType; + if (isMatchingReferenceDiscriminant(left, declaredType)) { + return narrowTypeByDiscriminant(type, (left), t => narrowTypeByEquality(t, operator, right, assumeTrue)); } - - if (operator === SyntaxKind.PlusEqualsToken) { - checkAssignmentOperator(resultType); + if (isMatchingReferenceDiscriminant(right, declaredType)) { + return narrowTypeByDiscriminant(type, (right), t => narrowTypeByEquality(t, operator, left, assumeTrue)); } - return resultType; - case SyntaxKind.LessThanToken: - case SyntaxKind.GreaterThanToken: - case SyntaxKind.LessThanEqualsToken: - case SyntaxKind.GreaterThanEqualsToken: - if (checkForDisallowedESSymbolOperand(operator)) { - leftType = getBaseTypeOfLiteralType(checkNonNullType(leftType, left)); - rightType = getBaseTypeOfLiteralType(checkNonNullType(rightType, right)); - reportOperatorErrorUnless((left, right) => - isTypeComparableTo(left, right) || isTypeComparableTo(right, left) || ( - isTypeAssignableTo(left, numberOrBigIntType) && isTypeAssignableTo(right, numberOrBigIntType))); + if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) { + return declaredType; } - return booleanType; - case SyntaxKind.EqualsEqualsToken: - case SyntaxKind.ExclamationEqualsToken: - case SyntaxKind.EqualsEqualsEqualsToken: - case SyntaxKind.ExclamationEqualsEqualsToken: - reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left)); - return booleanType; - + break; case SyntaxKind.InstanceOfKeyword: - return checkInstanceOfExpression(left, right, leftType, rightType); + return narrowTypeByInstanceof(type, expr, assumeTrue); case SyntaxKind.InKeyword: - return checkInExpression(left, right, leftType, rightType); - case SyntaxKind.AmpersandAmpersandToken: - return getTypeFacts(leftType) & TypeFacts.Truthy ? - getUnionType([extractDefinitelyFalsyTypes(strictNullChecks ? leftType : getBaseTypeOfLiteralType(rightType)), rightType]) : - leftType; - case SyntaxKind.BarBarToken: - return getTypeFacts(leftType) & TypeFacts.Falsy ? - getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], UnionReduction.Subtype) : - leftType; - case SyntaxKind.QuestionQuestionToken: - return getTypeFacts(leftType) & TypeFacts.EQUndefinedOrNull ? - getUnionType([getNonNullableType(leftType), rightType], UnionReduction.Subtype) : - leftType; - case SyntaxKind.EqualsToken: - const declKind = isBinaryExpression(left.parent) ? getAssignmentDeclarationKind(left.parent) : AssignmentDeclarationKind.None; - checkAssignmentDeclaration(declKind, rightType); - if (isAssignmentDeclaration(declKind)) { - if (!(rightType.flags & TypeFlags.Object) || - declKind !== AssignmentDeclarationKind.ModuleExports && - declKind !== AssignmentDeclarationKind.Prototype && - !isEmptyObjectType(rightType) && - !isFunctionObjectType(rightType as ObjectType) && - !(getObjectFlags(rightType) & ObjectFlags.Class)) { - // don't check assignability of module.exports=, C.prototype=, or expando types because they will necessarily be incomplete - checkAssignmentOperator(rightType); - } - return leftType; - } - else { - checkAssignmentOperator(rightType); - return getRegularTypeOfObjectLiteral(rightType); + const target = getReferenceCandidate(expr.right); + if (isStringLiteralLike(expr.left) && isMatchingReference(reference, target)) { + return narrowByInKeyword(type, expr.left, assumeTrue); } + break; case SyntaxKind.CommaToken: - if (!compilerOptions.allowUnreachableCode && isSideEffectFree(left) && !isEvalNode(right)) { - error(left, Diagnostics.Left_side_of_comma_operator_is_unused_and_has_no_side_effects); - } - return rightType; - - default: - return Debug.fail(); + return narrowType(type, expr.right, assumeTrue); } - - function bothAreBigIntLike(left: Type, right: Type): boolean { - return isTypeAssignableToKind(left, TypeFlags.BigIntLike) && isTypeAssignableToKind(right, TypeFlags.BigIntLike); - } - - function checkAssignmentDeclaration(kind: AssignmentDeclarationKind, rightType: Type) { - if (kind === AssignmentDeclarationKind.ModuleExports) { - for (const prop of getPropertiesOfObjectType(rightType)) { - const propType = getTypeOfSymbol(prop); - if (propType.symbol && propType.symbol.flags & SymbolFlags.Class) { - const name = prop.escapedName; - const symbol = resolveName(prop.valueDeclaration, name, SymbolFlags.Type, undefined, name, /*isUse*/ false); - if (symbol && symbol.declarations.some(isJSDocTypedefTag)) { - grammarErrorOnNode(symbol.declarations[0], Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name)); - return grammarErrorOnNode(prop.valueDeclaration, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name)); - } - } - } - } + return type; + } + function narrowTypeByOptionalChainContainment(type: ts.Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): ts.Type { + // We are in a branch of obj?.foo === value or obj?.foo !== value. We remove undefined and null from + // the type of obj if (a) the operator is === and the type of value doesn't include undefined or (b) the + // operator is !== and the type of value is undefined. + const effectiveTrue = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken ? assumeTrue : !assumeTrue; + const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken; + const valueNonNullish = !(getTypeFacts(getTypeOfExpression(value)) & (doubleEquals ? TypeFacts.EQUndefinedOrNull : TypeFacts.EQUndefined)); + return effectiveTrue === valueNonNullish ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; + } + function narrowTypeByEquality(type: ts.Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): ts.Type { + if (type.flags & TypeFlags.Any) { + return type; } - - function isEvalNode(node: Expression) { - return node.kind === SyntaxKind.Identifier && (node as Identifier).escapedText === "eval"; + if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { + assumeTrue = !assumeTrue; } - - // Return true if there was no error, false if there was an error. - function checkForDisallowedESSymbolOperand(operator: SyntaxKind): boolean { - const offendingSymbolOperand = - maybeTypeOfKind(leftType, TypeFlags.ESSymbolLike) ? left : - maybeTypeOfKind(rightType, TypeFlags.ESSymbolLike) ? right : - undefined; - - if (offendingSymbolOperand) { - error(offendingSymbolOperand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(operator)); - return false; + const valueType = getTypeOfExpression(value); + if ((type.flags & TypeFlags.Unknown) && assumeTrue && (operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)) { + if (valueType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) { + return valueType; } - - return true; - } - - function getSuggestedBooleanOperator(operator: SyntaxKind): SyntaxKind | undefined { - switch (operator) { - case SyntaxKind.BarToken: - case SyntaxKind.BarEqualsToken: - return SyntaxKind.BarBarToken; - case SyntaxKind.CaretToken: - case SyntaxKind.CaretEqualsToken: - return SyntaxKind.ExclamationEqualsEqualsToken; - case SyntaxKind.AmpersandToken: - case SyntaxKind.AmpersandEqualsToken: - return SyntaxKind.AmpersandAmpersandToken; - default: - return undefined; + if (valueType.flags & TypeFlags.Object) { + return nonPrimitiveType; } + return type; } - - function checkAssignmentOperator(valueType: Type): void { - if (produceDiagnostics && isAssignmentOperator(operator)) { - // TypeScript 1.0 spec (April 2014): 4.17 - // An assignment of the form - // VarExpr = ValueExpr - // requires VarExpr to be classified as a reference - // A compound assignment furthermore requires VarExpr to be classified as a reference (section 4.1) - // and the type of the non-compound operation to be assignable to the type of VarExpr. - - if (checkReferenceExpression(left, - Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access, - Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access) - && (!isIdentifier(left) || unescapeLeadingUnderscores(left.escapedText) !== "exports")) { - // to avoid cascading errors check assignability only if 'isReference' check succeeded and no errors were reported - checkTypeAssignableToAndOptionallyElaborate(valueType, leftType, left, right); - } + if (valueType.flags & TypeFlags.Nullable) { + if (!strictNullChecks) { + return type; } + const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken; + const facts = doubleEquals ? + assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull : + valueType.flags & TypeFlags.Null ? + assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull : + assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined; + return getTypeWithFacts(type, facts); + } + if (type.flags & TypeFlags.NotUnionOrUnit) { + return type; } - - function isAssignmentDeclaration(kind: AssignmentDeclarationKind) { - switch (kind) { - case AssignmentDeclarationKind.ModuleExports: - return true; - case AssignmentDeclarationKind.ExportsProperty: - case AssignmentDeclarationKind.Property: - case AssignmentDeclarationKind.Prototype: - case AssignmentDeclarationKind.PrototypeProperty: - case AssignmentDeclarationKind.ThisProperty: - const symbol = getSymbolOfNode(left); - const init = getAssignedExpandoInitializer(right); - return init && isObjectLiteralExpression(init) && - symbol && hasEntries(symbol.exports); - default: - return false; - } + if (assumeTrue) { + const filterFn: (t: ts.Type) => boolean = operator === SyntaxKind.EqualsEqualsToken ? + (t => areTypesComparable(t, valueType) || isCoercibleUnderDoubleEquals(t, valueType)) : + t => areTypesComparable(t, valueType); + const narrowedType = filterType(type, filterFn); + return narrowedType.flags & TypeFlags.Never ? type : replacePrimitivesWithLiterals(narrowedType, valueType); } - - /** - * Returns true if an error is reported - */ - function reportOperatorErrorUnless(typesAreCompatible: (left: Type, right: Type) => boolean): boolean { - if (!typesAreCompatible(leftType, rightType)) { - reportOperatorError(typesAreCompatible); - return true; - } - return false; + if (isUnitType(valueType)) { + const regularType = getRegularTypeOfLiteralType(valueType); + return filterType(type, t => getRegularTypeOfLiteralType(t) !== regularType); } - - function reportOperatorError(isRelated?: (left: Type, right: Type) => boolean) { - let wouldWorkWithAwait = false; - const errNode = errorNode || operatorToken; - if (isRelated) { - const awaitedLeftType = getAwaitedType(leftType); - const awaitedRightType = getAwaitedType(rightType); - wouldWorkWithAwait = !(awaitedLeftType === leftType && awaitedRightType === rightType) - && !!(awaitedLeftType && awaitedRightType) - && isRelated(awaitedLeftType, awaitedRightType); + return type; + } + function narrowTypeByTypeof(type: ts.Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): ts.Type { + // We have '==', '!=', '===', or !==' operator with 'typeof xxx' and string literal operands + if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { + assumeTrue = !assumeTrue; + } + const target = getReferenceCandidate(typeOfExpr.expression); + if (!isMatchingReference(reference, target)) { + if (strictNullChecks && optionalChainContainsReference(target, reference) && assumeTrue === (literal.text !== "undefined")) { + return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); } - - let effectiveLeft = leftType; - let effectiveRight = rightType; - if (!wouldWorkWithAwait && isRelated) { - [effectiveLeft, effectiveRight] = getBaseTypesIfUnrelated(leftType, rightType, isRelated); - } - const [leftStr, rightStr] = getTypeNamesForErrorDisplay(effectiveLeft, effectiveRight); - if (!tryGiveBetterPrimaryError(errNode, wouldWorkWithAwait, leftStr, rightStr)) { - errorAndMaybeSuggestAwait( - errNode, - wouldWorkWithAwait, - Diagnostics.Operator_0_cannot_be_applied_to_types_1_and_2, - tokenToString(operatorToken.kind), - leftStr, - rightStr, - ); + // For a reference of the form 'x.y', a 'typeof x === ...' type guard resets the + // narrowed type of 'y' to its declared type. + if (containsMatchingReference(reference, target)) { + return declaredType; } + return type; } - - function tryGiveBetterPrimaryError(errNode: Node, maybeMissingAwait: boolean, leftStr: string, rightStr: string) { - let typeName: string | undefined; - switch (operatorToken.kind) { - case SyntaxKind.EqualsEqualsEqualsToken: - case SyntaxKind.EqualsEqualsToken: - typeName = "false"; - break; - case SyntaxKind.ExclamationEqualsEqualsToken: - case SyntaxKind.ExclamationEqualsToken: - typeName = "true"; + if (type.flags & TypeFlags.Any && literal.text === "function") { + return type; + } + const facts = assumeTrue ? + typeofEQFacts.get(literal.text) || TypeFacts.TypeofEQHostObject : + typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject; + return getTypeWithFacts(assumeTrue ? mapType(type, narrowTypeForTypeof) : type, facts); + function narrowTypeForTypeof(type: ts.Type) { + if (type.flags & TypeFlags.Unknown && literal.text === "object") { + return getUnionType([nonPrimitiveType, nullType]); + } + // We narrow a non-union type to an exact primitive type if the non-union type + // is a supertype of that primitive type. For example, type 'any' can be narrowed + // to one of the primitive types. + const targetType = literal.text === "function" ? globalFunctionType : typeofTypesByName.get(literal.text); + if (targetType) { + if (isTypeSubtypeOf(type, targetType)) { + return type; + } + if (isTypeSubtypeOf(targetType, type)) { + return targetType; + } + if (type.flags & TypeFlags.Instantiable) { + const constraint = getBaseConstraintOfType(type) || anyType; + if (isTypeSubtypeOf(targetType, constraint)) { + return getIntersectionType([type, targetType]); + } + } } - - if (typeName) { - return errorAndMaybeSuggestAwait( - errNode, - maybeMissingAwait, - Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap, - typeName, leftStr, rightStr); + return type; + } + } + function narrowTypeBySwitchOptionalChainContainment(type: ts.Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number, clauseCheck: (type: ts.Type) => boolean) { + const everyClauseChecks = clauseStart !== clauseEnd && every(getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd), clauseCheck); + return everyClauseChecks ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; + } + function narrowTypeBySwitchOnDiscriminant(type: ts.Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { + // We only narrow if all case expressions specify + // values with unit types, except for the case where + // `type` is unknown. In this instance we map object + // types to the nonPrimitive type and narrow with that. + const switchTypes = getSwitchClauseTypes(switchStatement); + if (!switchTypes.length) { + return type; + } + const clauseTypes = switchTypes.slice(clauseStart, clauseEnd); + const hasDefaultClause = clauseStart === clauseEnd || contains(clauseTypes, neverType); + if ((type.flags & TypeFlags.Unknown) && !hasDefaultClause) { + let groundClauseTypes: ts.Type[] | undefined; + for (let i = 0; i < clauseTypes.length; i += 1) { + const t = clauseTypes[i]; + if (t.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) { + if (groundClauseTypes !== undefined) { + groundClauseTypes.push(t); + } + } + else if (t.flags & TypeFlags.Object) { + if (groundClauseTypes === undefined) { + groundClauseTypes = clauseTypes.slice(0, i); + } + groundClauseTypes.push(nonPrimitiveType); + } + else { + return type; + } } - - return undefined; + return getUnionType(groundClauseTypes === undefined ? clauseTypes : groundClauseTypes); } + const discriminantType = getUnionType(clauseTypes); + const caseType = discriminantType.flags & TypeFlags.Never ? neverType : + replacePrimitivesWithLiterals(filterType(type, t => areTypesComparable(discriminantType, t)), discriminantType); + if (!hasDefaultClause) { + return caseType; + } + const defaultType = filterType(type, t => !(isUnitType(t) && contains(switchTypes, getRegularTypeOfLiteralType(t)))); + return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]); } - - function getBaseTypesIfUnrelated(leftType: Type, rightType: Type, isRelated: (left: Type, right: Type) => boolean): [Type, Type] { - let effectiveLeft = leftType; - let effectiveRight = rightType; - const leftBase = getBaseTypeOfLiteralType(leftType); - const rightBase = getBaseTypeOfLiteralType(rightType); - if (!isRelated(leftBase, rightBase)) { - effectiveLeft = leftBase; - effectiveRight = rightBase; + function getImpliedTypeFromTypeofCase(type: ts.Type, text: string) { + switch (text) { + case "function": + return type.flags & TypeFlags.Any ? type : globalFunctionType; + case "object": + return type.flags & TypeFlags.Unknown ? getUnionType([nonPrimitiveType, nullType]) : type; + default: + return typeofTypesByName.get(text) || type; } - return [ effectiveLeft, effectiveRight ]; } - - function checkYieldExpression(node: YieldExpression): Type { - // Grammar checking - if (produceDiagnostics) { - if (!(node.flags & NodeFlags.YieldContext)) { - grammarErrorOnFirstToken(node, Diagnostics.A_yield_expression_is_only_allowed_in_a_generator_body); + function narrowTypeForTypeofSwitch(candidate: ts.Type) { + return (type: ts.Type) => { + if (isTypeSubtypeOf(candidate, type)) { + return candidate; } - - if (isInParameterInitializerBeforeContainingFunction(node)) { - error(node, Diagnostics.yield_expressions_cannot_be_used_in_a_parameter_initializer); + if (type.flags & TypeFlags.Instantiable) { + const constraint = getBaseConstraintOfType(type) || anyType; + if (isTypeSubtypeOf(candidate, constraint)) { + return getIntersectionType([type, candidate]); + } } + return type; + }; + } + function narrowBySwitchOnTypeOf(type: ts.Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): ts.Type { + const switchWitnesses = getSwitchClauseTypeOfWitnesses(switchStatement); + if (!switchWitnesses.length) { + return type; } - - const func = getContainingFunction(node); - if (!func) return anyType; - const functionFlags = getFunctionFlags(func); - - if (!(functionFlags & FunctionFlags.Generator)) { - // If the user's code is syntactically correct, the func should always have a star. After all, we are in a yield context. - return anyType; + // Equal start and end denotes implicit fallthrough; undefined marks explicit default clause + const defaultCaseLocation = findIndex(switchWitnesses, elem => elem === undefined); + const hasDefaultClause = clauseStart === clauseEnd || (defaultCaseLocation >= clauseStart && defaultCaseLocation < clauseEnd); + let clauseWitnesses: string[]; + let switchFacts: TypeFacts; + if (defaultCaseLocation > -1) { + // We no longer need the undefined denoting an + // explicit default case. Remove the undefined and + // fix-up clauseStart and clauseEnd. This means + // that we don't have to worry about undefined + // in the witness array. + const witnesses = switchWitnesses.filter(witness => witness !== undefined); + // The adjusted clause start and end after removing the `default` statement. + const fixedClauseStart = defaultCaseLocation < clauseStart ? clauseStart - 1 : clauseStart; + const fixedClauseEnd = defaultCaseLocation < clauseEnd ? clauseEnd - 1 : clauseEnd; + clauseWitnesses = witnesses.slice(fixedClauseStart, fixedClauseEnd); + switchFacts = getFactsFromTypeofSwitch(fixedClauseStart, fixedClauseEnd, witnesses, hasDefaultClause); } - - const isAsync = (functionFlags & FunctionFlags.Async) !== 0; - if (node.asteriskToken) { - // Async generator functions prior to ESNext require the __await, __asyncDelegator, - // and __asyncValues helpers - if (isAsync && languageVersion < ScriptTarget.ESNext) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncDelegatorIncludes); - } - - // Generator functions prior to ES2015 require the __values helper - if (!isAsync && languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Values); + else { + clauseWitnesses = switchWitnesses.slice(clauseStart, clauseEnd); + switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, switchWitnesses, hasDefaultClause); + } + if (hasDefaultClause) { + return filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts); + } + /* + The implied type is the raw type suggested by a + value being caught in this clause. + + When the clause contains a default case we ignore + the implied type and try to narrow using any facts + we can learn: see `switchFacts`. + + Example: + switch (typeof x) { + case 'number': + case 'string': break; + default: break; + case 'number': + case 'boolean': break + } + + In the first clause (case `number` and `string`) the + implied type is number | string. + + In the default clause we de not compute an implied type. + + In the third clause (case `number` and `boolean`) + the naive implied type is number | boolean, however + we use the type facts to narrow the implied type to + boolean. We know that number cannot be selected + because it is caught in the first clause. + */ + let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => getImpliedTypeFromTypeofCase(type, text))), switchFacts); + if (impliedType.flags & TypeFlags.Union) { + impliedType = getAssignmentReducedType((impliedType as UnionType), getBaseConstraintOrType(type)); + } + return getTypeWithFacts(mapType(type, narrowTypeForTypeofSwitch(impliedType)), switchFacts); + } + function narrowTypeByInstanceof(type: ts.Type, expr: BinaryExpression, assumeTrue: boolean): ts.Type { + const left = getReferenceCandidate(expr.left); + if (!isMatchingReference(reference, left)) { + if (assumeTrue && strictNullChecks && optionalChainContainsReference(left, reference)) { + return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); + } + // For a reference of the form 'x.y', an 'x instanceof T' type guard resets the + // narrowed type of 'y' to its declared type. We do this because preceding 'x.y' + // references might reference a different 'y' property. However, we make an exception + // for property accesses where x is a synthetic 'this' expression, indicating that we + // were called from isPropertyInitializedInConstructor. Without this exception, + // initializations of 'this' properties that occur before a 'this instanceof XXX' + // check would not be considered. + if (containsMatchingReference(reference, left) && !isSyntheticThisPropertyAccess(reference)) { + return declaredType; } + return type; } - - // There is no point in doing an assignability check if the function - // has no explicit return type because the return type is directly computed - // from the yield expressions. - const returnType = getReturnTypeFromAnnotation(func); - const iterationTypes = returnType && getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsync); - const signatureYieldType = iterationTypes && iterationTypes.yieldType || anyType; - const signatureNextType = iterationTypes && iterationTypes.nextType || anyType; - const resolvedSignatureNextType = isAsync ? getAwaitedType(signatureNextType) || anyType : signatureNextType; - const yieldExpressionType = node.expression ? checkExpression(node.expression) : undefinedWideningType; - const yieldedType = getYieldedTypeOfYieldExpression(node, yieldExpressionType, resolvedSignatureNextType, isAsync); - if (returnType && yieldedType) { - checkTypeAssignableToAndOptionallyElaborate(yieldedType, signatureYieldType, node.expression || node, node.expression); + // Check that right operand is a function type with a prototype property + const rightType = getTypeOfExpression(expr.right); + if (!isTypeDerivedFrom(rightType, globalFunctionType)) { + return type; } - - if (node.asteriskToken) { - const use = isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar; - return getIterationTypeOfIterable(use, IterationTypeKind.Return, yieldExpressionType, node.expression) - || anyType; + let targetType: ts.Type | undefined; + const prototypeProperty = getPropertyOfType(rightType, ("prototype" as __String)); + if (prototypeProperty) { + // Target type is type of the prototype property + const prototypePropertyType = getTypeOfSymbol(prototypeProperty); + if (!isTypeAny(prototypePropertyType)) { + targetType = prototypePropertyType; + } + } + // Don't narrow from 'any' if the target type is exactly 'Object' or 'Function' + if (isTypeAny(type) && (targetType === globalObjectType || targetType === globalFunctionType)) { + return type; } - else if (returnType) { - return getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, isAsync) - || anyType; + if (!targetType) { + const constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct); + targetType = constructSignatures.length ? + getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature)))) : + emptyObjectType; } - - return getContextualIterationType(IterationTypeKind.Next, func) || anyType; - } - - function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type { - checkTruthinessExpression(node.condition); - const type1 = checkExpression(node.whenTrue, checkMode); - const type2 = checkExpression(node.whenFalse, checkMode); - return getUnionType([type1, type2], UnionReduction.Subtype); - } - - function checkTemplateExpression(node: TemplateExpression): Type { - // We just want to check each expressions, but we are unconcerned with - // the type of each expression, as any value may be coerced into a string. - // It is worth asking whether this is what we really want though. - // A place where we actually *are* concerned with the expressions' types are - // in tagged templates. - forEach(node.templateSpans, templateSpan => { - if (maybeTypeOfKind(checkExpression(templateSpan.expression), TypeFlags.ESSymbolLike)) { - error(templateSpan.expression, Diagnostics.Implicit_conversion_of_a_symbol_to_a_string_will_fail_at_runtime_Consider_wrapping_this_expression_in_String); - } - }); - - return stringType; + return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom); } - - function getContextNode(node: Expression): Node { - if (node.kind === SyntaxKind.JsxAttributes && !isJsxSelfClosingElement(node.parent)) { - return node.parent.parent; // Needs to be the root JsxElement, so it encompasses the attributes _and_ the children (which are essentially part of the attributes) + function getNarrowedType(type: ts.Type, candidate: ts.Type, assumeTrue: boolean, isRelated: (source: ts.Type, target: ts.Type) => boolean) { + if (!assumeTrue) { + return filterType(type, t => !isRelated(t, candidate)); } - return node; - } - - function checkExpressionWithContextualType(node: Expression, contextualType: Type, inferenceContext: InferenceContext | undefined, checkMode: CheckMode): Type { - const context = getContextNode(node); - const saveContextualType = context.contextualType; - const saveInferenceContext = context.inferenceContext; - context.contextualType = contextualType; - context.inferenceContext = inferenceContext; - const type = checkExpression(node, checkMode | CheckMode.Contextual | (inferenceContext ? CheckMode.Inferential : 0)); - // We strip literal freshness when an appropriate contextual type is present such that contextually typed - // literals always preserve their literal types (otherwise they might widen during type inference). An alternative - // here would be to not mark contextually typed literals as fresh in the first place. - const result = maybeTypeOfKind(type, TypeFlags.Literal) && isLiteralOfContextualType(type, instantiateContextualType(contextualType, node)) ? - getRegularTypeOfLiteralType(type) : type; - context.contextualType = saveContextualType; - context.inferenceContext = saveInferenceContext; - return result; - } - - function checkExpressionCached(node: Expression | QualifiedName, checkMode?: CheckMode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - if (checkMode && checkMode !== CheckMode.Normal) { - return checkExpression(node, checkMode); - } - // When computing a type that we're going to cache, we need to ignore any ongoing control flow - // analysis because variables may have transient types in indeterminable states. Moving flowLoopStart - // to the top of the stack ensures all transient types are computed from a known point. - const saveFlowLoopStart = flowLoopStart; - const saveFlowTypeCache = flowTypeCache; - flowLoopStart = flowLoopCount; - flowTypeCache = undefined; - links.resolvedType = checkExpression(node, checkMode); - flowTypeCache = saveFlowTypeCache; - flowLoopStart = saveFlowLoopStart; - } - return links.resolvedType; - } - - function isTypeAssertion(node: Expression) { - node = skipParentheses(node); - return node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression; + // If the current type is a union type, remove all constituents that couldn't be instances of + // the candidate type. If one or more constituents remain, return a union of those. + if (type.flags & TypeFlags.Union) { + const assignableType = filterType(type, t => isRelated(t, candidate)); + if (!(assignableType.flags & TypeFlags.Never)) { + return assignableType; + } + } + // If the candidate type is a subtype of the target type, narrow to the candidate type. + // Otherwise, if the target type is assignable to the candidate type, keep the target type. + // Otherwise, if the candidate type is assignable to the target type, narrow to the candidate + // type. Otherwise, the types are completely unrelated, so narrow to an intersection of the + // two types. + return isTypeSubtypeOf(candidate, type) ? candidate : + isTypeAssignableTo(type, candidate) ? type : + isTypeAssignableTo(candidate, type) ? candidate : + getIntersectionType([type, candidate]); } - - function checkDeclarationInitializer(declaration: HasExpressionInitializer) { - const initializer = getEffectiveInitializer(declaration)!; - const type = getQuickTypeOfExpression(initializer) || checkExpressionCached(initializer); - const padded = isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern && - isTupleType(type) && !type.target.hasRestElement && getTypeReferenceArity(type) < declaration.name.elements.length ? - padTupleType(type, declaration.name) : type; - const widened = getCombinedNodeFlags(declaration) & NodeFlags.Const || - isDeclarationReadonly(declaration) || - isTypeAssertion(initializer) || - isLiteralOfContextualType(padded, getContextualType(initializer)) ? padded : getWidenedLiteralType(padded); - if (isInJSFile(declaration)) { - if (widened.flags & TypeFlags.Nullable) { - reportImplicitAny(declaration, anyType); - return anyType; - } - else if (isEmptyArrayLiteralType(widened)) { - reportImplicitAny(declaration, anyArrayType); - return anyArrayType; + function narrowTypeByCallExpression(type: ts.Type, callExpression: CallExpression, assumeTrue: boolean): ts.Type { + if (hasMatchingArgument(callExpression, reference)) { + const signature = assumeTrue || !isCallChain(callExpression) ? getEffectsSignature(callExpression) : undefined; + const predicate = signature && getTypePredicateOfSignature(signature); + if (predicate && (predicate.kind === TypePredicateKind.This || predicate.kind === TypePredicateKind.Identifier)) { + return narrowTypeByTypePredicate(type, predicate, callExpression, assumeTrue); } } - return widened; + return type; } - - function padTupleType(type: TupleTypeReference, pattern: ArrayBindingPattern) { - const patternElements = pattern.elements; - const arity = getTypeReferenceArity(type); - const elementTypes = arity ? getTypeArguments(type).slice() : []; - for (let i = arity; i < patternElements.length; i++) { - const e = patternElements[i]; - if (i < patternElements.length - 1 || !(e.kind === SyntaxKind.BindingElement && e.dotDotDotToken)) { - elementTypes.push(!isOmittedExpression(e) && hasDefaultValue(e) ? getTypeFromBindingElement(e, /*includePatternInType*/ false, /*reportErrors*/ false) : anyType); - if (!isOmittedExpression(e) && !hasDefaultValue(e)) { - reportImplicitAny(e, anyType); + function narrowTypeByTypePredicate(type: ts.Type, predicate: TypePredicate, callExpression: CallExpression, assumeTrue: boolean): ts.Type { + // Don't narrow from 'any' if the predicate type is exactly 'Object' or 'Function' + if (predicate.type && !(isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType))) { + const predicateArgument = getTypePredicateArgument(predicate, callExpression); + if (predicateArgument) { + if (isMatchingReference(reference, predicateArgument)) { + return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf); + } + if (strictNullChecks && assumeTrue && optionalChainContainsReference(predicateArgument, reference) && + !(getTypeFacts(predicate.type) & TypeFacts.EQUndefined)) { + return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); + } + if (containsMatchingReference(reference, predicateArgument)) { + return declaredType; } } } - return createTupleType(elementTypes, type.target.minLength, /*hasRestElement*/ false, type.target.readonly); + return type; } - - function isLiteralOfContextualType(candidateType: Type, contextualType: Type | undefined): boolean { - if (contextualType) { - if (contextualType.flags & TypeFlags.UnionOrIntersection) { - const types = (contextualType).types; - return some(types, t => isLiteralOfContextualType(candidateType, t)); - } - if (contextualType.flags & TypeFlags.InstantiableNonPrimitive) { - // If the contextual type is a type variable constrained to a primitive type, consider - // this a literal context for literals of that primitive type. For example, given a - // type parameter 'T extends string', infer string literal types for T. - const constraint = getBaseConstraintOfType(contextualType) || unknownType; - return maybeTypeOfKind(constraint, TypeFlags.String) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) || - maybeTypeOfKind(constraint, TypeFlags.Number) && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) || - maybeTypeOfKind(constraint, TypeFlags.BigInt) && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) || - maybeTypeOfKind(constraint, TypeFlags.ESSymbol) && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol) || - isLiteralOfContextualType(candidateType, constraint); - } - // If the contextual type is a literal of a particular primitive type, we consider this a - // literal context for all literals of that primitive type. - return !!(contextualType.flags & (TypeFlags.StringLiteral | TypeFlags.Index) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) || - contextualType.flags & TypeFlags.NumberLiteral && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) || - contextualType.flags & TypeFlags.BigIntLiteral && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) || - contextualType.flags & TypeFlags.BooleanLiteral && maybeTypeOfKind(candidateType, TypeFlags.BooleanLiteral) || - contextualType.flags & TypeFlags.UniqueESSymbol && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol)); + // Narrow the given type based on the given expression having the assumed boolean value. The returned type + // will be a subtype or the same type as the argument. + function narrowType(type: ts.Type, expr: Expression, assumeTrue: boolean): ts.Type { + // for `a?.b`, we emulate a synthetic `a !== null && a !== undefined` condition for `a` + if (isExpressionOfOptionalChainRoot(expr) || + isBinaryExpression(expr.parent) && expr.parent.operatorToken.kind === SyntaxKind.QuestionQuestionToken && expr.parent.left === expr) { + return narrowTypeByOptionality(type, expr, assumeTrue); } - return false; - } - - function isConstContext(node: Expression): boolean { - const parent = node.parent; - return isAssertionExpression(parent) && isConstTypeReference(parent.type) || - (isParenthesizedExpression(parent) || isArrayLiteralExpression(parent) || isSpreadElement(parent)) && isConstContext(parent) || - (isPropertyAssignment(parent) || isShorthandPropertyAssignment(parent)) && isConstContext(parent.parent); - } - - function checkExpressionForMutableLocation(node: Expression, checkMode: CheckMode | undefined, contextualType?: Type, forceTuple?: boolean): Type { - const type = checkExpression(node, checkMode, forceTuple); - return isConstContext(node) ? getRegularTypeOfLiteralType(type) : - isTypeAssertion(node) ? type : - getWidenedLiteralLikeTypeForContextualType(type, instantiateContextualType(arguments.length === 2 ? getContextualType(node) : contextualType, node)); - } - - function checkPropertyAssignment(node: PropertyAssignment, checkMode?: CheckMode): Type { - // Do not use hasDynamicName here, because that returns false for well known symbols. - // We want to perform checkComputedPropertyName for all computed properties, including - // well known symbols. - if (node.name.kind === SyntaxKind.ComputedPropertyName) { - checkComputedPropertyName(node.name); + switch (expr.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.ThisKeyword: + case SyntaxKind.SuperKeyword: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return narrowTypeByTruthiness(type, expr, assumeTrue); + case SyntaxKind.CallExpression: + return narrowTypeByCallExpression(type, (expr), assumeTrue); + case SyntaxKind.ParenthesizedExpression: + return narrowType(type, (expr).expression, assumeTrue); + case SyntaxKind.BinaryExpression: + return narrowTypeByBinaryExpression(type, (expr), assumeTrue); + case SyntaxKind.PrefixUnaryExpression: + if ((expr).operator === SyntaxKind.ExclamationToken) { + return narrowType(type, (expr).operand, !assumeTrue); + } + break; } - - return checkExpressionForMutableLocation(node.initializer, checkMode); + return type; } - - function checkObjectLiteralMethod(node: MethodDeclaration, checkMode?: CheckMode): Type { - // Grammar checking - checkGrammarMethod(node); - - // Do not use hasDynamicName here, because that returns false for well known symbols. - // We want to perform checkComputedPropertyName for all computed properties, including - // well known symbols. - if (node.name.kind === SyntaxKind.ComputedPropertyName) { - checkComputedPropertyName(node.name); + function narrowTypeByOptionality(type: ts.Type, expr: Expression, assumePresent: boolean): ts.Type { + if (isMatchingReference(reference, expr)) { + return getTypeWithFacts(type, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull); } - - const uninstantiatedType = checkFunctionExpressionOrObjectLiteralMethod(node, checkMode); - return instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode); - } - - function instantiateTypeWithSingleGenericCallSignature(node: Expression | MethodDeclaration | QualifiedName, type: Type, checkMode?: CheckMode) { - if (checkMode && checkMode & (CheckMode.Inferential | CheckMode.SkipGenericFunctions)) { - const callSignature = getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ true); - const constructSignature = getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ true); - const signature = callSignature || constructSignature; - if (signature && signature.typeParameters) { - const contextualType = getApparentTypeOfContextualType(node, ContextFlags.NoConstraints); - if (contextualType) { - const contextualSignature = getSingleSignature(getNonNullableType(contextualType), callSignature ? SignatureKind.Call : SignatureKind.Construct, /*allowMembers*/ false); - if (contextualSignature && !contextualSignature.typeParameters) { - if (checkMode & CheckMode.SkipGenericFunctions) { - skippedGenericFunction(node, checkMode); - return anyFunctionType; - } - const context = getInferenceContext(node)!; - // We have an expression that is an argument of a generic function for which we are performing - // type argument inference. The expression is of a function type with a single generic call - // signature and a contextual function type with a single non-generic call signature. Now check - // if the outer function returns a function type with a single non-generic call signature and - // if some of the outer function type parameters have no inferences so far. If so, we can - // potentially add inferred type parameters to the outer function return type. - const returnType = context.signature && getReturnTypeOfSignature(context.signature); - const returnSignature = returnType && getSingleCallOrConstructSignature(returnType); - if (returnSignature && !returnSignature.typeParameters && !every(context.inferences, hasInferenceCandidates)) { - // Instantiate the signature with its own type parameters as type arguments, possibly - // renaming the type parameters to ensure they have unique names. - const uniqueTypeParameters = getUniqueTypeParameters(context, signature.typeParameters); - const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, uniqueTypeParameters); - // Infer from the parameters of the instantiated signature to the parameters of the - // contextual signature starting with an empty set of inference candidates. - const inferences = map(context.inferences, info => createInferenceInfo(info.typeParameter)); - applyToParameterTypes(instantiatedSignature, contextualSignature, (source, target) => { - inferTypes(inferences, source, target, /*priority*/ 0, /*contravariant*/ true); - }); - if (some(inferences, hasInferenceCandidates)) { - // We have inference candidates, indicating that one or more type parameters are referenced - // in the parameter types of the contextual signature. Now also infer from the return type. - applyToReturnTypes(instantiatedSignature, contextualSignature, (source, target) => { - inferTypes(inferences, source, target); - }); - // If the type parameters for which we produced candidates do not have any inferences yet, - // we adopt the new inference candidates and add the type parameters of the expression type - // to the set of inferred type parameters for the outer function return type. - if (!hasOverlappingInferences(context.inferences, inferences)) { - mergeInferences(context.inferences, inferences); - context.inferredTypeParameters = concatenate(context.inferredTypeParameters, uniqueTypeParameters); - return getOrCreateTypeFromSignature(instantiatedSignature); - } - } - } - return getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, context)); - } - } - } + if (isMatchingReferenceDiscriminant(expr, declaredType)) { + return narrowTypeByDiscriminant(type, (expr), t => getTypeWithFacts(t, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull)); + } + if (containsMatchingReferenceDiscriminant(reference, expr)) { + return declaredType; } return type; } - - function skippedGenericFunction(node: Node, checkMode: CheckMode) { - if (checkMode & CheckMode.Inferential) { - // We have skipped a generic function during inferential typing. Obtain the inference context and - // indicate this has occurred such that we know a second pass of inference is be needed. - const context = getInferenceContext(node)!; - context.flags |= InferenceFlags.SkippedGenericFunction; + } + function getTypeOfSymbolAtLocation(symbol: ts.Symbol, location: Node) { + symbol = symbol.exportSymbol || symbol; + // If we have an identifier or a property access at the given location, if the location is + // an dotted name expression, and if the location is not an assignment target, obtain the type + // of the expression (which will reflect control flow analysis). If the expression indeed + // resolved to the given symbol, return the narrowed type. + if (location.kind === SyntaxKind.Identifier) { + if (isRightSideOfQualifiedNameOrPropertyAccess(location)) { + location = location.parent; + } + if (isExpressionNode(location) && !isAssignmentTarget(location)) { + const type = getTypeOfExpression((location)); + if (getExportSymbolOfValueSymbolIfExported(getNodeLinks(location).resolvedSymbol) === symbol) { + return type; + } } } - - function hasInferenceCandidates(info: InferenceInfo) { - return !!(info.candidates || info.contraCandidates); + // The location isn't a reference to the given symbol, meaning we're being asked + // a hypothetical question of what type the symbol would have if there was a reference + // to it at the given location. Since we have no control flow information for the + // hypothetical reference (control flow information is created and attached by the + // binder), we simply return the declared type of the symbol. + return getTypeOfSymbol(symbol); + } + function getControlFlowContainer(node: Node): Node { + return findAncestor(node.parent, node => isFunctionLike(node) && !getImmediatelyInvokedFunctionExpression(node) || + node.kind === SyntaxKind.ModuleBlock || + node.kind === SyntaxKind.SourceFile || + node.kind === SyntaxKind.PropertyDeclaration)!; + } + // Check if a parameter is assigned anywhere within its declaring function. + function isParameterAssigned(symbol: ts.Symbol) { + const func = (getRootDeclaration(symbol.valueDeclaration).parent); + const links = getNodeLinks(func); + if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) { + links.flags |= NodeCheckFlags.AssignmentsMarked; + if (!hasParentWithAssignmentsMarked(func)) { + markParameterAssignments(func); + } } - - function hasOverlappingInferences(a: InferenceInfo[], b: InferenceInfo[]) { - for (let i = 0; i < a.length; i++) { - if (hasInferenceCandidates(a[i]) && hasInferenceCandidates(b[i])) { - return true; + return symbol.isAssigned || false; + } + function hasParentWithAssignmentsMarked(node: Node) { + return !!findAncestor(node.parent, node => isFunctionLike(node) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked)); + } + function markParameterAssignments(node: Node) { + if (node.kind === SyntaxKind.Identifier) { + if (isAssignmentTarget(node)) { + const symbol = getResolvedSymbol((node)); + if (symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration).kind === SyntaxKind.Parameter) { + symbol.isAssigned = true; } } - return false; } - - function mergeInferences(target: InferenceInfo[], source: InferenceInfo[]) { - for (let i = 0; i < target.length; i++) { - if (!hasInferenceCandidates(target[i]) && hasInferenceCandidates(source[i])) { - target[i] = source[i]; + else { + forEachChild(node, markParameterAssignments); + } + } + function isConstVariable(symbol: ts.Symbol) { + return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0 && getTypeOfSymbol(symbol) !== autoArrayType; + } + /** remove undefined from the annotated type of a parameter when there is an initializer (that doesn't include undefined) */ + function removeOptionalityFromDeclaredType(declaredType: ts.Type, declaration: VariableLikeDeclaration): ts.Type { + const annotationIncludesUndefined = strictNullChecks && + declaration.kind === SyntaxKind.Parameter && + declaration.initializer && + getFalsyFlags(declaredType) & TypeFlags.Undefined && + !(getFalsyFlags(checkExpression(declaration.initializer)) & TypeFlags.Undefined); + return annotationIncludesUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : declaredType; + } + function isConstraintPosition(node: Node) { + const parent = node.parent; + return parent.kind === SyntaxKind.PropertyAccessExpression || + parent.kind === SyntaxKind.CallExpression && (parent).expression === node || + parent.kind === SyntaxKind.ElementAccessExpression && (parent).expression === node || + parent.kind === SyntaxKind.BindingElement && (parent).name === node && !!(parent).initializer; + } + function typeHasNullableConstraint(type: ts.Type) { + return type.flags & TypeFlags.InstantiableNonPrimitive && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.Nullable); + } + function getConstraintForLocation(type: ts.Type, node: Node): ts.Type; + function getConstraintForLocation(type: ts.Type | undefined, node: Node): ts.Type | undefined; + function getConstraintForLocation(type: ts.Type, node: Node): ts.Type | undefined { + // When a node is the left hand expression of a property access, element access, or call expression, + // and the type of the node includes type variables with constraints that are nullable, we fetch the + // apparent type of the node *before* performing control flow analysis such that narrowings apply to + // the constraint type. + if (type && isConstraintPosition(node) && forEachType(type, typeHasNullableConstraint)) { + return mapType(getWidenedType(type), getBaseConstraintOrType); + } + return type; + } + function isExportOrExportExpression(location: Node) { + return !!findAncestor(location, e => e.parent && isExportAssignment(e.parent) && e.parent.expression === e && isEntityNameExpression(e)); + } + function markAliasReferenced(symbol: ts.Symbol, location: Node) { + if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location) && ((compilerOptions.preserveConstEnums && isExportOrExportExpression(location)) || !isConstEnumOrConstEnumOnlyModule(resolveAlias(symbol)))) { + markAliasSymbolAsReferenced(symbol); + } + } + function checkIdentifier(node: Identifier): ts.Type { + const symbol = getResolvedSymbol(node); + if (symbol === unknownSymbol) { + return errorType; + } + // As noted in ECMAScript 6 language spec, arrow functions never have an arguments objects. + // Although in down-level emit of arrow function, we emit it using function expression which means that + // arguments objects will be bound to the inner object; emitting arrow function natively in ES6, arguments objects + // will be bound to non-arrow function that contain this arrow function. This results in inconsistent behavior. + // To avoid that we will give an error to users if they use arguments objects in arrow function so that they + // can explicitly bound arguments objects + if (symbol === argumentsSymbol) { + const container = (getContainingFunction(node)!); + if (languageVersion < ScriptTarget.ES2015) { + if (container.kind === SyntaxKind.ArrowFunction) { + error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_arrow_function_in_ES3_and_ES5_Consider_using_a_standard_function_expression); + } + else if (hasModifier(container, ModifierFlags.Async)) { + error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_async_function_or_method_in_ES3_and_ES5_Consider_using_a_standard_function_or_method); } } + getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments; + return getTypeOfSymbol(symbol); } - - function getUniqueTypeParameters(context: InferenceContext, typeParameters: readonly TypeParameter[]): readonly TypeParameter[] { - const result: TypeParameter[] = []; - let oldTypeParameters: TypeParameter[] | undefined; - let newTypeParameters: TypeParameter[] | undefined; - for (const tp of typeParameters) { - const name = tp.symbol.escapedName; - if (hasTypeParameterByName(context.inferredTypeParameters, name) || hasTypeParameterByName(result, name)) { - const newName = getUniqueTypeParameterName(concatenate(context.inferredTypeParameters, result), name); - const symbol = createSymbol(SymbolFlags.TypeParameter, newName); - const newTypeParameter = createTypeParameter(symbol); - newTypeParameter.target = tp; - oldTypeParameters = append(oldTypeParameters, tp); - newTypeParameters = append(newTypeParameters, newTypeParameter); - result.push(newTypeParameter); - } - else { - result.push(tp); + // We should only mark aliases as referenced if there isn't a local value declaration + // for the symbol. Also, don't mark any property access expression LHS - checkPropertyAccessExpression will handle that + if (!(node.parent && isPropertyAccessExpression(node.parent) && node.parent.expression === node)) { + markAliasReferenced(symbol, node); + } + const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); + let declaration: Declaration | undefined = localOrExportSymbol.valueDeclaration; + if (localOrExportSymbol.flags & SymbolFlags.Class) { + // Due to the emit for class decorators, any reference to the class from inside of the class body + // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind + // behavior of class names in ES6. + if (declaration.kind === SyntaxKind.ClassDeclaration + && nodeIsDecorated((declaration as ClassDeclaration))) { + let container = getContainingClass(node); + while (container !== undefined) { + if (container === declaration && container.name !== node) { + getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference; + getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass; + break; + } + container = getContainingClass(container); } } - if (newTypeParameters) { - const mapper = createTypeMapper(oldTypeParameters!, newTypeParameters); - for (const tp of newTypeParameters) { - tp.mapper = mapper; + else if (declaration.kind === SyntaxKind.ClassExpression) { + // When we emit a class expression with static members that contain a reference + // to the constructor in the initializer, we will need to substitute that + // binding with an alias as the class name is not in scope. + let container = getThisContainer(node, /*includeArrowFunctions*/ false); + while (container.kind !== SyntaxKind.SourceFile) { + if (container.parent === declaration) { + if (container.kind === SyntaxKind.PropertyDeclaration && hasModifier(container, ModifierFlags.Static)) { + getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference; + getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass; + } + break; + } + container = getThisContainer(container, /*includeArrowFunctions*/ false); } } - return result; - } - - function hasTypeParameterByName(typeParameters: readonly TypeParameter[] | undefined, name: __String) { - return some(typeParameters, tp => tp.symbol.escapedName === name); } - - function getUniqueTypeParameterName(typeParameters: readonly TypeParameter[], baseName: __String) { - let len = (baseName).length; - while (len > 1 && (baseName).charCodeAt(len - 1) >= CharacterCodes._0 && (baseName).charCodeAt(len - 1) <= CharacterCodes._9) len--; - const s = (baseName).slice(0, len); - for (let index = 1; true; index++) { - const augmentedName = <__String>(s + index); - if (!hasTypeParameterByName(typeParameters, augmentedName)) { - return augmentedName; + checkNestedBlockScopedBinding(node, symbol); + const type = getConstraintForLocation(getTypeOfSymbol(localOrExportSymbol), node); + const assignmentKind = getAssignmentTargetKind(node); + if (assignmentKind) { + if (!(localOrExportSymbol.flags & SymbolFlags.Variable) && + !(isInJSFile(node) && localOrExportSymbol.flags & SymbolFlags.ValueModule)) { + error(node, Diagnostics.Cannot_assign_to_0_because_it_is_not_a_variable, symbolToString(symbol)); + return errorType; + } + if (isReadonlySymbol(localOrExportSymbol)) { + if (localOrExportSymbol.flags & SymbolFlags.Variable) { + error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_constant, symbolToString(symbol)); } + else { + error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(symbol)); + } + return errorType; } } - - function getReturnTypeOfSingleNonGenericCallSignature(funcType: Type) { - const signature = getSingleCallSignature(funcType); - if (signature && !signature.typeParameters) { - return getReturnTypeOfSignature(signature); + const isAlias = localOrExportSymbol.flags & SymbolFlags.Alias; + // We only narrow variables and parameters occurring in a non-assignment position. For all other + // entities we simply return the declared type. + if (localOrExportSymbol.flags & SymbolFlags.Variable) { + if (assignmentKind === AssignmentKind.Definite) { + return type; } } - - function getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr: CallChain) { - const funcType = checkExpression(expr.expression); - const nonOptionalType = getOptionalExpressionType(funcType, expr.expression); - const returnType = getReturnTypeOfSingleNonGenericCallSignature(funcType); - return returnType && propagateOptionalTypeMarker(returnType, expr, nonOptionalType !== funcType); + else if (isAlias) { + declaration = find(symbol.declarations, isSomeImportDeclaration); } - - /** - * Returns the type of an expression. Unlike checkExpression, this function is simply concerned - * with computing the type and may not fully check all contained sub-expressions for errors. - */ - function getTypeOfExpression(node: Expression) { - // Don't bother caching types that require no flow analysis and are quick to compute. - const quickType = getQuickTypeOfExpression(node); - if (quickType) { - return quickType; - } - // If a type has been cached for the node, return it. - if (node.flags & NodeFlags.TypeCached && flowTypeCache) { - const cachedType = flowTypeCache[getNodeId(node)]; - if (cachedType) { - return cachedType; - } - } - const startInvocationCount = flowInvocationCount; - const type = checkExpression(node); - // If control flow analysis was required to determine the type, it is worth caching. - if (flowInvocationCount !== startInvocationCount) { - const cache = flowTypeCache || (flowTypeCache = []); - cache[getNodeId(node)] = type; - node.flags |= NodeFlags.TypeCached; - } + else { return type; } - - function getQuickTypeOfExpression(node: Expression) { - const expr = skipParentheses(node); - // Optimize for the common case of a call to a function with a single non-generic call - // signature where we can just fetch the return type without checking the arguments. - if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) { - const type = isCallChain(expr) ? getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr) : - getReturnTypeOfSingleNonGenericCallSignature(checkNonNullExpression(expr.expression)); - if (type) { - return type; - } - } - else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) { - return getTypeFromTypeNode((expr).type); - } - else if (node.kind === SyntaxKind.NumericLiteral || node.kind === SyntaxKind.StringLiteral || - node.kind === SyntaxKind.TrueKeyword || node.kind === SyntaxKind.FalseKeyword) { - return checkExpression(node); - } - return undefined; + if (!declaration) { + return type; } - - /** - * Returns the type of an expression. Unlike checkExpression, this function is simply concerned - * with computing the type and may not fully check all contained sub-expressions for errors. - * It is intended for uses where you know there is no contextual type, - * and requesting the contextual type might cause a circularity or other bad behaviour. - * It sets the contextual type of the node to any before calling getTypeOfExpression. - */ - function getContextFreeTypeOfExpression(node: Expression) { - const links = getNodeLinks(node); - if (links.contextFreeType) { - return links.contextFreeType; + // The declaration container is the innermost function that encloses the declaration of the variable + // or parameter. The flow container is the innermost function starting with which we analyze the control + // flow graph to determine the control flow based type. + const isParameter = getRootDeclaration(declaration).kind === SyntaxKind.Parameter; + const declarationContainer = getControlFlowContainer(declaration); + let flowContainer = getControlFlowContainer(node); + const isOuterVariable = flowContainer !== declarationContainer; + const isSpreadDestructuringAssignmentTarget = node.parent && node.parent.parent && isSpreadAssignment(node.parent) && isDestructuringAssignmentTarget(node.parent.parent); + const isModuleExports = symbol.flags & SymbolFlags.ModuleExports; + // When the control flow originates in a function expression or arrow function and we are referencing + // a const variable or parameter from an outer function, we extend the origin of the control flow + // analysis to include the immediately enclosing function. + while (flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression || + flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethod(flowContainer)) && + (isConstVariable(localOrExportSymbol) || isParameter && !isParameterAssigned(localOrExportSymbol))) { + flowContainer = getControlFlowContainer(flowContainer); + } + // We only look for uninitialized variables in strict null checking mode, and only when we can analyze + // the entire control flow graph from the variable's declaration (i.e. when the flow container and + // declaration container are the same). + const assumeInitialized = isParameter || isAlias || isOuterVariable || isSpreadDestructuringAssignmentTarget || isModuleExports || isBindingElement(declaration) || + type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void)) !== 0 || + isInTypeQuery(node) || node.parent.kind === SyntaxKind.ExportSpecifier) || + node.parent.kind === SyntaxKind.NonNullExpression || + declaration.kind === SyntaxKind.VariableDeclaration && (declaration).exclamationToken || + declaration.flags & NodeFlags.Ambient; + const initialType = assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, (declaration as VariableLikeDeclaration)) : type) : + type === autoType || type === autoArrayType ? undefinedType : + getOptionalType(type); + const flowType = getFlowTypeOfReference(node, type, initialType, flowContainer, !assumeInitialized); + // A variable is considered uninitialized when it is possible to analyze the entire control flow graph + // from declaration to use, and when the variable's declared type doesn't include undefined but the + // control flow based type does include undefined. + if (!isEvolvingArrayOperationTarget(node) && (type === autoType || type === autoArrayType)) { + if (flowType === autoType || flowType === autoArrayType) { + if (noImplicitAny) { + error(getNameOfDeclaration(declaration), Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined, symbolToString(symbol), typeToString(flowType)); + error(node, Diagnostics.Variable_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); + } + return convertAutoToAny(flowType); } - const saveContextualType = node.contextualType; - node.contextualType = anyType; - const type = links.contextFreeType = checkExpression(node, CheckMode.SkipContextSensitive); - node.contextualType = saveContextualType; - return type; } - - function checkExpression(node: Expression | QualifiedName, checkMode?: CheckMode, forceTuple?: boolean): Type { - const saveCurrentNode = currentNode; - currentNode = node; - instantiationCount = 0; - const uninstantiatedType = checkExpressionWorker(node, checkMode, forceTuple); - const type = instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode); - if (isConstEnumObjectType(type)) { - checkConstEnumAccess(node, type); + else if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) { + const diag = error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol)); + // See GH:32846 - if the user is using a variable whose type is () => T1 | ... | undefined + // they may have meant to specify the type as (() => T1 | ...) | undefined + // This is assumed if: the type is a FunctionType, the return type is a Union, the last constituent of + // the union is `undefined` + if (type.symbol && type.symbol.declarations.length === 1 && isFunctionTypeNode(type.symbol.declarations[0])) { + const funcTypeNode = (type.symbol.declarations[0]); + const returnType = getReturnTypeFromAnnotation(funcTypeNode); + if (returnType && returnType.flags & TypeFlags.Union) { + const unionTypes = (funcTypeNode.type).types; + if (unionTypes && unionTypes[unionTypes.length - 1].kind === SyntaxKind.UndefinedKeyword) { + const parenedFuncType = getMutableClone(funcTypeNode); + // Highlight to the end of the second to last constituent of the union + parenedFuncType.end = unionTypes[unionTypes.length - 2].end; + addRelatedInfo(diag, createDiagnosticForNode(parenedFuncType, Diagnostics.Did_you_mean_to_parenthesize_this_function_type)); + } + } } - currentNode = saveCurrentNode; + // Return the declared type to reduce follow-on errors return type; } - - function checkConstEnumAccess(node: Expression | QualifiedName, type: Type) { - // enum object type for const enums are only permitted in: - // - 'left' in property access - // - 'object' in indexed access - // - target in rhs of import statement - const ok = - (node.parent.kind === SyntaxKind.PropertyAccessExpression && (node.parent).expression === node) || - (node.parent.kind === SyntaxKind.ElementAccessExpression && (node.parent).expression === node) || - ((node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName) && isInRightSideOfImportOrExportAssignment(node) || - (node.parent.kind === SyntaxKind.TypeQuery && (node.parent).exprName === node)) || - (node.parent.kind === SyntaxKind.ExportSpecifier); // We allow reexporting const enums - - if (!ok) { - error(node, Diagnostics.const_enums_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment_or_type_query); - } - - if (compilerOptions.isolatedModules) { - Debug.assert(!!(type.symbol.flags & SymbolFlags.ConstEnum)); - const constEnumDeclaration = type.symbol.valueDeclaration as EnumDeclaration; - if (constEnumDeclaration.flags & NodeFlags.Ambient) { - error(node, Diagnostics.Cannot_access_ambient_const_enums_when_the_isolatedModules_flag_is_provided); + return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; + } + function isInsideFunction(node: Node, threshold: Node): boolean { + return !!findAncestor(node, n => n === threshold ? "quit" : isFunctionLike(n)); + } + function getPartOfForStatementContainingNode(node: Node, container: ForStatement) { + return findAncestor(node, n => n === container ? "quit" : n === container.initializer || n === container.condition || n === container.incrementor || n === container.statement); + } + function checkNestedBlockScopedBinding(node: Identifier, symbol: ts.Symbol): void { + if (languageVersion >= ScriptTarget.ES2015 || + (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.Class)) === 0 || + isSourceFile(symbol.valueDeclaration) || + symbol.valueDeclaration.parent.kind === SyntaxKind.CatchClause) { + return; + } + // 1. walk from the use site up to the declaration and check + // if there is anything function like between declaration and use-site (is binding/class is captured in function). + // 2. walk from the declaration up to the boundary of lexical environment and check + // if there is an iteration statement in between declaration and boundary (is binding/class declared inside iteration statement) + const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration); + const usedInFunction = isInsideFunction(node.parent, container); + let current = container; + let containedInIterationStatement = false; + while (current && !nodeStartsNewLexicalEnvironment(current)) { + if (isIterationStatement(current, /*lookInLabeledStatements*/ false)) { + containedInIterationStatement = true; + break; + } + current = current.parent; + } + if (containedInIterationStatement) { + if (usedInFunction) { + // mark iteration statement as containing block-scoped binding captured in some function + let capturesBlockScopeBindingInLoopBody = true; + if (isForStatement(container) && + getAncestor(symbol.valueDeclaration, SyntaxKind.VariableDeclarationList)!.parent === container) { + const part = getPartOfForStatementContainingNode(node.parent, container); + if (part) { + const links = getNodeLinks(part); + links.flags |= NodeCheckFlags.ContainsCapturedBlockScopeBinding; + const capturedBindings = links.capturedBlockScopeBindings || (links.capturedBlockScopeBindings = []); + pushIfUnique(capturedBindings, symbol); + if (part === container.initializer) { + capturesBlockScopeBindingInLoopBody = false; // Initializer is outside of loop body + } + } } + if (capturesBlockScopeBindingInLoopBody) { + getNodeLinks(current).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; + } + } + // mark variables that are declared in loop initializer and reassigned inside the body of ForStatement. + // if body of ForStatement will be converted to function then we'll need a extra machinery to propagate reassigned values back. + if (container.kind === SyntaxKind.ForStatement && + getAncestor(symbol.valueDeclaration, SyntaxKind.VariableDeclarationList)!.parent === container && + isAssignedInBodyOfForStatement(node, (container))) { + getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.NeedsLoopOutParameter; } + // set 'declared inside loop' bit on the block-scoped binding + getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop; } - - function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type { - const tag = isInJSFile(node) ? getJSDocTypeTag(node) : undefined; - if (tag) { - return checkAssertionWorker(tag, tag.typeExpression.type, node.expression, checkMode); + if (usedInFunction) { + getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.CapturedBlockScopedBinding; + } + } + function isBindingCapturedByNode(node: Node, decl: VariableDeclaration | BindingElement) { + const links = getNodeLinks(node); + return !!links && contains(links.capturedBlockScopeBindings, getSymbolOfNode(decl)); + } + function isAssignedInBodyOfForStatement(node: Identifier, container: ForStatement): boolean { + // skip parenthesized nodes + let current: Node = node; + while (current.parent.kind === SyntaxKind.ParenthesizedExpression) { + current = current.parent; + } + // check if node is used as LHS in some assignment expression + let isAssigned = false; + if (isAssignmentTarget(current)) { + isAssigned = true; + } + else if ((current.parent.kind === SyntaxKind.PrefixUnaryExpression || current.parent.kind === SyntaxKind.PostfixUnaryExpression)) { + const expr = (current.parent); + isAssigned = expr.operator === SyntaxKind.PlusPlusToken || expr.operator === SyntaxKind.MinusMinusToken; + } + if (!isAssigned) { + return false; + } + // at this point we know that node is the target of assignment + // now check that modification happens inside the statement part of the ForStatement + return !!findAncestor(current, n => n === container ? "quit" : n === container.statement); + } + function captureLexicalThis(node: Node, container: Node): void { + getNodeLinks(node).flags |= NodeCheckFlags.LexicalThis; + if (container.kind === SyntaxKind.PropertyDeclaration || container.kind === SyntaxKind.Constructor) { + const classNode = container.parent; + getNodeLinks(classNode).flags |= NodeCheckFlags.CaptureThis; + } + else { + getNodeLinks(container).flags |= NodeCheckFlags.CaptureThis; + } + } + function findFirstSuperCall(n: Node): SuperCall | undefined { + if (isSuperCall(n)) { + return n; + } + else if (isFunctionLike(n)) { + return undefined; + } + return forEachChild(n, findFirstSuperCall); + } + /** + * Return a cached result if super-statement is already found. + * Otherwise, find a super statement in a given constructor function and cache the result in the node-links of the constructor + * + * @param constructor constructor-function to look for super statement + */ + function getSuperCallInConstructor(constructor: ConstructorDeclaration): SuperCall | undefined { + const links = getNodeLinks(constructor); + // Only trying to find super-call if we haven't yet tried to find one. Once we try, we will record the result + if (links.hasSuperCall === undefined) { + links.superCall = findFirstSuperCall(constructor.body!); + links.hasSuperCall = links.superCall ? true : false; + } + return links.superCall!; + } + /** + * Check if the given class-declaration extends null then return true. + * Otherwise, return false + * @param classDecl a class declaration to check if it extends null + */ + function classDeclarationExtendsNull(classDecl: ClassDeclaration): boolean { + const classSymbol = getSymbolOfNode(classDecl); + const classInstanceType = (getDeclaredTypeOfSymbol(classSymbol)); + const baseConstructorType = getBaseConstructorTypeOfClass(classInstanceType); + return baseConstructorType === nullWideningType; + } + function checkThisBeforeSuper(node: Node, container: Node, diagnosticMessage: DiagnosticMessage) { + const containingClassDecl = (container.parent); + const baseTypeNode = getClassExtendsHeritageElement(containingClassDecl); + // If a containing class does not have extends clause or the class extends null + // skip checking whether super statement is called before "this" accessing. + if (baseTypeNode && !classDeclarationExtendsNull(containingClassDecl)) { + const superCall = getSuperCallInConstructor((container)); + // We should give an error in the following cases: + // - No super-call + // - "this" is accessing before super-call. + // i.e super(this) + // this.x; super(); + // We want to make sure that super-call is done before accessing "this" so that + // "this" is not accessed as a parameter of the super-call. + if (!superCall || superCall.end > node.pos) { + // In ES6, super inside constructor of class-declaration has to precede "this" accessing + error(node, diagnosticMessage); } - return checkExpression(node.expression, checkMode); } - - function checkExpressionWorker(node: Expression | QualifiedName, checkMode: CheckMode | undefined, forceTuple?: boolean): Type { - const kind = node.kind; - if (cancellationToken) { - // Only bother checking on a few construct kinds. We don't want to be excessively - // hitting the cancellation token on every node we check. - switch (kind) { - case SyntaxKind.ClassExpression: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - cancellationToken.throwIfCancellationRequested(); + } + function checkThisExpression(node: Node): ts.Type { + // Stop at the first arrow function so that we can + // tell whether 'this' needs to be captured. + let container = getThisContainer(node, /* includeArrowFunctions */ true); + let capturedByArrowFunction = false; + if (container.kind === SyntaxKind.Constructor) { + checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class); + } + // Now skip arrow functions to get the "real" owner of 'this'. + if (container.kind === SyntaxKind.ArrowFunction) { + container = getThisContainer(container, /* includeArrowFunctions */ false); + capturedByArrowFunction = true; + } + switch (container.kind) { + case SyntaxKind.ModuleDeclaration: + error(node, Diagnostics.this_cannot_be_referenced_in_a_module_or_namespace_body); + // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks + break; + case SyntaxKind.EnumDeclaration: + error(node, Diagnostics.this_cannot_be_referenced_in_current_location); + // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks + break; + case SyntaxKind.Constructor: + if (isInConstructorArgumentInitializer(node, container)) { + error(node, Diagnostics.this_cannot_be_referenced_in_constructor_arguments); + // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks } + break; + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + if (hasModifier(container, ModifierFlags.Static)) { + error(node, Diagnostics.this_cannot_be_referenced_in_a_static_property_initializer); + // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks + } + break; + case SyntaxKind.ComputedPropertyName: + error(node, Diagnostics.this_cannot_be_referenced_in_a_computed_property_name); + break; + } + // When targeting es6, mark that we'll need to capture `this` in its lexically bound scope. + if (capturedByArrowFunction && languageVersion < ScriptTarget.ES2015) { + captureLexicalThis(node, container); + } + const type = tryGetThisTypeAt(node, /*includeGlobalThis*/ true, container); + if (noImplicitThis) { + const globalThisType = getTypeOfSymbol(globalThisSymbol); + if (type === globalThisType && capturedByArrowFunction) { + error(node, Diagnostics.The_containing_arrow_function_captures_the_global_value_of_this); } - switch (kind) { - case SyntaxKind.Identifier: - return checkIdentifier(node); - case SyntaxKind.ThisKeyword: - return checkThisExpression(node); - case SyntaxKind.SuperKeyword: - return checkSuperExpression(node); - case SyntaxKind.NullKeyword: - return nullWideningType; - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.StringLiteral: - return getFreshTypeOfLiteralType(getLiteralType((node as StringLiteralLike).text)); - case SyntaxKind.NumericLiteral: - checkGrammarNumericLiteral(node as NumericLiteral); - return getFreshTypeOfLiteralType(getLiteralType(+(node as NumericLiteral).text)); - case SyntaxKind.BigIntLiteral: - checkGrammarBigIntLiteral(node as BigIntLiteral); - return getFreshTypeOfLiteralType(getBigIntLiteralType(node as BigIntLiteral)); - case SyntaxKind.TrueKeyword: - return trueType; - case SyntaxKind.FalseKeyword: - return falseType; - case SyntaxKind.TemplateExpression: - return checkTemplateExpression(node); - case SyntaxKind.RegularExpressionLiteral: - return globalRegExpType; - case SyntaxKind.ArrayLiteralExpression: - return checkArrayLiteral(node, checkMode, forceTuple); - case SyntaxKind.ObjectLiteralExpression: - return checkObjectLiteral(node, checkMode); - case SyntaxKind.PropertyAccessExpression: - return checkPropertyAccessExpression(node); - case SyntaxKind.QualifiedName: - return checkQualifiedName(node); - case SyntaxKind.ElementAccessExpression: - return checkIndexedAccess(node); - case SyntaxKind.CallExpression: - if ((node).expression.kind === SyntaxKind.ImportKeyword) { - return checkImportCallExpression(node); + else if (!type) { + // With noImplicitThis, functions may not reference 'this' if it has type 'any' + const diag = error(node, Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation); + if (!isSourceFile(container)) { + const outsideThis = tryGetThisTypeAt(container); + if (outsideThis && outsideThis !== globalThisType) { + addRelatedInfo(diag, createDiagnosticForNode(container, Diagnostics.An_outer_value_of_this_is_shadowed_by_this_container)); } - // falls through - case SyntaxKind.NewExpression: - return checkCallExpression(node, checkMode); - case SyntaxKind.TaggedTemplateExpression: - return checkTaggedTemplateExpression(node); - case SyntaxKind.ParenthesizedExpression: - return checkParenthesizedExpression(node, checkMode); - case SyntaxKind.ClassExpression: - return checkClassExpression(node); - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return checkFunctionExpressionOrObjectLiteralMethod(node, checkMode); - case SyntaxKind.TypeOfExpression: - return checkTypeOfExpression(node); - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.AsExpression: - return checkAssertion(node); - case SyntaxKind.NonNullExpression: - return checkNonNullAssertion(node); - case SyntaxKind.MetaProperty: - return checkMetaProperty(node); - case SyntaxKind.DeleteExpression: - return checkDeleteExpression(node); - case SyntaxKind.VoidExpression: - return checkVoidExpression(node); - case SyntaxKind.AwaitExpression: - return checkAwaitExpression(node); - case SyntaxKind.PrefixUnaryExpression: - return checkPrefixUnaryExpression(node); - case SyntaxKind.PostfixUnaryExpression: - return checkPostfixUnaryExpression(node); - case SyntaxKind.BinaryExpression: - return checkBinaryExpression(node, checkMode); - case SyntaxKind.ConditionalExpression: - return checkConditionalExpression(node, checkMode); - case SyntaxKind.SpreadElement: - return checkSpreadExpression(node, checkMode); - case SyntaxKind.OmittedExpression: - return undefinedWideningType; - case SyntaxKind.YieldExpression: - return checkYieldExpression(node); - case SyntaxKind.SyntheticExpression: - return (node).type; - case SyntaxKind.JsxExpression: - return checkJsxExpression(node, checkMode); - case SyntaxKind.JsxElement: - return checkJsxElement(node, checkMode); - case SyntaxKind.JsxSelfClosingElement: - return checkJsxSelfClosingElement(node, checkMode); - case SyntaxKind.JsxFragment: - return checkJsxFragment(node); - case SyntaxKind.JsxAttributes: - return checkJsxAttributes(node, checkMode); - case SyntaxKind.JsxOpeningElement: - Debug.fail("Shouldn't ever directly check a JsxOpeningElement"); + } } - return errorType; } - - // DECLARATION AND STATEMENT TYPE CHECKING - - function checkTypeParameter(node: TypeParameterDeclaration) { - // Grammar Checking - if (node.expression) { - grammarErrorOnFirstToken(node.expression, Diagnostics.Type_expected); - } - - checkSourceElement(node.constraint); - checkSourceElement(node.default); - const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node)); - // Resolve base constraint to reveal circularity errors - getBaseConstraintOfType(typeParameter); - if (!hasNonCircularTypeParameterDefault(typeParameter)) { - error(node.default, Diagnostics.Type_parameter_0_has_a_circular_default, typeToString(typeParameter)); + return type || anyType; + } + function tryGetThisTypeAt(node: Node, includeGlobalThis = true, container = getThisContainer(node, /*includeArrowFunctions*/ false)): ts.Type | undefined { + const isInJS = isInJSFile(node); + if (isFunctionLike(container) && + (!isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container))) { + // Note: a parameter initializer should refer to class-this unless function-this is explicitly annotated. + // If this is a function in a JS file, it might be a class method. + const className = getClassNameFromPrototypeMethod(container); + if (isInJS && className) { + const classSymbol = checkExpression(className).symbol; + if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) { + const classType = ((getDeclaredTypeOfSymbol(classSymbol) as InterfaceType).thisType!); + return getFlowTypeOfReference(node, classType); + } } - const constraintType = getConstraintOfTypeParameter(typeParameter); - const defaultType = getDefaultFromTypeParameter(typeParameter); - if (constraintType && defaultType) { - checkTypeAssignableTo(defaultType, getTypeWithThisArgument(instantiateType(constraintType, makeUnaryTypeMapper(typeParameter, defaultType)), defaultType), node.default, Diagnostics.Type_0_does_not_satisfy_the_constraint_1); + // Check if it's a constructor definition, can be either a variable decl or function decl + // i.e. + // * /** @constructor */ function [name]() { ... } + // * /** @constructor */ var x = function() { ... } + else if (isInJS && + (container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.FunctionDeclaration) && + getJSDocClassTag(container)) { + const classType = ((getDeclaredTypeOfSymbol(getMergedSymbol(container.symbol)) as InterfaceType).thisType!); + return getFlowTypeOfReference(node, classType); } - if (produceDiagnostics) { - checkTypeNameIsReserved(node.name, Diagnostics.Type_parameter_name_cannot_be_0); + const thisType = getThisTypeOfDeclaration(container) || getContextualThisParameterType(container); + if (thisType) { + return getFlowTypeOfReference(node, thisType); } } - - function checkParameter(node: ParameterDeclaration) { - // Grammar checking - // It is a SyntaxError if the Identifier "eval" or the Identifier "arguments" occurs as the - // Identifier in a PropertySetParameterList of a PropertyAssignment that is contained in strict code - // or if its FunctionBody is strict code(11.1.5). - checkGrammarDecoratorsAndModifiers(node); - - checkVariableLikeDeclaration(node); - const func = getContainingFunction(node)!; - if (hasModifier(node, ModifierFlags.ParameterPropertyModifier)) { - if (!(func.kind === SyntaxKind.Constructor && nodeIsPresent(func.body))) { - error(node, Diagnostics.A_parameter_property_is_only_allowed_in_a_constructor_implementation); - } - } - if (node.questionToken && isBindingPattern(node.name) && (func as FunctionLikeDeclaration).body) { - error(node, Diagnostics.A_binding_pattern_parameter_cannot_be_optional_in_an_implementation_signature); + if (isClassLike(container.parent)) { + const symbol = getSymbolOfNode(container.parent); + const type = hasModifier(container, ModifierFlags.Static) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol) as InterfaceType).thisType!; + return getFlowTypeOfReference(node, type); + } + if (isInJS) { + const type = getTypeForThisExpressionFromJSDoc(container); + if (type && type !== errorType) { + return getFlowTypeOfReference(node, type); } - if (node.name && isIdentifier(node.name) && (node.name.escapedText === "this" || node.name.escapedText === "new")) { - if (func.parameters.indexOf(node) !== 0) { - error(node, Diagnostics.A_0_parameter_must_be_the_first_parameter, node.name.escapedText as string); - } - if (func.kind === SyntaxKind.Constructor || func.kind === SyntaxKind.ConstructSignature || func.kind === SyntaxKind.ConstructorType) { - error(node, Diagnostics.A_constructor_cannot_have_a_this_parameter); - } - if (func.kind === SyntaxKind.ArrowFunction) { - error(node, Diagnostics.An_arrow_function_cannot_have_a_this_parameter); - } + } + if (isSourceFile(container)) { + // look up in the source file's locals or exports + if (container.commonJsModuleIndicator) { + const fileSymbol = getSymbolOfNode(container); + return fileSymbol && getTypeOfSymbol(fileSymbol); } - - // Only check rest parameter type if it's not a binding pattern. Since binding patterns are - // not allowed in a rest parameter, we already have an error from checkGrammarParameterList. - if (node.dotDotDotToken && !isBindingPattern(node.name) && !isTypeAssignableTo(getTypeOfSymbol(node.symbol), anyReadonlyArrayType)) { - error(node, Diagnostics.A_rest_parameter_must_be_of_an_array_type); + else if (includeGlobalThis) { + return getTypeOfSymbol(globalThisSymbol); } } - - function checkTypePredicate(node: TypePredicateNode): void { - const parent = getTypePredicateParent(node); - if (!parent) { - // The parent must not be valid. - error(node, Diagnostics.A_type_predicate_is_only_allowed_in_return_type_position_for_functions_and_methods); - return; + } + function getExplicitThisType(node: Expression) { + const container = getThisContainer(node, /*includeArrowFunctions*/ false); + if (isFunctionLike(container)) { + const signature = getSignatureFromDeclaration(container); + if (signature.thisParameter) { + return getExplicitTypeOfSymbol(signature.thisParameter); } - - const signature = getSignatureFromDeclaration(parent); - const typePredicate = getTypePredicateOfSignature(signature); - if (!typePredicate) { - return; + } + if (isClassLike(container.parent)) { + const symbol = getSymbolOfNode(container.parent); + return hasModifier(container, ModifierFlags.Static) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol) as InterfaceType).thisType!; + } + } + function getClassNameFromPrototypeMethod(container: Node) { + // Check if it's the RHS of a x.prototype.y = function [name]() { .... } + if (container.kind === SyntaxKind.FunctionExpression && + isBinaryExpression(container.parent) && + getAssignmentDeclarationKind(container.parent) === AssignmentDeclarationKind.PrototypeProperty) { + // Get the 'x' of 'x.prototype.y = container' + return ((container.parent // x.prototype.y = container + .left as PropertyAccessExpression) // x.prototype.y + .expression as PropertyAccessExpression) // x.prototype + .expression; // x + } + // x.prototype = { method() { } } + else if (container.kind === SyntaxKind.MethodDeclaration && + container.parent.kind === SyntaxKind.ObjectLiteralExpression && + isBinaryExpression(container.parent.parent) && + getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.Prototype) { + return (container.parent.parent.left as PropertyAccessExpression).expression; + } + // x.prototype = { method: function() { } } + else if (container.kind === SyntaxKind.FunctionExpression && + container.parent.kind === SyntaxKind.PropertyAssignment && + container.parent.parent.kind === SyntaxKind.ObjectLiteralExpression && + isBinaryExpression(container.parent.parent.parent) && + getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.Prototype) { + return (container.parent.parent.parent.left as PropertyAccessExpression).expression; + } + // Object.defineProperty(x, "method", { value: function() { } }); + // Object.defineProperty(x, "method", { set: (x: () => void) => void }); + // Object.defineProperty(x, "method", { get: () => function() { }) }); + else if (container.kind === SyntaxKind.FunctionExpression && + isPropertyAssignment(container.parent) && + isIdentifier(container.parent.name) && + (container.parent.name.escapedText === "value" || container.parent.name.escapedText === "get" || container.parent.name.escapedText === "set") && + isObjectLiteralExpression(container.parent.parent) && + isCallExpression(container.parent.parent.parent) && + container.parent.parent.parent.arguments[2] === container.parent.parent && + getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty) { + return (container.parent.parent.parent.arguments[0] as PropertyAccessExpression).expression; + } + // Object.defineProperty(x, "method", { value() { } }); + // Object.defineProperty(x, "method", { set(x: () => void) {} }); + // Object.defineProperty(x, "method", { get() { return () => {} } }); + else if (isMethodDeclaration(container) && + isIdentifier(container.name) && + (container.name.escapedText === "value" || container.name.escapedText === "get" || container.name.escapedText === "set") && + isObjectLiteralExpression(container.parent) && + isCallExpression(container.parent.parent) && + container.parent.parent.arguments[2] === container.parent && + getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty) { + return (container.parent.parent.arguments[0] as PropertyAccessExpression).expression; + } + } + function getTypeForThisExpressionFromJSDoc(node: Node) { + const jsdocType = getJSDocType(node); + if (jsdocType && jsdocType.kind === SyntaxKind.JSDocFunctionType) { + const jsDocFunctionType = (jsdocType); + if (jsDocFunctionType.parameters.length > 0 && + jsDocFunctionType.parameters[0].name && + (jsDocFunctionType.parameters[0].name as Identifier).escapedText === InternalSymbolName.This) { + return getTypeFromTypeNode(jsDocFunctionType.parameters[0].type!); } - - checkSourceElement(node.type); - - const { parameterName } = node; - if (typePredicate.kind === TypePredicateKind.This || typePredicate.kind === TypePredicateKind.AssertsThis) { - getTypeFromThisTypeNode(parameterName as ThisTypeNode); + } + const thisTag = getJSDocThisTag(node); + if (thisTag && thisTag.typeExpression) { + return getTypeFromTypeNode(thisTag.typeExpression); + } + } + function isInConstructorArgumentInitializer(node: Node, constructorDecl: Node): boolean { + return !!findAncestor(node, n => isFunctionLikeDeclaration(n) ? "quit" : n.kind === SyntaxKind.Parameter && n.parent === constructorDecl); + } + function checkSuperExpression(node: Node): ts.Type { + const isCallExpression = node.parent.kind === SyntaxKind.CallExpression && (node.parent).expression === node; + let container = getSuperContainer(node, /*stopOnFunctions*/ true); + let needToCaptureLexicalThis = false; + // adjust the container reference in case if super is used inside arrow functions with arbitrarily deep nesting + if (!isCallExpression) { + while (container && container.kind === SyntaxKind.ArrowFunction) { + container = getSuperContainer(container, /*stopOnFunctions*/ true); + needToCaptureLexicalThis = languageVersion < ScriptTarget.ES2015; + } + } + const canUseSuperExpression = isLegalUsageOfSuperExpression(container); + let nodeCheckFlag: NodeCheckFlags = 0; + if (!canUseSuperExpression) { + // issue more specific error if super is used in computed property name + // class A { foo() { return "1" }} + // class B { + // [super.foo()]() {} + // } + const current = findAncestor(node, n => n === container ? "quit" : n.kind === SyntaxKind.ComputedPropertyName); + if (current && current.kind === SyntaxKind.ComputedPropertyName) { + error(node, Diagnostics.super_cannot_be_referenced_in_a_computed_property_name); + } + else if (isCallExpression) { + error(node, Diagnostics.Super_calls_are_not_permitted_outside_constructors_or_in_nested_functions_inside_constructors); + } + else if (!container || !container.parent || !(isClassLike(container.parent) || container.parent.kind === SyntaxKind.ObjectLiteralExpression)) { + error(node, Diagnostics.super_can_only_be_referenced_in_members_of_derived_classes_or_object_literal_expressions); } else { - if (typePredicate.parameterIndex >= 0) { - if (signatureHasRestParameter(signature) && typePredicate.parameterIndex === signature.parameters.length - 1) { - error(parameterName, Diagnostics.A_type_predicate_cannot_reference_a_rest_parameter); - } - else { - if (typePredicate.type) { - const leadingError = () => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.A_type_predicate_s_type_must_be_assignable_to_its_parameter_s_type); - checkTypeAssignableTo(typePredicate.type, - getTypeOfSymbol(signature.parameters[typePredicate.parameterIndex]), - node.type, - /*headMessage*/ undefined, - leadingError); - } - } - } - else if (parameterName) { - let hasReportedError = false; - for (const { name } of parent.parameters) { - if (isBindingPattern(name) && - checkIfTypePredicateVariableIsDeclaredInBindingPattern(name, parameterName, typePredicate.parameterName)) { - hasReportedError = true; - break; - } - } - if (!hasReportedError) { - error(node.parameterName, Diagnostics.Cannot_find_parameter_0, typePredicate.parameterName); - } - } + error(node, Diagnostics.super_property_access_is_permitted_only_in_a_constructor_member_function_or_member_accessor_of_a_derived_class); } + return errorType; } - - function getTypePredicateParent(node: Node): SignatureDeclaration | undefined { - switch (node.parent.kind) { - case SyntaxKind.ArrowFunction: - case SyntaxKind.CallSignature: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionType: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - const parent = node.parent; - if (node === parent.type) { - return parent; - } - } + if (!isCallExpression && container.kind === SyntaxKind.Constructor) { + checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_a_property_of_super_in_the_constructor_of_a_derived_class); } - - function checkIfTypePredicateVariableIsDeclaredInBindingPattern( - pattern: BindingPattern, - predicateVariableNode: Node, - predicateVariableName: string) { - for (const element of pattern.elements) { - if (isOmittedExpression(element)) { - continue; - } - - const name = element.name; - if (name.kind === SyntaxKind.Identifier && name.escapedText === predicateVariableName) { - error(predicateVariableNode, - Diagnostics.A_type_predicate_cannot_reference_element_0_in_a_binding_pattern, - predicateVariableName); - return true; - } - else if (name.kind === SyntaxKind.ArrayBindingPattern || name.kind === SyntaxKind.ObjectBindingPattern) { - if (checkIfTypePredicateVariableIsDeclaredInBindingPattern( - name, - predicateVariableNode, - predicateVariableName)) { - return true; - } - } + if (hasModifier(container, ModifierFlags.Static) || isCallExpression) { + nodeCheckFlag = NodeCheckFlags.SuperStatic; + } + else { + nodeCheckFlag = NodeCheckFlags.SuperInstance; + } + getNodeLinks(node).flags |= nodeCheckFlag; + // Due to how we emit async functions, we need to specialize the emit for an async method that contains a `super` reference. + // This is due to the fact that we emit the body of an async function inside of a generator function. As generator + // functions cannot reference `super`, we emit a helper inside of the method body, but outside of the generator. This helper + // uses an arrow function, which is permitted to reference `super`. + // + // There are two primary ways we can access `super` from within an async method. The first is getting the value of a property + // or indexed access on super, either as part of a right-hand-side expression or call expression. The second is when setting the value + // of a property or indexed access, either as part of an assignment expression or destructuring assignment. + // + // The simplest case is reading a value, in which case we will emit something like the following: + // + // // ts + // ... + // async asyncMethod() { + // let x = await super.asyncMethod(); + // return x; + // } + // ... + // + // // js + // ... + // asyncMethod() { + // const _super = Object.create(null, { + // asyncMethod: { get: () => super.asyncMethod }, + // }); + // return __awaiter(this, arguments, Promise, function *() { + // let x = yield _super.asyncMethod.call(this); + // return x; + // }); + // } + // ... + // + // The more complex case is when we wish to assign a value, especially as part of a destructuring assignment. As both cases + // are legal in ES6, but also likely less frequent, we only emit setters if there is an assignment: + // + // // ts + // ... + // async asyncMethod(ar: Promise) { + // [super.a, super.b] = await ar; + // } + // ... + // + // // js + // ... + // asyncMethod(ar) { + // const _super = Object.create(null, { + // a: { get: () => super.a, set: (v) => super.a = v }, + // b: { get: () => super.b, set: (v) => super.b = v } + // }; + // return __awaiter(this, arguments, Promise, function *() { + // [_super.a, _super.b] = yield ar; + // }); + // } + // ... + // + // Creating an object that has getter and setters instead of just an accessor function is required for destructuring assignments + // as a call expression cannot be used as the target of a destructuring assignment while a property access can. + // + // For element access expressions (`super[x]`), we emit a generic helper that forwards the element access in both situations. + if (container.kind === SyntaxKind.MethodDeclaration && hasModifier(container, ModifierFlags.Async)) { + if (isSuperProperty(node.parent) && isAssignmentTarget(node.parent)) { + getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuperBinding; + } + else { + getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuper; } } - - function checkSignatureDeclaration(node: SignatureDeclaration) { - // Grammar checking - if (node.kind === SyntaxKind.IndexSignature) { - checkGrammarIndexSignature(node); + if (needToCaptureLexicalThis) { + // call expressions are allowed only in constructors so they should always capture correct 'this' + // super property access expressions can also appear in arrow functions - + // in this case they should also use correct lexical this + captureLexicalThis(node.parent, container); + } + if (container.parent.kind === SyntaxKind.ObjectLiteralExpression) { + if (languageVersion < ScriptTarget.ES2015) { + error(node, Diagnostics.super_is_only_allowed_in_members_of_object_literal_expressions_when_option_target_is_ES2015_or_higher); + return errorType; } - // TODO (yuisu): Remove this check in else-if when SyntaxKind.Construct is moved and ambient context is handled - else if (node.kind === SyntaxKind.FunctionType || node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.ConstructorType || - node.kind === SyntaxKind.CallSignature || node.kind === SyntaxKind.Constructor || - node.kind === SyntaxKind.ConstructSignature) { - checkGrammarFunctionLikeDeclaration(node); + else { + // for object literal assume that type of 'super' is 'any' + return anyType; } - - const functionFlags = getFunctionFlags(node); - if (!(functionFlags & FunctionFlags.Invalid)) { - // Async generators prior to ESNext require the __await and __asyncGenerator helpers - if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.AsyncGenerator && languageVersion < ScriptTarget.ESNext) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncGeneratorIncludes); - } - - // Async functions prior to ES2017 require the __awaiter helper - if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async && languageVersion < ScriptTarget.ES2017) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Awaiter); - } - - // Generator functions, Async functions, and Async Generator functions prior to - // ES2015 require the __generator helper - if ((functionFlags & FunctionFlags.AsyncGenerator) !== FunctionFlags.Normal && languageVersion < ScriptTarget.ES2015) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Generator); - } + } + // at this point the only legal case for parent is ClassLikeDeclaration + const classLikeDeclaration = (container.parent); + if (!getClassExtendsHeritageElement(classLikeDeclaration)) { + error(node, Diagnostics.super_can_only_be_referenced_in_a_derived_class); + return errorType; + } + const classType = (getDeclaredTypeOfSymbol(getSymbolOfNode(classLikeDeclaration))); + const baseClassType = classType && getBaseTypes(classType)[0]; + if (!baseClassType) { + return errorType; + } + if (container.kind === SyntaxKind.Constructor && isInConstructorArgumentInitializer(node, container)) { + // issue custom error message for super property access in constructor arguments (to be aligned with old compiler) + error(node, Diagnostics.super_cannot_be_referenced_in_constructor_arguments); + return errorType; + } + return nodeCheckFlag === NodeCheckFlags.SuperStatic + ? getBaseConstructorTypeOfClass(classType) + : getTypeWithThisArgument(baseClassType, classType.thisType); + function isLegalUsageOfSuperExpression(container: Node): boolean { + if (!container) { + return false; } - - checkTypeParameters(node.typeParameters); - - forEach(node.parameters, checkParameter); - - // TODO(rbuckton): Should we start checking JSDoc types? - if (node.type) { - checkSourceElement(node.type); + if (isCallExpression) { + // TS 1.0 SPEC (April 2014): 4.8.1 + // Super calls are only permitted in constructors of derived classes + return container.kind === SyntaxKind.Constructor; } - - if (produceDiagnostics) { - checkCollisionWithArgumentsInGeneratedCode(node); - const returnTypeNode = getEffectiveReturnTypeNode(node); - if (noImplicitAny && !returnTypeNode) { - switch (node.kind) { - case SyntaxKind.ConstructSignature: - error(node, Diagnostics.Construct_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type); - break; - case SyntaxKind.CallSignature: - error(node, Diagnostics.Call_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type); - break; - } - } - - if (returnTypeNode) { - const functionFlags = getFunctionFlags(node); - if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Generator)) === FunctionFlags.Generator) { - const returnType = getTypeFromTypeNode(returnTypeNode); - if (returnType === voidType) { - error(returnTypeNode, Diagnostics.A_generator_cannot_have_a_void_type_annotation); - } - else { - // Naively, one could check that Generator is assignable to the return type annotation. - // However, that would not catch the error in the following case. - // - // interface BadGenerator extends Iterable, Iterator { } - // function* g(): BadGenerator { } // Iterable and Iterator have different types! - // - const generatorYieldType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, returnType, (functionFlags & FunctionFlags.Async) !== 0) || anyType; - const generatorReturnType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, (functionFlags & FunctionFlags.Async) !== 0) || generatorYieldType; - const generatorNextType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, (functionFlags & FunctionFlags.Async) !== 0) || unknownType; - const generatorInstantiation = createGeneratorReturnType(generatorYieldType, generatorReturnType, generatorNextType, !!(functionFlags & FunctionFlags.Async)); - checkTypeAssignableTo(generatorInstantiation, returnType, returnTypeNode); - } + else { + // TS 1.0 SPEC (April 2014) + // 'super' property access is allowed + // - In a constructor, instance member function, instance member accessor, or instance member variable initializer where this references a derived class instance + // - In a static member function or static member accessor + // topmost container must be something that is directly nested in the class declaration\object literal expression + if (isClassLike(container.parent) || container.parent.kind === SyntaxKind.ObjectLiteralExpression) { + if (hasModifier(container, ModifierFlags.Static)) { + return container.kind === SyntaxKind.MethodDeclaration || + container.kind === SyntaxKind.MethodSignature || + container.kind === SyntaxKind.GetAccessor || + container.kind === SyntaxKind.SetAccessor; } - else if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { - checkAsyncFunctionReturnType(node, returnTypeNode); + else { + return container.kind === SyntaxKind.MethodDeclaration || + container.kind === SyntaxKind.MethodSignature || + container.kind === SyntaxKind.GetAccessor || + container.kind === SyntaxKind.SetAccessor || + container.kind === SyntaxKind.PropertyDeclaration || + container.kind === SyntaxKind.PropertySignature || + container.kind === SyntaxKind.Constructor; } } - if (node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.JSDocFunctionType) { - registerForUnusedIdentifiersCheck(node); - } } + return false; } - - function checkClassForDuplicateDeclarations(node: ClassLikeDeclaration) { - const instanceNames = createUnderscoreEscapedMap(); - const staticNames = createUnderscoreEscapedMap(); - for (const member of node.members) { - if (member.kind === SyntaxKind.Constructor) { - for (const param of (member as ConstructorDeclaration).parameters) { - if (isParameterPropertyDeclaration(param, member) && !isBindingPattern(param.name)) { - addName(instanceNames, param.name, param.name.escapedText, DeclarationMeaning.GetOrSetAccessor); - } + } + function getContainingObjectLiteral(func: SignatureDeclaration): ObjectLiteralExpression | undefined { + return (func.kind === SyntaxKind.MethodDeclaration || + func.kind === SyntaxKind.GetAccessor || + func.kind === SyntaxKind.SetAccessor) && func.parent.kind === SyntaxKind.ObjectLiteralExpression ? func.parent : + func.kind === SyntaxKind.FunctionExpression && func.parent.kind === SyntaxKind.PropertyAssignment ? func.parent.parent : + undefined; + } + function getThisTypeArgument(type: ts.Type): ts.Type | undefined { + return getObjectFlags(type) & ObjectFlags.Reference && (type).target === globalThisType ? getTypeArguments((type))[0] : undefined; + } + function getThisTypeFromContextualType(type: ts.Type): ts.Type | undefined { + return mapType(type, t => { + return t.flags & TypeFlags.Intersection ? forEach((t).types, getThisTypeArgument) : getThisTypeArgument(t); + }); + } + function getContextualThisParameterType(func: SignatureDeclaration): ts.Type | undefined { + if (func.kind === SyntaxKind.ArrowFunction) { + return undefined; + } + if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) { + const contextualSignature = getContextualSignature(func); + if (contextualSignature) { + const thisParameter = contextualSignature.thisParameter; + if (thisParameter) { + return getTypeOfSymbol(thisParameter); + } + } + } + const inJs = isInJSFile(func); + if (noImplicitThis || inJs) { + const containingLiteral = getContainingObjectLiteral(func); + if (containingLiteral) { + // We have an object literal method. Check if the containing object literal has a contextual type + // that includes a ThisType. If so, T is the contextual type for 'this'. We continue looking in + // any directly enclosing object literals. + const contextualType = getApparentTypeOfContextualType(containingLiteral); + let literal = containingLiteral; + let type = contextualType; + while (type) { + const thisType = getThisTypeFromContextualType(type); + if (thisType) { + return instantiateType(thisType, getMapperFromContext(getInferenceContext(containingLiteral))); + } + if (literal.parent.kind !== SyntaxKind.PropertyAssignment) { + break; } - } - else { - const isStatic = hasModifier(member, ModifierFlags.Static); - const names = isStatic ? staticNames : instanceNames; - - const name = member.name; - const memberName = name && getPropertyNameForPropertyNameNode(name); - if (name && memberName) { - switch (member.kind) { - case SyntaxKind.GetAccessor: - addName(names, name, memberName, DeclarationMeaning.GetAccessor); - break; - - case SyntaxKind.SetAccessor: - addName(names, name, memberName, DeclarationMeaning.SetAccessor); - break; - - case SyntaxKind.PropertyDeclaration: - addName(names, name, memberName, DeclarationMeaning.GetOrSetAccessor); - break; - - case SyntaxKind.MethodDeclaration: - addName(names, name, memberName, DeclarationMeaning.Method); - break; + literal = (literal.parent.parent); + type = getApparentTypeOfContextualType(literal); + } + // There was no contextual ThisType for the containing object literal, so the contextual type + // for 'this' is the non-null form of the contextual type for the containing object literal or + // the type of the object literal itself. + return getWidenedType(contextualType ? getNonNullableType(contextualType) : checkExpressionCached(containingLiteral)); + } + // In an assignment of the form 'obj.xxx = function(...)' or 'obj[xxx] = function(...)', the + // contextual type for 'this' is 'obj'. + const parent = func.parent; + if (parent.kind === SyntaxKind.BinaryExpression && (parent).operatorToken.kind === SyntaxKind.EqualsToken) { + const target = (parent).left; + if (target.kind === SyntaxKind.PropertyAccessExpression || target.kind === SyntaxKind.ElementAccessExpression) { + const { expression } = (target as AccessExpression); + // Don't contextually type `this` as `exports` in `exports.Point = function(x, y) { this.x = x; this.y = y; }` + if (inJs && isIdentifier(expression)) { + const sourceFile = getSourceFileOfNode(parent); + if (sourceFile.commonJsModuleIndicator && getResolvedSymbol(expression) === sourceFile.symbol) { + return undefined; } } + return getWidenedType(checkExpressionCached(expression)); } } - - function addName(names: UnderscoreEscapedMap, location: Node, name: __String, meaning: DeclarationMeaning) { - const prev = names.get(name); - if (prev) { - if (prev & DeclarationMeaning.Method) { - if (meaning !== DeclarationMeaning.Method) { - error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); - } - } - else if (prev & meaning) { - error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); - } - else { - names.set(name, prev | meaning); - } - } - else { - names.set(name, meaning); - } + } + return undefined; + } + // Return contextual type of parameter or undefined if no contextual type is available + function getContextuallyTypedParameterType(parameter: ParameterDeclaration, forCache: boolean): ts.Type | undefined { + const func = parameter.parent; + if (!isContextSensitiveFunctionOrObjectLiteralMethod(func)) { + return undefined; + } + const iife = getImmediatelyInvokedFunctionExpression(func); + if (iife && iife.arguments) { + const args = getEffectiveCallArguments(iife); + const indexOfParameter = func.parameters.indexOf(parameter); + if (parameter.dotDotDotToken) { + return getSpreadArgumentType(args, indexOfParameter, args.length, anyType, /*context*/ undefined); } + const links = getNodeLinks(iife); + const cached = links.resolvedSignature; + links.resolvedSignature = anySignature; + const type = indexOfParameter < args.length ? + getWidenedLiteralType(checkExpression(args[indexOfParameter])) : + parameter.initializer ? undefined : undefinedWideningType; + links.resolvedSignature = cached; + return type; } - - /** - * Static members being set on a constructor function may conflict with built-in properties - * of Function. Esp. in ECMAScript 5 there are non-configurable and non-writable - * built-in properties. This check issues a transpile error when a class has a static - * member with the same name as a non-writable built-in property. - * - * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.3 - * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.5 - * @see http://www.ecma-international.org/ecma-262/6.0/#sec-properties-of-the-function-constructor - * @see http://www.ecma-international.org/ecma-262/6.0/#sec-function-instances - */ - function checkClassForStaticPropertyNameConflicts(node: ClassLikeDeclaration) { - for (const member of node.members) { - const memberNameNode = member.name; - const isStatic = hasModifier(member, ModifierFlags.Static); - if (isStatic && memberNameNode) { - const memberName = getPropertyNameForPropertyNameNode(memberNameNode); - switch (memberName) { - case "name": - case "length": - case "caller": - case "arguments": - case "prototype": - const message = Diagnostics.Static_property_0_conflicts_with_built_in_property_Function_0_of_constructor_function_1; - const className = getNameOfSymbolAsWritten(getSymbolOfNode(node)); - error(memberNameNode, message, memberName, className); - break; - } + let contextualSignature = getContextualSignature(func); + if (contextualSignature) { + if (forCache) { + // Calling the below guarantees the types are primed and assigned in the same way + // as when the parameter is reached via `checkFunctionExpressionOrObjectLiteralMethod`. + // This should prevent any uninstantiated inference variables in the contextual signature + // from leaking, and should lock in cached parameter types via `assignContextualParameterTypes` + // which we will then immediately use the results of below. + contextuallyCheckFunctionExpressionOrObjectLiteralMethod(func); + const type = getTypeOfSymbol(getMergedSymbol(func.symbol)); + if (isTypeAny(type)) { + return type; } + contextualSignature = getSignaturesOfType(type, SignatureKind.Call)[0]; } + const index = func.parameters.indexOf(parameter) - (getThisParameter(func) ? 1 : 0); + return parameter.dotDotDotToken && lastOrUndefined(func.parameters) === parameter ? + getRestTypeAtPosition(contextualSignature, index) : + tryGetTypeAtPosition(contextualSignature, index); } - - function checkObjectTypeForDuplicateDeclarations(node: TypeLiteralNode | InterfaceDeclaration) { - const names = createMap(); - for (const member of node.members) { - if (member.kind === SyntaxKind.PropertySignature) { - let memberName: string; - const name = member.name!; - switch (name.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - memberName = name.text; - break; - case SyntaxKind.Identifier: - memberName = idText(name); - break; - default: - continue; - } - - if (names.get(memberName)) { - error(getNameOfDeclaration(member.symbol.valueDeclaration), Diagnostics.Duplicate_identifier_0, memberName); - error(member.name, Diagnostics.Duplicate_identifier_0, memberName); - } - else { - names.set(memberName, true); - } - } + } + function getContextualTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration): ts.Type | undefined { + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + return getTypeFromTypeNode(typeNode); + } + switch (declaration.kind) { + case SyntaxKind.Parameter: + return getContextuallyTypedParameterType(declaration, /*forCache*/ false); + case SyntaxKind.BindingElement: + return getContextualTypeForBindingElement(declaration); + // By default, do nothing and return undefined - only parameters and binding elements have context implied by a parent + } + } + function getContextualTypeForBindingElement(declaration: BindingElement): ts.Type | undefined { + const parentDeclaration = declaration.parent.parent; + const name = declaration.propertyName || declaration.name; + const parentType = getContextualTypeForVariableLikeDeclaration(parentDeclaration); + if (parentType && !isBindingPattern(name) && !isComputedNonLiteralName(name)) { + const nameType = getLiteralTypeFromPropertyName(name); + if (isTypeUsableAsPropertyName(nameType)) { + const text = getPropertyNameFromType(nameType); + return getTypeOfPropertyOfType(parentType, text); } } - - function checkTypeForDuplicateIndexSignatures(node: Node) { - if (node.kind === SyntaxKind.InterfaceDeclaration) { - const nodeSymbol = getSymbolOfNode(node as InterfaceDeclaration); - // in case of merging interface declaration it is possible that we'll enter this check procedure several times for every declaration - // to prevent this run check only for the first declaration of a given kind - if (nodeSymbol.declarations.length > 0 && nodeSymbol.declarations[0] !== node) { - return; - } + } + // In a variable, parameter or property declaration with a type annotation, + // the contextual type of an initializer expression is the type of the variable, parameter or property. + // Otherwise, in a parameter declaration of a contextually typed function expression, + // the contextual type of an initializer expression is the contextual type of the parameter. + // Otherwise, in a variable or parameter declaration with a binding pattern name, + // the contextual type of an initializer expression is the type implied by the binding pattern. + // Otherwise, in a binding pattern inside a variable or parameter declaration, + // the contextual type of an initializer expression is the type annotation of the containing declaration, if present. + function getContextualTypeForInitializerExpression(node: Expression): ts.Type | undefined { + const declaration = (node.parent); + if (hasInitializer(declaration) && node === declaration.initializer) { + const result = getContextualTypeForVariableLikeDeclaration(declaration); + if (result) { + return result; } - - // TypeScript 1.0 spec (April 2014) - // 3.7.4: An object type can contain at most one string index signature and one numeric index signature. - // 8.5: A class declaration can have at most one string index member declaration and one numeric index member declaration - const indexSymbol = getIndexSymbol(getSymbolOfNode(node)!); - if (indexSymbol) { - let seenNumericIndexer = false; - let seenStringIndexer = false; - for (const decl of indexSymbol.declarations) { - const declaration = decl; - if (declaration.parameters.length === 1 && declaration.parameters[0].type) { - switch (declaration.parameters[0].type.kind) { - case SyntaxKind.StringKeyword: - if (!seenStringIndexer) { - seenStringIndexer = true; - } - else { - error(declaration, Diagnostics.Duplicate_string_index_signature); - } - break; - case SyntaxKind.NumberKeyword: - if (!seenNumericIndexer) { - seenNumericIndexer = true; - } - else { - error(declaration, Diagnostics.Duplicate_number_index_signature); - } - break; - } - } + if (isBindingPattern(declaration.name)) { // This is less a contextual type and more an implied shape - in some cases, this may be undesirable + return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false); + } + } + return undefined; + } + function getContextualTypeForReturnExpression(node: Expression): ts.Type | undefined { + const func = getContainingFunction(node); + if (func) { + const functionFlags = getFunctionFlags(func); + if (functionFlags & FunctionFlags.Generator) { // AsyncGenerator function or Generator function + return undefined; + } + const contextualReturnType = getContextualReturnType(func); + if (contextualReturnType) { + if (functionFlags & FunctionFlags.Async) { // Async function + const contextualAwaitedType = getAwaitedTypeOfPromise(contextualReturnType); + return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); } + return contextualReturnType; // Regular function } } - - function checkPropertyDeclaration(node: PropertyDeclaration | PropertySignature) { - // Grammar checking - if (!checkGrammarDecoratorsAndModifiers(node) && !checkGrammarProperty(node)) checkGrammarComputedPropertyName(node.name); - checkVariableLikeDeclaration(node); + return undefined; + } + function getContextualTypeForAwaitOperand(node: AwaitExpression): ts.Type | undefined { + const contextualType = getContextualType(node); + if (contextualType) { + const contextualAwaitedType = getAwaitedType(contextualType); + return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); } - - function checkMethodDeclaration(node: MethodDeclaration | MethodSignature) { - // Grammar checking - if (!checkGrammarMethod(node)) checkGrammarComputedPropertyName(node.name); - - // Grammar checking for modifiers is done inside the function checkGrammarFunctionLikeDeclaration - checkFunctionOrMethodDeclaration(node); - - // Abstract methods cannot have an implementation. - // Extra checks are to avoid reporting multiple errors relating to the "abstractness" of the node. - if (hasModifier(node, ModifierFlags.Abstract) && node.kind === SyntaxKind.MethodDeclaration && node.body) { - error(node, Diagnostics.Method_0_cannot_have_an_implementation_because_it_is_marked_abstract, declarationNameToString(node.name)); + return undefined; + } + function getContextualTypeForYieldOperand(node: YieldExpression): ts.Type | undefined { + const func = getContainingFunction(node); + if (func) { + const functionFlags = getFunctionFlags(func); + const contextualReturnType = getContextualReturnType(func); + if (contextualReturnType) { + return node.asteriskToken + ? contextualReturnType + : getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, contextualReturnType, (functionFlags & FunctionFlags.Async) !== 0); } } - - function checkConstructorDeclaration(node: ConstructorDeclaration) { - // Grammar check on signature of constructor and modifier of the constructor is done in checkSignatureDeclaration function. - checkSignatureDeclaration(node); - // Grammar check for checking only related to constructorDeclaration - if (!checkGrammarConstructorTypeParameters(node)) checkGrammarConstructorTypeAnnotation(node); - - checkSourceElement(node.body); - - const symbol = getSymbolOfNode(node); - const firstDeclaration = getDeclarationOfKind(symbol, node.kind); - - // Only type check the symbol once - if (node === firstDeclaration) { - checkFunctionOrConstructorSymbol(symbol); - } - - // exit early in the case of signature - super checks are not relevant to them - if (nodeIsMissing(node.body)) { - return; - } - - if (!produceDiagnostics) { - return; + return undefined; + } + function isInParameterInitializerBeforeContainingFunction(node: Node) { + let inBindingInitializer = false; + while (node.parent && !isFunctionLike(node.parent)) { + if (isParameter(node.parent) && (inBindingInitializer || node.parent.initializer === node)) { + return true; } - - function isInstancePropertyWithInitializer(n: Node): boolean { - return n.kind === SyntaxKind.PropertyDeclaration && - !hasModifier(n, ModifierFlags.Static) && - !!(n).initializer; + if (isBindingElement(node.parent) && node.parent.initializer === node) { + inBindingInitializer = true; } - - // TS 1.0 spec (April 2014): 8.3.2 - // Constructors of classes with no extends clause may not contain super calls, whereas - // constructors of derived classes must contain at least one super call somewhere in their function body. - const containingClassDecl = node.parent; - if (getClassExtendsHeritageElement(containingClassDecl)) { - captureLexicalThis(node.parent, containingClassDecl); - const classExtendsNull = classDeclarationExtendsNull(containingClassDecl); - const superCall = getSuperCallInConstructor(node); - if (superCall) { - if (classExtendsNull) { - error(superCall, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null); - } - - // The first statement in the body of a constructor (excluding prologue directives) must be a super call - // if both of the following are true: - // - The containing class is a derived class. - // - The constructor declares parameter properties - // or the containing class declares instance member variables with initializers. - const superCallShouldBeFirst = - some((node.parent).members, isInstancePropertyWithInitializer) || - some(node.parameters, p => hasModifier(p, ModifierFlags.ParameterPropertyModifier)); - - // Skip past any prologue directives to find the first statement - // to ensure that it was a super call. - if (superCallShouldBeFirst) { - const statements = node.body!.statements; - let superCallStatement: ExpressionStatement | undefined; - - for (const statement of statements) { - if (statement.kind === SyntaxKind.ExpressionStatement && isSuperCall((statement).expression)) { - superCallStatement = statement; - break; - } - if (!isPrologueDirective(statement)) { - break; - } - } - if (!superCallStatement) { - error(node, Diagnostics.A_super_call_must_be_the_first_statement_in_the_constructor_when_a_class_contains_initialized_properties_or_has_parameter_properties); - } - } + node = node.parent; + } + return false; + } + function getContextualIterationType(kind: IterationTypeKind, functionDecl: SignatureDeclaration): ts.Type | undefined { + const isAsync = !!(getFunctionFlags(functionDecl) & FunctionFlags.Async); + const contextualReturnType = getContextualReturnType(functionDecl); + if (contextualReturnType) { + return getIterationTypeOfGeneratorFunctionReturnType(kind, contextualReturnType, isAsync) + || undefined; + } + return undefined; + } + function getContextualReturnType(functionDecl: SignatureDeclaration): ts.Type | undefined { + // If the containing function has a return type annotation, is a constructor, or is a get accessor whose + // corresponding set accessor has a type annotation, return statements in the function are contextually typed + const returnType = getReturnTypeFromAnnotation(functionDecl); + if (returnType) { + return returnType; + } + // Otherwise, if the containing function is contextually typed by a function type with exactly one call signature + // and that call signature is non-generic, return statements are contextually typed by the return type of the signature + const signature = getContextualSignatureForFunctionLikeDeclaration((functionDecl)); + if (signature && !isResolvingReturnTypeOfSignature(signature)) { + return getReturnTypeOfSignature(signature); + } + return undefined; + } + // In a typed function call, an argument or substitution expression is contextually typed by the type of the corresponding parameter. + function getContextualTypeForArgument(callTarget: CallLikeExpression, arg: Expression, contextFlags?: ContextFlags): ts.Type | undefined { + const args = getEffectiveCallArguments(callTarget); + const argIndex = args.indexOf(arg); // -1 for e.g. the expression of a CallExpression, or the tag of a TaggedTemplateExpression + return argIndex === -1 ? undefined : getContextualTypeForArgumentAtIndex(callTarget, argIndex, contextFlags); + } + function getContextualTypeForArgumentAtIndex(callTarget: CallLikeExpression, argIndex: number, contextFlags?: ContextFlags): ts.Type { + // If we're already in the process of resolving the given signature, don't resolve again as + // that could cause infinite recursion. Instead, return anySignature. + const signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget); + if (isJsxOpeningLikeElement(callTarget) && argIndex === 0) { + return getEffectiveFirstArgumentForJsxSignature(signature, callTarget); + } + if (contextFlags && contextFlags & ContextFlags.BaseConstraint && signature.target && !hasTypeArguments(callTarget)) { + const baseSignature = getBaseSignature(signature.target); + return getTypeAtPosition(baseSignature, argIndex); + } + return getTypeAtPosition(signature, argIndex); + } + function getContextualTypeForSubstitutionExpression(template: TemplateExpression, substitutionExpression: Expression) { + if (template.parent.kind === SyntaxKind.TaggedTemplateExpression) { + return getContextualTypeForArgument((template.parent), substitutionExpression); + } + return undefined; + } + function getContextualTypeForBinaryOperand(node: Expression, contextFlags?: ContextFlags): ts.Type | undefined { + const binaryExpression = (node.parent); + const { left, operatorToken, right } = binaryExpression; + switch (operatorToken.kind) { + case SyntaxKind.EqualsToken: + if (node !== right) { + return undefined; } - else if (!classExtendsNull) { - error(node, Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call); + const contextSensitive = getIsContextSensitiveAssignmentOrContextType(binaryExpression); + if (!contextSensitive) { + return undefined; } - } + return contextSensitive === true ? getTypeOfExpression(left) : contextSensitive; + case SyntaxKind.BarBarToken: + case SyntaxKind.QuestionQuestionToken: + // When an || expression has a contextual type, the operands are contextually typed by that type, except + // when that type originates in a binding pattern, the right operand is contextually typed by the type of + // the left operand. When an || expression has no contextual type, the right operand is contextually typed + // by the type of the left operand, except for the special case of Javascript declarations of the form + // `namespace.prop = namespace.prop || {}`. + const type = getContextualType(binaryExpression, contextFlags); + return node === right && (type && type.pattern || !type && !isDefaultedExpandoInitializer(binaryExpression)) ? + getTypeOfExpression(left) : type; + case SyntaxKind.AmpersandAmpersandToken: + case SyntaxKind.CommaToken: + return node === right ? getContextualType(binaryExpression, contextFlags) : undefined; + default: + return undefined; } - - function checkAccessorDeclaration(node: AccessorDeclaration) { - if (produceDiagnostics) { - // Grammar checking accessors - if (!checkGrammarFunctionLikeDeclaration(node) && !checkGrammarAccessor(node)) checkGrammarComputedPropertyName(node.name); - - checkDecorators(node); - checkSignatureDeclaration(node); - if (node.kind === SyntaxKind.GetAccessor) { - if (!(node.flags & NodeFlags.Ambient) && nodeIsPresent(node.body) && (node.flags & NodeFlags.HasImplicitReturn)) { - if (!(node.flags & NodeFlags.HasExplicitReturn)) { - error(node.name, Diagnostics.A_get_accessor_must_return_a_value); + } + // In an assignment expression, the right operand is contextually typed by the type of the left operand. + // Don't do this for assignment declarations unless there is a type tag on the assignment, to avoid circularity from checking the right operand. + function getIsContextSensitiveAssignmentOrContextType(binaryExpression: BinaryExpression): boolean | ts.Type { + const kind = getAssignmentDeclarationKind(binaryExpression); + switch (kind) { + case AssignmentDeclarationKind.None: + return true; + case AssignmentDeclarationKind.Property: + case AssignmentDeclarationKind.ExportsProperty: + case AssignmentDeclarationKind.Prototype: + case AssignmentDeclarationKind.PrototypeProperty: + // If `binaryExpression.left` was assigned a symbol, then this is a new declaration; otherwise it is an assignment to an existing declaration. + // See `bindStaticPropertyAssignment` in `binder.ts`. + if (!binaryExpression.left.symbol) { + return true; + } + else { + const decl = binaryExpression.left.symbol.valueDeclaration; + if (!decl) { + return false; + } + const lhs = cast(binaryExpression.left, isAccessExpression); + const overallAnnotation = getEffectiveTypeAnnotationNode(decl); + if (overallAnnotation) { + return getTypeFromTypeNode(overallAnnotation); + } + else if (isIdentifier(lhs.expression)) { + const id = lhs.expression; + const parentSymbol = resolveName(id, id.escapedText, SymbolFlags.Value, undefined, id.escapedText, /*isUse*/ true); + if (parentSymbol) { + const annotated = getEffectiveTypeAnnotationNode(parentSymbol.valueDeclaration); + if (annotated) { + const nameStr = getElementOrPropertyAccessName(lhs); + if (nameStr !== undefined) { + const type = getTypeOfPropertyOfContextualType(getTypeFromTypeNode(annotated), nameStr); + return type || false; + } + } + return false; } } + return !isInJSFile(decl); } - // Do not use hasDynamicName here, because that returns false for well known symbols. - // We want to perform checkComputedPropertyName for all computed properties, including - // well known symbols. - if (node.name.kind === SyntaxKind.ComputedPropertyName) { - checkComputedPropertyName(node.name); - } - if (!hasNonBindableDynamicName(node)) { - // TypeScript 1.0 spec (April 2014): 8.4.3 - // Accessors for the same member name must specify the same accessibility. - const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; - const otherAccessor = getDeclarationOfKind(getSymbolOfNode(node), otherKind); - if (otherAccessor) { - const nodeFlags = getModifierFlags(node); - const otherFlags = getModifierFlags(otherAccessor); - if ((nodeFlags & ModifierFlags.AccessibilityModifier) !== (otherFlags & ModifierFlags.AccessibilityModifier)) { - error(node.name, Diagnostics.Getter_and_setter_accessors_do_not_agree_in_visibility); - } - if ((nodeFlags & ModifierFlags.Abstract) !== (otherFlags & ModifierFlags.Abstract)) { - error(node.name, Diagnostics.Accessors_must_both_be_abstract_or_non_abstract); + case AssignmentDeclarationKind.ModuleExports: + case AssignmentDeclarationKind.ThisProperty: + if (!binaryExpression.symbol) + return true; + if (binaryExpression.symbol.valueDeclaration) { + const annotated = getEffectiveTypeAnnotationNode(binaryExpression.symbol.valueDeclaration); + if (annotated) { + const type = getTypeFromTypeNode(annotated); + if (type) { + return type; } - - // TypeScript 1.0 spec (April 2014): 4.5 - // If both accessors include type annotations, the specified types must be identical. - checkAccessorDeclarationTypesIdentical(node, otherAccessor, getAnnotatedAccessorType, Diagnostics.get_and_set_accessor_must_have_the_same_type); - checkAccessorDeclarationTypesIdentical(node, otherAccessor, getThisTypeOfDeclaration, Diagnostics.get_and_set_accessor_must_have_the_same_this_type); } } - const returnType = getTypeOfAccessors(getSymbolOfNode(node)); - if (node.kind === SyntaxKind.GetAccessor) { - checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); + if (kind === AssignmentDeclarationKind.ModuleExports) + return false; + const thisAccess = cast(binaryExpression.left, isAccessExpression); + if (!isObjectLiteralMethod(getThisContainer(thisAccess.expression, /*includeArrowFunctions*/ false))) { + return false; } - } - checkSourceElement(node.body); - } - - function checkAccessorDeclarationTypesIdentical(first: AccessorDeclaration, second: AccessorDeclaration, getAnnotatedType: (a: AccessorDeclaration) => Type | undefined, message: DiagnosticMessage) { - const firstType = getAnnotatedType(first); - const secondType = getAnnotatedType(second); - if (firstType && secondType && !isTypeIdenticalTo(firstType, secondType)) { - error(first, message); - } - } - - function checkMissingDeclaration(node: Node) { - checkDecorators(node); - } - - function getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[]): Type[] { - return fillMissingTypeArguments(map(node.typeArguments!, getTypeFromTypeNode), typeParameters, - getMinTypeArgumentCount(typeParameters), isInJSFile(node)); + const thisType = checkThisExpression(thisAccess.expression); + const nameStr = getElementOrPropertyAccessName(thisAccess); + return nameStr !== undefined && thisType && getTypeOfPropertyOfContextualType(thisType, nameStr) || false; + case AssignmentDeclarationKind.ObjectDefinePropertyValue: + case AssignmentDeclarationKind.ObjectDefinePropertyExports: + case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: + return Debug.fail("Does not apply"); + default: + return Debug.assertNever(kind); } - - function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[]): boolean { - let typeArguments: Type[] | undefined; - let mapper: TypeMapper | undefined; - let result = true; - for (let i = 0; i < typeParameters.length; i++) { - const constraint = getConstraintOfTypeParameter(typeParameters[i]); - if (constraint) { - if (!typeArguments) { - typeArguments = getEffectiveTypeArguments(node, typeParameters); - mapper = createTypeMapper(typeParameters, typeArguments); + } + function getTypeOfPropertyOfContextualType(type: ts.Type, name: __String) { + return mapType(type, t => { + if (isGenericMappedType(t)) { + const constraint = getConstraintTypeFromMappedType(t); + const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint; + const propertyNameType = getLiteralType(unescapeLeadingUnderscores(name)); + if (isTypeAssignableTo(propertyNameType, constraintOfConstraint)) { + return substituteIndexedMappedType(t, propertyNameType); + } + } + else if (t.flags & TypeFlags.StructuredType) { + const prop = getPropertyOfType(t, name); + if (prop) { + return getTypeOfSymbol(prop); + } + if (isTupleType(t)) { + const restType = getRestTypeOfTupleType(t); + if (restType && isNumericLiteralName(name) && +name >= 0) { + return restType; } - result = result && checkTypeAssignableTo( - typeArguments[i], - instantiateType(constraint, mapper), - node.typeArguments![i], - Diagnostics.Type_0_does_not_satisfy_the_constraint_1); } + return isNumericLiteralName(name) && getIndexTypeOfContextualType(t, IndexKind.Number) || + getIndexTypeOfContextualType(t, IndexKind.String); } - return result; + return undefined; + }, /*noReductions*/ true); + } + function getIndexTypeOfContextualType(type: ts.Type, kind: IndexKind) { + return mapType(type, t => getIndexTypeOfStructuredType(t, kind), /*noReductions*/ true); + } + // In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of + // the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one + // exists. Otherwise, it is the type of the string index signature in T, if one exists. + function getContextualTypeForObjectLiteralMethod(node: MethodDeclaration, contextFlags?: ContextFlags): ts.Type | undefined { + Debug.assert(isObjectLiteralMethod(node)); + if (node.flags & NodeFlags.InWithStatement) { + // We cannot answer semantic questions within a with block, do not proceed any further + return undefined; } - - function getTypeParametersForTypeReference(node: TypeReferenceNode | ExpressionWithTypeArguments) { - const type = getTypeFromTypeReference(node); - if (type !== errorType) { - const symbol = getNodeLinks(node).resolvedSymbol; - if (symbol) { - return symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters || - (getObjectFlags(type) & ObjectFlags.Reference ? (type).target.localTypeParameters : undefined); - } - } + return getContextualTypeForObjectLiteralElement(node, contextFlags); + } + function getContextualTypeForObjectLiteralElement(element: ObjectLiteralElementLike, contextFlags?: ContextFlags) { + const objectLiteral = (element.parent); + const type = getApparentTypeOfContextualType(objectLiteral, contextFlags); + if (type) { + if (!hasNonBindableDynamicName(element)) { + // For a (non-symbol) computed property, there is no reason to look up the name + // in the type. It will just be "__computed", which does not appear in any + // SymbolTable. + const symbolName = getSymbolOfNode(element).escapedName; + const propertyType = getTypeOfPropertyOfContextualType(type, symbolName); + if (propertyType) { + return propertyType; + } + } + return isNumericName(element.name!) && getIndexTypeOfContextualType(type, IndexKind.Number) || + getIndexTypeOfContextualType(type, IndexKind.String); + } + return undefined; + } + // In an array literal contextually typed by a type T, the contextual type of an element expression at index N is + // the type of the property with the numeric name N in T, if one exists. Otherwise, if T has a numeric index signature, + // it is the type of the numeric index signature in T. Otherwise, in ES6 and higher, the contextual type is the iterated + // type of T. + function getContextualTypeForElementExpression(arrayContextualType: ts.Type | undefined, index: number): ts.Type | undefined { + return arrayContextualType && (getTypeOfPropertyOfContextualType(arrayContextualType, ("" + index as __String)) + || getIteratedTypeOrElementType(IterationUse.Element, arrayContextualType, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false)); + } + // In a contextually typed conditional expression, the true/false expressions are contextually typed by the same type. + function getContextualTypeForConditionalOperand(node: Expression, contextFlags?: ContextFlags): ts.Type | undefined { + const conditional = (node.parent); + return node === conditional.whenTrue || node === conditional.whenFalse ? getContextualType(conditional, contextFlags) : undefined; + } + function getContextualTypeForChildJsxExpression(node: JsxElement, child: JsxChild) { + const attributesType = getApparentTypeOfContextualType(node.openingElement.tagName); + // JSX expression is in children of JSX Element, we will look for an "children" atttribute (we get the name from JSX.ElementAttributesProperty) + const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); + if (!(attributesType && !isTypeAny(attributesType) && jsxChildrenPropertyName && jsxChildrenPropertyName !== "")) { return undefined; } - - function checkTypeReferenceNode(node: TypeReferenceNode | ExpressionWithTypeArguments) { - checkGrammarTypeArguments(node, node.typeArguments); - if (node.kind === SyntaxKind.TypeReference && node.typeName.jsdocDotPos !== undefined && !isInJSFile(node) && !isInJSDoc(node)) { - grammarErrorAtPos(node, node.typeName.jsdocDotPos, 1, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); + const realChildren = getSemanticJsxChildren(node.children); + const childIndex = realChildren.indexOf(child); + const childFieldType = getTypeOfPropertyOfContextualType(attributesType, jsxChildrenPropertyName); + return childFieldType && (realChildren.length === 1 ? childFieldType : mapType(childFieldType, t => { + if (isArrayLikeType(t)) { + return getIndexedAccessType(t, getLiteralType(childIndex)); } - forEach(node.typeArguments, checkSourceElement); - const type = getTypeFromTypeReference(node); - if (type !== errorType) { - if (node.typeArguments && produceDiagnostics) { - const typeParameters = getTypeParametersForTypeReference(node); - if (typeParameters) { - checkTypeArgumentConstraints(node, typeParameters); - } + else { + return t; + } + }, /*noReductions*/ true)); + } + function getContextualTypeForJsxExpression(node: JsxExpression): ts.Type | undefined { + const exprParent = node.parent; + return isJsxAttributeLike(exprParent) + ? getContextualType(node) + : isJsxElement(exprParent) + ? getContextualTypeForChildJsxExpression(exprParent, node) + : undefined; + } + function getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute): ts.Type | undefined { + // When we trying to resolve JsxOpeningLikeElement as a stateless function element, we will already give its attributes a contextual type + // which is a type of the parameter of the signature we are trying out. + // If there is no contextual type (e.g. we are trying to resolve stateful component), get attributes type from resolving element's tagName + if (isJsxAttribute(attribute)) { + const attributesType = getApparentTypeOfContextualType(attribute.parent); + if (!attributesType || isTypeAny(attributesType)) { + return undefined; + } + return getTypeOfPropertyOfContextualType(attributesType, attribute.name.escapedText); + } + else { + return getContextualType(attribute.parent); + } + } + // Return true if the given expression is possibly a discriminant value. We limit the kinds of + // expressions we check to those that don't depend on their contextual type in order not to cause + // recursive (and possibly infinite) invocations of getContextualType. + function isPossiblyDiscriminantValue(node: Expression): boolean { + switch (node.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.Identifier: + case SyntaxKind.UndefinedKeyword: + return true; + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ParenthesizedExpression: + return isPossiblyDiscriminantValue((node).expression); + case SyntaxKind.JsxExpression: + return !(node as JsxExpression).expression || isPossiblyDiscriminantValue(((node as JsxExpression).expression!)); + } + return false; + } + function discriminateContextualTypeByObjectMembers(node: ObjectLiteralExpression, contextualType: UnionType) { + return discriminateTypeByDiscriminableItems(contextualType, map(filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.PropertyAssignment && isPossiblyDiscriminantValue(p.initializer) && isDiscriminantProperty(contextualType, p.symbol.escapedName)), prop => ([() => checkExpression((prop as PropertyAssignment).initializer), prop.symbol.escapedName] as [() => ts.Type, __String])), isTypeAssignableTo, contextualType); + } + function discriminateContextualTypeByJSXAttributes(node: JsxAttributes, contextualType: UnionType) { + return discriminateTypeByDiscriminableItems(contextualType, map(filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.JsxAttribute && isDiscriminantProperty(contextualType, p.symbol.escapedName) && (!p.initializer || isPossiblyDiscriminantValue(p.initializer))), prop => ([!(prop as JsxAttribute).initializer ? (() => trueType) : (() => checkExpression(((prop as JsxAttribute).initializer!))), prop.symbol.escapedName] as [() => ts.Type, __String])), isTypeAssignableTo, contextualType); + } + // Return the contextual type for a given expression node. During overload resolution, a contextual type may temporarily + // be "pushed" onto a node using the contextualType property. + function getApparentTypeOfContextualType(node: Expression | MethodDeclaration, contextFlags?: ContextFlags): ts.Type | undefined { + const contextualType = isObjectLiteralMethod(node) ? + getContextualTypeForObjectLiteralMethod(node, contextFlags) : + getContextualType(node, contextFlags); + const instantiatedType = instantiateContextualType(contextualType, node, contextFlags); + if (instantiatedType && !(contextFlags && contextFlags & ContextFlags.NoConstraints && instantiatedType.flags & TypeFlags.TypeVariable)) { + const apparentType = mapType(instantiatedType, getApparentType, /*noReductions*/ true); + if (apparentType.flags & TypeFlags.Union) { + if (isObjectLiteralExpression(node)) { + return discriminateContextualTypeByObjectMembers(node, (apparentType as UnionType)); } - if (type.flags & TypeFlags.Enum && getNodeLinks(node).resolvedSymbol!.flags & SymbolFlags.EnumMember) { - error(node, Diagnostics.Enum_type_0_has_members_with_initializers_that_are_not_literals, typeToString(type)); + else if (isJsxAttributes(node)) { + return discriminateContextualTypeByJSXAttributes(node, (apparentType as UnionType)); } } + return apparentType; } - - function getTypeArgumentConstraint(node: TypeNode): Type | undefined { - const typeReferenceNode = tryCast(node.parent, isTypeReferenceType); - if (!typeReferenceNode) return undefined; - const typeParameters = getTypeParametersForTypeReference(typeReferenceNode)!; // TODO: GH#18217 - const constraint = getConstraintOfTypeParameter(typeParameters[typeReferenceNode.typeArguments!.indexOf(node)]); - return constraint && instantiateType(constraint, createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReferenceNode, typeParameters))); + } + // If the given contextual type contains instantiable types and if a mapper representing + // return type inferences is available, instantiate those types using that mapper. + function instantiateContextualType(contextualType: ts.Type | undefined, node: Node, contextFlags?: ContextFlags): ts.Type | undefined { + if (contextualType && maybeTypeOfKind(contextualType, TypeFlags.Instantiable)) { + const inferenceContext = getInferenceContext(node); + // If no inferences have been made, nothing is gained from instantiating as type parameters + // would just be replaced with their defaults similar to the apparent type. + if (inferenceContext && some(inferenceContext.inferences, hasInferenceCandidates)) { + // For contextual signatures we incorporate all inferences made so far, e.g. from return + // types as well as arguments to the left in a function call. + if (contextFlags && contextFlags & ContextFlags.Signature) { + return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper); + } + // For other purposes (e.g. determining whether to produce literal types) we only + // incorporate inferences made from the return type in a function call. + if (inferenceContext.returnMapper) { + return instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper); + } + } + } + return contextualType; + } + // This function is similar to instantiateType, except that (a) it only instantiates types that + // are classified as instantiable (i.e. it doesn't instantiate object types), and (b) it performs + // no reductions on instantiated union types. + function instantiateInstantiableTypes(type: ts.Type, mapper: TypeMapper): ts.Type { + if (type.flags & TypeFlags.Instantiable) { + return instantiateType(type, mapper); } - - function checkTypeQuery(node: TypeQueryNode) { - getTypeFromTypeQueryNode(node); + if (type.flags & TypeFlags.Union) { + return getUnionType(map((type).types, t => instantiateInstantiableTypes(t, mapper)), UnionReduction.None); } - - function checkTypeLiteral(node: TypeLiteralNode) { - forEach(node.members, checkSourceElement); - if (produceDiagnostics) { - const type = getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); - checkIndexConstraints(type); - checkTypeForDuplicateIndexSignatures(node); - checkObjectTypeForDuplicateDeclarations(node); - } + if (type.flags & TypeFlags.Intersection) { + return getIntersectionType(map((type).types, t => instantiateInstantiableTypes(t, mapper))); } - - function checkArrayType(node: ArrayTypeNode) { - checkSourceElement(node.elementType); + return type; + } + /** + * Whoa! Do you really want to use this function? + * + * Unless you're trying to get the *non-apparent* type for a + * value-literal type or you're authoring relevant portions of this algorithm, + * you probably meant to use 'getApparentTypeOfContextualType'. + * Otherwise this may not be very useful. + * + * In cases where you *are* working on this function, you should understand + * when it is appropriate to use 'getContextualType' and 'getApparentTypeOfContextualType'. + * + * - Use 'getContextualType' when you are simply going to propagate the result to the expression. + * - Use 'getApparentTypeOfContextualType' when you're going to need the members of the type. + * + * @param node the expression whose contextual type will be returned. + * @returns the contextual type of an expression. + */ + function getContextualType(node: Expression, contextFlags?: ContextFlags): ts.Type | undefined { + if (node.flags & NodeFlags.InWithStatement) { + // We cannot answer semantic questions within a with block, do not proceed any further + return undefined; } - - function checkTupleType(node: TupleTypeNode) { - const elementTypes = node.elementTypes; - let seenOptionalElement = false; - for (let i = 0; i < elementTypes.length; i++) { - const e = elementTypes[i]; - if (e.kind === SyntaxKind.RestType) { - if (i !== elementTypes.length - 1) { - grammarErrorOnNode(e, Diagnostics.A_rest_element_must_be_last_in_a_tuple_type); - break; - } - if (!isArrayType(getTypeFromTypeNode((e).type))) { - error(e, Diagnostics.A_rest_element_type_must_be_an_array_type); - } - } - else if (e.kind === SyntaxKind.OptionalType) { - seenOptionalElement = true; + if (node.contextualType) { + return node.contextualType; + } + const { parent } = node; + switch (parent.kind) { + case SyntaxKind.VariableDeclaration: + case SyntaxKind.Parameter: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.BindingElement: + return getContextualTypeForInitializerExpression(node); + case SyntaxKind.ArrowFunction: + case SyntaxKind.ReturnStatement: + return getContextualTypeForReturnExpression(node); + case SyntaxKind.YieldExpression: + return getContextualTypeForYieldOperand((parent)); + case SyntaxKind.AwaitExpression: + return getContextualTypeForAwaitOperand((parent)); + case SyntaxKind.CallExpression: + if ((parent).expression.kind === SyntaxKind.ImportKeyword) { + return stringType; } - else if (seenOptionalElement) { - grammarErrorOnNode(e, Diagnostics.A_required_element_cannot_follow_an_optional_element); - break; + /* falls through */ + case SyntaxKind.NewExpression: + return getContextualTypeForArgument((parent), node, contextFlags); + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + return isConstTypeReference((parent).type) ? undefined : getTypeFromTypeNode((parent).type); + case SyntaxKind.BinaryExpression: + return getContextualTypeForBinaryOperand(node, contextFlags); + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + return getContextualTypeForObjectLiteralElement((parent), contextFlags); + case SyntaxKind.SpreadAssignment: + return getApparentTypeOfContextualType((parent.parent as ObjectLiteralExpression), contextFlags); + case SyntaxKind.ArrayLiteralExpression: { + const arrayLiteral = (parent); + const type = getApparentTypeOfContextualType(arrayLiteral, contextFlags); + return getContextualTypeForElementExpression(type, indexOfNode(arrayLiteral.elements, node)); + } + case SyntaxKind.ConditionalExpression: + return getContextualTypeForConditionalOperand(node, contextFlags); + case SyntaxKind.TemplateSpan: + Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression); + return getContextualTypeForSubstitutionExpression((parent.parent), node); + case SyntaxKind.ParenthesizedExpression: { + // Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast. + const tag = isInJSFile(parent) ? getJSDocTypeTag(parent) : undefined; + return tag ? getTypeFromTypeNode(tag.typeExpression.type) : getContextualType((parent), contextFlags); + } + case SyntaxKind.JsxExpression: + return getContextualTypeForJsxExpression((parent)); + case SyntaxKind.JsxAttribute: + case SyntaxKind.JsxSpreadAttribute: + return getContextualTypeForJsxAttribute((parent)); + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxSelfClosingElement: + return getContextualJsxElementAttributesType((parent)); + } + return undefined; + } + function getInferenceContext(node: Node) { + const ancestor = findAncestor(node, n => !!n.inferenceContext); + return ancestor && ancestor.inferenceContext!; + } + function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement) { + if (isJsxOpeningElement(node) && node.parent.contextualType) { + // Contextually applied type is moved from attributes up to the outer jsx attributes so when walking up from the children they get hit + // _However_ to hit them from the _attributes_ we must look for them here; otherwise we'll used the declared type + // (as below) instead! + return node.parent.contextualType; + } + return getContextualTypeForArgumentAtIndex(node, 0); + } + function getEffectiveFirstArgumentForJsxSignature(signature: ts.Signature, node: JsxOpeningLikeElement) { + return getJsxReferenceKind(node) !== JsxReferenceKind.Component ? getJsxPropsTypeFromCallSignature(signature, node) : getJsxPropsTypeFromClassType(signature, node); + } + function getJsxPropsTypeFromCallSignature(sig: ts.Signature, context: JsxOpeningLikeElement) { + let propsType = getTypeOfFirstParameterOfSignatureWithFallback(sig, unknownType); + propsType = getJsxManagedAttributesFromLocatedAttributes(context, getJsxNamespaceAt(context), propsType); + const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context); + if (intrinsicAttribs !== errorType) { + propsType = intersectTypes(intrinsicAttribs, propsType); + } + return propsType; + } + function getJsxPropsTypeForSignatureFromMember(sig: ts.Signature, forcedLookupLocation: __String) { + if (sig.unionSignatures) { + // JSX Elements using the legacy `props`-field based lookup (eg, react class components) need to treat the `props` member as an input + // instead of an output position when resolving the signature. We need to go back to the input signatures of the composite signature, + // get the type of `props` on each return type individually, and then _intersect them_, rather than union them (as would normally occur + // for a union signature). It's an unfortunate quirk of looking in the output of the signature for the type we want to use for the input. + // The default behavior of `getTypeOfFirstParameterOfSignatureWithFallback` when no `props` member name is defined is much more sane. + const results: ts.Type[] = []; + for (const signature of sig.unionSignatures) { + const instance = getReturnTypeOfSignature(signature); + if (isTypeAny(instance)) { + return instance; + } + const propType = getTypeOfPropertyOfType(instance, forcedLookupLocation); + if (!propType) { + return; } + results.push(propType); } - forEach(node.elementTypes, checkSourceElement); + return getIntersectionType(results); } - - function checkUnionOrIntersectionType(node: UnionOrIntersectionTypeNode) { - forEach(node.types, checkSourceElement); + const instanceType = getReturnTypeOfSignature(sig); + return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation); + } + function getStaticTypeOfReferencedJsxConstructor(context: JsxOpeningLikeElement) { + if (isJsxIntrinsicIdentifier(context.tagName)) { + const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(context); + const fakeSignature = createSignatureForJSXIntrinsic(context, result); + return getOrCreateTypeFromSignature(fakeSignature); + } + const tagType = checkExpressionCached(context.tagName); + if (tagType.flags & TypeFlags.StringLiteral) { + const result = getIntrinsicAttributesTypeFromStringLiteralType((tagType as StringLiteralType), context); + if (!result) { + return errorType; + } + const fakeSignature = createSignatureForJSXIntrinsic(context, result); + return getOrCreateTypeFromSignature(fakeSignature); } - - function checkIndexedAccessIndexType(type: Type, accessNode: IndexedAccessTypeNode | ElementAccessExpression) { - if (!(type.flags & TypeFlags.IndexedAccess)) { - return type; + return tagType; + } + function getJsxManagedAttributesFromLocatedAttributes(context: JsxOpeningLikeElement, ns: ts.Symbol, attributesType: ts.Type) { + const managedSym = getJsxLibraryManagedAttributes(ns); + if (managedSym) { + const declaredManagedType = getDeclaredTypeOfSymbol(managedSym); + const ctorType = getStaticTypeOfReferencedJsxConstructor(context); + if (length((declaredManagedType as GenericType).typeParameters) >= 2) { + const args = fillMissingTypeArguments([ctorType, attributesType], (declaredManagedType as GenericType).typeParameters, 2, isInJSFile(context)); + return createTypeReference((declaredManagedType as GenericType), args); } - // Check if the index type is assignable to 'keyof T' for the object type. - const objectType = (type).objectType; - const indexType = (type).indexType; - if (isTypeAssignableTo(indexType, getIndexType(objectType, /*stringsOnly*/ false))) { - if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && - getObjectFlags(objectType) & ObjectFlags.Mapped && getMappedTypeModifiers(objectType) & MappedTypeModifiers.IncludeReadonly) { - error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); - } - return type; + else if (length(declaredManagedType.aliasTypeArguments) >= 2) { + const args = fillMissingTypeArguments([ctorType, attributesType], declaredManagedType.aliasTypeArguments, 2, isInJSFile(context)); + return getTypeAliasInstantiation(declaredManagedType.aliasSymbol!, args); } - // Check if we're indexing with a numeric type and if either object or index types - // is a generic type with a constraint that has a numeric index signature. - const apparentObjectType = getApparentType(objectType); - if (getIndexInfoOfType(apparentObjectType, IndexKind.Number) && isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) { - return type; + } + return attributesType; + } + function getJsxPropsTypeFromClassType(sig: ts.Signature, context: JsxOpeningLikeElement) { + const ns = getJsxNamespaceAt(context); + const forcedLookupLocation = getJsxElementPropertiesName(ns); + let attributesType = forcedLookupLocation === undefined + // If there is no type ElementAttributesProperty, return the type of the first parameter of the signature, which should be the props type + ? getTypeOfFirstParameterOfSignatureWithFallback(sig, unknownType) + : forcedLookupLocation === "" + // If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead + ? getReturnTypeOfSignature(sig) + // Otherwise get the type of the property on the signature return type + : getJsxPropsTypeForSignatureFromMember(sig, forcedLookupLocation); + if (!attributesType) { + // There is no property named 'props' on this instance type + if (!!forcedLookupLocation && !!length(context.attributes.properties)) { + error(context, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, unescapeLeadingUnderscores(forcedLookupLocation)); } - if (isGenericObjectType(objectType)) { - const propertyName = getPropertyNameFromIndex(indexType, accessNode); - if (propertyName) { - const propertySymbol = forEachType(apparentObjectType, t => getPropertyOfType(t, propertyName)); - if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.NonPublicAccessibilityModifier) { - error(accessNode, Diagnostics.Private_or_protected_member_0_cannot_be_accessed_on_a_type_parameter, unescapeLeadingUnderscores(propertyName)); - return errorType; - } - } + return unknownType; + } + attributesType = getJsxManagedAttributesFromLocatedAttributes(context, ns, attributesType); + if (isTypeAny(attributesType)) { + // Props is of type 'any' or unknown + return attributesType; + } + else { + // Normal case -- add in IntrinsicClassElements and IntrinsicElements + let apparentAttributesType = attributesType; + const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes, context); + if (intrinsicClassAttribs !== errorType) { + const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol); + const hostClassType = getReturnTypeOfSignature(sig); + apparentAttributesType = intersectTypes(typeParams + ? createTypeReference((intrinsicClassAttribs), fillMissingTypeArguments([hostClassType], typeParams, getMinTypeArgumentCount(typeParams), isInJSFile(context))) + : intrinsicClassAttribs, apparentAttributesType); } - error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType)); - return errorType; + const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context); + if (intrinsicAttribs !== errorType) { + apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType); + } + return apparentAttributesType; } - - function checkIndexedAccessType(node: IndexedAccessTypeNode) { - checkSourceElement(node.objectType); - checkSourceElement(node.indexType); - checkIndexedAccessIndexType(getTypeFromIndexedAccessTypeNode(node), node); + } + // If the given type is an object or union type with a single signature, and if that signature has at + // least as many parameters as the given function, return the signature. Otherwise return undefined. + function getContextualCallSignature(type: ts.Type, node: SignatureDeclaration): ts.Signature | undefined { + const signatures = getSignaturesOfType(type, SignatureKind.Call); + if (signatures.length === 1) { + const signature = signatures[0]; + if (!isAritySmaller(signature, node)) { + return signature; + } } - - function checkMappedType(node: MappedTypeNode) { - checkSourceElement(node.typeParameter); - checkSourceElement(node.type); - - if (!node.type) { - reportImplicitAny(node, anyType); + } + /** If the contextual signature has fewer parameters than the function expression, do not use it */ + function isAritySmaller(signature: ts.Signature, target: SignatureDeclaration) { + let targetParameterCount = 0; + for (; targetParameterCount < target.parameters.length; targetParameterCount++) { + const param = target.parameters[targetParameterCount]; + if (param.initializer || param.questionToken || param.dotDotDotToken || isJSDocOptionalParameter(param)) { + break; } - - const type = getTypeFromMappedTypeNode(node); - const constraintType = getConstraintTypeFromMappedType(type); - checkTypeAssignableTo(constraintType, keyofConstraintType, getEffectiveConstraintOfTypeParameter(node.typeParameter)); } - - function checkThisType(node: ThisTypeNode) { - getTypeFromThisTypeNode(node); + if (target.parameters.length && parameterIsThisKeyword(target.parameters[0])) { + targetParameterCount--; } - - function checkTypeOperator(node: TypeOperatorNode) { - checkGrammarTypeOperatorNode(node); - checkSourceElement(node.type); + return !hasEffectiveRestParameter(signature) && getParameterCount(signature) < targetParameterCount; + } + function isFunctionExpressionOrArrowFunction(node: Node): node is FunctionExpression | ArrowFunction { + return node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.ArrowFunction; + } + function getContextualSignatureForFunctionLikeDeclaration(node: FunctionLikeDeclaration): ts.Signature | undefined { + // Only function expressions, arrow functions, and object literal methods are contextually typed. + return isFunctionExpressionOrArrowFunction(node) || isObjectLiteralMethod(node) + ? getContextualSignature((node)) + : undefined; + } + // Return the contextual signature for a given expression node. A contextual type provides a + // contextual signature if it has a single call signature and if that call signature is non-generic. + // If the contextual type is a union type, get the signature from each type possible and if they are + // all identical ignoring their return type, the result is same signature but with return type as + // union type of return types from these signatures + function getContextualSignature(node: FunctionExpression | ArrowFunction | MethodDeclaration): ts.Signature | undefined { + Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); + const typeTagSignature = getSignatureOfTypeTag(node); + if (typeTagSignature) { + return typeTagSignature; + } + const type = getApparentTypeOfContextualType(node, ContextFlags.Signature); + if (!type) { + return undefined; } - - function checkConditionalType(node: ConditionalTypeNode) { - forEachChild(node, checkSourceElement); + if (!(type.flags & TypeFlags.Union)) { + return getContextualCallSignature(type, node); } - - function checkInferType(node: InferTypeNode) { - if (!findAncestor(node, n => n.parent && n.parent.kind === SyntaxKind.ConditionalType && (n.parent).extendsType === n)) { - grammarErrorOnNode(node, Diagnostics.infer_declarations_are_only_permitted_in_the_extends_clause_of_a_conditional_type); + let signatureList: ts.Signature[] | undefined; + const types = (type).types; + for (const current of types) { + const signature = getContextualCallSignature(current, node); + if (signature) { + if (!signatureList) { + // This signature will contribute to contextual union signature + signatureList = [signature]; + } + else if (!compareSignaturesIdentical(signatureList[0], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ true, /*ignoreReturnTypes*/ true, compareTypesIdentical)) { + // Signatures aren't identical, do not use + return undefined; + } + else { + // Use this signature for contextual union signature + signatureList.push(signature); + } } - checkSourceElement(node.typeParameter); - registerForUnusedIdentifiersCheck(node); } - - function checkImportType(node: ImportTypeNode) { - checkSourceElement(node.argument); - getTypeFromTypeNode(node); + // Result is union of signatures collected (return type is union of return types of this signature set) + if (signatureList) { + return signatureList.length === 1 ? signatureList[0] : createUnionSignature(signatureList[0], signatureList); } - - function isPrivateWithinAmbient(node: Node): boolean { - return hasModifier(node, ModifierFlags.Private) && !!(node.flags & NodeFlags.Ambient); + } + function checkSpreadExpression(node: SpreadElement, checkMode?: CheckMode): ts.Type { + if (languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(node, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArrays); } - - function getEffectiveDeclarationFlags(n: Declaration, flagsToCheck: ModifierFlags): ModifierFlags { - let flags = getCombinedModifierFlags(n); - - // children of classes (even ambient classes) should not be marked as ambient or export - // because those flags have no useful semantics there. - if (n.parent.kind !== SyntaxKind.InterfaceDeclaration && - n.parent.kind !== SyntaxKind.ClassDeclaration && - n.parent.kind !== SyntaxKind.ClassExpression && - n.flags & NodeFlags.Ambient) { - if (!(flags & ModifierFlags.Ambient) && !(isModuleBlock(n.parent) && isModuleDeclaration(n.parent.parent) && isGlobalScopeAugmentation(n.parent.parent))) { - // It is nested in an ambient context, which means it is automatically exported - flags |= ModifierFlags.Export; + const arrayOrIterableType = checkExpression(node.expression, checkMode); + return checkIteratedTypeOrElementType(IterationUse.Spread, arrayOrIterableType, undefinedType, node.expression); + } + function hasDefaultValue(node: BindingElement | Expression): boolean { + return (node.kind === SyntaxKind.BindingElement && !!(node).initializer) || + (node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.EqualsToken); + } + function checkArrayLiteral(node: ArrayLiteralExpression, checkMode: CheckMode | undefined, forceTuple: boolean | undefined): ts.Type { + const elements = node.elements; + const elementCount = elements.length; + let hasNonEndingSpreadElement = false; + const elementTypes: ts.Type[] = []; + const inDestructuringPattern = isAssignmentTarget(node); + const contextualType = getApparentTypeOfContextualType(node); + const inConstContext = isConstContext(node); + for (let index = 0; index < elementCount; index++) { + const e = elements[index]; + if (inDestructuringPattern && e.kind === SyntaxKind.SpreadElement) { + // Given the following situation: + // var c: {}; + // [...c] = ["", 0]; + // + // c is represented in the tree as a spread element in an array literal. + // But c really functions as a rest element, and its purpose is to provide + // a contextual type for the right hand side of the assignment. Therefore, + // instead of calling checkExpression on "...c", which will give an error + // if c is not iterable/array-like, we need to act as if we are trying to + // get the contextual element type from it. So we do something similar to + // getContextualTypeForElementExpression, which will crucially not error + // if there is no index type / iterated type. + const restArrayType = checkExpression((e).expression, checkMode, forceTuple); + const restElementType = getIndexTypeOfType(restArrayType, IndexKind.Number) || + getIteratedTypeOrElementType(IterationUse.Destructuring, restArrayType, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false); + if (restElementType) { + elementTypes.push(restElementType); } - flags |= ModifierFlags.Ambient; } - - return flags & flagsToCheck; - } - - function checkFunctionOrConstructorSymbol(symbol: Symbol): void { - if (!produceDiagnostics) { - return; + else { + const elementContextualType = getContextualTypeForElementExpression(contextualType, index); + const type = checkExpressionForMutableLocation(e, checkMode, elementContextualType, forceTuple); + elementTypes.push(type); + } + if (index < elementCount - 1 && e.kind === SyntaxKind.SpreadElement) { + hasNonEndingSpreadElement = true; + } + } + if (!hasNonEndingSpreadElement) { + const hasRestElement = elementCount > 0 && elements[elementCount - 1].kind === SyntaxKind.SpreadElement; + const minLength = elementCount - (hasRestElement ? 1 : 0); + // If array literal is actually a destructuring pattern, mark it as an implied type. We do this such + // that we get the same behavior for "var [x, y] = []" and "[x, y] = []". + let tupleResult; + if (inDestructuringPattern && minLength > 0) { + const type = cloneTypeReference((createTupleType(elementTypes, minLength, hasRestElement))); + type.pattern = node; + return type; } - - function getCanonicalOverload(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined): Declaration { - // Consider the canonical set of flags to be the flags of the bodyDeclaration or the first declaration - // Error on all deviations from this canonical set of flags - // The caveat is that if some overloads are defined in lib.d.ts, we don't want to - // report the errors on those. To achieve this, we will say that the implementation is - // the canonical signature only if it is in the same container as the first overload - const implementationSharesContainerWithFirstOverload = implementation !== undefined && implementation.parent === overloads[0].parent; - return implementationSharesContainerWithFirstOverload ? implementation! : overloads[0]; + else if (tupleResult = getArrayLiteralTupleTypeIfApplicable(elementTypes, contextualType, hasRestElement, elementCount, inConstContext)) { + return createArrayLiteralType(tupleResult); } - - function checkFlagAgreementBetweenOverloads(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined, flagsToCheck: ModifierFlags, someOverloadFlags: ModifierFlags, allOverloadFlags: ModifierFlags): void { - // Error if some overloads have a flag that is not shared by all overloads. To find the - // deviations, we XOR someOverloadFlags with allOverloadFlags - const someButNotAllOverloadFlags = someOverloadFlags ^ allOverloadFlags; - if (someButNotAllOverloadFlags !== 0) { - const canonicalFlags = getEffectiveDeclarationFlags(getCanonicalOverload(overloads, implementation), flagsToCheck); - - forEach(overloads, o => { - const deviation = getEffectiveDeclarationFlags(o, flagsToCheck) ^ canonicalFlags; - if (deviation & ModifierFlags.Export) { - error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_exported_or_non_exported); - } - else if (deviation & ModifierFlags.Ambient) { - error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_ambient_or_non_ambient); - } - else if (deviation & (ModifierFlags.Private | ModifierFlags.Protected)) { - error(getNameOfDeclaration(o) || o, Diagnostics.Overload_signatures_must_all_be_public_private_or_protected); - } - else if (deviation & ModifierFlags.Abstract) { - error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_abstract_or_non_abstract); - } - }); - } + else if (forceTuple) { + return createArrayLiteralType(createTupleType(elementTypes, minLength, hasRestElement)); } - - function checkQuestionTokenAgreementBetweenOverloads(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined, someHaveQuestionToken: boolean, allHaveQuestionToken: boolean): void { - if (someHaveQuestionToken !== allHaveQuestionToken) { - const canonicalHasQuestionToken = hasQuestionToken(getCanonicalOverload(overloads, implementation)); - forEach(overloads, o => { - const deviation = hasQuestionToken(o) !== canonicalHasQuestionToken; - if (deviation) { - error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_optional_or_required); - } - }); - } + } + return createArrayLiteralType(createArrayType(elementTypes.length ? + getUnionType(elementTypes, UnionReduction.Subtype) : + strictNullChecks ? implicitNeverType : undefinedWideningType, inConstContext)); + } + function createArrayLiteralType(type: ObjectType) { + if (!(getObjectFlags(type) & ObjectFlags.Reference)) { + return type; + } + let literalType = (type).literalType; + if (!literalType) { + literalType = (type).literalType = cloneTypeReference((type)); + literalType.objectFlags |= ObjectFlags.ArrayLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + } + return literalType; + } + function getArrayLiteralTupleTypeIfApplicable(elementTypes: ts.Type[], contextualType: ts.Type | undefined, hasRestElement: boolean, elementCount = elementTypes.length, readonly = false) { + // Infer a tuple type when the contextual type is or contains a tuple-like type + if (readonly || (contextualType && forEachType(contextualType, isTupleLikeType))) { + return createTupleType(elementTypes, elementCount - (hasRestElement ? 1 : 0), hasRestElement, readonly); + } + } + function isNumericName(name: DeclarationName): boolean { + switch (name.kind) { + case SyntaxKind.ComputedPropertyName: + return isNumericComputedName(name); + case SyntaxKind.Identifier: + return isNumericLiteralName(name.escapedText); + case SyntaxKind.NumericLiteral: + case SyntaxKind.StringLiteral: + return isNumericLiteralName(name.text); + default: + return false; + } + } + function isNumericComputedName(name: ComputedPropertyName): boolean { + // It seems odd to consider an expression of type Any to result in a numeric name, + // but this behavior is consistent with checkIndexedAccess + return isTypeAssignableToKind(checkComputedPropertyName(name), TypeFlags.NumberLike); + } + function isInfinityOrNaNString(name: string | __String): boolean { + return name === "Infinity" || name === "-Infinity" || name === "NaN"; + } + function isNumericLiteralName(name: string | __String) { + // The intent of numeric names is that + // - they are names with text in a numeric form, and that + // - setting properties/indexing with them is always equivalent to doing so with the numeric literal 'numLit', + // acquired by applying the abstract 'ToNumber' operation on the name's text. + // + // The subtlety is in the latter portion, as we cannot reliably say that anything that looks like a numeric literal is a numeric name. + // In fact, it is the case that the text of the name must be equal to 'ToString(numLit)' for this to hold. + // + // Consider the property name '"0xF00D"'. When one indexes with '0xF00D', they are actually indexing with the value of 'ToString(0xF00D)' + // according to the ECMAScript specification, so it is actually as if the user indexed with the string '"61453"'. + // Thus, the text of all numeric literals equivalent to '61543' such as '0xF00D', '0xf00D', '0170015', etc. are not valid numeric names + // because their 'ToString' representation is not equal to their original text. + // This is motivated by ECMA-262 sections 9.3.1, 9.8.1, 11.1.5, and 11.2.1. + // + // Here, we test whether 'ToString(ToNumber(name))' is exactly equal to 'name'. + // The '+' prefix operator is equivalent here to applying the abstract ToNumber operation. + // Applying the 'toString()' method on a number gives us the abstract ToString operation on a number. + // + // Note that this accepts the values 'Infinity', '-Infinity', and 'NaN', and that this is intentional. + // This is desired behavior, because when indexing with them as numeric entities, you are indexing + // with the strings '"Infinity"', '"-Infinity"', and '"NaN"' respectively. + return (+name).toString() === name; + } + function checkComputedPropertyName(node: ComputedPropertyName): ts.Type { + const links = getNodeLinks(node.expression); + if (!links.resolvedType) { + links.resolvedType = checkExpression(node.expression); + // This will allow types number, string, symbol or any. It will also allow enums, the unknown + // type, and any union of these types (like string | number). + if (links.resolvedType.flags & TypeFlags.Nullable || + !isTypeAssignableToKind(links.resolvedType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike) && + !isTypeAssignableTo(links.resolvedType, stringNumberSymbolType)) { + error(node, Diagnostics.A_computed_property_name_must_be_of_type_string_number_symbol_or_any); } - - const flagsToCheck: ModifierFlags = ModifierFlags.Export | ModifierFlags.Ambient | ModifierFlags.Private | ModifierFlags.Protected | ModifierFlags.Abstract; - let someNodeFlags: ModifierFlags = ModifierFlags.None; - let allNodeFlags = flagsToCheck; - let someHaveQuestionToken = false; - let allHaveQuestionToken = true; - let hasOverloads = false; - let bodyDeclaration: FunctionLikeDeclaration | undefined; - let lastSeenNonAmbientDeclaration: FunctionLikeDeclaration | undefined; - let previousDeclaration: SignatureDeclaration | undefined; - - const declarations = symbol.declarations; - const isConstructor = (symbol.flags & SymbolFlags.Constructor) !== 0; - - function reportImplementationExpectedError(node: SignatureDeclaration): void { - if (node.name && nodeIsMissing(node.name)) { - return; - } - - let seen = false; - const subsequentNode = forEachChild(node.parent, c => { - if (seen) { - return c; - } - else { - seen = c === node; + else { + checkThatExpressionIsProperSymbolReference(node.expression, links.resolvedType, /*reportError*/ true); + } + } + return links.resolvedType; + } + function getObjectLiteralIndexInfo(node: ObjectLiteralExpression, offset: number, properties: ts.Symbol[], kind: IndexKind): IndexInfo { + const propTypes: ts.Type[] = []; + for (let i = 0; i < properties.length; i++) { + if (kind === IndexKind.String || isNumericName(node.properties[i + offset].name!)) { + propTypes.push(getTypeOfSymbol(properties[i])); + } + } + const unionType = propTypes.length ? getUnionType(propTypes, UnionReduction.Subtype) : undefinedType; + return createIndexInfo(unionType, isConstContext(node)); + } + function getImmediateAliasedSymbol(symbol: ts.Symbol): ts.Symbol | undefined { + Debug.assert((symbol.flags & SymbolFlags.Alias) !== 0, "Should only get Alias here."); + const links = getSymbolLinks(symbol); + if (!links.immediateTarget) { + const node = getDeclarationOfAliasSymbol(symbol); + if (!node) + return Debug.fail(); + links.immediateTarget = getTargetOfAliasDeclaration(node, /*dontRecursivelyResolve*/ true); + } + return links.immediateTarget; + } + function checkObjectLiteral(node: ObjectLiteralExpression, checkMode?: CheckMode): ts.Type { + const inDestructuringPattern = isAssignmentTarget(node); + // Grammar checking + checkGrammarObjectLiteralExpression(node, inDestructuringPattern); + let propertiesTable: SymbolTable; + let propertiesArray: ts.Symbol[] = []; + let spread: ts.Type = emptyObjectType; + const contextualType = getApparentTypeOfContextualType(node); + const contextualTypeHasPattern = contextualType && contextualType.pattern && + (contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern || contextualType.pattern.kind === SyntaxKind.ObjectLiteralExpression); + const inConstContext = isConstContext(node); + const checkFlags = inConstContext ? CheckFlags.Readonly : 0; + const isInJavascript = isInJSFile(node) && !isInJsonFile(node); + const enumTag = getJSDocEnumTag(node); + const isJSObjectLiteral = !contextualType && isInJavascript && !enumTag; + let objectFlags: ObjectFlags = freshObjectLiteralFlag; + let patternWithComputedProperties = false; + let hasComputedStringProperty = false; + let hasComputedNumberProperty = false; + propertiesTable = createSymbolTable(); + let offset = 0; + for (let i = 0; i < node.properties.length; i++) { + const memberDecl = node.properties[i]; + let member = getSymbolOfNode(memberDecl); + const computedNameType = memberDecl.name && memberDecl.name.kind === SyntaxKind.ComputedPropertyName && !isWellKnownSymbolSyntactically(memberDecl.name.expression) ? + checkComputedPropertyName(memberDecl.name) : undefined; + if (memberDecl.kind === SyntaxKind.PropertyAssignment || + memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment || + isObjectLiteralMethod(memberDecl)) { + let type = memberDecl.kind === SyntaxKind.PropertyAssignment ? checkPropertyAssignment(memberDecl, checkMode) : + memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment ? checkExpressionForMutableLocation(memberDecl.name, checkMode) : + checkObjectLiteralMethod(memberDecl, checkMode); + if (isInJavascript) { + const jsDocType = getTypeForDeclarationFromJSDocComment(memberDecl); + if (jsDocType) { + checkTypeAssignableTo(type, jsDocType, memberDecl); + type = jsDocType; } - }); - // We may be here because of some extra nodes between overloads that could not be parsed into a valid node. - // In this case the subsequent node is not really consecutive (.pos !== node.end), and we must ignore it here. - if (subsequentNode && subsequentNode.pos === node.end) { - if (subsequentNode.kind === node.kind) { - const errorNode: Node = (subsequentNode).name || subsequentNode; - // TODO: GH#17345: These are methods, so handle computed name case. (`Always allowing computed property names is *not* the correct behavior!) - const subsequentName = (subsequentNode).name; - if (node.name && subsequentName && - (isComputedPropertyName(node.name) && isComputedPropertyName(subsequentName) || - !isComputedPropertyName(node.name) && !isComputedPropertyName(subsequentName) && getEscapedTextOfIdentifierOrLiteral(node.name) === getEscapedTextOfIdentifierOrLiteral(subsequentName))) { - const reportError = - (node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature) && - hasModifier(node, ModifierFlags.Static) !== hasModifier(subsequentNode, ModifierFlags.Static); - // we can get here in two cases - // 1. mixed static and instance class members - // 2. something with the same name was defined before the set of overloads that prevents them from merging - // here we'll report error only for the first case since for second we should already report error in binder - if (reportError) { - const diagnostic = hasModifier(node, ModifierFlags.Static) ? Diagnostics.Function_overload_must_be_static : Diagnostics.Function_overload_must_not_be_static; - error(errorNode, diagnostic); - } - return; - } - else if (nodeIsPresent((subsequentNode).body)) { - error(errorNode, Diagnostics.Function_implementation_name_must_be_0, declarationNameToString(node.name)); - return; - } + else if (enumTag && enumTag.typeExpression) { + checkTypeAssignableTo(type, getTypeFromTypeNode(enumTag.typeExpression), memberDecl); } } - const errorNode: Node = node.name || node; - if (isConstructor) { - error(errorNode, Diagnostics.Constructor_implementation_is_missing); + objectFlags |= getObjectFlags(type) & ObjectFlags.PropagatingFlags; + const nameType = computedNameType && isTypeUsableAsPropertyName(computedNameType) ? computedNameType : undefined; + const prop = nameType ? + createSymbol(SymbolFlags.Property | member.flags, getPropertyNameFromType(nameType), checkFlags | CheckFlags.Late) : + createSymbol(SymbolFlags.Property | member.flags, member.escapedName, checkFlags); + if (nameType) { + prop.nameType = nameType; } - else { - // Report different errors regarding non-consecutive blocks of declarations depending on whether - // the node in question is abstract. - if (hasModifier(node, ModifierFlags.Abstract)) { - error(errorNode, Diagnostics.All_declarations_of_an_abstract_method_must_be_consecutive); + if (inDestructuringPattern) { + // If object literal is an assignment pattern and if the assignment pattern specifies a default value + // for the property, make the property optional. + const isOptional = (memberDecl.kind === SyntaxKind.PropertyAssignment && hasDefaultValue(memberDecl.initializer)) || + (memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment && memberDecl.objectAssignmentInitializer); + if (isOptional) { + prop.flags |= SymbolFlags.Optional; } - else { - error(errorNode, Diagnostics.Function_implementation_is_missing_or_not_immediately_following_the_declaration); + } + else if (contextualTypeHasPattern && !(getObjectFlags((contextualType!)) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) { + // If object literal is contextually typed by the implied type of a binding pattern, and if the + // binding pattern specifies a default value for the property, make the property optional. + const impliedProp = getPropertyOfType(contextualType!, member.escapedName); + if (impliedProp) { + prop.flags |= impliedProp.flags & SymbolFlags.Optional; + } + else if (!compilerOptions.suppressExcessPropertyErrors && !getIndexInfoOfType((contextualType!), IndexKind.String)) { + error(memberDecl.name, Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, symbolToString(member), typeToString(contextualType!)); } } - } - - let duplicateFunctionDeclaration = false; - let multipleConstructorImplementation = false; - let hasNonAmbientClass = false; - for (const current of declarations) { - const node = current; - const inAmbientContext = node.flags & NodeFlags.Ambient; - const inAmbientContextOrInterface = node.parent.kind === SyntaxKind.InterfaceDeclaration || node.parent.kind === SyntaxKind.TypeLiteral || inAmbientContext; - if (inAmbientContextOrInterface) { - // check if declarations are consecutive only if they are non-ambient - // 1. ambient declarations can be interleaved - // i.e. this is legal - // declare function foo(); - // declare function bar(); - // declare function foo(); - // 2. mixing ambient and non-ambient declarations is a separate error that will be reported - do not want to report an extra one - previousDeclaration = undefined; + prop.declarations = member.declarations; + prop.parent = member.parent; + if (member.valueDeclaration) { + prop.valueDeclaration = member.valueDeclaration; } - - if ((node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression) && !inAmbientContext) { - hasNonAmbientClass = true; + prop.type = type; + prop.target = member; + member = prop; + } + else if (memberDecl.kind === SyntaxKind.SpreadAssignment) { + if (languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(memberDecl, ExternalEmitHelpers.Assign); } - - if (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature || node.kind === SyntaxKind.Constructor) { - const currentNodeFlags = getEffectiveDeclarationFlags(node, flagsToCheck); - someNodeFlags |= currentNodeFlags; - allNodeFlags &= currentNodeFlags; - someHaveQuestionToken = someHaveQuestionToken || hasQuestionToken(node); - allHaveQuestionToken = allHaveQuestionToken && hasQuestionToken(node); - - if (nodeIsPresent((node as FunctionLikeDeclaration).body) && bodyDeclaration) { - if (isConstructor) { - multipleConstructorImplementation = true; - } - else { - duplicateFunctionDeclaration = true; - } - } - else if (previousDeclaration && previousDeclaration.parent === node.parent && previousDeclaration.end !== node.pos) { - reportImplementationExpectedError(previousDeclaration); - } - - if (nodeIsPresent((node as FunctionLikeDeclaration).body)) { - if (!bodyDeclaration) { - bodyDeclaration = node as FunctionLikeDeclaration; - } + if (propertiesArray.length > 0) { + spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext); + propertiesArray = []; + propertiesTable = createSymbolTable(); + hasComputedStringProperty = false; + hasComputedNumberProperty = false; + } + const type = checkExpression(memberDecl.expression); + if (!isValidSpreadType(type)) { + error(memberDecl, Diagnostics.Spread_types_may_only_be_created_from_object_types); + return errorType; + } + spread = getSpreadType(spread, type, node.symbol, objectFlags, inConstContext); + offset = i + 1; + continue; + } + else { + // TypeScript 1.0 spec (April 2014) + // A get accessor declaration is processed in the same manner as + // an ordinary function declaration(section 6.1) with no parameters. + // A set accessor declaration is processed in the same manner + // as an ordinary function declaration with a single parameter and a Void return type. + Debug.assert(memberDecl.kind === SyntaxKind.GetAccessor || memberDecl.kind === SyntaxKind.SetAccessor); + checkNodeDeferred(memberDecl); + } + if (computedNameType && !(computedNameType.flags & TypeFlags.StringOrNumberLiteralOrUnique)) { + if (isTypeAssignableTo(computedNameType, stringNumberSymbolType)) { + if (isTypeAssignableTo(computedNameType, numberType)) { + hasComputedNumberProperty = true; } else { - hasOverloads = true; + hasComputedStringProperty = true; } - - previousDeclaration = node; - - if (!inAmbientContextOrInterface) { - lastSeenNonAmbientDeclaration = node as FunctionLikeDeclaration; + if (inDestructuringPattern) { + patternWithComputedProperties = true; } } } - - if (multipleConstructorImplementation) { - forEach(declarations, declaration => { - error(declaration, Diagnostics.Multiple_constructor_implementations_are_not_allowed); - }); + else { + propertiesTable.set(member.escapedName, member); } - - if (duplicateFunctionDeclaration) { - forEach(declarations, declaration => { - error(getNameOfDeclaration(declaration), Diagnostics.Duplicate_function_implementation); - }); + propertiesArray.push(member); + } + // If object literal is contextually typed by the implied type of a binding pattern, augment the result + // type with those properties for which the binding pattern specifies a default value. + if (contextualTypeHasPattern) { + for (const prop of getPropertiesOfType(contextualType!)) { + if (!propertiesTable.get(prop.escapedName) && !(spread && getPropertyOfType(spread, prop.escapedName))) { + if (!(prop.flags & SymbolFlags.Optional)) { + error(prop.valueDeclaration || (prop).bindingElement, Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value); + } + propertiesTable.set(prop.escapedName, prop); + propertiesArray.push(prop); + } } - - if (hasNonAmbientClass && !isConstructor && symbol.flags & SymbolFlags.Function) { - // A non-ambient class cannot be an implementation for a non-constructor function/class merge - // TODO: The below just replicates our older error from when classes and functions were - // entirely unable to merge - a more helpful message like "Class declaration cannot implement overload list" - // might be warranted. :shrug: - forEach(declarations, declaration => { - addDuplicateDeclarationError(getNameOfDeclaration(declaration) || declaration, Diagnostics.Duplicate_identifier_0, symbolName(symbol), filter(declarations, d => d !== declaration)); - }); + } + if (spread !== emptyObjectType) { + if (propertiesArray.length > 0) { + spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext); + propertiesArray = []; + propertiesTable = createSymbolTable(); + hasComputedStringProperty = false; + hasComputedNumberProperty = false; } - - // Abstract methods can't have an implementation -- in particular, they don't need one. - if (lastSeenNonAmbientDeclaration && !lastSeenNonAmbientDeclaration.body && - !hasModifier(lastSeenNonAmbientDeclaration, ModifierFlags.Abstract) && !lastSeenNonAmbientDeclaration.questionToken) { - reportImplementationExpectedError(lastSeenNonAmbientDeclaration); + // remap the raw emptyObjectType fed in at the top into a fresh empty object literal type, unique to this use site + return mapType(spread, t => t === emptyObjectType ? createObjectLiteralType() : t); + } + return createObjectLiteralType(); + function createObjectLiteralType() { + const stringIndexInfo = hasComputedStringProperty ? getObjectLiteralIndexInfo(node, offset, propertiesArray, IndexKind.String) : undefined; + const numberIndexInfo = hasComputedNumberProperty ? getObjectLiteralIndexInfo(node, offset, propertiesArray, IndexKind.Number) : undefined; + const result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); + result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + if (isJSObjectLiteral) { + result.objectFlags |= ObjectFlags.JSLiteral; } - - if (hasOverloads) { - checkFlagAgreementBetweenOverloads(declarations, bodyDeclaration, flagsToCheck, someNodeFlags, allNodeFlags); - checkQuestionTokenAgreementBetweenOverloads(declarations, bodyDeclaration, someHaveQuestionToken, allHaveQuestionToken); - - if (bodyDeclaration) { - const signatures = getSignaturesOfSymbol(symbol); - const bodySignature = getSignatureFromDeclaration(bodyDeclaration); - for (const signature of signatures) { - if (!isImplementationCompatibleWithOverload(bodySignature, signature)) { - addRelatedInfo( - error(signature.declaration, Diagnostics.This_overload_signature_is_not_compatible_with_its_implementation_signature), - createDiagnosticForNode(bodyDeclaration, Diagnostics.The_implementation_signature_is_declared_here) - ); - break; - } - } - } + if (patternWithComputedProperties) { + result.objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties; + } + if (inDestructuringPattern) { + result.pattern = node; } + return result; } - - function checkExportsOnMergedDeclarations(node: Node): void { - if (!produceDiagnostics) { - return; + } + function isValidSpreadType(type: ts.Type): boolean { + if (type.flags & TypeFlags.Instantiable) { + const constraint = getBaseConstraintOfType(type); + if (constraint !== undefined) { + return isValidSpreadType(constraint); } - - // if localSymbol is defined on node then node itself is exported - check is required - let symbol = node.localSymbol; - if (!symbol) { - // local symbol is undefined => this declaration is non-exported. - // however symbol might contain other declarations that are exported - symbol = getSymbolOfNode(node)!; - if (!symbol.exportSymbol) { - // this is a pure local symbol (all declarations are non-exported) - no need to check anything - return; + } + return !!(type.flags & (TypeFlags.Any | TypeFlags.NonPrimitive | TypeFlags.Object | TypeFlags.InstantiableNonPrimitive) || + getFalsyFlags(type) & TypeFlags.DefinitelyFalsy && isValidSpreadType(removeDefinitelyFalsyTypes(type)) || + type.flags & TypeFlags.UnionOrIntersection && every((type).types, isValidSpreadType)); + } + function checkJsxSelfClosingElementDeferred(node: JsxSelfClosingElement) { + checkJsxOpeningLikeElementOrOpeningFragment(node); + } + function checkJsxSelfClosingElement(node: JsxSelfClosingElement, _checkMode: CheckMode | undefined): ts.Type { + checkNodeDeferred(node); + return getJsxElementTypeAt(node) || anyType; + } + function checkJsxElementDeferred(node: JsxElement) { + // Check attributes + checkJsxOpeningLikeElementOrOpeningFragment(node.openingElement); + // Perform resolution on the closing tag so that rename/go to definition/etc work + if (isJsxIntrinsicIdentifier(node.closingElement.tagName)) { + getIntrinsicTagSymbol(node.closingElement); + } + else { + checkExpression(node.closingElement.tagName); + } + checkJsxChildren(node); + } + function checkJsxElement(node: JsxElement, _checkMode: CheckMode | undefined): ts.Type { + checkNodeDeferred(node); + return getJsxElementTypeAt(node) || anyType; + } + function checkJsxFragment(node: JsxFragment): ts.Type { + checkJsxOpeningLikeElementOrOpeningFragment(node.openingFragment); + if (compilerOptions.jsx === JsxEmit.React && (compilerOptions.jsxFactory || getSourceFileOfNode(node).pragmas.has("jsx"))) { + error(node, compilerOptions.jsxFactory + ? Diagnostics.JSX_fragment_is_not_supported_when_using_jsxFactory + : Diagnostics.JSX_fragment_is_not_supported_when_using_an_inline_JSX_factory_pragma); + } + checkJsxChildren(node); + return getJsxElementTypeAt(node) || anyType; + } + /** + * Returns true iff the JSX element name would be a valid JS identifier, ignoring restrictions about keywords not being identifiers + */ + function isUnhyphenatedJsxName(name: string | __String) { + // - is the only character supported in JSX attribute names that isn't valid in JavaScript identifiers + return !stringContains((name as string), "-"); + } + /** + * Returns true iff React would emit this tag name as a string rather than an identifier or qualified name + */ + function isJsxIntrinsicIdentifier(tagName: JsxTagNameExpression): boolean { + return tagName.kind === SyntaxKind.Identifier && isIntrinsicJsxName(tagName.escapedText); + } + function checkJsxAttribute(node: JsxAttribute, checkMode?: CheckMode) { + return node.initializer + ? checkExpressionForMutableLocation(node.initializer, checkMode) + : trueType; // is sugar for + } + /** + * Get attributes type of the JSX opening-like element. The result is from resolving "attributes" property of the opening-like element. + * + * @param openingLikeElement a JSX opening-like element + * @param filter a function to remove attributes that will not participate in checking whether attributes are assignable + * @return an anonymous type (similar to the one returned by checkObjectLiteral) in which its properties are attributes property. + * @remarks Because this function calls getSpreadType, it needs to use the same checks as checkObjectLiteral, + * which also calls getSpreadType. + */ + function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxOpeningLikeElement, checkMode: CheckMode | undefined) { + const attributes = openingLikeElement.attributes; + let attributesTable = createSymbolTable(); + let spread: ts.Type = emptyJsxObjectType; + let hasSpreadAnyType = false; + let typeToIntersect: ts.Type | undefined; + let explicitlySpecifyChildrenAttribute = false; + let objectFlags: ObjectFlags = ObjectFlags.JsxAttributes; + const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(openingLikeElement)); + for (const attributeDecl of attributes.properties) { + const member = attributeDecl.symbol; + if (isJsxAttribute(attributeDecl)) { + const exprType = checkJsxAttribute(attributeDecl, checkMode); + objectFlags |= getObjectFlags(exprType) & ObjectFlags.PropagatingFlags; + const attributeSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient | member.flags, member.escapedName); + attributeSymbol.declarations = member.declarations; + attributeSymbol.parent = member.parent; + if (member.valueDeclaration) { + attributeSymbol.valueDeclaration = member.valueDeclaration; + } + attributeSymbol.type = exprType; + attributeSymbol.target = member; + attributesTable.set(attributeSymbol.escapedName, attributeSymbol); + if (attributeDecl.name.escapedText === jsxChildrenPropertyName) { + explicitlySpecifyChildrenAttribute = true; } } - - // run the check only for the first declaration in the list - if (getDeclarationOfKind(symbol, node.kind) !== node) { - return; - } - - let exportedDeclarationSpaces = DeclarationSpaces.None; - let nonExportedDeclarationSpaces = DeclarationSpaces.None; - let defaultExportedDeclarationSpaces = DeclarationSpaces.None; - for (const d of symbol.declarations) { - const declarationSpaces = getDeclarationSpaces(d); - const effectiveDeclarationFlags = getEffectiveDeclarationFlags(d, ModifierFlags.Export | ModifierFlags.Default); - - if (effectiveDeclarationFlags & ModifierFlags.Export) { - if (effectiveDeclarationFlags & ModifierFlags.Default) { - defaultExportedDeclarationSpaces |= declarationSpaces; - } - else { - exportedDeclarationSpaces |= declarationSpaces; - } + else { + Debug.assert(attributeDecl.kind === SyntaxKind.JsxSpreadAttribute); + if (attributesTable.size > 0) { + spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false); + attributesTable = createSymbolTable(); + } + const exprType = checkExpressionCached(attributeDecl.expression, checkMode); + if (isTypeAny(exprType)) { + hasSpreadAnyType = true; + } + if (isValidSpreadType(exprType)) { + spread = getSpreadType(spread, exprType, attributes.symbol, objectFlags, /*readonly*/ false); } else { - nonExportedDeclarationSpaces |= declarationSpaces; + typeToIntersect = typeToIntersect ? getIntersectionType([typeToIntersect, exprType]) : exprType; + } + } + } + if (!hasSpreadAnyType) { + if (attributesTable.size > 0) { + spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false); + } + } + // Handle children attribute + const parent = openingLikeElement.parent.kind === SyntaxKind.JsxElement ? openingLikeElement.parent as JsxElement : undefined; + // We have to check that openingElement of the parent is the one we are visiting as this may not be true for selfClosingElement + if (parent && parent.openingElement === openingLikeElement && parent.children.length > 0) { + const childrenTypes: ts.Type[] = checkJsxChildren(parent, checkMode); + if (!hasSpreadAnyType && jsxChildrenPropertyName && jsxChildrenPropertyName !== "") { + // Error if there is a attribute named "children" explicitly specified and children element. + // This is because children element will overwrite the value from attributes. + // Note: we will not warn "children" attribute overwritten if "children" attribute is specified in object spread. + if (explicitlySpecifyChildrenAttribute) { + error(attributes, Diagnostics._0_are_specified_twice_The_attribute_named_0_will_be_overwritten, unescapeLeadingUnderscores(jsxChildrenPropertyName)); + } + const contextualType = getApparentTypeOfContextualType(openingLikeElement.attributes); + const childrenContextualType = contextualType && getTypeOfPropertyOfContextualType(contextualType, jsxChildrenPropertyName); + // If there are children in the body of JSX element, create dummy attribute "children" with the union of children types so that it will pass the attribute checking process + const childrenPropSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, jsxChildrenPropertyName); + childrenPropSymbol.type = childrenTypes.length === 1 ? + childrenTypes[0] : + (getArrayLiteralTupleTypeIfApplicable(childrenTypes, childrenContextualType, /*hasRestElement*/ false) || createArrayType(getUnionType(childrenTypes))); + // Fake up a property declaration for the children + childrenPropSymbol.valueDeclaration = createPropertySignature(/*modifiers*/ undefined, unescapeLeadingUnderscores(jsxChildrenPropertyName), /*questionToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined); + childrenPropSymbol.valueDeclaration.parent = attributes; + childrenPropSymbol.valueDeclaration.symbol = childrenPropSymbol; + const childPropMap = createSymbolTable(); + childPropMap.set(jsxChildrenPropertyName, childrenPropSymbol); + spread = getSpreadType(spread, createAnonymousType(attributes.symbol, childPropMap, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined), attributes.symbol, objectFlags, /*readonly*/ false); + } + } + if (hasSpreadAnyType) { + return anyType; + } + if (typeToIntersect && spread !== emptyJsxObjectType) { + return getIntersectionType([typeToIntersect, spread]); + } + return typeToIntersect || (spread === emptyJsxObjectType ? createJsxAttributesType() : spread); + /** + * Create anonymous type from given attributes symbol table. + * @param symbol a symbol of JsxAttributes containing attributes corresponding to attributesTable + * @param attributesTable a symbol table of attributes property + */ + function createJsxAttributesType() { + objectFlags |= freshObjectLiteralFlag; + const result = createAnonymousType(attributes.symbol, attributesTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined); + result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + return result; + } + } + function checkJsxChildren(node: JsxElement | JsxFragment, checkMode?: CheckMode) { + const childrenTypes: ts.Type[] = []; + for (const child of node.children) { + // In React, JSX text that contains only whitespaces will be ignored so we don't want to type-check that + // because then type of children property will have constituent of string type. + if (child.kind === SyntaxKind.JsxText) { + if (!child.containsOnlyTriviaWhiteSpaces) { + childrenTypes.push(stringType); } } - - // Spaces for anything not declared a 'default export'. - const nonDefaultExportedDeclarationSpaces = exportedDeclarationSpaces | nonExportedDeclarationSpaces; - - const commonDeclarationSpacesForExportsAndLocals = exportedDeclarationSpaces & nonExportedDeclarationSpaces; - const commonDeclarationSpacesForDefaultAndNonDefault = defaultExportedDeclarationSpaces & nonDefaultExportedDeclarationSpaces; - - if (commonDeclarationSpacesForExportsAndLocals || commonDeclarationSpacesForDefaultAndNonDefault) { - // declaration spaces for exported and non-exported declarations intersect - for (const d of symbol.declarations) { - const declarationSpaces = getDeclarationSpaces(d); - - const name = getNameOfDeclaration(d); - // Only error on the declarations that contributed to the intersecting spaces. - if (declarationSpaces & commonDeclarationSpacesForDefaultAndNonDefault) { - error(name, Diagnostics.Merged_declaration_0_cannot_include_a_default_export_declaration_Consider_adding_a_separate_export_default_0_declaration_instead, declarationNameToString(name)); - } - else if (declarationSpaces & commonDeclarationSpacesForExportsAndLocals) { - error(name, Diagnostics.Individual_declarations_in_merged_declaration_0_must_be_all_exported_or_all_local, declarationNameToString(name)); - } + else { + childrenTypes.push(checkExpressionForMutableLocation(child, checkMode)); + } + } + return childrenTypes; + } + /** + * Check attributes property of opening-like element. This function is called during chooseOverload to get call signature of a JSX opening-like element. + * (See "checkApplicableSignatureForJsxOpeningLikeElement" for how the function is used) + * @param node a JSXAttributes to be resolved of its type + */ + function checkJsxAttributes(node: JsxAttributes, checkMode: CheckMode | undefined) { + return createJsxAttributesTypeFromAttributesProperty(node.parent, checkMode); + } + function getJsxType(name: __String, location: Node | undefined) { + const namespace = getJsxNamespaceAt(location); + const exports = namespace && getExportsOfSymbol(namespace); + const typeSymbol = exports && getSymbol(exports, name, SymbolFlags.Type); + return typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType; + } + /** + * Looks up an intrinsic tag name and returns a symbol that either points to an intrinsic + * property (in which case nodeLinks.jsxFlags will be IntrinsicNamedElement) or an intrinsic + * string index signature (in which case nodeLinks.jsxFlags will be IntrinsicIndexedElement). + * May also return unknownSymbol if both of these lookups fail. + */ + function getIntrinsicTagSymbol(node: JsxOpeningLikeElement | JsxClosingElement): ts.Symbol { + const links = getNodeLinks(node); + if (!links.resolvedSymbol) { + const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, node); + if (intrinsicElementsType !== errorType) { + // Property case + if (!isIdentifier(node.tagName)) + return Debug.fail(); + const intrinsicProp = getPropertyOfType(intrinsicElementsType, node.tagName.escapedText); + if (intrinsicProp) { + links.jsxFlags |= JsxFlags.IntrinsicNamedElement; + return links.resolvedSymbol = intrinsicProp; + } + // Intrinsic string indexer case + const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, IndexKind.String); + if (indexSignatureType) { + links.jsxFlags |= JsxFlags.IntrinsicIndexedElement; + return links.resolvedSymbol = intrinsicElementsType.symbol; } + // Wasn't found + error(node, Diagnostics.Property_0_does_not_exist_on_type_1, idText(node.tagName), "JSX." + JsxNames.IntrinsicElements); + return links.resolvedSymbol = unknownSymbol; } - - function getDeclarationSpaces(decl: Declaration): DeclarationSpaces { - let d = decl as Node; - switch (d.kind) { - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - - // A jsdoc typedef and callback are, by definition, type aliases. - // falls through - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocEnumTag: - return DeclarationSpaces.ExportType; - case SyntaxKind.ModuleDeclaration: - return isAmbientModule(d as ModuleDeclaration) || getModuleInstanceState(d as ModuleDeclaration) !== ModuleInstanceState.NonInstantiated - ? DeclarationSpaces.ExportNamespace | DeclarationSpaces.ExportValue - : DeclarationSpaces.ExportNamespace; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.EnumDeclaration: - return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue; - case SyntaxKind.SourceFile: - return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue | DeclarationSpaces.ExportNamespace; - case SyntaxKind.ExportAssignment: - // Export assigned entity name expressions act as aliases and should fall through, otherwise they export values - if (!isEntityNameExpression((d as ExportAssignment).expression)) { - return DeclarationSpaces.ExportValue; - } - d = (d as ExportAssignment).expression; - - // The below options all declare an Alias, which is allowed to merge with other values within the importing module. - // falls through - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.NamespaceImport: - case SyntaxKind.ImportClause: - let result = DeclarationSpaces.None; - const target = resolveAlias(getSymbolOfNode(d)!); - forEach(target.declarations, d => { result |= getDeclarationSpaces(d); }); - return result; - case SyntaxKind.VariableDeclaration: - case SyntaxKind.BindingElement: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ImportSpecifier: // https://github.com/Microsoft/TypeScript/pull/7591 - return DeclarationSpaces.ExportValue; - default: - return Debug.failBadSyntaxKind(d); + else { + if (noImplicitAny) { + error(node, Diagnostics.JSX_element_implicitly_has_type_any_because_no_interface_JSX_0_exists, unescapeLeadingUnderscores(JsxNames.IntrinsicElements)); } + return links.resolvedSymbol = unknownSymbol; } } - - function getAwaitedTypeOfPromise(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { - const promisedType = getPromisedTypeOfPromise(type, errorNode); - return promisedType && getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0); + return links.resolvedSymbol; + } + function getJsxNamespaceAt(location: Node | undefined): ts.Symbol { + const links = location && getNodeLinks(location); + if (links && links.jsxNamespace) { + return links.jsxNamespace; + } + if (!links || links.jsxNamespace !== false) { + const namespaceName = getJsxNamespace(location); + const resolvedNamespace = resolveName(location, namespaceName, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined, namespaceName, /*isUse*/ false); + if (resolvedNamespace) { + const candidate = resolveSymbol(getSymbol(getExportsOfSymbol(resolveSymbol(resolvedNamespace)), JsxNames.JSX, SymbolFlags.Namespace)); + if (candidate) { + if (links) { + links.jsxNamespace = candidate; + } + return candidate; + } + if (links) { + links.jsxNamespace = false; + } + } } - - /** - * Gets the "promised type" of a promise. - * @param type The type of the promise. - * @remarks The "promised type" of a type is the type of the "value" parameter of the "onfulfilled" callback. - */ - function getPromisedTypeOfPromise(promise: Type, errorNode?: Node): Type | undefined { - // - // { // promise - // then( // thenFunction - // onfulfilled: ( // onfulfilledParameterType - // value: T // valueParameterType - // ) => any - // ): any; - // } - // - - if (isTypeAny(promise)) { - return undefined; + // JSX global fallback + return getGlobalSymbol(JsxNames.JSX, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined)!; // TODO: GH#18217 + } + /** + * Look into JSX namespace and then look for container with matching name as nameOfAttribPropContainer. + * Get a single property from that container if existed. Report an error if there are more than one property. + * + * @param nameOfAttribPropContainer a string of value JsxNames.ElementAttributesPropertyNameContainer or JsxNames.ElementChildrenAttributeNameContainer + * if other string is given or the container doesn't exist, return undefined. + */ + function getNameFromJsxElementAttributesContainer(nameOfAttribPropContainer: __String, jsxNamespace: ts.Symbol): __String | undefined { + // JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute [symbol] + const jsxElementAttribPropInterfaceSym = jsxNamespace && getSymbol((jsxNamespace.exports!), nameOfAttribPropContainer, SymbolFlags.Type); + // JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute [type] + const jsxElementAttribPropInterfaceType = jsxElementAttribPropInterfaceSym && getDeclaredTypeOfSymbol(jsxElementAttribPropInterfaceSym); + // The properties of JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute + const propertiesOfJsxElementAttribPropInterface = jsxElementAttribPropInterfaceType && getPropertiesOfType(jsxElementAttribPropInterfaceType); + if (propertiesOfJsxElementAttribPropInterface) { + // Element Attributes has zero properties, so the element attributes type will be the class instance type + if (propertiesOfJsxElementAttribPropInterface.length === 0) { + return "" as __String; + } + // Element Attributes has one property, so the element attributes type will be the type of the corresponding + // property of the class instance type + else if (propertiesOfJsxElementAttribPropInterface.length === 1) { + return propertiesOfJsxElementAttribPropInterface[0].escapedName; + } + else if (propertiesOfJsxElementAttribPropInterface.length > 1) { + // More than one property on ElementAttributesProperty is an error + error(jsxElementAttribPropInterfaceSym!.declarations[0], Diagnostics.The_global_type_JSX_0_may_not_have_more_than_one_property, unescapeLeadingUnderscores(nameOfAttribPropContainer)); + } + } + return undefined; + } + function getJsxLibraryManagedAttributes(jsxNamespace: ts.Symbol) { + // JSX.LibraryManagedAttributes [symbol] + return jsxNamespace && getSymbol((jsxNamespace.exports!), JsxNames.LibraryManagedAttributes, SymbolFlags.Type); + } + /// e.g. "props" for React.d.ts, + /// or 'undefined' if ElementAttributesProperty doesn't exist (which means all + /// non-intrinsic elements' attributes type is 'any'), + /// or '' if it has 0 properties (which means every + /// non-intrinsic elements' attributes type is the element instance type) + function getJsxElementPropertiesName(jsxNamespace: ts.Symbol) { + return getNameFromJsxElementAttributesContainer(JsxNames.ElementAttributesPropertyNameContainer, jsxNamespace); + } + function getJsxElementChildrenPropertyName(jsxNamespace: ts.Symbol): __String | undefined { + return getNameFromJsxElementAttributesContainer(JsxNames.ElementChildrenAttributeNameContainer, jsxNamespace); + } + function getUninstantiatedJsxSignaturesOfType(elementType: ts.Type, caller: JsxOpeningLikeElement): readonly ts.Signature[] { + if (elementType.flags & TypeFlags.String) { + return [anySignature]; + } + else if (elementType.flags & TypeFlags.StringLiteral) { + const intrinsicType = getIntrinsicAttributesTypeFromStringLiteralType((elementType as StringLiteralType), caller); + if (!intrinsicType) { + error(caller, Diagnostics.Property_0_does_not_exist_on_type_1, (elementType as StringLiteralType).value, "JSX." + JsxNames.IntrinsicElements); + return emptyArray; } - - const typeAsPromise = promise; - if (typeAsPromise.promisedTypeOfPromise) { - return typeAsPromise.promisedTypeOfPromise; + else { + const fakeSignature = createSignatureForJSXIntrinsic(caller, intrinsicType); + return [fakeSignature]; } - - if (isReferenceToType(promise, getGlobalPromiseType(/*reportErrors*/ false))) { - return typeAsPromise.promisedTypeOfPromise = getTypeArguments(promise)[0]; + } + const apparentElemType = getApparentType(elementType); + // Resolve the signatures, preferring constructor + let signatures = getSignaturesOfType(apparentElemType, SignatureKind.Construct); + if (signatures.length === 0) { + // No construct signatures, try call signatures + signatures = getSignaturesOfType(apparentElemType, SignatureKind.Call); + } + if (signatures.length === 0 && apparentElemType.flags & TypeFlags.Union) { + // If each member has some combination of new/call signatures; make a union signature list for those + signatures = getUnionSignatures(map((apparentElemType as UnionType).types, t => getUninstantiatedJsxSignaturesOfType(t, caller))); + } + return signatures; + } + function getIntrinsicAttributesTypeFromStringLiteralType(type: StringLiteralType, location: Node): ts.Type | undefined { + // If the elemType is a stringLiteral type, we can then provide a check to make sure that the string literal type is one of the Jsx intrinsic element type + // For example: + // var CustomTag: "h1" = "h1"; + // Hello World + const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, location); + if (intrinsicElementsType !== errorType) { + const stringLiteralTypeName = type.value; + const intrinsicProp = getPropertyOfType(intrinsicElementsType, escapeLeadingUnderscores(stringLiteralTypeName)); + if (intrinsicProp) { + return getTypeOfSymbol(intrinsicProp); + } + const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, IndexKind.String); + if (indexSignatureType) { + return indexSignatureType; } - - const thenFunction = getTypeOfPropertyOfType(promise, "then" as __String)!; // TODO: GH#18217 - if (isTypeAny(thenFunction)) { - return undefined; + return undefined; + } + // If we need to report an error, we already done so here. So just return any to prevent any more error downstream + return anyType; + } + function checkJsxReturnAssignableToAppropriateBound(refKind: JsxReferenceKind, elemInstanceType: ts.Type, openingLikeElement: Node) { + if (refKind === JsxReferenceKind.Function) { + const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement); + if (sfcReturnConstraint) { + checkTypeRelatedTo(elemInstanceType, sfcReturnConstraint, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements); } - - const thenSignatures = thenFunction ? getSignaturesOfType(thenFunction, SignatureKind.Call) : emptyArray; - if (thenSignatures.length === 0) { - if (errorNode) { - error(errorNode, Diagnostics.A_promise_must_have_a_then_method); - } - return undefined; + } + else if (refKind === JsxReferenceKind.Component) { + const classConstraint = getJsxElementClassTypeAt(openingLikeElement); + if (classConstraint) { + // Issue an error if this return type isn't assignable to JSX.ElementClass or JSX.Element, failing that + checkTypeRelatedTo(elemInstanceType, classConstraint, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements); } - - const onfulfilledParameterType = getTypeWithFacts(getUnionType(map(thenSignatures, getTypeOfFirstParameterOfSignature)), TypeFacts.NEUndefinedOrNull); - if (isTypeAny(onfulfilledParameterType)) { - return undefined; + } + else { // Mixed + const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement); + const classConstraint = getJsxElementClassTypeAt(openingLikeElement); + if (!sfcReturnConstraint || !classConstraint) { + return; } - - const onfulfilledParameterSignatures = getSignaturesOfType(onfulfilledParameterType, SignatureKind.Call); - if (onfulfilledParameterSignatures.length === 0) { - if (errorNode) { - error(errorNode, Diagnostics.The_first_parameter_of_the_then_method_of_a_promise_must_be_a_callback); - } - return undefined; + const combined = getUnionType([sfcReturnConstraint, classConstraint]); + checkTypeRelatedTo(elemInstanceType, combined, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements); + } + } + /** + * Get attributes type of the given intrinsic opening-like Jsx element by resolving the tag name. + * The function is intended to be called from a function which has checked that the opening element is an intrinsic element. + * @param node an intrinsic JSX opening-like element + */ + function getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node: JsxOpeningLikeElement): ts.Type { + Debug.assert(isJsxIntrinsicIdentifier(node.tagName)); + const links = getNodeLinks(node); + if (!links.resolvedJsxElementAttributesType) { + const symbol = getIntrinsicTagSymbol(node); + if (links.jsxFlags & JsxFlags.IntrinsicNamedElement) { + return links.resolvedJsxElementAttributesType = getTypeOfSymbol(symbol); + } + else if (links.jsxFlags & JsxFlags.IntrinsicIndexedElement) { + return links.resolvedJsxElementAttributesType = + (getIndexTypeOfType(getDeclaredTypeOfSymbol(symbol), IndexKind.String)!); + } + else { + return links.resolvedJsxElementAttributesType = errorType; } - - return typeAsPromise.promisedTypeOfPromise = getUnionType(map(onfulfilledParameterSignatures, getTypeOfFirstParameterOfSignature), UnionReduction.Subtype); } - - /** - * Gets the "awaited type" of a type. - * @param type The type to await. - * @remarks The "awaited type" of an expression is its "promised type" if the expression is a - * Promise-like type; otherwise, it is the type of the expression. This is used to reflect - * The runtime behavior of the `await` keyword. - */ - function checkAwaitedType(type: Type, errorNode: Node, diagnosticMessage: DiagnosticMessage, arg0?: string | number): Type { - const awaitedType = getAwaitedType(type, errorNode, diagnosticMessage, arg0); - return awaitedType || errorType; + return links.resolvedJsxElementAttributesType; + } + function getJsxElementClassTypeAt(location: Node): ts.Type | undefined { + const type = getJsxType(JsxNames.ElementClass, location); + if (type === errorType) + return undefined; + return type; + } + function getJsxElementTypeAt(location: Node): ts.Type { + return getJsxType(JsxNames.Element, location); + } + function getJsxStatelessElementTypeAt(location: Node): ts.Type | undefined { + const jsxElementType = getJsxElementTypeAt(location); + if (jsxElementType) { + return getUnionType([jsxElementType, nullType]); } - - function getAwaitedType(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { - const typeAsAwaitable = type; - if (typeAsAwaitable.awaitedTypeOfType) { - return typeAsAwaitable.awaitedTypeOfType; + } + /** + * Returns all the properties of the Jsx.IntrinsicElements interface + */ + function getJsxIntrinsicTagNamesAt(location: Node): ts.Symbol[] { + const intrinsics = getJsxType(JsxNames.IntrinsicElements, location); + return intrinsics ? getPropertiesOfType(intrinsics) : emptyArray; + } + function checkJsxPreconditions(errorNode: Node) { + // Preconditions for using JSX + if ((compilerOptions.jsx || JsxEmit.None) === JsxEmit.None) { + error(errorNode, Diagnostics.Cannot_use_JSX_unless_the_jsx_flag_is_provided); + } + if (getJsxElementTypeAt(errorNode) === undefined) { + if (noImplicitAny) { + error(errorNode, Diagnostics.JSX_element_implicitly_has_type_any_because_the_global_type_JSX_Element_does_not_exist); } - - if (isTypeAny(type)) { - return typeAsAwaitable.awaitedTypeOfType = type; + } + } + function checkJsxOpeningLikeElementOrOpeningFragment(node: JsxOpeningLikeElement | JsxOpeningFragment) { + const isNodeOpeningLikeElement = isJsxOpeningLikeElement(node); + if (isNodeOpeningLikeElement) { + checkGrammarJsxElement((node)); + } + checkJsxPreconditions(node); + // The reactNamespace/jsxFactory's root symbol should be marked as 'used' so we don't incorrectly elide its import. + // And if there is no reactNamespace/jsxFactory's symbol in scope when targeting React emit, we should issue an error. + const reactRefErr = diagnostics && compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined; + const reactNamespace = getJsxNamespace(node); + const reactLocation = isNodeOpeningLikeElement ? (node).tagName : node; + const reactSym = resolveName(reactLocation, reactNamespace, SymbolFlags.Value, reactRefErr, reactNamespace, /*isUse*/ true); + if (reactSym) { + // Mark local symbol as referenced here because it might not have been marked + // if jsx emit was not react as there wont be error being emitted + reactSym.isReferenced = SymbolFlags.All; + // If react symbol is alias, mark it as refereced + if (reactSym.flags & SymbolFlags.Alias) { + markAliasSymbolAsReferenced(reactSym); + } + } + if (isNodeOpeningLikeElement) { + const sig = getResolvedSignature((node as JsxOpeningLikeElement)); + checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind((node as JsxOpeningLikeElement)), getReturnTypeOfSignature(sig), node); + } + } + /** + * Check if a property with the given name is known anywhere in the given type. In an object type, a property + * is considered known if + * 1. the object type is empty and the check is for assignability, or + * 2. if the object type has index signatures, or + * 3. if the property is actually declared in the object type + * (this means that 'toString', for example, is not usually a known property). + * 4. In a union or intersection type, + * a property is considered known if it is known in any constituent type. + * @param targetType a type to search a given name in + * @param name a property name to search + * @param isComparingJsxAttributes a boolean flag indicating whether we are searching in JsxAttributesType + */ + function isKnownProperty(targetType: ts.Type, name: __String, isComparingJsxAttributes: boolean): boolean { + if (targetType.flags & TypeFlags.Object) { + const resolved = resolveStructuredTypeMembers((targetType as ObjectType)); + if (resolved.stringIndexInfo || + resolved.numberIndexInfo && isNumericLiteralName(name) || + getPropertyOfObjectType(targetType, name) || + isComparingJsxAttributes && !isUnhyphenatedJsxName(name)) { + // For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known. + return true; } - - if (type.flags & TypeFlags.Union) { - let types: Type[] | undefined; - for (const constituentType of (type).types) { - types = append(types, getAwaitedType(constituentType, errorNode, diagnosticMessage, arg0)); - } - - if (!types) { - return undefined; + } + else if (targetType.flags & TypeFlags.UnionOrIntersection && isExcessPropertyCheckTarget(targetType)) { + for (const t of (targetType as UnionOrIntersectionType).types) { + if (isKnownProperty(t, name, isComparingJsxAttributes)) { + return true; } - - return typeAsAwaitable.awaitedTypeOfType = getUnionType(types); } - - const promisedType = getPromisedTypeOfPromise(type); - if (promisedType) { - if (type.id === promisedType.id || awaitedTypeStack.indexOf(promisedType.id) >= 0) { - // Verify that we don't have a bad actor in the form of a promise whose - // promised type is the same as the promise type, or a mutually recursive - // promise. If so, we return undefined as we cannot guess the shape. If this - // were the actual case in the JavaScript, this Promise would never resolve. - // - // An example of a bad actor with a singly-recursive promise type might - // be: - // - // interface BadPromise { - // then( - // onfulfilled: (value: BadPromise) => any, - // onrejected: (error: any) => any): BadPromise; - // } - // The above interface will pass the PromiseLike check, and return a - // promised type of `BadPromise`. Since this is a self reference, we - // don't want to keep recursing ad infinitum. - // - // An example of a bad actor in the form of a mutually-recursive - // promise type might be: - // - // interface BadPromiseA { - // then( - // onfulfilled: (value: BadPromiseB) => any, - // onrejected: (error: any) => any): BadPromiseB; - // } - // - // interface BadPromiseB { - // then( - // onfulfilled: (value: BadPromiseA) => any, - // onrejected: (error: any) => any): BadPromiseA; - // } - // - if (errorNode) { - error(errorNode, Diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method); - } - return undefined; - } - - // Keep track of the type we're about to unwrap to avoid bad recursive promise types. - // See the comments above for more information. - awaitedTypeStack.push(type.id); - const awaitedType = getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0); - awaitedTypeStack.pop(); - - if (!awaitedType) { - return undefined; - } - - return typeAsAwaitable.awaitedTypeOfType = awaitedType; + } + return false; + } + function isExcessPropertyCheckTarget(type: ts.Type): boolean { + return !!(type.flags & TypeFlags.Object && !(getObjectFlags(type) & ObjectFlags.ObjectLiteralPatternWithComputedProperties) || + type.flags & TypeFlags.NonPrimitive || + type.flags & TypeFlags.Union && some((type).types, isExcessPropertyCheckTarget) || + type.flags & TypeFlags.Intersection && every((type).types, isExcessPropertyCheckTarget)); + } + function checkJsxExpression(node: JsxExpression, checkMode?: CheckMode) { + checkGrammarJsxExpression(node); + if (node.expression) { + const type = checkExpression(node.expression, checkMode); + if (node.dotDotDotToken && type !== anyType && !isArrayType(type)) { + error(node, Diagnostics.JSX_spread_child_must_be_an_array_type); } - - // The type was not a promise, so it could not be unwrapped any further. - // As long as the type does not have a callable "then" property, it is - // safe to return the type; otherwise, an error will be reported in - // the call to getNonThenableType and we will return undefined. - // - // An example of a non-promise "thenable" might be: - // - // await { then(): void {} } - // - // The "thenable" does not match the minimal definition for a promise. When - // a Promise/A+-compatible or ES6 promise tries to adopt this value, the promise - // will never settle. We treat this as an error to help flag an early indicator - // of a runtime problem. If the user wants to return this value from an async - // function, they would need to wrap it in some other value. If they want it to - // be treated as a promise, they can cast to . - const thenFunction = getTypeOfPropertyOfType(type, "then" as __String); - if (thenFunction && getSignaturesOfType(thenFunction, SignatureKind.Call).length > 0) { - if (errorNode) { - if (!diagnosticMessage) return Debug.fail(); - error(errorNode, diagnosticMessage, arg0); + return type; + } + else { + return errorType; + } + } + function getDeclarationNodeFlagsFromSymbol(s: ts.Symbol): NodeFlags { + return s.valueDeclaration ? getCombinedNodeFlags(s.valueDeclaration) : 0; + } + /** + * Return whether this symbol is a member of a prototype somewhere + * Note that this is not tracked well within the compiler, so the answer may be incorrect. + */ + function isPrototypeProperty(symbol: ts.Symbol) { + if (symbol.flags & SymbolFlags.Method || getCheckFlags(symbol) & CheckFlags.SyntheticMethod) { + return true; + } + if (isInJSFile(symbol.valueDeclaration)) { + const parent = symbol.valueDeclaration.parent; + return parent && isBinaryExpression(parent) && + getAssignmentDeclarationKind(parent) === AssignmentDeclarationKind.PrototypeProperty; + } + } + /** + * Check whether the requested property access is valid. + * Returns true if node is a valid property access, and false otherwise. + * @param node The node to be checked. + * @param isSuper True if the access is from `super.`. + * @param type The type of the object whose property is being accessed. (Not the type of the property.) + * @param prop The symbol for the property being accessed. + */ + function checkPropertyAccessibility(node: PropertyAccessExpression | QualifiedName | PropertyAccessExpression | VariableDeclaration | ParameterDeclaration | ImportTypeNode | PropertyAssignment | ShorthandPropertyAssignment | BindingElement, isSuper: boolean, type: ts.Type, prop: ts.Symbol): boolean { + const flags = getDeclarationModifierFlagsFromSymbol(prop); + const errorNode = node.kind === SyntaxKind.QualifiedName ? node.right : node.kind === SyntaxKind.ImportType ? node : node.name; + if (getCheckFlags(prop) & CheckFlags.ContainsPrivate) { + // Synthetic property with private constituent property + error(errorNode, Diagnostics.Property_0_has_conflicting_declarations_and_is_inaccessible_in_type_1, symbolToString(prop), typeToString(type)); + return false; + } + if (isSuper) { + // TS 1.0 spec (April 2014): 4.8.2 + // - In a constructor, instance member function, instance member accessor, or + // instance member variable initializer where this references a derived class instance, + // a super property access is permitted and must specify a public instance member function of the base class. + // - In a static member function or static member accessor + // where this references the constructor function object of a derived class, + // a super property access is permitted and must specify a public static member function of the base class. + if (languageVersion < ScriptTarget.ES2015) { + if (symbolHasNonMethodDeclaration(prop)) { + error(errorNode, Diagnostics.Only_public_and_protected_methods_of_the_base_class_are_accessible_via_the_super_keyword); + return false; } - return undefined; } - - return typeAsAwaitable.awaitedTypeOfType = type; + if (flags & ModifierFlags.Abstract) { + // A method cannot be accessed in a super property access if the method is abstract. + // This error could mask a private property access error. But, a member + // cannot simultaneously be private and abstract, so this will trigger an + // additional error elsewhere. + error(errorNode, Diagnostics.Abstract_method_0_in_class_1_cannot_be_accessed_via_super_expression, symbolToString(prop), typeToString(getDeclaringClass(prop)!)); + return false; + } + } + // Referencing abstract properties within their own constructors is not allowed + if ((flags & ModifierFlags.Abstract) && isThisProperty(node) && symbolHasNonMethodDeclaration(prop)) { + const declaringClassDeclaration = getClassLikeDeclarationOfSymbol((getParentOfSymbol(prop)!)); + if (declaringClassDeclaration && isNodeUsedDuringClassInitialization(node)) { + error(errorNode, Diagnostics.Abstract_property_0_in_class_1_cannot_be_accessed_in_the_constructor, symbolToString(prop), getTextOfIdentifierOrLiteral((declaringClassDeclaration.name!))); // TODO: GH#18217 + return false; + } + } + // Public properties are otherwise accessible. + if (!(flags & ModifierFlags.NonPublicAccessibilityModifier)) { + return true; + } + // Property is known to be private or protected at this point + // Private property is accessible if the property is within the declaring class + if (flags & ModifierFlags.Private) { + const declaringClassDeclaration = (getClassLikeDeclarationOfSymbol((getParentOfSymbol(prop)!))!); + if (!isNodeWithinClass(node, declaringClassDeclaration)) { + error(errorNode, Diagnostics.Property_0_is_private_and_only_accessible_within_class_1, symbolToString(prop), typeToString(getDeclaringClass(prop)!)); + return false; + } + return true; + } + // Property is known to be protected at this point + // All protected properties of a supertype are accessible in a super access + if (isSuper) { + return true; + } + // Find the first enclosing class that has the declaring classes of the protected constituents + // of the property as base classes + let enclosingClass = forEachEnclosingClass(node, enclosingDeclaration => { + const enclosingClass = (getDeclaredTypeOfSymbol(getSymbolOfNode(enclosingDeclaration)!)); + return isClassDerivedFromDeclaringClasses(enclosingClass, prop) ? enclosingClass : undefined; + }); + // A protected property is accessible if the property is within the declaring class or classes derived from it + if (!enclosingClass) { + // allow PropertyAccessibility if context is in function with this parameter + // static member access is disallow + let thisParameter: ParameterDeclaration | undefined; + if (flags & ModifierFlags.Static || !(thisParameter = getThisParameterFromNodeContext(node)) || !thisParameter.type) { + error(errorNode, Diagnostics.Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses, symbolToString(prop), typeToString(getDeclaringClass(prop) || type)); + return false; + } + const thisType = getTypeFromTypeNode(thisParameter.type); + enclosingClass = (((thisType.flags & TypeFlags.TypeParameter) ? getConstraintOfTypeParameter((thisType)) : thisType) as InterfaceType); + } + // No further restrictions for static properties + if (flags & ModifierFlags.Static) { + return true; + } + if (type.flags & TypeFlags.TypeParameter) { + // get the original type -- represented as the type constraint of the 'this' type + type = (type as TypeParameter).isThisType ? getConstraintOfTypeParameter((type))! : getBaseConstraintOfType((type))!; // TODO: GH#18217 Use a different variable that's allowed to be undefined + } + if (!type || !hasBaseType(type, enclosingClass)) { + error(errorNode, Diagnostics.Property_0_is_protected_and_only_accessible_through_an_instance_of_class_1, symbolToString(prop), typeToString(enclosingClass)); + return false; + } + return true; + } + function getThisParameterFromNodeContext(node: Node) { + const thisContainer = getThisContainer(node, /* includeArrowFunctions */ false); + return thisContainer && isFunctionLike(thisContainer) ? getThisParameter(thisContainer) : undefined; + } + function symbolHasNonMethodDeclaration(symbol: ts.Symbol) { + return !!forEachProperty(symbol, prop => !(prop.flags & SymbolFlags.Method)); + } + function checkNonNullExpression(node: Expression | QualifiedName) { + return checkNonNullType(checkExpression(node), node); + } + function isNullableType(type: ts.Type) { + return !!((strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable); + } + function getNonNullableTypeIfNeeded(type: ts.Type) { + return isNullableType(type) ? getNonNullableType(type) : type; + } + function reportObjectPossiblyNullOrUndefinedError(node: Node, flags: TypeFlags) { + error(node, flags & TypeFlags.Undefined ? flags & TypeFlags.Null ? + Diagnostics.Object_is_possibly_null_or_undefined : + Diagnostics.Object_is_possibly_undefined : + Diagnostics.Object_is_possibly_null); + } + function reportCannotInvokePossiblyNullOrUndefinedError(node: Node, flags: TypeFlags) { + error(node, flags & TypeFlags.Undefined ? flags & TypeFlags.Null ? + Diagnostics.Cannot_invoke_an_object_which_is_possibly_null_or_undefined : + Diagnostics.Cannot_invoke_an_object_which_is_possibly_undefined : + Diagnostics.Cannot_invoke_an_object_which_is_possibly_null); + } + function checkNonNullTypeWithReporter(type: ts.Type, node: Node, reportError: (node: Node, kind: TypeFlags) => void): ts.Type { + if (strictNullChecks && type.flags & TypeFlags.Unknown) { + error(node, Diagnostics.Object_is_of_type_unknown); + return errorType; } - - /** - * Checks the return type of an async function to ensure it is a compatible - * Promise implementation. - * - * This checks that an async function has a valid Promise-compatible return type. - * An async function has a valid Promise-compatible return type if the resolved value - * of the return type has a construct signature that takes in an `initializer` function - * that in turn supplies a `resolve` function as one of its arguments and results in an - * object with a callable `then` signature. - * - * @param node The signature to check - */ - function checkAsyncFunctionReturnType(node: FunctionLikeDeclaration | MethodSignature, returnTypeNode: TypeNode) { - // As part of our emit for an async function, we will need to emit the entity name of - // the return type annotation as an expression. To meet the necessary runtime semantics - // for __awaiter, we must also check that the type of the declaration (e.g. the static - // side or "constructor" of the promise type) is compatible `PromiseConstructorLike`. - // - // An example might be (from lib.es6.d.ts): - // - // interface Promise { ... } - // interface PromiseConstructor { - // new (...): Promise; - // } - // declare var Promise: PromiseConstructor; - // - // When an async function declares a return type annotation of `Promise`, we - // need to get the type of the `Promise` variable declaration above, which would - // be `PromiseConstructor`. - // - // The same case applies to a class: - // - // declare class Promise { - // constructor(...); - // then(...): Promise; - // } - // - const returnType = getTypeFromTypeNode(returnTypeNode); - - if (languageVersion >= ScriptTarget.ES2015) { - if (returnType === errorType) { - return; - } - const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true); - if (globalPromiseType !== emptyGenericType && !isReferenceToType(returnType, globalPromiseType)) { - // The promise type was not a valid type reference to the global promise type, so we - // report an error and return the unknown type. - error(returnTypeNode, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type); - return; - } + const kind = (strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable; + if (kind) { + reportError(node, kind); + const t = getNonNullableType(type); + return t.flags & (TypeFlags.Nullable | TypeFlags.Never) ? errorType : t; + } + return type; + } + function checkNonNullType(type: ts.Type, node: Node) { + return checkNonNullTypeWithReporter(type, node, reportObjectPossiblyNullOrUndefinedError); + } + function checkNonNullNonVoidType(type: ts.Type, node: Node): ts.Type { + const nonNullType = checkNonNullType(type, node); + if (nonNullType !== errorType && nonNullType.flags & TypeFlags.Void) { + error(node, Diagnostics.Object_is_possibly_undefined); + } + return nonNullType; + } + function checkPropertyAccessExpression(node: PropertyAccessExpression) { + return node.flags & NodeFlags.OptionalChain ? checkPropertyAccessChain((node as PropertyAccessChain)) : + checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullExpression(node.expression), node.name); + } + function checkPropertyAccessChain(node: PropertyAccessChain) { + const leftType = checkExpression(node.expression); + const nonOptionalType = getOptionalExpressionType(leftType, node.expression); + return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name), node, nonOptionalType !== leftType); + } + function checkQualifiedName(node: QualifiedName) { + return checkPropertyAccessExpressionOrQualifiedName(node, node.left, checkNonNullExpression(node.left), node.right); + } + function isMethodAccessForCall(node: Node) { + while (node.parent.kind === SyntaxKind.ParenthesizedExpression) { + node = node.parent; + } + return isCallOrNewExpression(node.parent) && node.parent.expression === node; + } + function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: ts.Type, right: Identifier) { + const parentSymbol = getNodeLinks(left).resolvedSymbol; + const assignmentKind = getAssignmentTargetKind(node); + const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType); + if (isTypeAny(apparentType) || apparentType === silentNeverType) { + if (isIdentifier(left) && parentSymbol) { + markAliasReferenced(parentSymbol, node); } - else { - // Always mark the type node as referenced if it points to a value - markTypeNodeAsReferenced(returnTypeNode); - - if (returnType === errorType) { - return; - } - - const promiseConstructorName = getEntityNameFromTypeNode(returnTypeNode); - if (promiseConstructorName === undefined) { - error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, typeToString(returnType)); - return; + return apparentType; + } + const prop = getPropertyOfType(apparentType, right.escapedText); + if (isIdentifier(left) && parentSymbol && !(prop && isConstEnumOrConstEnumOnlyModule(prop))) { + markAliasReferenced(parentSymbol, node); + } + let propType: ts.Type; + if (!prop) { + const indexInfo = assignmentKind === AssignmentKind.None || !isGenericObjectType(leftType) || isThisTypeParameter(leftType) ? getIndexInfoOfType(apparentType, IndexKind.String) : undefined; + if (!(indexInfo && indexInfo.type)) { + if (isJSLiteralType(leftType)) { + return anyType; } - - const promiseConstructorSymbol = resolveEntityName(promiseConstructorName, SymbolFlags.Value, /*ignoreErrors*/ true); - const promiseConstructorType = promiseConstructorSymbol ? getTypeOfSymbol(promiseConstructorSymbol) : errorType; - if (promiseConstructorType === errorType) { - if (promiseConstructorName.kind === SyntaxKind.Identifier && promiseConstructorName.escapedText === "Promise" && getTargetType(returnType) === getGlobalPromiseType(/*reportErrors*/ false)) { - error(returnTypeNode, Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option); + if (leftType.symbol === globalThisSymbol) { + if (globalThisSymbol.exports!.has(right.escapedText) && (globalThisSymbol.exports!.get(right.escapedText)!.flags & SymbolFlags.BlockScoped)) { + error(right, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(right.escapedText), typeToString(leftType)); } - else { - error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName)); + else if (noImplicitAny) { + error(right, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(leftType)); } - return; - } - - const globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType(/*reportErrors*/ true); - if (globalPromiseConstructorLikeType === emptyObjectType) { - // If we couldn't resolve the global PromiseConstructorLike type we cannot verify - // compatibility with __awaiter. - error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName)); - return; - } - - if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, returnTypeNode, - Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value)) { - return; + return anyType; } - - // Verify there is no local declaration that could collide with the promise constructor. - const rootName = promiseConstructorName && getFirstIdentifier(promiseConstructorName); - const collidingSymbol = getSymbol(node.locals!, rootName.escapedText, SymbolFlags.Value); - if (collidingSymbol) { - error(collidingSymbol.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions, - idText(rootName), - entityNameToString(promiseConstructorName)); - return; + if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) { + reportNonexistentProperty(right, isThisTypeParameter(leftType) ? apparentType : leftType); } + return errorType; + } + if (indexInfo.isReadonly && (isAssignmentTarget(node) || isDeleteTarget(node))) { + error(node, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(apparentType)); } - checkAwaitedType(returnType, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + propType = indexInfo.type; } - - /** Check a decorator */ - function checkDecorator(node: Decorator): void { - const signature = getResolvedSignature(node); - const returnType = getReturnTypeOfSignature(signature); - if (returnType.flags & TypeFlags.Any) { - return; + else { + checkPropertyNotUsedBeforeDeclaration(prop, node, right); + markPropertyAsReferenced(prop, node, left.kind === SyntaxKind.ThisKeyword); + getNodeLinks(node).resolvedSymbol = prop; + checkPropertyAccessibility(node, left.kind === SyntaxKind.SuperKeyword, apparentType, prop); + if (assignmentKind) { + if (isReferenceToReadonlyEntity((node), prop) || isReferenceThroughNamespaceImport((node))) { + error(right, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, idText(right)); + return errorType; + } } - - let expectedReturnType: Type; - const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node); - let errorInfo: DiagnosticMessageChain | undefined; - switch (node.parent.kind) { - case SyntaxKind.ClassDeclaration: - const classSymbol = getSymbolOfNode(node.parent); - const classConstructorType = getTypeOfSymbol(classSymbol); - expectedReturnType = getUnionType([classConstructorType, voidType]); - break; - - case SyntaxKind.Parameter: - expectedReturnType = voidType; - errorInfo = chainDiagnosticMessages( - /*details*/ undefined, - Diagnostics.The_return_type_of_a_parameter_decorator_function_must_be_either_void_or_any); - - break; - + propType = getConstraintForLocation(getTypeOfSymbol(prop), node); + } + return getFlowTypeOfAccessExpression(node, prop, propType, right); + } + function getFlowTypeOfAccessExpression(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: ts.Symbol | undefined, propType: ts.Type, errorNode: Node) { + // Only compute control flow type if this is a property access expression that isn't an + // assignment target, and the referenced property was declared as a variable, property, + // accessor, or optional method. + const assignmentKind = getAssignmentTargetKind(node); + if (node.kind !== SyntaxKind.ElementAccessExpression && node.kind !== SyntaxKind.PropertyAccessExpression || + assignmentKind === AssignmentKind.Definite || + prop && !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) && !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) { + return propType; + } + // If strict null checks and strict property initialization checks are enabled, if we have + // a this.xxx property access, if the property is an instance property without an initializer, + // and if we are in a constructor of the same class as the property declaration, assume that + // the property is uninitialized at the top of the control flow. + let assumeUninitialized = false; + if (strictNullChecks && strictPropertyInitialization && node.expression.kind === SyntaxKind.ThisKeyword) { + const declaration = prop && prop.valueDeclaration; + if (declaration && isInstancePropertyWithoutInitializer(declaration)) { + const flowContainer = getControlFlowContainer(node); + if (flowContainer.kind === SyntaxKind.Constructor && flowContainer.parent === declaration.parent) { + assumeUninitialized = true; + } + } + } + else if (strictNullChecks && prop && prop.valueDeclaration && + isPropertyAccessExpression(prop.valueDeclaration) && + getAssignmentDeclarationPropertyAccessKind(prop.valueDeclaration) && + getControlFlowContainer(node) === getControlFlowContainer(prop.valueDeclaration)) { + assumeUninitialized = true; + } + const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType); + if (assumeUninitialized && !(getFalsyFlags(propType) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) { + error(errorNode, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217 + // Return the declared type to reduce follow-on errors + return propType; + } + return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; + } + function checkPropertyNotUsedBeforeDeclaration(prop: ts.Symbol, node: PropertyAccessExpression | QualifiedName, right: Identifier): void { + const { valueDeclaration } = prop; + if (!valueDeclaration || getSourceFileOfNode(node).isDeclarationFile) { + return; + } + let diagnosticMessage; + const declarationName = idText(right); + if (isInPropertyInitializer(node) && + !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right) + && !isPropertyDeclaredInAncestorClass(prop)) { + diagnosticMessage = error(right, Diagnostics.Property_0_is_used_before_its_initialization, declarationName); + } + else if (valueDeclaration.kind === SyntaxKind.ClassDeclaration && + node.parent.kind !== SyntaxKind.TypeReference && + !(valueDeclaration.flags & NodeFlags.Ambient) && + !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right)) { + diagnosticMessage = error(right, Diagnostics.Class_0_used_before_its_declaration, declarationName); + } + if (diagnosticMessage) { + addRelatedInfo(diagnosticMessage, createDiagnosticForNode(valueDeclaration, Diagnostics._0_is_declared_here, declarationName)); + } + } + function isInPropertyInitializer(node: Node): boolean { + return !!findAncestor(node, node => { + switch (node.kind) { case SyntaxKind.PropertyDeclaration: - expectedReturnType = voidType; - errorInfo = chainDiagnosticMessages( - /*details*/ undefined, - Diagnostics.The_return_type_of_a_property_decorator_function_must_be_either_void_or_any); - break; - + return true; + case SyntaxKind.PropertyAssignment: case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: - const methodType = getTypeOfNode(node.parent); - const descriptorType = createTypedPropertyDescriptorType(methodType); - expectedReturnType = getUnionType([descriptorType, voidType]); - break; - + case SyntaxKind.SpreadAssignment: + case SyntaxKind.ComputedPropertyName: + case SyntaxKind.TemplateSpan: + case SyntaxKind.JsxExpression: + case SyntaxKind.JsxAttribute: + case SyntaxKind.JsxAttributes: + case SyntaxKind.JsxSpreadAttribute: + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.ExpressionWithTypeArguments: + case SyntaxKind.HeritageClause: + return false; default: - return Debug.fail(); + return isExpressionNode(node) ? false : "quit"; } - - checkTypeAssignableTo( - returnType, - expectedReturnType, - node, - headMessage, - () => errorInfo); - } - - /** - * If a TypeNode can be resolved to a value symbol imported from an external module, it is - * marked as referenced to prevent import elision. - */ - function markTypeNodeAsReferenced(node: TypeNode) { - markEntityNameOrEntityExpressionAsReference(node && getEntityNameFromTypeNode(node)); + }); + } + /** + * It's possible that "prop.valueDeclaration" is a local declaration, but the property was also declared in a superclass. + * In that case we won't consider it used before its declaration, because it gets its value from the superclass' declaration. + */ + function isPropertyDeclaredInAncestorClass(prop: ts.Symbol): boolean { + if (!(prop.parent!.flags & SymbolFlags.Class)) { + return false; } - - function markEntityNameOrEntityExpressionAsReference(typeName: EntityNameOrEntityNameExpression | undefined) { - if (!typeName) return; - - const rootName = getFirstIdentifier(typeName); - const meaning = (typeName.kind === SyntaxKind.Identifier ? SymbolFlags.Type : SymbolFlags.Namespace) | SymbolFlags.Alias; - const rootSymbol = resolveName(rootName, rootName.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isRefernce*/ true); - if (rootSymbol - && rootSymbol.flags & SymbolFlags.Alias - && symbolIsValue(rootSymbol) - && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol))) { - markAliasSymbolAsReferenced(rootSymbol); + let classType: InterfaceType | undefined = (getTypeOfSymbol(prop.parent!) as InterfaceType); + while (true) { + classType = classType.symbol && (getSuperClass(classType) as InterfaceType | undefined); + if (!classType) { + return false; } - } - - /** - * This function marks the type used for metadata decorator as referenced if it is import - * from external module. - * This is different from markTypeNodeAsReferenced because it tries to simplify type nodes in - * union and intersection type - * @param node - */ - function markDecoratorMedataDataTypeNodeAsReferenced(node: TypeNode | undefined): void { - const entityName = getEntityNameForDecoratorMetadata(node); - if (entityName && isEntityName(entityName)) { - markEntityNameOrEntityExpressionAsReference(entityName); + const superProperty = getPropertyOfType(classType, prop.escapedName); + if (superProperty && superProperty.valueDeclaration) { + return true; } } - - function getEntityNameForDecoratorMetadata(node: TypeNode | undefined): EntityName | undefined { - if (node) { - switch (node.kind) { - case SyntaxKind.IntersectionType: - case SyntaxKind.UnionType: - return getEntityNameForDecoratorMetadataFromTypeList((node).types); - - case SyntaxKind.ConditionalType: - return getEntityNameForDecoratorMetadataFromTypeList([(node).trueType, (node).falseType]); - - case SyntaxKind.ParenthesizedType: - return getEntityNameForDecoratorMetadata((node).type); - - case SyntaxKind.TypeReference: - return (node).typeName; + } + function getSuperClass(classType: InterfaceType): ts.Type | undefined { + const x = getBaseTypes(classType); + if (x.length === 0) { + return undefined; + } + return getIntersectionType(x); + } + function reportNonexistentProperty(propNode: Identifier, containingType: ts.Type) { + let errorInfo: DiagnosticMessageChain | undefined; + let relatedInfo: Diagnostic | undefined; + if (containingType.flags & TypeFlags.Union && !(containingType.flags & TypeFlags.Primitive)) { + for (const subtype of (containingType as UnionType).types) { + if (!getPropertyOfType(subtype, propNode.escapedText) && !getIndexInfoOfType(subtype, IndexKind.String)) { + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(subtype)); + break; } } } - - function getEntityNameForDecoratorMetadataFromTypeList(types: readonly TypeNode[]): EntityName | undefined { - let commonEntityName: EntityName | undefined; - for (let typeNode of types) { - while (typeNode.kind === SyntaxKind.ParenthesizedType) { - typeNode = (typeNode as ParenthesizedTypeNode).type; // Skip parens if need be - } - if (typeNode.kind === SyntaxKind.NeverKeyword) { - continue; // Always elide `never` from the union/intersection if possible - } - if (!strictNullChecks && (typeNode.kind === SyntaxKind.NullKeyword || typeNode.kind === SyntaxKind.UndefinedKeyword)) { - continue; // Elide null and undefined from unions for metadata, just like what we did prior to the implementation of strict null checks - } - const individualEntityName = getEntityNameForDecoratorMetadata(typeNode); - if (!individualEntityName) { - // Individual is something like string number - // So it would be serialized to either that type or object - // Safe to return here - return undefined; - } - - if (commonEntityName) { - // Note this is in sync with the transformation that happens for type node. - // Keep this in sync with serializeUnionOrIntersectionType - // Verify if they refer to same entity and is identifier - // return undefined if they dont match because we would emit object - if (!isIdentifier(commonEntityName) || - !isIdentifier(individualEntityName) || - commonEntityName.escapedText !== individualEntityName.escapedText) { - return undefined; - } + if (typeHasStaticProperty(propNode.escapedText, containingType)) { + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_is_a_static_member_of_type_1, declarationNameToString(propNode), typeToString(containingType)); + } + else { + const promisedType = getPromisedTypeOfPromise(containingType); + if (promisedType && getPropertyOfType(promisedType, propNode.escapedText)) { + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType)); + relatedInfo = createDiagnosticForNode(propNode, Diagnostics.Did_you_forget_to_use_await); + } + else { + const suggestion = getSuggestedSymbolForNonexistentProperty(propNode, containingType); + if (suggestion !== undefined) { + const suggestedName = symbolName(suggestion); + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, declarationNameToString(propNode), typeToString(containingType), suggestedName); + relatedInfo = suggestion.valueDeclaration && createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestedName); } else { - commonEntityName = individualEntityName; + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType)); } } - return commonEntityName; } - - function getParameterTypeNodeForDecoratorCheck(node: ParameterDeclaration): TypeNode | undefined { - const typeNode = getEffectiveTypeAnnotationNode(node); - return isRestParameter(node) ? getRestParameterElementType(typeNode) : typeNode; + const resultDiagnostic = createDiagnosticForNodeFromMessageChain(propNode, errorInfo); + if (relatedInfo) { + addRelatedInfo(resultDiagnostic, relatedInfo); } - - /** Check the decorators of a node */ - function checkDecorators(node: Node): void { - if (!node.decorators) { - return; + diagnostics.add(resultDiagnostic); + } + function typeHasStaticProperty(propName: __String, containingType: ts.Type): boolean { + const prop = containingType.symbol && getPropertyOfType(getTypeOfSymbol(containingType.symbol), propName); + return prop !== undefined && prop.valueDeclaration && hasModifier(prop.valueDeclaration, ModifierFlags.Static); + } + function getSuggestedSymbolForNonexistentProperty(name: Identifier | string, containingType: ts.Type): ts.Symbol | undefined { + return getSpellingSuggestionForName(isString(name) ? name : idText(name), getPropertiesOfType(containingType), SymbolFlags.Value); + } + function getSuggestionForNonexistentProperty(name: Identifier | string, containingType: ts.Type): string | undefined { + const suggestion = getSuggestedSymbolForNonexistentProperty(name, containingType); + return suggestion && symbolName(suggestion); + } + function getSuggestedSymbolForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): ts.Symbol | undefined { + Debug.assert(outerName !== undefined, "outername should always be defined"); + const result = resolveNameHelper(location, outerName, meaning, /*nameNotFoundMessage*/ undefined, outerName, /*isUse*/ false, /*excludeGlobals*/ false, (symbols, name, meaning) => { + Debug.assertEqual(outerName, name, "name should equal outerName"); + const symbol = getSymbol(symbols, name, meaning); + // Sometimes the symbol is found when location is a return type of a function: `typeof x` and `x` is declared in the body of the function + // So the table *contains* `x` but `x` isn't actually in scope. + // However, resolveNameHelper will continue and call this callback again, so we'll eventually get a correct suggestion. + return symbol || getSpellingSuggestionForName(unescapeLeadingUnderscores(name), arrayFrom(symbols.values()), meaning); + }); + return result; + } + function getSuggestionForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): string | undefined { + const symbolResult = getSuggestedSymbolForNonexistentSymbol(location, outerName, meaning); + return symbolResult && symbolName(symbolResult); + } + function getSuggestedSymbolForNonexistentModule(name: Identifier, targetModule: ts.Symbol): ts.Symbol | undefined { + return targetModule.exports && getSpellingSuggestionForName(idText(name), getExportsOfModuleAsArray(targetModule), SymbolFlags.ModuleMember); + } + function getSuggestionForNonexistentExport(name: Identifier, targetModule: ts.Symbol): string | undefined { + const suggestion = getSuggestedSymbolForNonexistentModule(name, targetModule); + return suggestion && symbolName(suggestion); + } + function getSuggestionForNonexistentIndexSignature(objectType: ts.Type, expr: ElementAccessExpression, keyedType: ts.Type): string | undefined { + // check if object type has setter or getter + function hasProp(name: "set" | "get") { + const prop = getPropertyOfObjectType(objectType, (<__String>name)); + if (prop) { + const s = getSingleCallSignature(getTypeOfSymbol(prop)); + return !!s && getMinArgumentCount(s) >= 1 && isTypeAssignableTo(keyedType, getTypeAtPosition(s, 0)); } - - // skip this check for nodes that cannot have decorators. These should have already had an error reported by - // checkGrammarDecorators. - if (!nodeCanBeDecorated(node, node.parent, node.parent.parent)) { + return false; + } + ; + const suggestedMethod = isAssignmentTarget(expr) ? "set" : "get"; + if (!hasProp(suggestedMethod)) { + return undefined; + } + let suggestion = tryGetPropertyAccessOrIdentifierToString(expr.expression); + if (suggestion === undefined) { + suggestion = suggestedMethod; + } + else { + suggestion += "." + suggestedMethod; + } + return suggestion; + } + /** + * Given a name and a list of symbols whose names are *not* equal to the name, return a spelling suggestion if there is one that is close enough. + * Names less than length 3 only check for case-insensitive equality, not levenshtein distance. + * + * If there is a candidate that's the same except for case, return that. + * If there is a candidate that's within one edit of the name, return that. + * Otherwise, return the candidate with the smallest Levenshtein distance, + * except for candidates: + * * With no name + * * Whose meaning doesn't match the `meaning` parameter. + * * Whose length differs from the target name by more than 0.34 of the length of the name. + * * Whose levenshtein distance is more than 0.4 of the length of the name + * (0.4 allows 1 substitution/transposition for every 5 characters, + * and 1 insertion/deletion at 3 characters) + */ + function getSpellingSuggestionForName(name: string, symbols: ts.Symbol[], meaning: SymbolFlags): ts.Symbol | undefined { + return getSpellingSuggestion(name, symbols, getCandidateName); + function getCandidateName(candidate: ts.Symbol) { + const candidateName = symbolName(candidate); + return !startsWith(candidateName, "\"") && candidate.flags & meaning ? candidateName : undefined; + } + } + function markPropertyAsReferenced(prop: ts.Symbol, nodeForCheckWriteOnly: Node | undefined, isThisAccess: boolean) { + if (!prop || !(prop.flags & SymbolFlags.ClassMember) || !prop.valueDeclaration || !hasModifier(prop.valueDeclaration, ModifierFlags.Private)) { + return; + } + if (nodeForCheckWriteOnly && isWriteOnlyAccess(nodeForCheckWriteOnly) && !(prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor))) { + return; + } + if (isThisAccess) { + // Find any FunctionLikeDeclaration because those create a new 'this' binding. But this should only matter for methods (or getters/setters). + const containingMethod = findAncestor(nodeForCheckWriteOnly, isFunctionLikeDeclaration); + if (containingMethod && containingMethod.symbol === prop) { return; } - - if (!compilerOptions.experimentalDecorators) { - error(node, Diagnostics.Experimental_support_for_decorators_is_a_feature_that_is_subject_to_change_in_a_future_release_Set_the_experimentalDecorators_option_in_your_tsconfig_or_jsconfig_to_remove_this_warning); - } - - const firstDecorator = node.decorators[0]; - checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Decorate); - if (node.kind === SyntaxKind.Parameter) { - checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Param); + } + (getCheckFlags(prop) & CheckFlags.Instantiated ? getSymbolLinks(prop).target : prop)!.isReferenced = SymbolFlags.All; + } + function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, propertyName: __String): boolean { + switch (node.kind) { + case SyntaxKind.PropertyAccessExpression: + return isValidPropertyAccessWithType(node, node.expression.kind === SyntaxKind.SuperKeyword, propertyName, getWidenedType(checkExpression(node.expression))); + case SyntaxKind.QualifiedName: + return isValidPropertyAccessWithType(node, /*isSuper*/ false, propertyName, getWidenedType(checkExpression(node.left))); + case SyntaxKind.ImportType: + return isValidPropertyAccessWithType(node, /*isSuper*/ false, propertyName, getTypeFromTypeNode(node)); + } + } + function isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode | QualifiedName, type: ts.Type, property: ts.Symbol): boolean { + return isValidPropertyAccessWithType(node, node.kind === SyntaxKind.PropertyAccessExpression && node.expression.kind === SyntaxKind.SuperKeyword, property.escapedName, type); + // Previously we validated the 'this' type of methods but this adversely affected performance. See #31377 for more context. + } + function isValidPropertyAccessWithType(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, isSuper: boolean, propertyName: __String, type: ts.Type): boolean { + if (type === errorType || isTypeAny(type)) { + return true; + } + const prop = getPropertyOfType(type, propertyName); + return prop ? checkPropertyAccessibility(node, isSuper, type, prop) + // In js files properties of unions are allowed in completion + : isInJSFile(node) && (type.flags & TypeFlags.Union) !== 0 && (type).types.some(elementType => isValidPropertyAccessWithType(node, isSuper, propertyName, elementType)); + } + /** + * Return the symbol of the for-in variable declared or referenced by the given for-in statement. + */ + function getForInVariableSymbol(node: ForInStatement): ts.Symbol | undefined { + const initializer = node.initializer; + if (initializer.kind === SyntaxKind.VariableDeclarationList) { + const variable = (initializer).declarations[0]; + if (variable && !isBindingPattern(variable.name)) { + return getSymbolOfNode(variable); } - - if (compilerOptions.emitDecoratorMetadata) { - checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Metadata); - - // we only need to perform these checks if we are emitting serialized type metadata for the target of a decorator. - switch (node.kind) { - case SyntaxKind.ClassDeclaration: - const constructor = getFirstConstructorWithBody(node); - if (constructor) { - for (const parameter of constructor.parameters) { - markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); - } - } - break; - - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; - const otherAccessor = getDeclarationOfKind(getSymbolOfNode(node as AccessorDeclaration), otherKind); - markDecoratorMedataDataTypeNodeAsReferenced(getAnnotatedAccessorTypeNode(node as AccessorDeclaration) || otherAccessor && getAnnotatedAccessorTypeNode(otherAccessor)); - break; - case SyntaxKind.MethodDeclaration: - for (const parameter of (node).parameters) { - markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); - } - - markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(node)); - break; - - case SyntaxKind.PropertyDeclaration: - markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveTypeAnnotationNode(node)); - break; - - case SyntaxKind.Parameter: - markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(node)); - const containingSignature = (node as ParameterDeclaration).parent; - for (const parameter of containingSignature.parameters) { - markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); - } - break; + } + else if (initializer.kind === SyntaxKind.Identifier) { + return getResolvedSymbol((initializer)); + } + return undefined; + } + /** + * Return true if the given type is considered to have numeric property names. + */ + function hasNumericPropertyNames(type: ts.Type) { + return getIndexTypeOfType(type, IndexKind.Number) && !getIndexTypeOfType(type, IndexKind.String); + } + /** + * Return true if given node is an expression consisting of an identifier (possibly parenthesized) + * that references a for-in variable for an object with numeric property names. + */ + function isForInVariableForNumericPropertyNames(expr: Expression) { + const e = skipParentheses(expr); + if (e.kind === SyntaxKind.Identifier) { + const symbol = getResolvedSymbol((e)); + if (symbol.flags & SymbolFlags.Variable) { + let child: Node = expr; + let node = expr.parent; + while (node) { + if (node.kind === SyntaxKind.ForInStatement && + child === (node).statement && + getForInVariableSymbol((node)) === symbol && + hasNumericPropertyNames(getTypeOfExpression((node).expression))) { + return true; + } + child = node; + node = node.parent; } } - - forEach(node.decorators, checkDecorator); } - - function checkFunctionDeclaration(node: FunctionDeclaration): void { - if (produceDiagnostics) { - checkFunctionOrMethodDeclaration(node); - checkGrammarForGenerator(node); - checkCollisionWithRequireExportsInGeneratedCode(node, node.name!); - checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name!); - } + return false; + } + function checkIndexedAccess(node: ElementAccessExpression): ts.Type { + return node.flags & NodeFlags.OptionalChain ? checkElementAccessChain((node as ElementAccessChain)) : + checkElementAccessExpression(node, checkNonNullExpression(node.expression)); + } + function checkElementAccessChain(node: ElementAccessChain) { + const exprType = checkExpression(node.expression); + const nonOptionalType = getOptionalExpressionType(exprType, node.expression); + return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression)), node, nonOptionalType !== exprType); + } + function checkElementAccessExpression(node: ElementAccessExpression, exprType: ts.Type): ts.Type { + const objectType = getAssignmentTargetKind(node) !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(exprType) : exprType; + const indexExpression = node.argumentExpression; + const indexType = checkExpression(indexExpression); + if (objectType === errorType || objectType === silentNeverType) { + return objectType; + } + if (isConstEnumObjectType(objectType) && !isStringLiteralLike(indexExpression)) { + error(indexExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal); + return errorType; } - - function checkJSDocTypeAliasTag(node: JSDocTypedefTag | JSDocCallbackTag) { - if (!node.typeExpression) { - // If the node had `@property` tags, `typeExpression` would have been set to the first property tag. - error(node.name, Diagnostics.JSDoc_typedef_tag_should_either_have_a_type_annotation_or_be_followed_by_property_or_member_tags); - } - - if (node.name) { - checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); + const effectiveIndexType = isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType; + const accessFlags = isAssignmentTarget(node) ? + AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) : + AccessFlags.None; + const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, node, accessFlags) || errorType; + return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node); + } + function checkThatExpressionIsProperSymbolReference(expression: Expression, expressionType: ts.Type, reportError: boolean): boolean { + if (expressionType === errorType) { + // There is already an error, so no need to report one. + return false; + } + if (!isWellKnownSymbolSyntactically(expression)) { + return false; + } + // Make sure the property type is the primitive symbol type + if ((expressionType.flags & TypeFlags.ESSymbolLike) === 0) { + if (reportError) { + error(expression, Diagnostics.A_computed_property_name_of_the_form_0_must_be_of_type_symbol, getTextOfNode(expression)); } - checkSourceElement(node.typeExpression); + return false; } - - function checkJSDocTemplateTag(node: JSDocTemplateTag): void { - checkSourceElement(node.constraint); - for (const tp of node.typeParameters) { - checkSourceElement(tp); + // The name is Symbol., so make sure Symbol actually resolves to the + // global Symbol object + const leftHandSide = ((expression).expression); + const leftHandSideSymbol = getResolvedSymbol(leftHandSide); + if (!leftHandSideSymbol) { + return false; + } + const globalESSymbol = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ true); + if (!globalESSymbol) { + // Already errored when we tried to look up the symbol + return false; + } + if (leftHandSideSymbol !== globalESSymbol) { + if (reportError) { + error(leftHandSide, Diagnostics.Symbol_reference_does_not_refer_to_the_global_Symbol_constructor_object); } + return false; } - - function checkJSDocTypeTag(node: JSDocTypeTag) { - checkSourceElement(node.typeExpression); + return true; + } + function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement { + return isCallOrNewExpression(node) || isTaggedTemplateExpression(node) || isJsxOpeningLikeElement(node); + } + function resolveUntypedCall(node: CallLikeExpression): ts.Signature { + if (callLikeExpressionMayHaveTypeArguments(node)) { + // Check type arguments even though we will give an error that untyped calls may not accept type arguments. + // This gets us diagnostics for the type arguments and marks them as referenced. + forEach(node.typeArguments, checkSourceElement); } - - function checkJSDocParameterTag(node: JSDocParameterTag) { - checkSourceElement(node.typeExpression); - if (!getParameterSymbolFromJSDoc(node)) { - const decl = getHostSignatureFromJSDoc(node); - // don't issue an error for invalid hosts -- just functions -- - // and give a better error message when the host function mentions `arguments` - // but the tag doesn't have an array type - if (decl) { - const i = getJSDocTags(decl).filter(isJSDocParameterTag).indexOf(node); - if (i > -1 && i < decl.parameters.length && isBindingPattern(decl.parameters[i].name)) { - return; - } - if (!containsArgumentsReference(decl)) { - if (isQualifiedName(node.name)) { - error(node.name, - Diagnostics.Qualified_name_0_is_not_allowed_without_a_leading_param_object_1, - entityNameToString(node.name), - entityNameToString(node.name.left)); - } - else { - error(node.name, - Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name, - idText(node.name)); - } - } - else if (findLast(getJSDocTags(decl), isJSDocParameterTag) === node && - node.typeExpression && node.typeExpression.type && - !isArrayType(getTypeFromTypeNode(node.typeExpression.type))) { - error(node.name, - Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name_It_would_match_arguments_if_it_had_an_array_type, - idText(node.name.kind === SyntaxKind.QualifiedName ? node.name.right : node.name)); - } + if (node.kind === SyntaxKind.TaggedTemplateExpression) { + checkExpression(node.template); + } + else if (isJsxOpeningLikeElement(node)) { + checkExpression(node.attributes); + } + else if (node.kind !== SyntaxKind.Decorator) { + forEach((node).arguments, argument => { + checkExpression(argument); + }); + } + return anySignature; + } + function resolveErrorCall(node: CallLikeExpression): ts.Signature { + resolveUntypedCall(node); + return unknownSignature; + } + // Re-order candidate signatures into the result array. Assumes the result array to be empty. + // The candidate list orders groups in reverse, but within a group signatures are kept in declaration order + // A nit here is that we reorder only signatures that belong to the same symbol, + // so order how inherited signatures are processed is still preserved. + // interface A { (x: string): void } + // interface B extends A { (x: 'foo'): string } + // const b: B; + // b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void] + function reorderCandidates(signatures: readonly ts.Signature[], result: ts.Signature[], callChainFlags: SignatureFlags): void { + let lastParent: Node | undefined; + let lastSymbol: ts.Symbol | undefined; + let cutoffIndex = 0; + let index: number | undefined; + let specializedIndex = -1; + let spliceIndex: number; + Debug.assert(!result.length); + for (const signature of signatures) { + const symbol = signature.declaration && getSymbolOfNode(signature.declaration); + const parent = signature.declaration && signature.declaration.parent; + if (!lastSymbol || symbol === lastSymbol) { + if (lastParent && parent === lastParent) { + index = index! + 1; + } + else { + lastParent = parent; + index = cutoffIndex; } } + else { + // current declaration belongs to a different symbol + // set cutoffIndex so re-orderings in the future won't change result set from 0 to cutoffIndex + index = cutoffIndex = result.length; + lastParent = parent; + } + lastSymbol = symbol; + // specialized signatures always need to be placed before non-specialized signatures regardless + // of the cutoff position; see GH#1133 + if (signatureHasLiteralTypes(signature)) { + specializedIndex++; + spliceIndex = specializedIndex; + // The cutoff index always needs to be greater than or equal to the specialized signature index + // in order to prevent non-specialized signatures from being added before a specialized + // signature. + cutoffIndex++; + } + else { + spliceIndex = index; + } + result.splice(spliceIndex, 0, callChainFlags ? getOptionalCallSignature(signature, callChainFlags) : signature); } - - function checkJSDocFunctionType(node: JSDocFunctionType): void { - if (produceDiagnostics && !node.type && !isJSDocConstructSignature(node)) { - reportImplicitAny(node, anyType); + } + function isSpreadArgument(arg: Expression | undefined): arg is Expression { + return !!arg && (arg.kind === SyntaxKind.SpreadElement || arg.kind === SyntaxKind.SyntheticExpression && (arg).isSpread); + } + function getSpreadArgumentIndex(args: readonly Expression[]): number { + return findIndex(args, isSpreadArgument); + } + function acceptsVoid(t: ts.Type): boolean { + return !!(t.flags & TypeFlags.Void); + } + function hasCorrectArity(node: CallLikeExpression, args: readonly Expression[], signature: ts.Signature, signatureHelpTrailingComma = false) { + let argCount: number; + let callIsIncomplete = false; // In incomplete call we want to be lenient when we have too few arguments + let effectiveParameterCount = getParameterCount(signature); + let effectiveMinimumArguments = getMinArgumentCount(signature); + if (node.kind === SyntaxKind.TaggedTemplateExpression) { + argCount = args.length; + if (node.template.kind === SyntaxKind.TemplateExpression) { + // If a tagged template expression lacks a tail literal, the call is incomplete. + // Specifically, a template only can end in a TemplateTail or a Missing literal. + const lastSpan = last(node.template.templateSpans); // we should always have at least one span. + callIsIncomplete = nodeIsMissing(lastSpan.literal) || !!lastSpan.literal.isUnterminated; + } + else { + // If the template didn't end in a backtick, or its beginning occurred right prior to EOF, + // then this might actually turn out to be a TemplateHead in the future; + // so we consider the call to be incomplete. + const templateLiteral = (node.template); + Debug.assert(templateLiteral.kind === SyntaxKind.NoSubstitutionTemplateLiteral); + callIsIncomplete = !!templateLiteral.isUnterminated; + } + } + else if (node.kind === SyntaxKind.Decorator) { + argCount = getDecoratorArgumentCount(node, signature); + } + else if (isJsxOpeningLikeElement(node)) { + callIsIncomplete = node.attributes.end === node.end; + if (callIsIncomplete) { + return true; } - checkSignatureDeclaration(node); + argCount = effectiveMinimumArguments === 0 ? args.length : 1; + effectiveParameterCount = args.length === 0 ? effectiveParameterCount : 1; // class may have argumentless ctor functions - still resolve ctor and compare vs props member type + effectiveMinimumArguments = Math.min(effectiveMinimumArguments, 1); // sfc may specify context argument - handled by framework and not typechecked } - - function checkJSDocAugmentsTag(node: JSDocAugmentsTag): void { - const classLike = getJSDocHost(node); - if (!isClassDeclaration(classLike) && !isClassExpression(classLike)) { - error(classLike, Diagnostics.JSDoc_0_is_not_attached_to_a_class, idText(node.tagName)); - return; + else { + if (!node.arguments) { + // This only happens when we have something of the form: 'new C' + Debug.assert(node.kind === SyntaxKind.NewExpression); + return getMinArgumentCount(signature) === 0; } - - const augmentsTags = getJSDocTags(classLike).filter(isJSDocAugmentsTag); - Debug.assert(augmentsTags.length > 0); - if (augmentsTags.length > 1) { - error(augmentsTags[1], Diagnostics.Class_declarations_cannot_have_more_than_one_augments_or_extends_tag); + argCount = signatureHelpTrailingComma ? args.length + 1 : args.length; + // If we are missing the close parenthesis, the call is incomplete. + callIsIncomplete = node.arguments.end === node.end; + // If a spread argument is present, check that it corresponds to a rest parameter or at least that it's in the valid range. + const spreadArgIndex = getSpreadArgumentIndex(args); + if (spreadArgIndex >= 0) { + return spreadArgIndex >= getMinArgumentCount(signature) && (hasEffectiveRestParameter(signature) || spreadArgIndex < getParameterCount(signature)); } - - const name = getIdentifierFromEntityNameExpression(node.class.expression); - const extend = getClassExtendsHeritageElement(classLike); - if (extend) { - const className = getIdentifierFromEntityNameExpression(extend.expression); - if (className && name.escapedText !== className.escapedText) { - error(name, Diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, idText(node.tagName), idText(name), idText(className)); + } + // Too many arguments implies incorrect arity. + if (!hasEffectiveRestParameter(signature) && argCount > effectiveParameterCount) { + return false; + } + // If the call is incomplete, we should skip the lower bound check. + // JSX signatures can have extra parameters provided by the library which we don't check + if (callIsIncomplete || argCount >= effectiveMinimumArguments) { + return true; + } + for (let i = argCount; i < effectiveMinimumArguments; i++) { + const type = getTypeAtPosition(signature, i); + if (filterType(type, acceptsVoid).flags & TypeFlags.Never) { + return false; + } + } + return true; + } + function hasCorrectTypeArgumentArity(signature: ts.Signature, typeArguments: NodeArray | undefined) { + // If the user supplied type arguments, but the number of type arguments does not match + // the declared number of type parameters, the call has an incorrect arity. + const numTypeParameters = length(signature.typeParameters); + const minTypeArgumentCount = getMinTypeArgumentCount(signature.typeParameters); + return !some(typeArguments) || + (typeArguments.length >= minTypeArgumentCount && typeArguments.length <= numTypeParameters); + } + // If type has a single call signature and no other members, return that signature. Otherwise, return undefined. + function getSingleCallSignature(type: ts.Type): ts.Signature | undefined { + return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false); + } + function getSingleCallOrConstructSignature(type: ts.Type): ts.Signature | undefined { + return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false) || + getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ false); + } + function getSingleSignature(type: ts.Type, kind: SignatureKind, allowMembers: boolean): ts.Signature | undefined { + if (type.flags & TypeFlags.Object) { + const resolved = resolveStructuredTypeMembers((type)); + if (allowMembers || resolved.properties.length === 0 && !resolved.stringIndexInfo && !resolved.numberIndexInfo) { + if (kind === SignatureKind.Call && resolved.callSignatures.length === 1 && resolved.constructSignatures.length === 0) { + return resolved.callSignatures[0]; + } + if (kind === SignatureKind.Construct && resolved.constructSignatures.length === 1 && resolved.callSignatures.length === 0) { + return resolved.constructSignatures[0]; } } } - - function getIdentifierFromEntityNameExpression(node: Identifier | PropertyAccessExpression): Identifier; - function getIdentifierFromEntityNameExpression(node: Expression): Identifier | undefined; - function getIdentifierFromEntityNameExpression(node: Expression): Identifier | undefined { - switch (node.kind) { - case SyntaxKind.Identifier: - return node as Identifier; - case SyntaxKind.PropertyAccessExpression: - return (node as PropertyAccessExpression).name; - default: + return undefined; + } + // Instantiate a generic signature in the context of a non-generic signature (section 3.8.5 in TypeScript spec) + function instantiateSignatureInContextOf(signature: ts.Signature, contextualSignature: ts.Signature, inferenceContext?: InferenceContext, compareTypes?: TypeComparer): ts.Signature { + const context = createInferenceContext((signature.typeParameters!), signature, InferenceFlags.None, compareTypes); + // We clone the inferenceContext to avoid fixing. For example, when the source signature is (x: T) => T[] and + // the contextual signature is (...args: A) => B, we want to infer the element type of A's constraint (say 'any') + // for T but leave it possible to later infer '[any]' back to A. + const restType = getEffectiveRestType(contextualSignature); + const mapper = inferenceContext && (restType && restType.flags & TypeFlags.TypeParameter ? inferenceContext.nonFixingMapper : inferenceContext.mapper); + const sourceSignature = mapper ? instantiateSignature(contextualSignature, mapper) : contextualSignature; + applyToParameterTypes(sourceSignature, signature, (source, target) => { + // Type parameters from outer context referenced by source type are fixed by instantiation of the source type + inferTypes(context.inferences, source, target); + }); + if (!inferenceContext) { + applyToReturnTypes(contextualSignature, signature, (source, target) => { + inferTypes(context.inferences, source, target, InferencePriority.ReturnType); + }); + } + return getSignatureInstantiation(signature, getInferredTypes(context), isInJSFile(contextualSignature.declaration)); + } + function inferJsxTypeArguments(node: JsxOpeningLikeElement, signature: ts.Signature, checkMode: CheckMode, context: InferenceContext): ts.Type[] { + const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node); + const checkAttrType = checkExpressionWithContextualType(node.attributes, paramType, context, checkMode); + inferTypes(context.inferences, checkAttrType, paramType); + return getInferredTypes(context); + } + function inferTypeArguments(node: CallLikeExpression, signature: ts.Signature, args: readonly Expression[], checkMode: CheckMode, context: InferenceContext): ts.Type[] { + if (isJsxOpeningLikeElement(node)) { + return inferJsxTypeArguments(node, signature, checkMode, context); + } + // If a contextual type is available, infer from that type to the return type of the call expression. For + // example, given a 'function wrap(cb: (x: T) => U): (x: T) => U' and a call expression + // 'let f: (x: string) => number = wrap(s => s.length)', we infer from the declared type of 'f' to the + // return type of 'wrap'. + if (node.kind !== SyntaxKind.Decorator) { + const contextualType = getContextualType(node); + if (contextualType) { + // We clone the inference context to avoid disturbing a resolution in progress for an + // outer call expression. Effectively we just want a snapshot of whatever has been + // inferred for any outer call expression so far. + const outerContext = getInferenceContext(node); + const outerMapper = getMapperFromContext(cloneInferenceContext(outerContext, InferenceFlags.NoDefault)); + const instantiatedType = instantiateType(contextualType, outerMapper); + // If the contextual type is a generic function type with a single call signature, we + // instantiate the type with its own type parameters and type arguments. This ensures that + // the type parameters are not erased to type any during type inference such that they can + // be inferred as actual types from the contextual type. For example: + // declare function arrayMap(f: (x: T) => U): (a: T[]) => U[]; + // const boxElements: (a: A[]) => { value: A }[] = arrayMap(value => ({ value })); + // Above, the type of the 'value' parameter is inferred to be 'A'. + const contextualSignature = getSingleCallSignature(instantiatedType); + const inferenceSourceType = contextualSignature && contextualSignature.typeParameters ? + getOrCreateTypeFromSignature(getSignatureInstantiationWithoutFillingInTypeArguments(contextualSignature, contextualSignature.typeParameters)) : + instantiatedType; + const inferenceTargetType = getReturnTypeOfSignature(signature); + // Inferences made from return types have lower priority than all other inferences. + inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType); + // Create a type mapper for instantiating generic contextual types using the inferences made + // from the return type. We need a separate inference pass here because (a) instantiation of + // the source type uses the outer context's return mapper (which excludes inferences made from + // outer arguments), and (b) we don't want any further inferences going into this context. + const returnContext = createInferenceContext(signature.typeParameters!, signature, context.flags); + const returnSourceType = instantiateType(contextualType, outerContext && outerContext.returnMapper); + inferTypes(returnContext.inferences, returnSourceType, inferenceTargetType); + context.returnMapper = some(returnContext.inferences, hasInferenceCandidates) ? getMapperFromContext(cloneInferredPartOfContext(returnContext)) : undefined; + } + } + const thisType = getThisTypeOfSignature(signature); + if (thisType) { + const thisArgumentNode = getThisArgumentOfCall(node); + const thisArgumentType = thisArgumentNode ? checkExpression(thisArgumentNode) : voidType; + inferTypes(context.inferences, thisArgumentType, thisType); + } + const restType = getNonArrayRestType(signature); + const argCount = restType ? Math.min(getParameterCount(signature) - 1, args.length) : args.length; + for (let i = 0; i < argCount; i++) { + const arg = args[i]; + if (arg.kind !== SyntaxKind.OmittedExpression) { + const paramType = getTypeAtPosition(signature, i); + const argType = checkExpressionWithContextualType(arg, paramType, context, checkMode); + inferTypes(context.inferences, argType, paramType); + } + } + if (restType) { + const spreadType = getSpreadArgumentType(args, argCount, args.length, restType, context); + inferTypes(context.inferences, spreadType, restType); + } + return getInferredTypes(context); + } + function getArrayifiedType(type: ts.Type) { + return type.flags & TypeFlags.Union ? mapType(type, getArrayifiedType) : + type.flags & (TypeFlags.Any | TypeFlags.Instantiable) || isMutableArrayOrTuple(type) ? type : + isTupleType(type) ? createTupleType(getTypeArguments(type), type.target.minLength, type.target.hasRestElement, /*readonly*/ false, type.target.associatedNames) : + createArrayType(getIndexedAccessType(type, numberType)); + } + function getSpreadArgumentType(args: readonly Expression[], index: number, argCount: number, restType: ts.Type, context: InferenceContext | undefined) { + if (index >= argCount - 1) { + const arg = args[argCount - 1]; + if (isSpreadArgument(arg)) { + // We are inferring from a spread expression in the last argument position, i.e. both the parameter + // and the argument are ...x forms. + return arg.kind === SyntaxKind.SyntheticExpression ? + createArrayType((arg).type) : + getArrayifiedType(checkExpressionWithContextualType((arg).expression, restType, context, CheckMode.Normal)); + } + } + const types = []; + let spreadIndex = -1; + for (let i = index; i < argCount; i++) { + const contextualType = getIndexedAccessType(restType, getLiteralType(i - index)); + const argType = checkExpressionWithContextualType(args[i], contextualType, context, CheckMode.Normal); + if (spreadIndex < 0 && isSpreadArgument(args[i])) { + spreadIndex = i - index; + } + const hasPrimitiveContextualType = maybeTypeOfKind(contextualType, TypeFlags.Primitive | TypeFlags.Index); + types.push(hasPrimitiveContextualType ? getRegularTypeOfLiteralType(argType) : getWidenedLiteralType(argType)); + } + return spreadIndex < 0 ? + createTupleType(types) : + createTupleType(append(types.slice(0, spreadIndex), getUnionType(types.slice(spreadIndex))), spreadIndex, /*hasRestElement*/ true); + } + function checkTypeArguments(signature: ts.Signature, typeArgumentNodes: readonly TypeNode[], reportErrors: boolean, headMessage?: DiagnosticMessage): ts.Type[] | undefined { + const isJavascript = isInJSFile(signature.declaration); + const typeParameters = signature.typeParameters!; + const typeArgumentTypes = fillMissingTypeArguments(map(typeArgumentNodes, getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isJavascript); + let mapper: TypeMapper | undefined; + for (let i = 0; i < typeArgumentNodes.length; i++) { + Debug.assert(typeParameters[i] !== undefined, "Should not call checkTypeArguments with too many type arguments"); + const constraint = getConstraintOfTypeParameter(typeParameters[i]); + if (constraint) { + const errorInfo = reportErrors && headMessage ? (() => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Type_0_does_not_satisfy_the_constraint_1)) : undefined; + const typeArgumentHeadMessage = headMessage || Diagnostics.Type_0_does_not_satisfy_the_constraint_1; + if (!mapper) { + mapper = createTypeMapper(typeParameters, typeArgumentTypes); + } + const typeArgument = typeArgumentTypes[i]; + if (!checkTypeAssignableTo(typeArgument, getTypeWithThisArgument(instantiateType(constraint, mapper), typeArgument), reportErrors ? typeArgumentNodes[i] : undefined, typeArgumentHeadMessage, errorInfo)) { return undefined; + } } } - - function checkFunctionOrMethodDeclaration(node: FunctionDeclaration | MethodDeclaration | MethodSignature): void { - checkDecorators(node); - checkSignatureDeclaration(node); - const functionFlags = getFunctionFlags(node); - - // Do not use hasDynamicName here, because that returns false for well known symbols. - // We want to perform checkComputedPropertyName for all computed properties, including - // well known symbols. - if (node.name && node.name.kind === SyntaxKind.ComputedPropertyName) { - // This check will account for methods in class/interface declarations, - // as well as accessors in classes/object literals - checkComputedPropertyName(node.name); + return typeArgumentTypes; + } + function getJsxReferenceKind(node: JsxOpeningLikeElement): JsxReferenceKind { + if (isJsxIntrinsicIdentifier(node.tagName)) { + return JsxReferenceKind.Mixed; + } + const tagType = getApparentType(checkExpression(node.tagName)); + if (length(getSignaturesOfType(tagType, SignatureKind.Construct))) { + return JsxReferenceKind.Component; + } + if (length(getSignaturesOfType(tagType, SignatureKind.Call))) { + return JsxReferenceKind.Function; + } + return JsxReferenceKind.Mixed; + } + /** + * Check if the given signature can possibly be a signature called by the JSX opening-like element. + * @param node a JSX opening-like element we are trying to figure its call signature + * @param signature a candidate signature we are trying whether it is a call signature + * @param relation a relationship to check parameter and argument type + */ + function checkApplicableSignatureForJsxOpeningLikeElement(node: JsxOpeningLikeElement, signature: ts.Signature, relation: ts.Map, checkMode: CheckMode, reportErrors: boolean, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: { + errors?: Diagnostic[]; + skipLogging?: boolean; + }) { + // Stateless function components can have maximum of three arguments: "props", "context", and "updater". + // However "context" and "updater" are implicit and can't be specify by users. Only the first parameter, props, + // can be specified by users through attributes property. + const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node); + const attributesType = checkExpressionWithContextualType(node.attributes, paramType, /*inferenceContext*/ undefined, checkMode); + return checkTypeRelatedToAndOptionallyElaborate(attributesType, paramType, relation, reportErrors ? node.tagName : undefined, node.attributes, + /*headMessage*/ undefined, containingMessageChain, errorOutputContainer); + } + function getSignatureApplicabilityError(node: CallLikeExpression, args: readonly Expression[], signature: ts.Signature, relation: ts.Map, checkMode: CheckMode, reportErrors: boolean, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined): readonly Diagnostic[] | undefined { + const errorOutputContainer: { + errors?: Diagnostic[]; + skipLogging?: boolean; + } = { errors: undefined, skipLogging: true }; + if (isJsxOpeningLikeElement(node)) { + if (!checkApplicableSignatureForJsxOpeningLikeElement(node, signature, relation, checkMode, reportErrors, containingMessageChain, errorOutputContainer)) { + Debug.assert(!reportErrors || !!errorOutputContainer.errors, "jsx should have errors when reporting errors"); + return errorOutputContainer.errors || emptyArray; } - - if (!hasNonBindableDynamicName(node)) { - // first we want to check the local symbol that contain this declaration - // - if node.localSymbol !== undefined - this is current declaration is exported and localSymbol points to the local symbol - // - if node.localSymbol === undefined - this node is non-exported so we can just pick the result of getSymbolOfNode - const symbol = getSymbolOfNode(node); - const localSymbol = node.localSymbol || symbol; - - // Since the javascript won't do semantic analysis like typescript, - // if the javascript file comes before the typescript file and both contain same name functions, - // checkFunctionOrConstructorSymbol wouldn't be called if we didnt ignore javascript function. - const firstDeclaration = find(localSymbol.declarations, - // Get first non javascript function declaration - declaration => declaration.kind === node.kind && !(declaration.flags & NodeFlags.JavaScriptFile)); - - // Only type check the symbol once - if (node === firstDeclaration) { - checkFunctionOrConstructorSymbol(localSymbol); + return undefined; + } + const thisType = getThisTypeOfSignature(signature); + if (thisType && thisType !== voidType && node.kind !== SyntaxKind.NewExpression) { + // If the called expression is not of the form `x.f` or `x["f"]`, then sourceType = voidType + // If the signature's 'this' type is voidType, then the check is skipped -- anything is compatible. + // If the expression is a new expression, then the check is skipped. + const thisArgumentNode = getThisArgumentOfCall(node); + let thisArgumentType: ts.Type; + if (thisArgumentNode) { + thisArgumentType = checkExpression(thisArgumentNode); + if (isOptionalChainRoot(thisArgumentNode.parent)) { + thisArgumentType = getNonNullableType(thisArgumentType); } - - if (symbol.parent) { - // run check once for the first declaration - if (getDeclarationOfKind(symbol, node.kind) === node) { - // run check on export symbol to check that modifiers agree across all exported declarations - checkFunctionOrConstructorSymbol(symbol); - } + else if (isOptionalChain(thisArgumentNode.parent)) { + thisArgumentType = removeOptionalTypeMarker(thisArgumentType); } } - - const body = node.kind === SyntaxKind.MethodSignature ? undefined : node.body; - checkSourceElement(body); - checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, getReturnTypeFromAnnotation(node)); - - if (produceDiagnostics && !getEffectiveReturnTypeNode(node)) { - // Report an implicit any error if there is no body, no explicit return type, and node is not a private method - // in an ambient context - if (nodeIsMissing(body) && !isPrivateWithinAmbient(node)) { - reportImplicitAny(node, anyType); - } - - if (functionFlags & FunctionFlags.Generator && nodeIsPresent(body)) { - // A generator with a body and no type annotation can still cause errors. It can error if the - // yielded values have no common supertype, or it can give an implicit any error if it has no - // yielded values. The only way to trigger these errors is to try checking its return type. - getReturnTypeOfSignature(getSignatureFromDeclaration(node)); + else { + thisArgumentType = voidType; + } + const errorNode = reportErrors ? (thisArgumentNode || node) : undefined; + const headMessage = Diagnostics.The_this_context_of_type_0_is_not_assignable_to_method_s_this_of_type_1; + if (!checkTypeRelatedTo(thisArgumentType, thisType, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer)) { + Debug.assert(!reportErrors || !!errorOutputContainer.errors, "this parameter should have errors when reporting errors"); + return errorOutputContainer.errors || emptyArray; + } + } + const headMessage = Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1; + const restType = getNonArrayRestType(signature); + const argCount = restType ? Math.min(getParameterCount(signature) - 1, args.length) : args.length; + for (let i = 0; i < argCount; i++) { + const arg = args[i]; + if (arg.kind !== SyntaxKind.OmittedExpression) { + const paramType = getTypeAtPosition(signature, i); + const argType = checkExpressionWithContextualType(arg, paramType, /*inferenceContext*/ undefined, checkMode); + // If one or more arguments are still excluded (as indicated by CheckMode.SkipContextSensitive), + // we obtain the regular type of any object literal arguments because we may not have inferred complete + // parameter types yet and therefore excess property checks may yield false positives (see #17041). + const checkArgType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(argType) : argType; + if (!checkTypeRelatedToAndOptionallyElaborate(checkArgType, paramType, relation, reportErrors ? arg : undefined, arg, headMessage, containingMessageChain, errorOutputContainer)) { + Debug.assert(!reportErrors || !!errorOutputContainer.errors, "parameter should have errors when reporting errors"); + maybeAddMissingAwaitInfo(arg, checkArgType, paramType); + return errorOutputContainer.errors || emptyArray; } } - - // A js function declaration can have a @type tag instead of a return type node, but that type must have a call signature - if (isInJSFile(node)) { - const typeTag = getJSDocTypeTag(node); - if (typeTag && typeTag.typeExpression && !getContextualCallSignature(getTypeFromTypeNode(typeTag.typeExpression), node)) { - error(typeTag, Diagnostics.The_type_of_a_function_declaration_must_match_the_function_s_signature); - } + } + if (restType) { + const spreadType = getSpreadArgumentType(args, argCount, args.length, restType, /*context*/ undefined); + const errorNode = reportErrors ? argCount < args.length ? args[argCount] : node : undefined; + if (!checkTypeRelatedTo(spreadType, restType, relation, errorNode, headMessage, /*containingMessageChain*/ undefined, errorOutputContainer)) { + Debug.assert(!reportErrors || !!errorOutputContainer.errors, "rest parameter should have errors when reporting errors"); + maybeAddMissingAwaitInfo(errorNode, spreadType, restType); + return errorOutputContainer.errors || emptyArray; } } - - function registerForUnusedIdentifiersCheck(node: PotentiallyUnusedIdentifier): void { - // May be in a call such as getTypeOfNode that happened to call this. But potentiallyUnusedIdentifiers is only defined in the scope of `checkSourceFile`. - if (produceDiagnostics) { - const sourceFile = getSourceFileOfNode(node); - let potentiallyUnusedIdentifiers = allPotentiallyUnusedIdentifiers.get(sourceFile.path); - if (!potentiallyUnusedIdentifiers) { - potentiallyUnusedIdentifiers = []; - allPotentiallyUnusedIdentifiers.set(sourceFile.path, potentiallyUnusedIdentifiers); + return undefined; + function maybeAddMissingAwaitInfo(errorNode: Node | undefined, source: ts.Type, target: ts.Type) { + if (errorNode && reportErrors && errorOutputContainer.errors && errorOutputContainer.errors.length) { + // Bail if target is Promise-like---something else is wrong + if (getAwaitedTypeOfPromise(target)) { + return; + } + const awaitedTypeOfSource = getAwaitedTypeOfPromise(source); + if (awaitedTypeOfSource && isTypeRelatedTo(awaitedTypeOfSource, target, relation)) { + addRelatedInfo(errorOutputContainer.errors[0], createDiagnosticForNode(errorNode, Diagnostics.Did_you_forget_to_use_await)); } - // TODO: GH#22580 - // Debug.assert(addToSeen(seenPotentiallyUnusedIdentifiers, getNodeId(node)), "Adding potentially-unused identifier twice"); - potentiallyUnusedIdentifiers.push(node); } } - - type PotentiallyUnusedIdentifier = - | SourceFile | ModuleDeclaration | ClassLikeDeclaration | InterfaceDeclaration - | Block | CaseBlock | ForStatement | ForInStatement | ForOfStatement - | Exclude | TypeAliasDeclaration - | InferTypeNode; - - function checkUnusedIdentifiers(potentiallyUnusedIdentifiers: readonly PotentiallyUnusedIdentifier[], addDiagnostic: AddUnusedDiagnostic) { - for (const node of potentiallyUnusedIdentifiers) { - switch (node.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - checkUnusedClassMembers(node, addDiagnostic); - checkUnusedTypeParameters(node, addDiagnostic); - break; - case SyntaxKind.SourceFile: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.Block: - case SyntaxKind.CaseBlock: - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - checkUnusedLocalsAndParameters(node, addDiagnostic); - break; - case SyntaxKind.Constructor: - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ArrowFunction: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - if (node.body) { // Don't report unused parameters in overloads - checkUnusedLocalsAndParameters(node, addDiagnostic); - } - checkUnusedTypeParameters(node, addDiagnostic); - break; - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.InterfaceDeclaration: - checkUnusedTypeParameters(node, addDiagnostic); - break; - case SyntaxKind.InferType: - checkUnusedInferTypeParameter(node, addDiagnostic); - break; - default: - Debug.assertNever(node, "Node should not have been registered for unused identifiers check"); - } + } + /** + * Returns the this argument in calls like x.f(...) and x[f](...). Undefined otherwise. + */ + function getThisArgumentOfCall(node: CallLikeExpression): LeftHandSideExpression | undefined { + if (node.kind === SyntaxKind.CallExpression) { + const callee = skipOuterExpressions(node.expression); + if (callee.kind === SyntaxKind.PropertyAccessExpression || callee.kind === SyntaxKind.ElementAccessExpression) { + return (callee as AccessExpression).expression; } } - - function errorUnusedLocal(declaration: Declaration, name: string, addDiagnostic: AddUnusedDiagnostic) { - const node = getNameOfDeclaration(declaration) || declaration; - const message = isTypeDeclaration(declaration) ? Diagnostics._0_is_declared_but_never_used : Diagnostics._0_is_declared_but_its_value_is_never_read; - addDiagnostic(declaration, UnusedKind.Local, createDiagnosticForNode(node, message, name)); + } + function createSyntheticExpression(parent: Node, type: ts.Type, isSpread?: boolean) { + const result = (createNode(SyntaxKind.SyntheticExpression, parent.pos, parent.end)); + result.parent = parent; + result.type = type; + result.isSpread = isSpread || false; + return result; + } + /** + * Returns the effective arguments for an expression that works like a function invocation. + */ + function getEffectiveCallArguments(node: CallLikeExpression): readonly Expression[] { + if (node.kind === SyntaxKind.TaggedTemplateExpression) { + const template = node.template; + const args: Expression[] = [createSyntheticExpression(template, getGlobalTemplateStringsArrayType())]; + if (template.kind === SyntaxKind.TemplateExpression) { + forEach(template.templateSpans, span => { + args.push(span.expression); + }); + } + return args; } - - function isIdentifierThatStartsWithUnderscore(node: Node) { - return isIdentifier(node) && idText(node).charCodeAt(0) === CharacterCodes._; + if (node.kind === SyntaxKind.Decorator) { + return getEffectiveDecoratorArguments(node); } - - function checkUnusedClassMembers(node: ClassDeclaration | ClassExpression, addDiagnostic: AddUnusedDiagnostic): void { - for (const member of node.members) { - switch (member.kind) { - case SyntaxKind.MethodDeclaration: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - if (member.kind === SyntaxKind.SetAccessor && member.symbol.flags & SymbolFlags.GetAccessor) { - // Already would have reported an error on the getter. - break; - } - const symbol = getSymbolOfNode(member); - if (!symbol.isReferenced && hasModifier(member, ModifierFlags.Private)) { - addDiagnostic(member, UnusedKind.Local, createDiagnosticForNode(member.name!, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolToString(symbol))); - } - break; - case SyntaxKind.Constructor: - for (const parameter of (member).parameters) { - if (!parameter.symbol.isReferenced && hasModifier(parameter, ModifierFlags.Private)) { - addDiagnostic(parameter, UnusedKind.Local, createDiagnosticForNode(parameter.name, Diagnostics.Property_0_is_declared_but_its_value_is_never_read, symbolName(parameter.symbol))); - } - } - break; - case SyntaxKind.IndexSignature: - case SyntaxKind.SemicolonClassElement: - // Can't be private - break; - default: - Debug.fail(); - } - } + if (isJsxOpeningLikeElement(node)) { + return node.attributes.properties.length > 0 || (isJsxOpeningElement(node) && node.parent.children.length > 0) ? [node.attributes] : emptyArray; } - - function checkUnusedInferTypeParameter(node: InferTypeNode, addDiagnostic: AddUnusedDiagnostic): void { - const { typeParameter } = node; - if (isTypeParameterUnused(typeParameter)) { - addDiagnostic(node, UnusedKind.Parameter, createDiagnosticForNode(node, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(typeParameter.name))); + const args = node.arguments || emptyArray; + const length = args.length; + if (length && isSpreadArgument(args[length - 1]) && getSpreadArgumentIndex(args) === length - 1) { + // We have a spread argument in the last position and no other spread arguments. If the type + // of the argument is a tuple type, spread the tuple elements into the argument list. We can + // call checkExpressionCached because spread expressions never have a contextual type. + const spreadArgument = (args[length - 1]); + const type = flowLoopCount ? checkExpression(spreadArgument.expression) : checkExpressionCached(spreadArgument.expression); + if (isTupleType(type)) { + const typeArguments = getTypeArguments((type)); + const restIndex = type.target.hasRestElement ? typeArguments.length - 1 : -1; + const syntheticArgs = map(typeArguments, (t, i) => createSyntheticExpression(spreadArgument, t, /*isSpread*/ i === restIndex)); + return concatenate(args.slice(0, length - 1), syntheticArgs); } } - - function checkUnusedTypeParameters(node: ClassLikeDeclaration | SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration, addDiagnostic: AddUnusedDiagnostic): void { - // Only report errors on the last declaration for the type parameter container; - // this ensures that all uses have been accounted for. - if (last(getSymbolOfNode(node).declarations) !== node) return; - - const typeParameters = getEffectiveTypeParameterDeclarations(node); - const seenParentsWithEveryUnused = new NodeSet(); - - for (const typeParameter of typeParameters) { - if (!isTypeParameterUnused(typeParameter)) continue; - - const name = idText(typeParameter.name); - const { parent } = typeParameter; - if (parent.kind !== SyntaxKind.InferType && parent.typeParameters!.every(isTypeParameterUnused)) { - if (seenParentsWithEveryUnused.tryAdd(parent)) { - const range = isJSDocTemplateTag(parent) - // Whole @template tag - ? rangeOfNode(parent) - // Include the `<>` in the error message - : rangeOfTypeParameters(parent.typeParameters!); - const only = typeParameters.length === 1; - const message = only ? Diagnostics._0_is_declared_but_its_value_is_never_read : Diagnostics.All_type_parameters_are_unused; - const arg0 = only ? name : undefined; - addDiagnostic(typeParameter, UnusedKind.Parameter, createFileDiagnostic(getSourceFileOfNode(parent), range.pos, range.end - range.pos, message, arg0)); - } - } - else { - addDiagnostic(typeParameter, UnusedKind.Parameter, createDiagnosticForNode(typeParameter, Diagnostics._0_is_declared_but_its_value_is_never_read, name)); - } + return args; + } + /** + * Returns the synthetic argument list for a decorator invocation. + */ + function getEffectiveDecoratorArguments(node: Decorator): readonly Expression[] { + const parent = node.parent; + const expr = node.expression; + switch (parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + // For a class decorator, the `target` is the type of the class (e.g. the + // "static" or "constructor" side of the class). + return [ + createSyntheticExpression(expr, getTypeOfSymbol(getSymbolOfNode(parent))) + ]; + case SyntaxKind.Parameter: + // A parameter declaration decorator will have three arguments (see + // `ParameterDecorator` in core.d.ts). + const func = (parent.parent); + return [ + createSyntheticExpression(expr, parent.parent.kind === SyntaxKind.Constructor ? getTypeOfSymbol(getSymbolOfNode(func)) : errorType), + createSyntheticExpression(expr, anyType), + createSyntheticExpression(expr, numberType) + ]; + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + // A method or accessor declaration decorator will have two or three arguments (see + // `PropertyDecorator` and `MethodDecorator` in core.d.ts). If we are emitting decorators + // for ES3, we will only pass two arguments. + const hasPropDesc = parent.kind !== SyntaxKind.PropertyDeclaration && languageVersion !== ScriptTarget.ES3; + return [ + createSyntheticExpression(expr, getParentTypeOfClassElement((parent))), + createSyntheticExpression(expr, getClassElementPropertyKeyType((parent))), + createSyntheticExpression(expr, hasPropDesc ? createTypedPropertyDescriptorType(getTypeOfNode(parent)) : anyType) + ]; + } + return Debug.fail(); + } + /** + * Returns the argument count for a decorator node that works like a function invocation. + */ + function getDecoratorArgumentCount(node: Decorator, signature: ts.Signature) { + switch (node.parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + return 1; + case SyntaxKind.PropertyDeclaration: + return 2; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + // For ES3 or decorators with only two parameters we supply only two arguments + return languageVersion === ScriptTarget.ES3 || signature.parameters.length <= 2 ? 2 : 3; + case SyntaxKind.Parameter: + return 3; + default: + return Debug.fail(); + } + } + function getDiagnosticSpanForCallNode(node: CallExpression, doNotIncludeArguments?: boolean) { + let start: number; + let length: number; + const sourceFile = getSourceFileOfNode(node); + if (isPropertyAccessExpression(node.expression)) { + const nameSpan = getErrorSpanForNode(sourceFile, node.expression.name); + start = nameSpan.start; + length = doNotIncludeArguments ? nameSpan.length : node.end - start; + } + else { + const expressionSpan = getErrorSpanForNode(sourceFile, node.expression); + start = expressionSpan.start; + length = doNotIncludeArguments ? expressionSpan.length : node.end - start; + } + return { start, length, sourceFile }; + } + function getDiagnosticForCallNode(node: CallLikeExpression, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): DiagnosticWithLocation { + if (isCallExpression(node)) { + const { sourceFile, start, length } = getDiagnosticSpanForCallNode(node); + return createFileDiagnostic(sourceFile, start, length, message, arg0, arg1, arg2, arg3); + } + else { + return createDiagnosticForNode(node, message, arg0, arg1, arg2, arg3); + } + } + function getArgumentArityError(node: CallLikeExpression, signatures: readonly ts.Signature[], args: readonly Expression[]) { + let min = Number.POSITIVE_INFINITY; + let max = Number.NEGATIVE_INFINITY; + let belowArgCount = Number.NEGATIVE_INFINITY; + let aboveArgCount = Number.POSITIVE_INFINITY; + let argCount = args.length; + let closestSignature: ts.Signature | undefined; + for (const sig of signatures) { + const minCount = getMinArgumentCount(sig); + const maxCount = getParameterCount(sig); + if (minCount < argCount && minCount > belowArgCount) + belowArgCount = minCount; + if (argCount < maxCount && maxCount < aboveArgCount) + aboveArgCount = maxCount; + if (minCount < min) { + min = minCount; + closestSignature = sig; + } + max = Math.max(max, maxCount); + } + const hasRestParameter = some(signatures, hasEffectiveRestParameter); + const paramRange = hasRestParameter ? min : + min < max ? min + "-" + max : + min; + const hasSpreadArgument = getSpreadArgumentIndex(args) > -1; + if (argCount <= max && hasSpreadArgument) { + argCount--; + } + let spanArray: NodeArray; + let related: DiagnosticWithLocation | undefined; + const error = hasRestParameter || hasSpreadArgument ? hasRestParameter && hasSpreadArgument ? Diagnostics.Expected_at_least_0_arguments_but_got_1_or_more : + hasRestParameter ? Diagnostics.Expected_at_least_0_arguments_but_got_1 : + Diagnostics.Expected_0_arguments_but_got_1_or_more : Diagnostics.Expected_0_arguments_but_got_1; + if (closestSignature && getMinArgumentCount(closestSignature) > argCount && closestSignature.declaration) { + const paramDecl = closestSignature.declaration.parameters[closestSignature.thisParameter ? argCount + 1 : argCount]; + if (paramDecl) { + related = createDiagnosticForNode(paramDecl, isBindingPattern(paramDecl.name) ? Diagnostics.An_argument_matching_this_binding_pattern_was_not_provided : Diagnostics.An_argument_for_0_was_not_provided, !paramDecl.name ? argCount : !isBindingPattern(paramDecl.name) ? idText(getFirstIdentifier(paramDecl.name)) : undefined); } } - function isTypeParameterUnused(typeParameter: TypeParameterDeclaration): boolean { - return !(getMergedSymbol(typeParameter.symbol).isReferenced! & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderscore(typeParameter.name); + if (min < argCount && argCount < max) { + return getDiagnosticForCallNode(node, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, argCount, belowArgCount, aboveArgCount); } - - function addToGroup(map: Map<[K, V[]]>, key: K, value: V, getKey: (key: K) => number | string): void { - const keyString = String(getKey(key)); - const group = map.get(keyString); - if (group) { - group[1].push(value); + if (!hasSpreadArgument && argCount < min) { + const diagnostic = getDiagnosticForCallNode(node, error, paramRange, argCount); + return related ? addRelatedInfo(diagnostic, related) : diagnostic; + } + if (hasRestParameter || hasSpreadArgument) { + spanArray = createNodeArray(args); + if (hasSpreadArgument && argCount) { + const nextArg = elementAt(args, getSpreadArgumentIndex(args) + 1) || undefined; + spanArray = createNodeArray(args.slice(max > argCount && nextArg ? args.indexOf(nextArg) : Math.min(max, args.length - 1))); } - else { - map.set(keyString, [key, [value]]); + } + else { + spanArray = createNodeArray(args.slice(max)); + } + spanArray.pos = first(spanArray).pos; + spanArray.end = last(spanArray).end; + if (spanArray.end === spanArray.pos) { + spanArray.end++; + } + const diagnostic = createDiagnosticForNodeArray(getSourceFileOfNode(node), spanArray, error, paramRange, argCount); + return related ? addRelatedInfo(diagnostic, related) : diagnostic; + } + function getTypeArgumentArityError(node: Node, signatures: readonly ts.Signature[], typeArguments: NodeArray) { + const argCount = typeArguments.length; + // No overloads exist + if (signatures.length === 1) { + const sig = signatures[0]; + const min = getMinTypeArgumentCount(sig.typeParameters); + const max = length(sig.typeParameters); + return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, min < max ? min + "-" + max : min, argCount); + } + // Overloads exist + let belowArgCount = -Infinity; + let aboveArgCount = Infinity; + for (const sig of signatures) { + const min = getMinTypeArgumentCount(sig.typeParameters); + const max = length(sig.typeParameters); + if (min > argCount) { + aboveArgCount = Math.min(aboveArgCount, min); + } + else if (max < argCount) { + belowArgCount = Math.max(belowArgCount, max); + } + } + if (belowArgCount !== -Infinity && aboveArgCount !== Infinity) { + return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.No_overload_expects_0_type_arguments_but_overloads_do_exist_that_expect_either_1_or_2_type_arguments, argCount, belowArgCount, aboveArgCount); + } + return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount); + } + function resolveCall(node: CallLikeExpression, signatures: readonly ts.Signature[], candidatesOutArray: ts.Signature[] | undefined, checkMode: CheckMode, callChainFlags: SignatureFlags, fallbackError?: DiagnosticMessage): ts.Signature { + const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; + const isDecorator = node.kind === SyntaxKind.Decorator; + const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node); + const reportErrors = !candidatesOutArray; + let typeArguments: NodeArray | undefined; + if (!isDecorator) { + typeArguments = (node).typeArguments; + // We already perform checking on the type arguments on the class declaration itself. + if (isTaggedTemplate || isJsxOpeningOrSelfClosingElement || (node).expression.kind !== SyntaxKind.SuperKeyword) { + forEach(typeArguments, checkSourceElement); + } + } + const candidates = candidatesOutArray || []; + // reorderCandidates fills up the candidates array directly + reorderCandidates(signatures, candidates, callChainFlags); + if (!candidates.length) { + if (reportErrors) { + diagnostics.add(getDiagnosticForCallNode(node, Diagnostics.Call_target_does_not_contain_any_signatures)); } + return resolveErrorCall(node); } - - function tryGetRootParameterDeclaration(node: Node): ParameterDeclaration | undefined { - return tryCast(getRootDeclaration(node), isParameter); + const args = getEffectiveCallArguments(node); + // The excludeArgument array contains true for each context sensitive argument (an argument + // is context sensitive it is susceptible to a one-time permanent contextual typing). + // + // The idea is that we will perform type argument inference & assignability checking once + // without using the susceptible parameters that are functions, and once more for those + // parameters, contextually typing each as we go along. + // + // For a tagged template, then the first argument be 'undefined' if necessary because it + // represents a TemplateStringsArray. + // + // For a decorator, no arguments are susceptible to contextual typing due to the fact + // decorators are applied to a declaration by the emitter, and not to an expression. + const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters; + let argCheckMode = !isDecorator && !isSingleNonGenericCandidate && some(args, isContextSensitive) ? CheckMode.SkipContextSensitive : CheckMode.Normal; + // The following variables are captured and modified by calls to chooseOverload. + // If overload resolution or type argument inference fails, we want to report the + // best error possible. The best error is one which says that an argument was not + // assignable to a parameter. This implies that everything else about the overload + // was fine. So if there is any overload that is only incorrect because of an + // argument, we will report an error on that one. + // + // function foo(s: string): void; + // function foo(n: number): void; // Report argument error on this overload + // function foo(): void; + // foo(true); + // + // If none of the overloads even made it that far, there are two possibilities. + // There was a problem with type arguments for some overload, in which case + // report an error on that. Or none of the overloads even had correct arity, + // in which case give an arity error. + // + // function foo(x: T): void; // Report type argument error + // function foo(): void; + // foo(0); + // + let candidatesForArgumentError: ts.Signature[] | undefined; + let candidateForArgumentArityError: ts.Signature | undefined; + let candidateForTypeArgumentError: ts.Signature | undefined; + let result: ts.Signature | undefined; + // If we are in signature help, a trailing comma indicates that we intend to provide another argument, + // so we will only accept overloads with arity at least 1 higher than the current number of provided arguments. + const signatureHelpTrailingComma = !!(checkMode & CheckMode.IsForSignatureHelp) && node.kind === SyntaxKind.CallExpression && node.arguments.hasTrailingComma; + // Section 4.12.1: + // if the candidate list contains one or more signatures for which the type of each argument + // expression is a subtype of each corresponding parameter type, the return type of the first + // of those signatures becomes the return type of the function call. + // Otherwise, the return type of the first signature in the candidate list becomes the return + // type of the function call. + // + // Whether the call is an error is determined by assignability of the arguments. The subtype pass + // is just important for choosing the best signature. So in the case where there is only one + // signature, the subtype pass is useless. So skipping it is an optimization. + if (candidates.length > 1) { + result = chooseOverload(candidates, subtypeRelation, signatureHelpTrailingComma); } - - function checkUnusedLocalsAndParameters(nodeWithLocals: Node, addDiagnostic: AddUnusedDiagnostic): void { - // Ideally we could use the ImportClause directly as a key, but must wait until we have full ES6 maps. So must store key along with value. - const unusedImports = createMap<[ImportClause, ImportedDeclaration[]]>(); - const unusedDestructures = createMap<[ObjectBindingPattern, BindingElement[]]>(); - const unusedVariables = createMap<[VariableDeclarationList, VariableDeclaration[]]>(); - nodeWithLocals.locals!.forEach(local => { - // If it's purely a type parameter, ignore, will be checked in `checkUnusedTypeParameters`. - // If it's a type parameter merged with a parameter, check if the parameter-side is used. - if (local.flags & SymbolFlags.TypeParameter ? !(local.flags & SymbolFlags.Variable && !(local.isReferenced! & SymbolFlags.Variable)) : local.isReferenced || local.exportSymbol) { - return; - } - - for (const declaration of local.declarations) { - if (isAmbientModule(declaration) || - (isVariableDeclaration(declaration) && isForInOrOfStatement(declaration.parent.parent) || isImportedDeclaration(declaration)) && isIdentifierThatStartsWithUnderscore(declaration.name!)) { - continue; - } - - if (isImportedDeclaration(declaration)) { - addToGroup(unusedImports, importClauseFromImported(declaration), declaration, getNodeId); - } - else if (isBindingElement(declaration) && isObjectBindingPattern(declaration.parent)) { - // In `{ a, ...b }, `a` is considered used since it removes a property from `b`. `b` may still be unused though. - const lastElement = last(declaration.parent.elements); - if (declaration === lastElement || !last(declaration.parent.elements).dotDotDotToken) { - addToGroup(unusedDestructures, declaration.parent, declaration, getNodeId); + if (!result) { + result = chooseOverload(candidates, assignableRelation, signatureHelpTrailingComma); + } + if (result) { + return result; + } + // No signatures were applicable. Now report errors based on the last applicable signature with + // no arguments excluded from assignability checks. + // If candidate is undefined, it means that no candidates had a suitable arity. In that case, + // skip the checkApplicableSignature check. + if (reportErrors) { + if (candidatesForArgumentError) { + if (candidatesForArgumentError.length === 1 || candidatesForArgumentError.length > 3) { + const last = candidatesForArgumentError[candidatesForArgumentError.length - 1]; + let chain: DiagnosticMessageChain | undefined; + if (candidatesForArgumentError.length > 3) { + chain = chainDiagnosticMessages(chain, Diagnostics.The_last_overload_gave_the_following_error); + chain = chainDiagnosticMessages(chain, Diagnostics.No_overload_matches_this_call); + } + const diags = getSignatureApplicabilityError(node, args, last, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, () => chain); + if (diags) { + for (const d of diags) { + if (last.declaration && candidatesForArgumentError.length > 3) { + addRelatedInfo(d, createDiagnosticForNode(last.declaration, Diagnostics.The_last_overload_is_declared_here)); + } + diagnostics.add(d); } } - else if (isVariableDeclaration(declaration)) { - addToGroup(unusedVariables, declaration.parent, declaration, getNodeId); - } else { - const parameter = local.valueDeclaration && tryGetRootParameterDeclaration(local.valueDeclaration); - const name = local.valueDeclaration && getNameOfDeclaration(local.valueDeclaration); - if (parameter && name) { - if (!isParameterPropertyDeclaration(parameter, parameter.parent) && !parameterIsThisKeyword(parameter) && !isIdentifierThatStartsWithUnderscore(name)) { - addDiagnostic(parameter, UnusedKind.Parameter, createDiagnosticForNode(name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(local))); + Debug.fail("No error for last overload signature"); + } + } + else { + const allDiagnostics: (readonly DiagnosticRelatedInformation[])[] = []; + let max = 0; + let min = Number.MAX_VALUE; + let minIndex = 0; + let i = 0; + for (const c of candidatesForArgumentError) { + const chain = () => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Overload_0_of_1_2_gave_the_following_error, i + 1, candidates.length, signatureToString(c)); + const diags = getSignatureApplicabilityError(node, args, c, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, chain); + if (diags) { + if (diags.length <= min) { + min = diags.length; + minIndex = i; } + max = Math.max(max, diags.length); + allDiagnostics.push(diags); } else { - errorUnusedLocal(declaration, symbolName(local), addDiagnostic); + Debug.fail("No error for 3 or fewer overload signatures"); } + i++; + } + const diags = max > 1 ? allDiagnostics[minIndex] : flatten(allDiagnostics); + Debug.assert(diags.length > 0, "No errors reported for 3 or fewer overload signatures"); + const chain = chainDiagnosticMessages(map(diags, d => typeof d.messageText === "string" ? (d as DiagnosticMessageChain) : d.messageText), Diagnostics.No_overload_matches_this_call); + const related = (flatMap(diags, d => (d as Diagnostic).relatedInformation) as DiagnosticRelatedInformation[]); + if (every(diags, d => d.start === diags[0].start && d.length === diags[0].length && d.file === diags[0].file)) { + const { file, start, length } = diags[0]; + diagnostics.add({ file, start, length, code: chain.code, category: chain.category, messageText: chain, relatedInformation: related }); + } + else { + diagnostics.add(createDiagnosticForNodeFromMessageChain(node, chain, related)); } } - }); - unusedImports.forEach(([importClause, unuseds]) => { - const importDecl = importClause.parent; - const nDeclarations = (importClause.name ? 1 : 0) + - (importClause.namedBindings ? - (importClause.namedBindings.kind === SyntaxKind.NamespaceImport ? 1 : importClause.namedBindings.elements.length) - : 0); - if (nDeclarations === unuseds.length) { - addDiagnostic(importDecl, UnusedKind.Local, unuseds.length === 1 - ? createDiagnosticForNode(importDecl, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(first(unuseds).name!)) - : createDiagnosticForNode(importDecl, Diagnostics.All_imports_in_import_declaration_are_unused)); + } + else if (candidateForArgumentArityError) { + diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args)); + } + else if (candidateForTypeArgumentError) { + checkTypeArguments(candidateForTypeArgumentError, ((node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!), /*reportErrors*/ true, fallbackError); + } + else { + const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments)); + if (signaturesWithCorrectTypeArgumentArity.length === 0) { + diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments!)); } - else { - for (const unused of unuseds) errorUnusedLocal(unused, idText(unused.name!), addDiagnostic); + else if (!isDecorator) { + diagnostics.add(getArgumentArityError(node, signaturesWithCorrectTypeArgumentArity, args)); } - }); - unusedDestructures.forEach(([bindingPattern, bindingElements]) => { - const kind = tryGetRootParameterDeclaration(bindingPattern.parent) ? UnusedKind.Parameter : UnusedKind.Local; - if (bindingPattern.elements.length === bindingElements.length) { - if (bindingElements.length === 1 && bindingPattern.parent.kind === SyntaxKind.VariableDeclaration && bindingPattern.parent.parent.kind === SyntaxKind.VariableDeclarationList) { - addToGroup(unusedVariables, bindingPattern.parent.parent, bindingPattern.parent, getNodeId); + else if (fallbackError) { + diagnostics.add(getDiagnosticForCallNode(node, fallbackError)); + } + } + } + return produceDiagnostics || !args ? resolveErrorCall(node) : getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray); + function chooseOverload(candidates: ts.Signature[], relation: ts.Map, signatureHelpTrailingComma = false) { + candidatesForArgumentError = undefined; + candidateForArgumentArityError = undefined; + candidateForTypeArgumentError = undefined; + if (isSingleNonGenericCandidate) { + const candidate = candidates[0]; + if (some(typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { + return undefined; + } + if (getSignatureApplicabilityError(node, args, candidate, relation, CheckMode.Normal, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { + candidatesForArgumentError = [candidate]; + return undefined; + } + return candidate; + } + for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) { + const candidate = candidates[candidateIndex]; + if (!hasCorrectTypeArgumentArity(candidate, typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { + continue; + } + let checkCandidate: ts.Signature; + let inferenceContext: InferenceContext | undefined; + if (candidate.typeParameters) { + let typeArgumentTypes: ts.Type[] | undefined; + if (some(typeArguments)) { + typeArgumentTypes = checkTypeArguments(candidate, typeArguments, /*reportErrors*/ false); + if (!typeArgumentTypes) { + candidateForTypeArgumentError = candidate; + continue; + } } else { - addDiagnostic(bindingPattern, kind, bindingElements.length === 1 - ? createDiagnosticForNode(bindingPattern, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(first(bindingElements).name)) - : createDiagnosticForNode(bindingPattern, Diagnostics.All_destructured_elements_are_unused)); + inferenceContext = createInferenceContext(candidate.typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None); + typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext); + argCheckMode |= inferenceContext.flags & InferenceFlags.SkippedGenericFunction ? CheckMode.SkipGenericFunctions : CheckMode.Normal; + } + checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters); + // If the original signature has a generic rest type, instantiation may produce a + // signature with different arity and we need to perform another arity check. + if (getNonArrayRestType(candidate) && !hasCorrectArity(node, args, checkCandidate, signatureHelpTrailingComma)) { + candidateForArgumentArityError = checkCandidate; + continue; } } else { - for (const e of bindingElements) { - addDiagnostic(e, kind, createDiagnosticForNode(e, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(e.name))); - } + checkCandidate = candidate; } - }); - unusedVariables.forEach(([declarationList, declarations]) => { - if (declarationList.declarations.length === declarations.length) { - addDiagnostic(declarationList, UnusedKind.Local, declarations.length === 1 - ? createDiagnosticForNode(first(declarations).name, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(first(declarations).name)) - : createDiagnosticForNode(declarationList.parent.kind === SyntaxKind.VariableStatement ? declarationList.parent : declarationList, Diagnostics.All_variables_are_unused)); + if (getSignatureApplicabilityError(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { + // Give preference to error candidates that have no rest parameters (as they are more specific) + (candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate); + continue; } - else { - for (const decl of declarations) { - addDiagnostic(decl, UnusedKind.Local, createDiagnosticForNode(decl, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(decl.name))); + if (argCheckMode) { + // If one or more context sensitive arguments were excluded, we start including + // them now (and keeping do so for any subsequent candidates) and perform a second + // round of type inference and applicability checking for this particular candidate. + argCheckMode = CheckMode.Normal; + if (inferenceContext) { + const typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode, inferenceContext); + checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters); + // If the original signature has a generic rest type, instantiation may produce a + // signature with different arity and we need to perform another arity check. + if (getNonArrayRestType(candidate) && !hasCorrectArity(node, args, checkCandidate, signatureHelpTrailingComma)) { + candidateForArgumentArityError = checkCandidate; + continue; + } + } + if (getSignatureApplicabilityError(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { + // Give preference to error candidates that have no rest parameters (as they are more specific) + (candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate); + continue; } } - }); + candidates[candidateIndex] = checkCandidate; + return checkCandidate; + } + return undefined; } - - function bindingNameText(name: BindingName): string { - switch (name.kind) { - case SyntaxKind.Identifier: - return idText(name); - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.ObjectBindingPattern: - return bindingNameText(cast(first(name.elements), isBindingElement).name); - default: - return Debug.assertNever(name); + } + // No signature was applicable. We have already reported the errors for the invalid signature. + // If this is a type resolution session, e.g. Language Service, try to get better information than anySignature. + function getCandidateForOverloadFailure(node: CallLikeExpression, candidates: ts.Signature[], args: readonly Expression[], hasCandidatesOutArray: boolean): ts.Signature { + Debug.assert(candidates.length > 0); // Else should not have called this. + // Normally we will combine overloads. Skip this if they have type parameters since that's hard to combine. + // Don't do this if there is a `candidatesOutArray`, + // because then we want the chosen best candidate to be one of the overloads, not a combination. + return hasCandidatesOutArray || candidates.length === 1 || candidates.some(c => !!c.typeParameters) + ? pickLongestCandidateSignature(node, candidates, args) + : createUnionOfSignaturesForOverloadFailure(candidates); + } + function createUnionOfSignaturesForOverloadFailure(candidates: readonly ts.Signature[]): ts.Signature { + const thisParameters = mapDefined(candidates, c => c.thisParameter); + let thisParameter: ts.Symbol | undefined; + if (thisParameters.length) { + thisParameter = createCombinedSymbolFromTypes(thisParameters, thisParameters.map(getTypeOfParameter)); + } + const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters); + const parameters: ts.Symbol[] = []; + for (let i = 0; i < maxNonRestParam; i++) { + const symbols = mapDefined(candidates, s => signatureHasRestParameter(s) ? + i < s.parameters.length - 1 ? s.parameters[i] : last(s.parameters) : + i < s.parameters.length ? s.parameters[i] : undefined); + Debug.assert(symbols.length !== 0); + parameters.push(createCombinedSymbolFromTypes(symbols, mapDefined(candidates, candidate => tryGetTypeAtPosition(candidate, i)))); + } + const restParameterSymbols = mapDefined(candidates, c => signatureHasRestParameter(c) ? last(c.parameters) : undefined); + let flags = SignatureFlags.None; + if (restParameterSymbols.length !== 0) { + const type = createArrayType(getUnionType(mapDefined(candidates, tryGetRestTypeOfSignature), UnionReduction.Subtype)); + parameters.push(createCombinedSymbolForOverloadFailure(restParameterSymbols, type)); + flags |= SignatureFlags.HasRestParameter; + } + if (candidates.some(signatureHasLiteralTypes)) { + flags |= SignatureFlags.HasLiteralTypes; + } + return createSignature(candidates[0].declaration, + /*typeParameters*/ undefined, // Before calling this we tested for `!candidates.some(c => !!c.typeParameters)`. + thisParameter, parameters, + /*resolvedReturnType*/ getIntersectionType(candidates.map(getReturnTypeOfSignature)), + /*typePredicate*/ undefined, minArgumentCount, flags); + } + function getNumNonRestParameters(signature: ts.Signature): number { + const numParams = signature.parameters.length; + return signatureHasRestParameter(signature) ? numParams - 1 : numParams; + } + function createCombinedSymbolFromTypes(sources: readonly ts.Symbol[], types: ts.Type[]): ts.Symbol { + return createCombinedSymbolForOverloadFailure(sources, getUnionType(types, UnionReduction.Subtype)); + } + function createCombinedSymbolForOverloadFailure(sources: readonly ts.Symbol[], type: ts.Type): ts.Symbol { + // This function is currently only used for erroneous overloads, so it's good enough to just use the first source. + return createSymbolWithType(first(sources), type); + } + function pickLongestCandidateSignature(node: CallLikeExpression, candidates: ts.Signature[], args: readonly Expression[]): ts.Signature { + // Pick the longest signature. This way we can get a contextual type for cases like: + // declare function f(a: { xa: number; xb: number; }, b: number); + // f({ | + // Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like: + // declare function f(k: keyof T); + // f(" + const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args.length : apparentArgumentCount); + const candidate = candidates[bestIndex]; + const { typeParameters } = candidate; + if (!typeParameters) { + return candidate; + } + const typeArgumentNodes: readonly TypeNode[] | undefined = callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments : undefined; + const instantiated = typeArgumentNodes + ? createSignatureInstantiation(candidate, getTypeArgumentsFromNodes(typeArgumentNodes, typeParameters, isInJSFile(node))) + : inferSignatureInstantiationForOverloadFailure(node, typeParameters, candidate, args); + candidates[bestIndex] = instantiated; + return instantiated; + } + function getTypeArgumentsFromNodes(typeArgumentNodes: readonly TypeNode[], typeParameters: readonly TypeParameter[], isJs: boolean): readonly ts.Type[] { + const typeArguments = typeArgumentNodes.map(getTypeOfNode); + while (typeArguments.length > typeParameters.length) { + typeArguments.pop(); + } + while (typeArguments.length < typeParameters.length) { + typeArguments.push(getConstraintOfTypeParameter(typeParameters[typeArguments.length]) || getDefaultTypeArgumentType(isJs)); + } + return typeArguments; + } + function inferSignatureInstantiationForOverloadFailure(node: CallLikeExpression, typeParameters: readonly TypeParameter[], candidate: ts.Signature, args: readonly Expression[]): ts.Signature { + const inferenceContext = createInferenceContext(typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None); + const typeArgumentTypes = inferTypeArguments(node, candidate, args, CheckMode.SkipContextSensitive | CheckMode.SkipGenericFunctions, inferenceContext); + return createSignatureInstantiation(candidate, typeArgumentTypes); + } + function getLongestCandidateIndex(candidates: ts.Signature[], argsCount: number): number { + let maxParamsIndex = -1; + let maxParams = -1; + for (let i = 0; i < candidates.length; i++) { + const candidate = candidates[i]; + const paramCount = getParameterCount(candidate); + if (hasEffectiveRestParameter(candidate) || paramCount >= argsCount) { + return i; + } + if (paramCount > maxParams) { + maxParams = paramCount; + maxParamsIndex = i; } } - - type ImportedDeclaration = ImportClause | ImportSpecifier | NamespaceImport; - function isImportedDeclaration(node: Node): node is ImportedDeclaration { - return node.kind === SyntaxKind.ImportClause || node.kind === SyntaxKind.ImportSpecifier || node.kind === SyntaxKind.NamespaceImport; + return maxParamsIndex; + } + function resolveCallExpression(node: CallExpression, candidatesOutArray: ts.Signature[] | undefined, checkMode: CheckMode): ts.Signature { + if (node.expression.kind === SyntaxKind.SuperKeyword) { + const superType = checkSuperExpression(node.expression); + if (isTypeAny(superType)) { + for (const arg of node.arguments) { + checkExpression(arg); // Still visit arguments so they get marked for visibility, etc + } + return anySignature; + } + if (superType !== errorType) { + // In super call, the candidate signatures are the matching arity signatures of the base constructor function instantiated + // with the type arguments specified in the extends clause. + const baseTypeNode = getEffectiveBaseTypeNode((getContainingClass(node)!)); + if (baseTypeNode) { + const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode); + return resolveCall(node, baseConstructors, candidatesOutArray, checkMode, SignatureFlags.None); + } + } + return resolveUntypedCall(node); + } + let callChainFlags: SignatureFlags; + let funcType = checkExpression(node.expression); + if (isCallChain(node)) { + const nonOptionalType = getOptionalExpressionType(funcType, node.expression); + callChainFlags = nonOptionalType === funcType ? SignatureFlags.None : + isOutermostOptionalChain(node) ? SignatureFlags.IsOuterCallChain : + SignatureFlags.IsInnerCallChain; + funcType = nonOptionalType; + } + else { + callChainFlags = SignatureFlags.None; } - function importClauseFromImported(decl: ImportedDeclaration): ImportClause { - return decl.kind === SyntaxKind.ImportClause ? decl : decl.kind === SyntaxKind.NamespaceImport ? decl.parent : decl.parent.parent; + funcType = checkNonNullTypeWithReporter(funcType, node.expression, reportCannotInvokePossiblyNullOrUndefinedError); + if (funcType === silentNeverType) { + return silentNeverSignature; } - - function checkBlock(node: Block) { - // Grammar checking for SyntaxKind.Block - if (node.kind === SyntaxKind.Block) { - checkGrammarStatementInAmbientContext(node); - } - if (isFunctionOrModuleBlock(node)) { - const saveFlowAnalysisDisabled = flowAnalysisDisabled; - forEach(node.statements, checkSourceElement); - flowAnalysisDisabled = saveFlowAnalysisDisabled; + const apparentType = getApparentType(funcType); + if (apparentType === errorType) { + // Another error has already been reported + return resolveErrorCall(node); + } + // Technically, this signatures list may be incomplete. We are taking the apparent type, + // but we are not including call signatures that may have been added to the Object or + // Function interface, since they have none by default. This is a bit of a leap of faith + // that the user will not add any. + const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; + // TS 1.0 Spec: 4.12 + // In an untyped function call no TypeArgs are permitted, Args can be any argument list, no contextual + // types are provided for the argument expressions, and the result is always of type Any. + if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) { + // The unknownType indicates that an error already occurred (and was reported). No + // need to report another error in this case. + if (funcType !== errorType && node.typeArguments) { + error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments); + } + return resolveUntypedCall(node); + } + // If FuncExpr's apparent type(section 3.8.1) is a function type, the call is a typed function call. + // TypeScript employs overload resolution in typed function calls in order to support functions + // with multiple call signatures. + if (!callSignatures.length) { + if (numConstructSignatures) { + error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType)); } else { - forEach(node.statements, checkSourceElement); - } - if (node.locals) { - registerForUnusedIdentifiersCheck(node); + let relatedInformation: DiagnosticRelatedInformation | undefined; + if (node.arguments.length === 1) { + const text = getSourceFileOfNode(node).text; + if (isLineBreak(text.charCodeAt(skipTrivia(text, node.expression.end, /* stopAfterLineBreak */ true) - 1))) { + relatedInformation = createDiagnosticForNode(node.expression, Diagnostics.It_is_highly_likely_that_you_are_missing_a_semicolon); + } + } + invocationError(node.expression, apparentType, SignatureKind.Call, relatedInformation); } + return resolveErrorCall(node); } - - function checkCollisionWithArgumentsInGeneratedCode(node: SignatureDeclaration) { - // no rest parameters \ declaration context \ overload - no codegen impact - if (languageVersion >= ScriptTarget.ES2015 || compilerOptions.noEmit || !hasRestParameter(node) || node.flags & NodeFlags.Ambient || nodeIsMissing((node).body)) { - return; - } - - forEach(node.parameters, p => { - if (p.name && !isBindingPattern(p.name) && p.name.escapedText === argumentsSymbol.escapedName) { - error(p, Diagnostics.Duplicate_identifier_arguments_Compiler_uses_arguments_to_initialize_rest_parameters); - } - }); + // When a call to a generic function is an argument to an outer call to a generic function for which + // inference is in process, we have a choice to make. If the inner call relies on inferences made from + // its contextual type to its return type, deferring the inner call processing allows the best possible + // contextual type to accumulate. But if the outer call relies on inferences made from the return type of + // the inner call, the inner call should be processed early. There's no sure way to know which choice is + // right (only a full unification algorithm can determine that), so we resort to the following heuristic: + // If no type arguments are specified in the inner call and at least one call signature is generic and + // returns a function type, we choose to defer processing. This narrowly permits function composition + // operators to flow inferences through return types, but otherwise processes calls right away. We + // use the resolvingSignature singleton to indicate that we deferred processing. This result will be + // propagated out and eventually turned into nonInferrableType (a type that is assignable to anything and + // from which we never make inferences). + if (checkMode & CheckMode.SkipGenericFunctions && !node.typeArguments && callSignatures.some(isGenericFunctionReturningFunction)) { + skippedGenericFunction(node, checkMode); + return resolvingSignature; + } + // If the function is explicitly marked with `@class`, then it must be constructed. + if (callSignatures.some(sig => isInJSFile(sig.declaration) && !!getJSDocClassTag((sig.declaration!)))) { + error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType)); + return resolveErrorCall(node); } - - function needCollisionCheckForIdentifier(node: Node, identifier: Identifier | undefined, name: string): boolean { - if (!(identifier && identifier.escapedText === name)) { - return false; + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, callChainFlags); + } + function isGenericFunctionReturningFunction(signature: ts.Signature) { + return !!(signature.typeParameters && isFunctionType(getReturnTypeOfSignature(signature))); + } + /** + * TS 1.0 spec: 4.12 + * If FuncExpr is of type Any, or of an object type that has no call or construct signatures + * but is a subtype of the Function interface, the call is an untyped function call. + */ + function isUntypedFunctionCall(funcType: ts.Type, apparentFuncType: ts.Type, numCallSignatures: number, numConstructSignatures: number): boolean { + // We exclude union types because we may have a union of function types that happen to have no common signatures. + return isTypeAny(funcType) || isTypeAny(apparentFuncType) && !!(funcType.flags & TypeFlags.TypeParameter) || + !numCallSignatures && !numConstructSignatures && !(apparentFuncType.flags & (TypeFlags.Union | TypeFlags.Never)) && isTypeAssignableTo(funcType, globalFunctionType); + } + function resolveNewExpression(node: NewExpression, candidatesOutArray: ts.Signature[] | undefined, checkMode: CheckMode): ts.Signature { + if (node.arguments && languageVersion < ScriptTarget.ES5) { + const spreadIndex = getSpreadArgumentIndex(node.arguments); + if (spreadIndex >= 0) { + error(node.arguments[spreadIndex], Diagnostics.Spread_operator_in_new_expressions_is_only_available_when_targeting_ECMAScript_5_and_higher); + } + } + let expressionType = checkNonNullExpression(node.expression); + if (expressionType === silentNeverType) { + return silentNeverSignature; + } + // If expressionType's apparent type(section 3.8.1) is an object type with one or + // more construct signatures, the expression is processed in the same manner as a + // function call, but using the construct signatures as the initial set of candidate + // signatures for overload resolution. The result type of the function call becomes + // the result type of the operation. + expressionType = getApparentType(expressionType); + if (expressionType === errorType) { + // Another error has already been reported + return resolveErrorCall(node); + } + // TS 1.0 spec: 4.11 + // If expressionType is of type Any, Args can be any argument + // list and the result of the operation is of type Any. + if (isTypeAny(expressionType)) { + if (node.typeArguments) { + error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments); } - - if (node.kind === SyntaxKind.PropertyDeclaration || - node.kind === SyntaxKind.PropertySignature || - node.kind === SyntaxKind.MethodDeclaration || - node.kind === SyntaxKind.MethodSignature || - node.kind === SyntaxKind.GetAccessor || - node.kind === SyntaxKind.SetAccessor) { - // it is ok to have member named '_super' or '_this' - member access is always qualified - return false; + return resolveUntypedCall(node); + } + // Technically, this signatures list may be incomplete. We are taking the apparent type, + // but we are not including construct signatures that may have been added to the Object or + // Function interface, since they have none by default. This is a bit of a leap of faith + // that the user will not add any. + const constructSignatures = getSignaturesOfType(expressionType, SignatureKind.Construct); + if (constructSignatures.length) { + if (!isConstructorAccessible(node, constructSignatures[0])) { + return resolveErrorCall(node); } - - if (node.flags & NodeFlags.Ambient) { - // ambient context - no codegen impact - return false; + // If the expression is a class of abstract type, then it cannot be instantiated. + // Note, only class declarations can be declared abstract. + // In the case of a merged class-module or class-interface declaration, + // only the class declaration node will have the Abstract flag set. + const valueDecl = expressionType.symbol && getClassLikeDeclarationOfSymbol(expressionType.symbol); + if (valueDecl && hasModifier(valueDecl, ModifierFlags.Abstract)) { + error(node, Diagnostics.Cannot_create_an_instance_of_an_abstract_class); + return resolveErrorCall(node); } - - const root = getRootDeclaration(node); - if (root.kind === SyntaxKind.Parameter && nodeIsMissing((root.parent).body)) { - // just an overload - no codegen impact - return false; + return resolveCall(node, constructSignatures, candidatesOutArray, checkMode, SignatureFlags.None); + } + // If expressionType's apparent type is an object type with no construct signatures but + // one or more call signatures, the expression is processed as a function call. A compile-time + // error occurs if the result of the function call is not Void. The type of the result of the + // operation is Any. It is an error to have a Void this type. + const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call); + if (callSignatures.length) { + const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); + if (!noImplicitAny) { + if (signature.declaration && !isJSConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) { + error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword); + } + if (getThisTypeOfSignature(signature) === voidType) { + error(node, Diagnostics.A_function_that_is_called_with_the_new_keyword_cannot_have_a_this_type_that_is_void); + } } - - return true; + return signature; } - - // this function will run after checking the source file so 'CaptureThis' is correct for all nodes - function checkIfThisIsCapturedInEnclosingScope(node: Node): void { - findAncestor(node, current => { - if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureThis) { - const isDeclaration = node.kind !== SyntaxKind.Identifier; - if (isDeclaration) { - error(getNameOfDeclaration(node), Diagnostics.Duplicate_identifier_this_Compiler_uses_variable_declaration_this_to_capture_this_reference); - } - else { - error(node, Diagnostics.Expression_resolves_to_variable_declaration_this_that_compiler_uses_to_capture_this_reference); + invocationError(node.expression, expressionType, SignatureKind.Construct); + return resolveErrorCall(node); + } + function typeHasProtectedAccessibleBase(target: ts.Symbol, type: InterfaceType): boolean { + const baseTypes = getBaseTypes(type); + if (!length(baseTypes)) { + return false; + } + const firstBase = baseTypes[0]; + if (firstBase.flags & TypeFlags.Intersection) { + const types = (firstBase as IntersectionType).types; + const mixinFlags = findMixins(types); + let i = 0; + for (const intersectionMember of (firstBase as IntersectionType).types) { + // We want to ignore mixin ctors + if (!mixinFlags[i]) { + if (getObjectFlags(intersectionMember) & (ObjectFlags.Class | ObjectFlags.Interface)) { + if (intersectionMember.symbol === target) { + return true; + } + if (typeHasProtectedAccessibleBase(target, (intersectionMember as InterfaceType))) { + return true; + } } - return true; } - return false; - }); + i++; + } + return false; } - - function checkIfNewTargetIsCapturedInEnclosingScope(node: Node): void { - findAncestor(node, current => { - if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureNewTarget) { - const isDeclaration = node.kind !== SyntaxKind.Identifier; - if (isDeclaration) { - error(getNameOfDeclaration(node), Diagnostics.Duplicate_identifier_newTarget_Compiler_uses_variable_declaration_newTarget_to_capture_new_target_meta_property_reference); - } - else { - error(node, Diagnostics.Expression_resolves_to_variable_declaration_newTarget_that_compiler_uses_to_capture_new_target_meta_property_reference); - } + if (firstBase.symbol === target) { + return true; + } + return typeHasProtectedAccessibleBase(target, (firstBase as InterfaceType)); + } + function isConstructorAccessible(node: NewExpression, signature: ts.Signature) { + if (!signature || !signature.declaration) { + return true; + } + const declaration = signature.declaration; + const modifiers = getSelectedModifierFlags(declaration, ModifierFlags.NonPublicAccessibilityModifier); + // Public constructor is accessible. + if (!modifiers) { + return true; + } + const declaringClassDeclaration = (getClassLikeDeclarationOfSymbol(declaration.parent.symbol)!); + const declaringClass = (getDeclaredTypeOfSymbol(declaration.parent.symbol)); + // A private or protected constructor can only be instantiated within its own class (or a subclass, for protected) + if (!isNodeWithinClass(node, declaringClassDeclaration)) { + const containingClass = getContainingClass(node); + if (containingClass && modifiers & ModifierFlags.Protected) { + const containingType = getTypeOfNode(containingClass); + if (typeHasProtectedAccessibleBase(declaration.parent.symbol, (containingType as InterfaceType))) { return true; } - return false; - }); - } - - function checkCollisionWithRequireExportsInGeneratedCode(node: Node, name: Identifier) { - // No need to check for require or exports for ES6 modules and later - if (moduleKind >= ModuleKind.ES2015 || compilerOptions.noEmit) { - return; - } - - if (!needCollisionCheckForIdentifier(node, name, "require") && !needCollisionCheckForIdentifier(node, name, "exports")) { - return; } - - // Uninstantiated modules shouldnt do this check - if (isModuleDeclaration(node) && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { - return; + if (modifiers & ModifierFlags.Private) { + error(node, Diagnostics.Constructor_of_class_0_is_private_and_only_accessible_within_the_class_declaration, typeToString(declaringClass)); } - - // In case of variable declaration, node.parent is variable statement so look at the variable statement's parent - const parent = getDeclarationContainer(node); - if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(parent)) { - // If the declaration happens to be in external module, report error that require and exports are reserved keywords - error(name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module, - declarationNameToString(name), declarationNameToString(name)); + if (modifiers & ModifierFlags.Protected) { + error(node, Diagnostics.Constructor_of_class_0_is_protected_and_only_accessible_within_the_class_declaration, typeToString(declaringClass)); } + return false; } - - function checkCollisionWithGlobalPromiseInGeneratedCode(node: Node, name: Identifier): void { - if (languageVersion >= ScriptTarget.ES2017 || compilerOptions.noEmit || !needCollisionCheckForIdentifier(node, name, "Promise")) { - return; + return true; + } + function invocationErrorDetails(apparentType: ts.Type, kind: SignatureKind): { + messageChain: DiagnosticMessageChain; + relatedMessage: DiagnosticMessage | undefined; + } { + let errorInfo: DiagnosticMessageChain | undefined; + const isCall = kind === SignatureKind.Call; + const awaitedType = getAwaitedType(apparentType); + const maybeMissingAwait = awaitedType && getSignaturesOfType(awaitedType, kind).length > 0; + if (apparentType.flags & TypeFlags.Union) { + const types = (apparentType as UnionType).types; + let hasSignatures = false; + for (const constituent of types) { + const signatures = getSignaturesOfType(constituent, kind); + if (signatures.length !== 0) { + hasSignatures = true; + if (errorInfo) { + // Bail early if we already have an error, no chance of "No constituent of type is callable" + break; + } + } + else { + // Error on the first non callable constituent only + if (!errorInfo) { + errorInfo = chainDiagnosticMessages(errorInfo, isCall ? + Diagnostics.Type_0_has_no_call_signatures : + Diagnostics.Type_0_has_no_construct_signatures, typeToString(constituent)); + errorInfo = chainDiagnosticMessages(errorInfo, isCall ? + Diagnostics.Not_all_constituents_of_type_0_are_callable : + Diagnostics.Not_all_constituents_of_type_0_are_constructable, typeToString(apparentType)); + } + if (hasSignatures) { + // Bail early if we already found a siganture, no chance of "No constituent of type is callable" + break; + } + } } - - // Uninstantiated modules shouldnt do this check - if (isModuleDeclaration(node) && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { - return; + if (!hasSignatures) { + errorInfo = chainDiagnosticMessages( + /* detials */ undefined, isCall ? + Diagnostics.No_constituent_of_type_0_is_callable : + Diagnostics.No_constituent_of_type_0_is_constructable, typeToString(apparentType)); } - - // In case of variable declaration, node.parent is variable statement so look at the variable statement's parent - const parent = getDeclarationContainer(node); - if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(parent) && parent.flags & NodeFlags.HasAsyncFunctions) { - // If the declaration happens to be in external module, report error that Promise is a reserved identifier. - error(name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module_containing_async_functions, - declarationNameToString(name), declarationNameToString(name)); + if (!errorInfo) { + errorInfo = chainDiagnosticMessages(errorInfo, isCall ? + Diagnostics.Each_member_of_the_union_type_0_has_signatures_but_none_of_those_signatures_are_compatible_with_each_other : + Diagnostics.Each_member_of_the_union_type_0_has_construct_signatures_but_none_of_those_signatures_are_compatible_with_each_other, typeToString(apparentType)); } } - - function checkVarDeclaredNamesNotShadowed(node: VariableDeclaration | BindingElement) { - // - ScriptBody : StatementList - // It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList - // also occurs in the VarDeclaredNames of StatementList. - - // - Block : { StatementList } - // It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList - // also occurs in the VarDeclaredNames of StatementList. - - // Variable declarations are hoisted to the top of their function scope. They can shadow - // block scoped declarations, which bind tighter. this will not be flagged as duplicate definition - // by the binder as the declaration scope is different. - // A non-initialized declaration is a no-op as the block declaration will resolve before the var - // declaration. the problem is if the declaration has an initializer. this will act as a write to the - // block declared value. this is fine for let, but not const. - // Only consider declarations with initializers, uninitialized const declarations will not - // step on a let/const variable. - // Do not consider const and const declarations, as duplicate block-scoped declarations - // are handled by the binder. - // We are only looking for const declarations that step on let\const declarations from a - // different scope. e.g.: - // { - // const x = 0; // localDeclarationSymbol obtained after name resolution will correspond to this declaration - // const x = 0; // symbol for this declaration will be 'symbol' - // } - - // skip block-scoped variables and parameters - if ((getCombinedNodeFlags(node) & NodeFlags.BlockScoped) !== 0 || isParameterDeclaration(node)) { + else { + errorInfo = chainDiagnosticMessages(errorInfo, isCall ? + Diagnostics.Type_0_has_no_call_signatures : + Diagnostics.Type_0_has_no_construct_signatures, typeToString(apparentType)); + } + return { + messageChain: chainDiagnosticMessages(errorInfo, isCall ? Diagnostics.This_expression_is_not_callable : Diagnostics.This_expression_is_not_constructable), + relatedMessage: maybeMissingAwait ? Diagnostics.Did_you_forget_to_use_await : undefined, + }; + } + function invocationError(errorTarget: Node, apparentType: ts.Type, kind: SignatureKind, relatedInformation?: DiagnosticRelatedInformation) { + const { messageChain, relatedMessage: relatedInfo } = invocationErrorDetails(apparentType, kind); + const diagnostic = createDiagnosticForNodeFromMessageChain(errorTarget, messageChain); + if (relatedInfo) { + addRelatedInfo(diagnostic, createDiagnosticForNode(errorTarget, relatedInfo)); + } + if (isCallExpression(errorTarget.parent)) { + const { start, length } = getDiagnosticSpanForCallNode(errorTarget.parent, /* doNotIncludeArguments */ true); + diagnostic.start = start; + diagnostic.length = length; + } + diagnostics.add(diagnostic); + invocationErrorRecovery(apparentType, kind, relatedInformation ? addRelatedInfo(diagnostic, relatedInformation) : diagnostic); + } + function invocationErrorRecovery(apparentType: ts.Type, kind: SignatureKind, diagnostic: Diagnostic) { + if (!apparentType.symbol) { + return; + } + const importNode = getSymbolLinks(apparentType.symbol).originatingImport; + // Create a diagnostic on the originating import if possible onto which we can attach a quickfix + // An import call expression cannot be rewritten into another form to correct the error - the only solution is to use `.default` at the use-site + if (importNode && !isImportCall(importNode)) { + const sigs = getSignaturesOfType(getTypeOfSymbol(getSymbolLinks(apparentType.symbol).target!), kind); + if (!sigs || !sigs.length) return; + addRelatedInfo(diagnostic, createDiagnosticForNode(importNode, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead)); + } + } + function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: ts.Signature[] | undefined, checkMode: CheckMode): ts.Signature { + const tagType = checkExpression(node.tag); + const apparentType = getApparentType(tagType); + if (apparentType === errorType) { + // Another error has already been reported + return resolveErrorCall(node); + } + const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; + if (isUntypedFunctionCall(tagType, apparentType, callSignatures.length, numConstructSignatures)) { + return resolveUntypedCall(node); + } + if (!callSignatures.length) { + invocationError(node.tag, apparentType, SignatureKind.Call); + return resolveErrorCall(node); + } + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); + } + /** + * Gets the localized diagnostic head message to use for errors when resolving a decorator as a call expression. + */ + function getDiagnosticHeadMessageForDecoratorResolution(node: Decorator) { + switch (node.parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + return Diagnostics.Unable_to_resolve_signature_of_class_decorator_when_called_as_an_expression; + case SyntaxKind.Parameter: + return Diagnostics.Unable_to_resolve_signature_of_parameter_decorator_when_called_as_an_expression; + case SyntaxKind.PropertyDeclaration: + return Diagnostics.Unable_to_resolve_signature_of_property_decorator_when_called_as_an_expression; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return Diagnostics.Unable_to_resolve_signature_of_method_decorator_when_called_as_an_expression; + default: + return Debug.fail(); + } + } + /** + * Resolves a decorator as if it were a call expression. + */ + function resolveDecorator(node: Decorator, candidatesOutArray: ts.Signature[] | undefined, checkMode: CheckMode): ts.Signature { + const funcType = checkExpression(node.expression); + const apparentType = getApparentType(funcType); + if (apparentType === errorType) { + return resolveErrorCall(node); + } + const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; + if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) { + return resolveUntypedCall(node); + } + if (isPotentiallyUncalledDecorator(node, callSignatures)) { + const nodeStr = getTextOfNode(node.expression, /*includeTrivia*/ false); + error(node, Diagnostics._0_accepts_too_few_arguments_to_be_used_as_a_decorator_here_Did_you_mean_to_call_it_first_and_write_0, nodeStr); + return resolveErrorCall(node); + } + const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node); + if (!callSignatures.length) { + const errorDetails = invocationErrorDetails(apparentType, SignatureKind.Call); + const messageChain = chainDiagnosticMessages(errorDetails.messageChain, headMessage); + const diag = createDiagnosticForNodeFromMessageChain(node.expression, messageChain); + if (errorDetails.relatedMessage) { + addRelatedInfo(diag, createDiagnosticForNode(node.expression, errorDetails.relatedMessage)); } - - // skip variable declarations that don't have initializers - // NOTE: in ES6 spec initializer is required in variable declarations where name is binding pattern - // so we'll always treat binding elements as initialized - if (node.kind === SyntaxKind.VariableDeclaration && !node.initializer) { - return; + diagnostics.add(diag); + invocationErrorRecovery(apparentType, SignatureKind.Call, diag); + return resolveErrorCall(node); + } + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None, headMessage); + } + function createSignatureForJSXIntrinsic(node: JsxOpeningLikeElement, result: ts.Type): ts.Signature { + const namespace = getJsxNamespaceAt(node); + const exports = namespace && getExportsOfSymbol(namespace); + // We fake up a SFC signature for each intrinsic, however a more specific per-element signature drawn from the JSX declaration + // file would probably be preferable. + const typeSymbol = exports && getSymbol(exports, JsxNames.Element, SymbolFlags.Type); + const returnNode = typeSymbol && nodeBuilder.symbolToEntityName(typeSymbol, SymbolFlags.Type, node); + const declaration = createFunctionTypeNode(/*typeParameters*/ undefined, [createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotdotdot*/ undefined, "props", /*questionMark*/ undefined, nodeBuilder.typeToTypeNode(result, node))], returnNode ? createTypeReferenceNode(returnNode, /*typeArguments*/ undefined) : createKeywordTypeNode(SyntaxKind.AnyKeyword)); + const parameterSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, ("props" as __String)); + parameterSymbol.type = result; + return createSignature(declaration, + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, [parameterSymbol], typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType, + /*returnTypePredicate*/ undefined, 1, SignatureFlags.None); + } + function resolveJsxOpeningLikeElement(node: JsxOpeningLikeElement, candidatesOutArray: ts.Signature[] | undefined, checkMode: CheckMode): ts.Signature { + if (isJsxIntrinsicIdentifier(node.tagName)) { + const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node); + const fakeSignature = createSignatureForJSXIntrinsic(node, result); + checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*mapper*/ undefined, CheckMode.Normal), result, node.tagName, node.attributes); + return fakeSignature; + } + const exprTypes = checkExpression(node.tagName); + const apparentType = getApparentType(exprTypes); + if (apparentType === errorType) { + return resolveErrorCall(node); + } + const signatures = getUninstantiatedJsxSignaturesOfType(exprTypes, node); + if (isUntypedFunctionCall(exprTypes, apparentType, signatures.length, /*constructSignatures*/ 0)) { + return resolveUntypedCall(node); + } + if (signatures.length === 0) { + // We found no signatures at all, which is an error + error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName)); + return resolveErrorCall(node); + } + return resolveCall(node, signatures, candidatesOutArray, checkMode, SignatureFlags.None); + } + /** + * Sometimes, we have a decorator that could accept zero arguments, + * but is receiving too many arguments as part of the decorator invocation. + * In those cases, a user may have meant to *call* the expression before using it as a decorator. + */ + function isPotentiallyUncalledDecorator(decorator: Decorator, signatures: readonly ts.Signature[]) { + return signatures.length && every(signatures, signature => signature.minArgumentCount === 0 && + !signatureHasRestParameter(signature) && + signature.parameters.length < getDecoratorArgumentCount(decorator, signature)); + } + function resolveSignature(node: CallLikeExpression, candidatesOutArray: ts.Signature[] | undefined, checkMode: CheckMode): ts.Signature { + switch (node.kind) { + case SyntaxKind.CallExpression: + return resolveCallExpression(node, candidatesOutArray, checkMode); + case SyntaxKind.NewExpression: + return resolveNewExpression(node, candidatesOutArray, checkMode); + case SyntaxKind.TaggedTemplateExpression: + return resolveTaggedTemplateExpression(node, candidatesOutArray, checkMode); + case SyntaxKind.Decorator: + return resolveDecorator(node, candidatesOutArray, checkMode); + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxSelfClosingElement: + return resolveJsxOpeningLikeElement(node, candidatesOutArray, checkMode); + } + throw Debug.assertNever(node, "Branch in 'resolveSignature' should be unreachable."); + } + /** + * Resolve a signature of a given call-like expression. + * @param node a call-like expression to try resolve a signature for + * @param candidatesOutArray an array of signature to be filled in by the function. It is passed by signature help in the language service; + * the function will fill it up with appropriate candidate signatures + * @return a signature of the call-like expression or undefined if one can't be found + */ + function getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: ts.Signature[] | undefined, checkMode?: CheckMode): ts.Signature { + const links = getNodeLinks(node); + // If getResolvedSignature has already been called, we will have cached the resolvedSignature. + // However, it is possible that either candidatesOutArray was not passed in the first time, + // or that a different candidatesOutArray was passed in. Therefore, we need to redo the work + // to correctly fill the candidatesOutArray. + const cached = links.resolvedSignature; + if (cached && cached !== resolvingSignature && !candidatesOutArray) { + return cached; + } + links.resolvedSignature = resolvingSignature; + const result = resolveSignature(node, candidatesOutArray, checkMode || CheckMode.Normal); + // When CheckMode.SkipGenericFunctions is set we use resolvingSignature to indicate that call + // resolution should be deferred. + if (result !== resolvingSignature) { + // If signature resolution originated in control flow type analysis (for example to compute the + // assigned type in a flow assignment) we don't cache the result as it may be based on temporary + // types from the control flow analysis. + links.resolvedSignature = flowLoopStart === flowLoopCount ? result : cached; + } + return result; + } + /** + * Indicates whether a declaration can be treated as a constructor in a JavaScript + * file. + */ + function isJSConstructor(node: Node | undefined): node is FunctionDeclaration | FunctionExpression { + if (!node || !isInJSFile(node)) { + return false; + } + const func = isFunctionDeclaration(node) || isFunctionExpression(node) ? node : + isVariableDeclaration(node) && node.initializer && isFunctionExpression(node.initializer) ? node.initializer : + undefined; + if (func) { + // If the node has a @class tag, treat it like a constructor. + if (getJSDocClassTag(node)) + return true; + // If the symbol of the node has members, treat it like a constructor. + const symbol = getSymbolOfNode(func); + return !!symbol && hasEntries(symbol.members); + } + return false; + } + function mergeJSSymbols(target: ts.Symbol, source: ts.Symbol | undefined) { + if (source) { + const links = getSymbolLinks(source); + if (!links.inferredClassSymbol || !links.inferredClassSymbol.has("" + getSymbolId(target))) { + const inferred = isTransientSymbol(target) ? target : cloneSymbol(target) as TransientSymbol; + inferred.exports = inferred.exports || createSymbolTable(); + inferred.members = inferred.members || createSymbolTable(); + inferred.flags |= source.flags & SymbolFlags.Class; + if (hasEntries(source.exports)) { + mergeSymbolTable(inferred.exports, source.exports); + } + if (hasEntries(source.members)) { + mergeSymbolTable(inferred.members, source.members); + } + (links.inferredClassSymbol || (links.inferredClassSymbol = createMap())).set("" + getSymbolId(inferred), inferred); + return inferred; } - - const symbol = getSymbolOfNode(node); - if (symbol.flags & SymbolFlags.FunctionScopedVariable) { - if (!isIdentifier(node.name)) return Debug.fail(); - const localDeclarationSymbol = resolveName(node, node.name.escapedText, SymbolFlags.Variable, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); - if (localDeclarationSymbol && - localDeclarationSymbol !== symbol && - localDeclarationSymbol.flags & SymbolFlags.BlockScopedVariable) { - if (getDeclarationNodeFlagsFromSymbol(localDeclarationSymbol) & NodeFlags.BlockScoped) { - const varDeclList = getAncestor(localDeclarationSymbol.valueDeclaration, SyntaxKind.VariableDeclarationList)!; - const container = - varDeclList.parent.kind === SyntaxKind.VariableStatement && varDeclList.parent.parent - ? varDeclList.parent.parent - : undefined; - - // names of block-scoped and function scoped variables can collide only - // if block scoped variable is defined in the function\module\source file scope (because of variable hoisting) - const namesShareScope = - container && - (container.kind === SyntaxKind.Block && isFunctionLike(container.parent) || - container.kind === SyntaxKind.ModuleBlock || - container.kind === SyntaxKind.ModuleDeclaration || - container.kind === SyntaxKind.SourceFile); - - // here we know that function scoped variable is shadowed by block scoped one - // if they are defined in the same scope - binder has already reported redeclaration error - // otherwise if variable has an initializer - show error that initialization will fail - // since LHS will be block scoped name instead of function scoped - if (!namesShareScope) { - const name = symbolToString(localDeclarationSymbol); - error(node, Diagnostics.Cannot_initialize_outer_scoped_variable_0_in_the_same_scope_as_block_scoped_declaration_1, name, name); - } - } + return links.inferredClassSymbol.get("" + getSymbolId(target)); + } + } + function getAssignedClassSymbol(decl: Declaration): ts.Symbol | undefined { + const assignmentSymbol = decl && decl.parent && + (isFunctionDeclaration(decl) && getSymbolOfNode(decl) || + isBinaryExpression(decl.parent) && getSymbolOfNode(decl.parent.left) || + isVariableDeclaration(decl.parent) && getSymbolOfNode(decl.parent)); + const prototype = assignmentSymbol && assignmentSymbol.exports && assignmentSymbol.exports.get(("prototype" as __String)); + const init = prototype && prototype.valueDeclaration && getAssignedJSPrototype(prototype.valueDeclaration); + return init ? getSymbolOfNode(init) : undefined; + } + function getAssignedJSPrototype(node: Node) { + if (!node.parent) { + return false; + } + let parent: Node = node.parent; + while (parent && parent.kind === SyntaxKind.PropertyAccessExpression) { + parent = parent.parent; + } + if (parent && isBinaryExpression(parent) && isPrototypeAccess(parent.left) && parent.operatorToken.kind === SyntaxKind.EqualsToken) { + const right = getInitializerOfBinaryExpression(parent); + return isObjectLiteralExpression(right) && right; + } + } + /** + * Syntactically and semantically checks a call or new expression. + * @param node The call/new expression to be checked. + * @returns On success, the expression's signature's return type. On failure, anyType. + */ + function checkCallExpression(node: CallExpression | NewExpression, checkMode?: CheckMode): ts.Type { + if (!checkGrammarTypeArguments(node, node.typeArguments)) + checkGrammarArguments(node.arguments); + const signature = getResolvedSignature(node, /*candidatesOutArray*/ undefined, checkMode); + if (signature === resolvingSignature) { + // CheckMode.SkipGenericFunctions is enabled and this is a call to a generic function that + // returns a function type. We defer checking and return nonInferrableType. + return nonInferrableType; + } + if (node.expression.kind === SyntaxKind.SuperKeyword) { + return voidType; + } + if (node.kind === SyntaxKind.NewExpression) { + const declaration = signature.declaration; + if (declaration && + declaration.kind !== SyntaxKind.Constructor && + declaration.kind !== SyntaxKind.ConstructSignature && + declaration.kind !== SyntaxKind.ConstructorType && + !isJSDocConstructSignature(declaration) && + !isJSConstructor(declaration)) { + // When resolved signature is a call signature (and not a construct signature) the result type is any + if (noImplicitAny) { + error(node, Diagnostics.new_expression_whose_target_lacks_a_construct_signature_implicitly_has_an_any_type); } + return anyType; } } - - function convertAutoToAny(type: Type) { - return type === autoType ? anyType : type === autoArrayType ? anyArrayType : type; + // In JavaScript files, calls to any identifier 'require' are treated as external module imports + if (isInJSFile(node) && isCommonJsRequire(node)) { + return resolveExternalModuleTypeByLiteral((node.arguments![0] as StringLiteral)); } - - // Check variable, parameter, or property declaration - function checkVariableLikeDeclaration(node: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement) { - checkDecorators(node); - if (!isBindingElement(node)) { - checkSourceElement(node.type); + const returnType = getReturnTypeOfSignature(signature); + // Treat any call to the global 'Symbol' function that is part of a const variable or readonly property + // as a fresh unique symbol literal type. + if (returnType.flags & TypeFlags.ESSymbolLike && isSymbolOrSymbolForCall(node)) { + return getESSymbolLikeTypeForNode(walkUpParenthesizedExpressions(node.parent)); + } + if (node.kind === SyntaxKind.CallExpression && node.parent.kind === SyntaxKind.ExpressionStatement && + returnType.flags & TypeFlags.Void && getTypePredicateOfSignature(signature)) { + if (!isDottedName(node.expression)) { + error(node.expression, Diagnostics.Assertions_require_the_call_target_to_be_an_identifier_or_qualified_name); } - - // JSDoc `function(string, string): string` syntax results in parameters with no name - if (!node.name) { - return; + else if (!getEffectsSignature(node)) { + const diagnostic = error(node.expression, Diagnostics.Assertions_require_every_name_in_the_call_target_to_be_declared_with_an_explicit_type_annotation); + getTypeOfDottedName(node.expression, diagnostic); } - // For a computed property, just check the initializer and exit - // Do not use hasDynamicName here, because that returns false for well known symbols. - // We want to perform checkComputedPropertyName for all computed properties, including - // well known symbols. - if (node.name.kind === SyntaxKind.ComputedPropertyName) { - checkComputedPropertyName(node.name); - if (node.initializer) { - checkExpressionCached(node.initializer); + } + if (isInJSFile(node)) { + const decl = getDeclarationOfExpando(node); + if (decl) { + const jsSymbol = getSymbolOfNode(decl); + if (jsSymbol && hasEntries(jsSymbol.exports)) { + const jsAssignmentType = createAnonymousType(jsSymbol, jsSymbol.exports, emptyArray, emptyArray, undefined, undefined); + jsAssignmentType.objectFlags |= ObjectFlags.JSLiteral; + return getIntersectionType([returnType, jsAssignmentType]); } } - - if (node.kind === SyntaxKind.BindingElement) { - if (node.parent.kind === SyntaxKind.ObjectBindingPattern && languageVersion < ScriptTarget.ESNext) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Rest); - } - // check computed properties inside property names of binding elements - if (node.propertyName && node.propertyName.kind === SyntaxKind.ComputedPropertyName) { - checkComputedPropertyName(node.propertyName); + } + return returnType; + } + function isSymbolOrSymbolForCall(node: Node) { + if (!isCallExpression(node)) + return false; + let left = node.expression; + if (isPropertyAccessExpression(left) && left.name.escapedText === "for") { + left = left.expression; + } + if (!isIdentifier(left) || left.escapedText !== "Symbol") { + return false; + } + // make sure `Symbol` is the global symbol + const globalESSymbol = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false); + if (!globalESSymbol) { + return false; + } + return globalESSymbol === resolveName(left, ("Symbol" as __String), SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); + } + function checkImportCallExpression(node: ImportCall): ts.Type { + // Check grammar of dynamic import + if (!checkGrammarArguments(node.arguments)) + checkGrammarImportCallExpression(node); + if (node.arguments.length === 0) { + return createPromiseReturnType(node, anyType); + } + const specifier = node.arguments[0]; + const specifierType = checkExpressionCached(specifier); + // Even though multiple arguments is grammatically incorrect, type-check extra arguments for completion + for (let i = 1; i < node.arguments.length; ++i) { + checkExpressionCached(node.arguments[i]); + } + if (specifierType.flags & TypeFlags.Undefined || specifierType.flags & TypeFlags.Null || !isTypeAssignableTo(specifierType, stringType)) { + error(specifier, Diagnostics.Dynamic_import_s_specifier_must_be_of_type_string_but_here_has_type_0, typeToString(specifierType)); + } + // resolveExternalModuleName will return undefined if the moduleReferenceExpression is not a string literal + const moduleSymbol = resolveExternalModuleName(node, specifier); + if (moduleSymbol) { + const esModuleSymbol = resolveESModuleSymbol(moduleSymbol, specifier, /*dontRecursivelyResolve*/ true, /*suppressUsageError*/ false); + if (esModuleSymbol) { + return createPromiseReturnType(node, getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol)); + } + } + return createPromiseReturnType(node, anyType); + } + function getTypeWithSyntheticDefaultImportType(type: ts.Type, symbol: ts.Symbol, originalSymbol: ts.Symbol): ts.Type { + if (allowSyntheticDefaultImports && type && type !== errorType) { + const synthType = (type as SyntheticDefaultModuleType); + if (!synthType.syntheticType) { + const file = find(originalSymbol.declarations, isSourceFile); + const hasSyntheticDefault = canHaveSyntheticDefault(file, originalSymbol, /*dontResolveAlias*/ false); + if (hasSyntheticDefault) { + const memberTable = createSymbolTable(); + const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default); + newSymbol.nameType = getLiteralType("default"); + newSymbol.target = resolveSymbol(symbol); + memberTable.set(InternalSymbolName.Default, newSymbol); + const anonymousSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); + const defaultContainingObject = createAnonymousType(anonymousSymbol, memberTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined); + anonymousSymbol.type = defaultContainingObject; + synthType.syntheticType = isValidSpreadType(type) ? getSpreadType(type, defaultContainingObject, anonymousSymbol, /*objectFlags*/ 0, /*readonly*/ false) : defaultContainingObject; } - - // check private/protected variable access - const parent = node.parent.parent; - const parentType = getTypeForBindingElementParent(parent); - const name = node.propertyName || node.name; - if (parentType && !isBindingPattern(name)) { - const exprType = getLiteralTypeFromPropertyName(name); - if (isTypeUsableAsPropertyName(exprType)) { - const nameText = getPropertyNameFromType(exprType); - const property = getPropertyOfType(parentType, nameText); - if (property) { - markPropertyAsReferenced(property, /*nodeForCheckWriteOnly*/ undefined, /*isThisAccess*/ false); // A destructuring is never a write-only reference. - checkPropertyAccessibility(parent, !!parent.initializer && parent.initializer.kind === SyntaxKind.SuperKeyword, parentType, property); - } - } + else { + synthType.syntheticType = type; } } - - // For a binding pattern, check contained binding elements - if (isBindingPattern(node.name)) { - if (node.name.kind === SyntaxKind.ArrayBindingPattern && languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Read); - } - - forEach(node.name.elements, checkSourceElement); + return synthType.syntheticType; + } + return type; + } + function isCommonJsRequire(node: Node): boolean { + if (!isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) { + return false; + } + // Make sure require is not a local function + if (!isIdentifier(node.expression)) + return Debug.fail(); + const resolvedRequire = (resolveName(node.expression, node.expression.escapedText, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true)!); // TODO: GH#18217 + if (resolvedRequire === requireSymbol) { + return true; + } + // project includes symbol named 'require' - make sure that it is ambient and local non-alias + if (resolvedRequire.flags & SymbolFlags.Alias) { + return false; + } + const targetDeclarationKind = resolvedRequire.flags & SymbolFlags.Function + ? SyntaxKind.FunctionDeclaration + : resolvedRequire.flags & SymbolFlags.Variable + ? SyntaxKind.VariableDeclaration + : SyntaxKind.Unknown; + if (targetDeclarationKind !== SyntaxKind.Unknown) { + const decl = (getDeclarationOfKind(resolvedRequire, targetDeclarationKind)!); + // function/variable declaration should be ambient + return !!decl && !!(decl.flags & NodeFlags.Ambient); + } + return false; + } + function checkTaggedTemplateExpression(node: TaggedTemplateExpression): ts.Type { + if (!checkGrammarTaggedTemplateChain(node)) + checkGrammarTypeArguments(node, node.typeArguments); + if (languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject); + } + return getReturnTypeOfSignature(getResolvedSignature(node)); + } + function checkAssertion(node: AssertionExpression) { + return checkAssertionWorker(node, node.type, node.expression); + } + function isValidConstAssertionArgument(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.ObjectLiteralExpression: + return true; + case SyntaxKind.ParenthesizedExpression: + return isValidConstAssertionArgument((node).expression); + case SyntaxKind.PrefixUnaryExpression: + const op = (node).operator; + const arg = (node).operand; + return op === SyntaxKind.MinusToken && (arg.kind === SyntaxKind.NumericLiteral || arg.kind === SyntaxKind.BigIntLiteral) || + op === SyntaxKind.PlusToken && arg.kind === SyntaxKind.NumericLiteral; + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + const expr = (node).expression; + if (isIdentifier(expr)) { + let symbol = getSymbolAtLocation(expr); + if (symbol && symbol.flags & SymbolFlags.Alias) { + symbol = resolveAlias(symbol); + } + return !!(symbol && (symbol.flags & SymbolFlags.Enum) && getEnumKind(symbol) === EnumKind.Literal); + } + } + return false; + } + function checkAssertionWorker(errNode: Node, type: TypeNode, expression: UnaryExpression | Expression, checkMode?: CheckMode) { + let exprType = checkExpression(expression, checkMode); + if (isConstTypeReference(type)) { + if (!isValidConstAssertionArgument(expression)) { + error(expression, Diagnostics.A_const_assertions_can_only_be_applied_to_references_to_enum_members_or_string_number_boolean_array_or_object_literals); } - // For a parameter declaration with an initializer, error and exit if the containing function doesn't have a body - if (node.initializer && getRootDeclaration(node).kind === SyntaxKind.Parameter && nodeIsMissing((getContainingFunction(node) as FunctionLikeDeclaration).body)) { - error(node, Diagnostics.A_parameter_initializer_is_only_allowed_in_a_function_or_constructor_implementation); - return; + return getRegularTypeOfLiteralType(exprType); + } + checkSourceElement(type); + exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(exprType)); + const targetType = getTypeFromTypeNode(type); + if (produceDiagnostics && targetType !== errorType) { + const widenedType = getWidenedType(exprType); + if (!isTypeComparableTo(targetType, widenedType)) { + checkTypeComparableTo(exprType, targetType, errNode, Diagnostics.Conversion_of_type_0_to_type_1_may_be_a_mistake_because_neither_type_sufficiently_overlaps_with_the_other_If_this_was_intentional_convert_the_expression_to_unknown_first); } - // For a binding pattern, validate the initializer and exit - if (isBindingPattern(node.name)) { - const needCheckInitializer = node.initializer && node.parent.parent.kind !== SyntaxKind.ForInStatement; - const needCheckWidenedType = node.name.elements.length === 0; - if (needCheckInitializer || needCheckWidenedType) { - // Don't validate for-in initializer as it is already an error - const widenedType = getWidenedTypeForVariableLikeDeclaration(node); - if (needCheckInitializer) { - const initializerType = checkExpressionCached(node.initializer!); - if (strictNullChecks && needCheckWidenedType) { - checkNonNullNonVoidType(initializerType, node); - } - else { - checkTypeAssignableToAndOptionallyElaborate(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, node.initializer); - } - } - // check the binding pattern with empty elements - if (needCheckWidenedType) { - if (isArrayBindingPattern(node.name)) { - checkIteratedTypeOrElementType(IterationUse.Destructuring, widenedType, undefinedType, node); - } - else if (strictNullChecks) { - checkNonNullNonVoidType(widenedType, node); - } - } - } - return; + } + return targetType; + } + function checkNonNullAssertion(node: NonNullExpression) { + return getNonNullableType(checkExpression(node.expression)); + } + function checkMetaProperty(node: MetaProperty): ts.Type { + checkGrammarMetaProperty(node); + if (node.keywordToken === SyntaxKind.NewKeyword) { + return checkNewTargetMetaProperty(node); + } + if (node.keywordToken === SyntaxKind.ImportKeyword) { + return checkImportMetaProperty(node); + } + return Debug.assertNever(node.keywordToken); + } + function checkNewTargetMetaProperty(node: MetaProperty) { + const container = getNewTargetContainer(node); + if (!container) { + error(node, Diagnostics.Meta_property_0_is_only_allowed_in_the_body_of_a_function_declaration_function_expression_or_constructor, "new.target"); + return errorType; + } + else if (container.kind === SyntaxKind.Constructor) { + const symbol = getSymbolOfNode((container.parent as ClassLikeDeclaration)); + return getTypeOfSymbol(symbol); + } + else { + const symbol = getSymbolOfNode(container)!; + return getTypeOfSymbol(symbol); + } + } + function checkImportMetaProperty(node: MetaProperty) { + if (moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.System) { + error(node, Diagnostics.The_import_meta_meta_property_is_only_allowed_when_the_module_option_is_esnext_or_system); + } + const file = getSourceFileOfNode(node); + Debug.assert(!!(file.flags & NodeFlags.PossiblyContainsImportMeta), "Containing file is missing import meta node flag."); + Debug.assert(!!file.externalModuleIndicator, "Containing file should be a module."); + return node.name.escapedText === "meta" ? getGlobalImportMetaType() : errorType; + } + function getTypeOfParameter(symbol: ts.Symbol) { + const type = getTypeOfSymbol(symbol); + if (strictNullChecks) { + const declaration = symbol.valueDeclaration; + if (declaration && hasInitializer(declaration)) { + return getOptionalType(type); } - const symbol = getSymbolOfNode(node); - const type = convertAutoToAny(getTypeOfSymbol(symbol)); - if (node === symbol.valueDeclaration) { - // Node is the primary declaration of the symbol, just validate the initializer - // Don't validate for-in initializer as it is already an error - const initializer = getEffectiveInitializer(node); - if (initializer) { - const isJSObjectLiteralInitializer = isInJSFile(node) && - isObjectLiteralExpression(initializer) && - (initializer.properties.length === 0 || isPrototypeAccess(node.name)) && - hasEntries(symbol.exports); - if (!isJSObjectLiteralInitializer && node.parent.parent.kind !== SyntaxKind.ForInStatement) { - checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(initializer), type, node, initializer, /*headMessage*/ undefined); - } - } - if (symbol.declarations.length > 1) { - if (some(symbol.declarations, d => d !== node && isVariableLike(d) && !areDeclarationFlagsIdentical(d, node))) { - error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name)); - } + } + return type; + } + function getParameterNameAtPosition(signature: ts.Signature, pos: number) { + const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); + if (pos < paramCount) { + return signature.parameters[pos].escapedName; + } + const restParameter = signature.parameters[paramCount] || unknownSymbol; + const restType = getTypeOfSymbol(restParameter); + if (isTupleType(restType)) { + const associatedNames = ((restType).target).associatedNames; + const index = pos - paramCount; + return associatedNames && associatedNames[index] || (restParameter.escapedName + "_" + index as __String); + } + return restParameter.escapedName; + } + function getTypeAtPosition(signature: ts.Signature, pos: number): ts.Type { + return tryGetTypeAtPosition(signature, pos) || anyType; + } + function tryGetTypeAtPosition(signature: ts.Signature, pos: number): ts.Type | undefined { + const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); + if (pos < paramCount) { + return getTypeOfParameter(signature.parameters[pos]); + } + if (signatureHasRestParameter(signature)) { + // We want to return the value undefined for an out of bounds parameter position, + // so we need to check bounds here before calling getIndexedAccessType (which + // otherwise would return the type 'undefined'). + const restType = getTypeOfSymbol(signature.parameters[paramCount]); + const index = pos - paramCount; + if (!isTupleType(restType) || restType.target.hasRestElement || index < getTypeArguments(restType).length) { + return getIndexedAccessType(restType, getLiteralType(index)); + } + } + return undefined; + } + function getRestTypeAtPosition(source: ts.Signature, pos: number): ts.Type { + const paramCount = getParameterCount(source); + const restType = getEffectiveRestType(source); + const nonRestCount = paramCount - (restType ? 1 : 0); + if (restType && pos === nonRestCount) { + return restType; + } + const types = []; + const names = []; + for (let i = pos; i < nonRestCount; i++) { + types.push(getTypeAtPosition(source, i)); + names.push(getParameterNameAtPosition(source, i)); + } + if (restType) { + types.push(getIndexedAccessType(restType, numberType)); + names.push(getParameterNameAtPosition(source, nonRestCount)); + } + const minArgumentCount = getMinArgumentCount(source); + const minLength = minArgumentCount < pos ? 0 : minArgumentCount - pos; + return createTupleType(types, minLength, !!restType, /*readonly*/ false, names); + } + function getParameterCount(signature: ts.Signature) { + const length = signature.parameters.length; + if (signatureHasRestParameter(signature)) { + const restType = getTypeOfSymbol(signature.parameters[length - 1]); + if (isTupleType(restType)) { + return length + getTypeArguments(restType).length - 1; + } + } + return length; + } + function getMinArgumentCount(signature: ts.Signature) { + if (signatureHasRestParameter(signature)) { + const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); + if (isTupleType(restType)) { + const minLength = restType.target.minLength; + if (minLength > 0) { + return signature.parameters.length - 1 + minLength; } } - else { - // Node is a secondary declaration, check that type is identical to primary declaration and check that - // initializer is consistent with type associated with the node - const declarationType = convertAutoToAny(getWidenedTypeForVariableLikeDeclaration(node)); - - if (type !== errorType && declarationType !== errorType && - !isTypeIdenticalTo(type, declarationType) && - !(symbol.flags & SymbolFlags.Assignment)) { - errorNextVariableOrPropertyDeclarationMustHaveSameType(symbol.valueDeclaration, type, node, declarationType); - } - if (node.initializer) { - checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(node.initializer), declarationType, node, node.initializer, /*headMessage*/ undefined); - } - if (!areDeclarationFlagsIdentical(node, symbol.valueDeclaration)) { - error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name)); + } + return signature.minArgumentCount; + } + function hasEffectiveRestParameter(signature: ts.Signature) { + if (signatureHasRestParameter(signature)) { + const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); + return !isTupleType(restType) || restType.target.hasRestElement; + } + return false; + } + function getEffectiveRestType(signature: ts.Signature) { + if (signatureHasRestParameter(signature)) { + const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); + return isTupleType(restType) ? getRestArrayTypeOfTupleType(restType) : restType; + } + return undefined; + } + function getNonArrayRestType(signature: ts.Signature) { + const restType = getEffectiveRestType(signature); + return restType && !isArrayType(restType) && !isTypeAny(restType) ? restType : undefined; + } + function getTypeOfFirstParameterOfSignature(signature: ts.Signature) { + return getTypeOfFirstParameterOfSignatureWithFallback(signature, neverType); + } + function getTypeOfFirstParameterOfSignatureWithFallback(signature: ts.Signature, fallbackType: ts.Type) { + return signature.parameters.length > 0 ? getTypeAtPosition(signature, 0) : fallbackType; + } + function inferFromAnnotatedParameters(signature: ts.Signature, context: ts.Signature, inferenceContext: InferenceContext) { + const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); + for (let i = 0; i < len; i++) { + const declaration = (signature.parameters[i].valueDeclaration); + if (declaration.type) { + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + inferTypes(inferenceContext.inferences, getTypeFromTypeNode(typeNode), getTypeAtPosition(context, i)); } } - if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature) { - // We know we don't have a binding pattern or computed name here - checkExportsOnMergedDeclarations(node); - if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { - checkVarDeclaredNamesNotShadowed(node); + } + const restType = getEffectiveRestType(context); + if (restType && restType.flags & TypeFlags.TypeParameter) { + // The contextual signature has a generic rest parameter. We first instantiate the contextual + // signature (without fixing type parameters) and assign types to contextually typed parameters. + const instantiatedContext = instantiateSignature(context, inferenceContext.nonFixingMapper); + assignContextualParameterTypes(signature, instantiatedContext); + // We then infer from a tuple type representing the parameters that correspond to the contextual + // rest parameter. + const restPos = getParameterCount(context) - 1; + inferTypes(inferenceContext.inferences, getRestTypeAtPosition(signature, restPos), restType); + } + } + function assignContextualParameterTypes(signature: ts.Signature, context: ts.Signature) { + signature.typeParameters = context.typeParameters; + if (context.thisParameter) { + const parameter = signature.thisParameter; + if (!parameter || parameter.valueDeclaration && !(parameter.valueDeclaration).type) { + if (!parameter) { + signature.thisParameter = createSymbolWithType(context.thisParameter, /*type*/ undefined); } - checkCollisionWithRequireExportsInGeneratedCode(node, node.name); - checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name); + assignTypeToParameterAndFixTypeParameters(signature.thisParameter!, getTypeOfSymbol(context.thisParameter)); } } - - function errorNextVariableOrPropertyDeclarationMustHaveSameType(firstDeclaration: Declaration | undefined, firstType: Type, nextDeclaration: Declaration, nextType: Type): void { - const nextDeclarationName = getNameOfDeclaration(nextDeclaration); - const message = nextDeclaration.kind === SyntaxKind.PropertyDeclaration || nextDeclaration.kind === SyntaxKind.PropertySignature - ? Diagnostics.Subsequent_property_declarations_must_have_the_same_type_Property_0_must_be_of_type_1_but_here_has_type_2 - : Diagnostics.Subsequent_variable_declarations_must_have_the_same_type_Variable_0_must_be_of_type_1_but_here_has_type_2; - const declName = declarationNameToString(nextDeclarationName); - const err = error( - nextDeclarationName, - message, - declName, - typeToString(firstType), - typeToString(nextType) - ); - if (firstDeclaration) { - addRelatedInfo(err, - createDiagnosticForNode(firstDeclaration, Diagnostics._0_was_also_declared_here, declName) - ); + const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); + for (let i = 0; i < len; i++) { + const parameter = signature.parameters[i]; + if (!getEffectiveTypeAnnotationNode((parameter.valueDeclaration))) { + const contextualParameterType = getTypeAtPosition(context, i); + assignTypeToParameterAndFixTypeParameters(parameter, contextualParameterType); } } - - function areDeclarationFlagsIdentical(left: Declaration, right: Declaration) { - if ((left.kind === SyntaxKind.Parameter && right.kind === SyntaxKind.VariableDeclaration) || - (left.kind === SyntaxKind.VariableDeclaration && right.kind === SyntaxKind.Parameter)) { - // Differences in optionality between parameters and variables are allowed. - return true; + if (signatureHasRestParameter(signature)) { + // parameter might be a transient symbol generated by use of `arguments` in the function body. + const parameter = last(signature.parameters); + if (isTransientSymbol(parameter) || !getEffectiveTypeAnnotationNode((parameter.valueDeclaration))) { + const contextualParameterType = getRestTypeAtPosition(context, len); + assignTypeToParameterAndFixTypeParameters(parameter, contextualParameterType); } - - if (hasQuestionToken(left) !== hasQuestionToken(right)) { - return false; + } + } + // When contextual typing assigns a type to a parameter that contains a binding pattern, we also need to push + // the destructured type into the contained binding elements. + function assignBindingElementTypes(pattern: BindingPattern) { + for (const element of pattern.elements) { + if (!isOmittedExpression(element)) { + if (element.name.kind === SyntaxKind.Identifier) { + getSymbolLinks(getSymbolOfNode(element)).type = getTypeForBindingElement(element); + } + else { + assignBindingElementTypes(element.name); + } } - - const interestingFlags = ModifierFlags.Private | - ModifierFlags.Protected | - ModifierFlags.Async | - ModifierFlags.Abstract | - ModifierFlags.Readonly | - ModifierFlags.Static; - - return getSelectedModifierFlags(left, interestingFlags) === getSelectedModifierFlags(right, interestingFlags); } - - function checkVariableDeclaration(node: VariableDeclaration) { - checkGrammarVariableDeclaration(node); - return checkVariableLikeDeclaration(node); + } + function assignTypeToParameterAndFixTypeParameters(parameter: ts.Symbol, contextualType: ts.Type) { + const links = getSymbolLinks(parameter); + if (!links.type) { + links.type = contextualType; + const decl = (parameter.valueDeclaration as ParameterDeclaration); + if (decl.name.kind !== SyntaxKind.Identifier) { + // if inference didn't come up with anything but unknown, fall back to the binding pattern if present. + if (links.type === unknownType) { + links.type = getTypeFromBindingPattern(decl.name); + } + assignBindingElementTypes(decl.name); + } } - - function checkBindingElement(node: BindingElement) { - checkGrammarBindingElement(node); - return checkVariableLikeDeclaration(node); + } + function createPromiseType(promisedType: ts.Type): ts.Type { + // creates a `Promise` type where `T` is the promisedType argument + const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true); + if (globalPromiseType !== emptyGenericType) { + // if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type + promisedType = getAwaitedType(promisedType) || unknownType; + return createTypeReference(globalPromiseType, [promisedType]); + } + return unknownType; + } + function createPromiseLikeType(promisedType: ts.Type): ts.Type { + // creates a `PromiseLike` type where `T` is the promisedType argument + const globalPromiseLikeType = getGlobalPromiseLikeType(/*reportErrors*/ true); + if (globalPromiseLikeType !== emptyGenericType) { + // if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type + promisedType = getAwaitedType(promisedType) || unknownType; + return createTypeReference(globalPromiseLikeType, [promisedType]); + } + return unknownType; + } + function createPromiseReturnType(func: FunctionLikeDeclaration | ImportCall, promisedType: ts.Type) { + const promiseType = createPromiseType(promisedType); + if (promiseType === unknownType) { + error(func, isImportCall(func) ? + Diagnostics.A_dynamic_import_call_returns_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option : + Diagnostics.An_async_function_or_method_must_return_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option); + return errorType; } - - function checkVariableStatement(node: VariableStatement) { - // Grammar checking - if (!checkGrammarDecoratorsAndModifiers(node) && !checkGrammarVariableDeclarationList(node.declarationList)) checkGrammarForDisallowedLetOrConstStatement(node); - forEach(node.declarationList.declarations, checkSourceElement); + else if (!getGlobalPromiseConstructorSymbol(/*reportErrors*/ true)) { + error(func, isImportCall(func) ? + Diagnostics.A_dynamic_import_call_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option : + Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option); } - - function checkExpressionStatement(node: ExpressionStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); - - checkExpression(node.expression); + return promiseType; + } + function getReturnTypeFromBody(func: FunctionLikeDeclaration, checkMode?: CheckMode): ts.Type { + if (!func.body) { + return errorType; } - - function checkIfStatement(node: IfStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); - - const type = checkTruthinessExpression(node.expression); - checkTestingKnownTruthyCallableType(node, type); - checkSourceElement(node.thenStatement); - - if (node.thenStatement.kind === SyntaxKind.EmptyStatement) { - error(node.thenStatement, Diagnostics.The_body_of_an_if_statement_cannot_be_the_empty_statement); + const functionFlags = getFunctionFlags(func); + const isAsync = (functionFlags & FunctionFlags.Async) !== 0; + const isGenerator = (functionFlags & FunctionFlags.Generator) !== 0; + let returnType: ts.Type | undefined; + let yieldType: ts.Type | undefined; + let nextType: ts.Type | undefined; + let fallbackReturnType: ts.Type = voidType; + if (func.body.kind !== SyntaxKind.Block) { // Async or normal arrow function + returnType = checkExpressionCached(func.body, checkMode && checkMode & ~CheckMode.SkipGenericFunctions); + if (isAsync) { + // From within an async function you can return either a non-promise value or a promise. Any + // Promise/A+ compatible implementation will always assimilate any foreign promise, so the + // return type of the body should be unwrapped to its awaited type, which we will wrap in + // the native Promise type later in this function. + returnType = checkAwaitedType(returnType, /*errorNode*/ func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); } - - checkSourceElement(node.elseStatement); } - - function checkTestingKnownTruthyCallableType(ifStatement: IfStatement, type: Type) { - if (!strictNullChecks) { - return; - } - - const testedNode = isIdentifier(ifStatement.expression) - ? ifStatement.expression - : isPropertyAccessExpression(ifStatement.expression) - ? ifStatement.expression.name - : undefined; - - if (!testedNode) { - return; + else if (isGenerator) { // Generator or AsyncGenerator function + const returnTypes = checkAndAggregateReturnExpressionTypes(func, checkMode); + if (!returnTypes) { + fallbackReturnType = neverType; } - - const possiblyFalsy = getFalsyFlags(type); - if (possiblyFalsy) { - return; - } - - // While it technically should be invalid for any known-truthy value - // to be tested, we de-scope to functions unrefenced in the block as a - // heuristic to identify the most common bugs. There are too many - // false positives for values sourced from type definitions without - // strictNullChecks otherwise. - const callSignatures = getSignaturesOfType(type, SignatureKind.Call); - if (callSignatures.length === 0) { - return; + else if (returnTypes.length > 0) { + returnType = getUnionType(returnTypes, UnionReduction.Subtype); } - - const testedFunctionSymbol = getSymbolAtLocation(testedNode); - if (!testedFunctionSymbol) { - return; + const { yieldTypes, nextTypes } = checkAndAggregateYieldOperandTypes(func, checkMode); + yieldType = some(yieldTypes) ? getUnionType(yieldTypes, UnionReduction.Subtype) : undefined; + nextType = some(nextTypes) ? getIntersectionType(nextTypes) : undefined; + } + else { // Async or normal function + const types = checkAndAggregateReturnExpressionTypes(func, checkMode); + if (!types) { + // For an async function, the return type will not be never, but rather a Promise for never. + return functionFlags & FunctionFlags.Async + ? createPromiseReturnType(func, neverType) // Async function + : neverType; // Normal function } - - const functionIsUsedInBody = forEachChild(ifStatement.thenStatement, function check(childNode): boolean | undefined { - if (isIdentifier(childNode)) { - const childSymbol = getSymbolAtLocation(childNode); - if (childSymbol && childSymbol.id === testedFunctionSymbol.id) { - return true; - } + if (types.length === 0) { + // For an async function, the return type will not be void, but rather a Promise for void. + return functionFlags & FunctionFlags.Async + ? createPromiseReturnType(func, voidType) // Async function + : voidType; // Normal function + } + // Return a union of the return expression types. + returnType = getUnionType(types, UnionReduction.Subtype); + } + if (returnType || yieldType || nextType) { + const contextualSignature = getContextualSignatureForFunctionLikeDeclaration(func); + if (!contextualSignature) { + if (yieldType) + reportErrorsFromWidening(func, yieldType, WideningKind.GeneratorYield); + if (returnType) + reportErrorsFromWidening(func, returnType); + if (nextType) + reportErrorsFromWidening(func, nextType); + } + if (returnType && isUnitType(returnType) || + yieldType && isUnitType(yieldType) || + nextType && isUnitType(nextType)) { + const contextualType = !contextualSignature ? undefined : + contextualSignature === getSignatureFromDeclaration(func) ? isGenerator ? undefined : returnType : + instantiateContextualType(getReturnTypeOfSignature(contextualSignature), func); + if (isGenerator) { + yieldType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(yieldType, contextualType, IterationTypeKind.Yield, isAsync); + returnType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(returnType, contextualType, IterationTypeKind.Return, isAsync); + nextType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(nextType, contextualType, IterationTypeKind.Next, isAsync); + } + else { + returnType = getWidenedLiteralLikeTypeForContextualReturnTypeIfNeeded(returnType, contextualType, isAsync); } - - return forEachChild(childNode, check); - }); - - if (!functionIsUsedInBody) { - error(ifStatement.expression, Diagnostics.This_condition_will_always_return_true_since_the_function_is_always_defined_Did_you_mean_to_call_it_instead); } + if (yieldType) + yieldType = getWidenedType(yieldType); + if (returnType) + returnType = getWidenedType(returnType); + if (nextType) + nextType = getWidenedType(nextType); } - - function checkDoStatement(node: DoStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); - - checkSourceElement(node.statement); - checkTruthinessExpression(node.expression); + if (isGenerator) { + return createGeneratorReturnType(yieldType || neverType, returnType || fallbackReturnType, nextType || getContextualIterationType(IterationTypeKind.Next, func) || unknownType, isAsync); } - - function checkWhileStatement(node: WhileStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); - - checkTruthinessExpression(node.expression); - checkSourceElement(node.statement); + else { + // From within an async function you can return either a non-promise value or a promise. Any + // Promise/A+ compatible implementation will always assimilate any foreign promise, so the + // return type of the body is awaited type of the body, wrapped in a native Promise type. + return isAsync + ? createPromiseType(returnType || fallbackReturnType) + : returnType || fallbackReturnType; } - - function checkTruthinessExpression(node: Expression, checkMode?: CheckMode) { - const type = checkExpression(node, checkMode); - if (type.flags & TypeFlags.Void) { - error(node, Diagnostics.An_expression_of_type_void_cannot_be_tested_for_truthiness); + } + function createGeneratorReturnType(yieldType: ts.Type, returnType: ts.Type, nextType: ts.Type, isAsyncGenerator: boolean) { + const resolver = isAsyncGenerator ? asyncIterationTypesResolver : syncIterationTypesResolver; + const globalGeneratorType = resolver.getGlobalGeneratorType(/*reportErrors*/ false); + yieldType = resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || unknownType; + returnType = resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || unknownType; + nextType = resolver.resolveIterationType(nextType, /*errorNode*/ undefined) || unknownType; + if (globalGeneratorType === emptyGenericType) { + // Fall back to the global IterableIterator if returnType is assignable to the expected return iteration + // type of IterableIterator, and the expected next iteration type of IterableIterator is assignable to + // nextType. + const globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false); + const iterationTypes = globalType !== emptyGenericType ? getIterationTypesOfGlobalIterableType(globalType, resolver) : undefined; + const iterableIteratorReturnType = iterationTypes ? iterationTypes.returnType : anyType; + const iterableIteratorNextType = iterationTypes ? iterationTypes.nextType : undefinedType; + if (isTypeAssignableTo(returnType, iterableIteratorReturnType) && + isTypeAssignableTo(iterableIteratorNextType, nextType)) { + if (globalType !== emptyGenericType) { + return createTypeFromGenericGlobalType(globalType, [yieldType]); + } + // The global IterableIterator type doesn't exist, so report an error + resolver.getGlobalIterableIteratorType(/*reportErrors*/ true); + return emptyObjectType; } - return type; + // The global Generator type doesn't exist, so report an error + resolver.getGlobalGeneratorType(/*reportErrors*/ true); + return emptyObjectType; } - - function checkForStatement(node: ForStatement) { - // Grammar checking - if (!checkGrammarStatementInAmbientContext(node)) { - if (node.initializer && node.initializer.kind === SyntaxKind.VariableDeclarationList) { - checkGrammarVariableDeclarationList(node.initializer); - } + return createTypeFromGenericGlobalType(globalGeneratorType, [yieldType, returnType, nextType]); + } + function checkAndAggregateYieldOperandTypes(func: FunctionLikeDeclaration, checkMode: CheckMode | undefined) { + const yieldTypes: ts.Type[] = []; + const nextTypes: ts.Type[] = []; + const isAsync = (getFunctionFlags(func) & FunctionFlags.Async) !== 0; + forEachYieldExpression((func.body), yieldExpression => { + const yieldExpressionType = yieldExpression.expression ? checkExpression(yieldExpression.expression, checkMode) : undefinedWideningType; + pushIfUnique(yieldTypes, getYieldedTypeOfYieldExpression(yieldExpression, yieldExpressionType, anyType, isAsync)); + let nextType: ts.Type | undefined; + if (yieldExpression.asteriskToken) { + const iterationTypes = getIterationTypesOfIterable(yieldExpressionType, isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar, yieldExpression.expression); + nextType = iterationTypes && iterationTypes.nextType; } - - if (node.initializer) { - if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { - forEach((node.initializer).declarations, checkVariableDeclaration); + else { + nextType = getContextualType(yieldExpression); + } + if (nextType) + pushIfUnique(nextTypes, nextType); + }); + return { yieldTypes, nextTypes }; + } + function getYieldedTypeOfYieldExpression(node: YieldExpression, expressionType: ts.Type, sentType: ts.Type, isAsync: boolean): ts.Type | undefined { + const errorNode = node.expression || node; + // A `yield*` expression effectively yields everything that its operand yields + const yieldedType = node.asteriskToken ? checkIteratedTypeOrElementType(isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar, expressionType, sentType, errorNode) : expressionType; + return !isAsync ? yieldedType : getAwaitedType(yieldedType, errorNode, node.asteriskToken + ? Diagnostics.Type_of_iterated_elements_of_a_yield_Asterisk_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member + : Diagnostics.Type_of_yield_operand_in_an_async_generator_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + } + /** + * Collect the TypeFacts learned from a typeof switch with + * total clauses `witnesses`, and the active clause ranging + * from `start` to `end`. Parameter `hasDefault` denotes + * whether the active clause contains a default clause. + */ + function getFactsFromTypeofSwitch(start: number, end: number, witnesses: string[], hasDefault: boolean): TypeFacts { + let facts: TypeFacts = TypeFacts.None; + // When in the default we only collect inequality facts + // because default is 'in theory' a set of infinite + // equalities. + if (hasDefault) { + // Value is not equal to any types after the active clause. + for (let i = end; i < witnesses.length; i++) { + facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject; + } + // Remove inequalities for types that appear in the + // active clause because they appear before other + // types collected so far. + for (let i = start; i < end; i++) { + facts &= ~(typeofNEFacts.get(witnesses[i]) || 0); + } + // Add inequalities for types before the active clause unconditionally. + for (let i = 0; i < start; i++) { + facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject; + } + } + // When in an active clause without default the set of + // equalities is finite. + else { + // Add equalities for all types in the active clause. + for (let i = start; i < end; i++) { + facts |= typeofEQFacts.get(witnesses[i]) || TypeFacts.TypeofEQHostObject; + } + // Remove equalities for types that appear before the + // active clause. + for (let i = 0; i < start; i++) { + facts &= ~(typeofEQFacts.get(witnesses[i]) || 0); + } + } + return facts; + } + function isExhaustiveSwitchStatement(node: SwitchStatement): boolean { + const links = getNodeLinks(node); + return links.isExhaustive !== undefined ? links.isExhaustive : (links.isExhaustive = computeExhaustiveSwitchStatement(node)); + } + function computeExhaustiveSwitchStatement(node: SwitchStatement): boolean { + if (node.expression.kind === SyntaxKind.TypeOfExpression) { + const operandType = getTypeOfExpression((node.expression as TypeOfExpression).expression); + // This cast is safe because the switch is possibly exhaustive and does not contain a default case, so there can be no undefined. + const witnesses = getSwitchClauseTypeOfWitnesses(node); + // notEqualFacts states that the type of the switched value is not equal to every type in the switch. + const notEqualFacts = getFactsFromTypeofSwitch(0, 0, witnesses, /*hasDefault*/ true); + const type = getBaseConstraintOfType(operandType) || operandType; + return !!(filterType(type, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts).flags & TypeFlags.Never); + } + const type = getTypeOfExpression(node.expression); + if (!isLiteralType(type)) { + return false; + } + const switchTypes = getSwitchClauseTypes(node); + if (!switchTypes.length || some(switchTypes, isNeitherUnitTypeNorNever)) { + return false; + } + return eachTypeContainedIn(mapType(type, getRegularTypeOfLiteralType), switchTypes); + } + function functionHasImplicitReturn(func: FunctionLikeDeclaration) { + return func.endFlowNode && isReachableFlowNode(func.endFlowNode); + } + /** NOTE: Return value of `[]` means a different thing than `undefined`. `[]` means func returns `void`, `undefined` means it returns `never`. */ + function checkAndAggregateReturnExpressionTypes(func: FunctionLikeDeclaration, checkMode: CheckMode | undefined): ts.Type[] | undefined { + const functionFlags = getFunctionFlags(func); + const aggregatedTypes: ts.Type[] = []; + let hasReturnWithNoExpression = functionHasImplicitReturn(func); + let hasReturnOfTypeNever = false; + forEachReturnStatement((func.body), returnStatement => { + const expr = returnStatement.expression; + if (expr) { + let type = checkExpressionCached(expr, checkMode && checkMode & ~CheckMode.SkipGenericFunctions); + if (functionFlags & FunctionFlags.Async) { + // From within an async function you can return either a non-promise value or a promise. Any + // Promise/A+ compatible implementation will always assimilate any foreign promise, so the + // return type of the body should be unwrapped to its awaited type, which should be wrapped in + // the native Promise type by the caller. + type = checkAwaitedType(type, func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); } - else { - checkExpression(node.initializer); + if (type.flags & TypeFlags.Never) { + hasReturnOfTypeNever = true; } + pushIfUnique(aggregatedTypes, type); } - - if (node.condition) checkTruthinessExpression(node.condition); - if (node.incrementor) checkExpression(node.incrementor); - checkSourceElement(node.statement); - if (node.locals) { - registerForUnusedIdentifiersCheck(node); + else { + hasReturnWithNoExpression = true; } + }); + if (aggregatedTypes.length === 0 && !hasReturnWithNoExpression && (hasReturnOfTypeNever || mayReturnNever(func))) { + return undefined; } - - function checkForOfStatement(node: ForOfStatement): void { - checkGrammarForInOrForOfStatement(node); - - if (node.awaitModifier) { - const functionFlags = getFunctionFlags(getContainingFunction(node)); - if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Async)) === FunctionFlags.Async && languageVersion < ScriptTarget.ESNext) { - // for..await..of in an async function or async generator function prior to ESNext requires the __asyncValues helper - checkExternalEmitHelpers(node, ExternalEmitHelpers.ForAwaitOfIncludes); + if (strictNullChecks && aggregatedTypes.length && hasReturnWithNoExpression && + !(isJSConstructor(func) && aggregatedTypes.some(t => t.symbol === func.symbol))) { + // Javascript "callable constructors", containing eg `if (!(this instanceof A)) return new A()` should not add undefined + pushIfUnique(aggregatedTypes, undefinedType); + } + return aggregatedTypes; + } + function mayReturnNever(func: FunctionLikeDeclaration): boolean { + switch (func.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return true; + case SyntaxKind.MethodDeclaration: + return func.parent.kind === SyntaxKind.ObjectLiteralExpression; + default: + return false; + } + } + /** + * TypeScript Specification 1.0 (6.3) - July 2014 + * An explicitly typed function whose return type isn't the Void type, + * the Any type, or a union type containing the Void or Any type as a constituent + * must have at least one return statement somewhere in its body. + * An exception to this rule is if the function implementation consists of a single 'throw' statement. + * + * @param returnType - return type of the function, can be undefined if return type is not explicitly specified + */ + function checkAllCodePathsInNonVoidFunctionReturnOrThrow(func: FunctionLikeDeclaration | MethodSignature, returnType: ts.Type | undefined): void { + if (!produceDiagnostics) { + return; + } + const functionFlags = getFunctionFlags(func); + const type = returnType && getReturnOrPromisedType(returnType, functionFlags); + // Functions with with an explicitly specified 'void' or 'any' return type don't need any return expressions. + if (type && maybeTypeOfKind(type, TypeFlags.Any | TypeFlags.Void)) { + return; + } + // If all we have is a function signature, or an arrow function with an expression body, then there is nothing to check. + // also if HasImplicitReturn flag is not set this means that all codepaths in function body end with return or throw + if (func.kind === SyntaxKind.MethodSignature || nodeIsMissing(func.body) || func.body!.kind !== SyntaxKind.Block || !functionHasImplicitReturn(func)) { + return; + } + const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn; + if (type && type.flags & TypeFlags.Never) { + error(getEffectiveReturnTypeNode(func), Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point); + } + else if (type && !hasExplicitReturn) { + // minimal check: function has syntactic return type annotation and no explicit return statements in the body + // this function does not conform to the specification. + // NOTE: having returnType !== undefined is a precondition for entering this branch so func.type will always be present + error(getEffectiveReturnTypeNode(func), Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value); + } + else if (type && strictNullChecks && !isTypeAssignableTo(undefinedType, type)) { + error(getEffectiveReturnTypeNode(func) || func, Diagnostics.Function_lacks_ending_return_statement_and_return_type_does_not_include_undefined); + } + else if (compilerOptions.noImplicitReturns) { + if (!type) { + // If return type annotation is omitted check if function has any explicit return statements. + // If it does not have any - its inferred return type is void - don't do any checks. + // Otherwise get inferred return type from function body and report error only if it is not void / anytype + if (!hasExplicitReturn) { + return; + } + const inferredReturnType = getReturnTypeOfSignature(getSignatureFromDeclaration(func)); + if (isUnwrappedReturnTypeVoidOrAny(func, inferredReturnType)) { + return; } } - else if (compilerOptions.downlevelIteration && languageVersion < ScriptTarget.ES2015) { - // for..of prior to ES2015 requires the __values helper when downlevelIteration is enabled - checkExternalEmitHelpers(node, ExternalEmitHelpers.ForOfIncludes); + error(getEffectiveReturnTypeNode(func) || func, Diagnostics.Not_all_code_paths_return_a_value); + } + } + function checkFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | MethodDeclaration, checkMode?: CheckMode): ts.Type { + Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); + checkNodeDeferred(node); + // The identityMapper object is used to indicate that function expressions are wildcards + if (checkMode && checkMode & CheckMode.SkipContextSensitive && isContextSensitive(node)) { + // Skip parameters, return signature with return type that retains noncontextual parts so inferences can still be drawn in an early stage + if (!getEffectiveReturnTypeNode(node) && hasContextSensitiveReturnExpression(node)) { + // Return plain anyFunctionType if there is no possibility we'll make inferences from the return type + const contextualSignature = getContextualSignature(node); + if (contextualSignature && couldContainTypeVariables(getReturnTypeOfSignature(contextualSignature))) { + const links = getNodeLinks(node); + if (links.contextFreeType) { + return links.contextFreeType; + } + const returnType = getReturnTypeFromBody(node, checkMode); + const returnOnlySignature = createSignature(undefined, undefined, undefined, emptyArray, returnType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + const returnOnlyType = createAnonymousType(node.symbol, emptySymbols, [returnOnlySignature], emptyArray, undefined, undefined); + returnOnlyType.objectFlags |= ObjectFlags.NonInferrableType; + return links.contextFreeType = returnOnlyType; + } } - - // Check the LHS and RHS - // If the LHS is a declaration, just check it as a variable declaration, which will in turn check the RHS - // via checkRightHandSideOfForOf. - // If the LHS is an expression, check the LHS, as a destructuring assignment or as a reference. - // Then check that the RHS is assignable to it. - if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { - checkForInOrForOfVariableDeclaration(node); + return anyFunctionType; + } + // Grammar checking + const hasGrammarError = checkGrammarFunctionLikeDeclaration(node); + if (!hasGrammarError && node.kind === SyntaxKind.FunctionExpression) { + checkGrammarForGenerator(node); + } + const type = getTypeOfSymbol(getMergedSymbol(node.symbol)); + if (isTypeAny(type)) { + return type; + } + contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node, checkMode); + return type; + } + function contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | ArrowFunction | MethodDeclaration, checkMode?: CheckMode) { + const links = getNodeLinks(node); + // Check if function expression is contextually typed and assign parameter types if so. + if (!(links.flags & NodeCheckFlags.ContextChecked)) { + const contextualSignature = getContextualSignature(node); + // If a type check is started at a function expression that is an argument of a function call, obtaining the + // contextual type may recursively get back to here during overload resolution of the call. If so, we will have + // already assigned contextual types. + if (!(links.flags & NodeCheckFlags.ContextChecked)) { + links.flags |= NodeCheckFlags.ContextChecked; + if (contextualSignature) { + const type = getTypeOfSymbol(getMergedSymbol(node.symbol)); + if (isTypeAny(type)) { + return; + } + const signature = getSignaturesOfType(type, SignatureKind.Call)[0]; + if (isContextSensitive(node)) { + const inferenceContext = getInferenceContext(node); + if (checkMode && checkMode & CheckMode.Inferential) { + inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!); + } + const instantiatedContextualSignature = inferenceContext ? + instantiateSignature(contextualSignature, inferenceContext.mapper) : contextualSignature; + assignContextualParameterTypes(signature, instantiatedContextualSignature); + } + if (!getReturnTypeFromAnnotation(node) && !signature.resolvedReturnType) { + const returnType = getReturnTypeFromBody(node, checkMode); + if (!signature.resolvedReturnType) { + signature.resolvedReturnType = returnType; + } + } + } + checkSignatureDeclaration(node); + } + } + } + function getReturnOrPromisedType(type: ts.Type | undefined, functionFlags: FunctionFlags) { + const isGenerator = !!(functionFlags & FunctionFlags.Generator); + const isAsync = !!(functionFlags & FunctionFlags.Async); + return type && isGenerator ? getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, type, isAsync) || errorType : + type && isAsync ? getAwaitedType(type) || errorType : + type; + } + function checkFunctionExpressionOrObjectLiteralMethodDeferred(node: ArrowFunction | FunctionExpression | MethodDeclaration) { + Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); + const functionFlags = getFunctionFlags(node); + const returnType = getReturnTypeFromAnnotation(node); + checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); + if (node.body) { + if (!getEffectiveReturnTypeNode(node)) { + // There are some checks that are only performed in getReturnTypeFromBody, that may produce errors + // we need. An example is the noImplicitAny errors resulting from widening the return expression + // of a function. Because checking of function expression bodies is deferred, there was never an + // appropriate time to do this during the main walk of the file (see the comment at the top of + // checkFunctionExpressionBodies). So it must be done now. + getReturnTypeOfSignature(getSignatureFromDeclaration(node)); + } + if (node.body.kind === SyntaxKind.Block) { + checkSourceElement(node.body); } else { - const varExpr = node.initializer; - const iteratedType = checkRightHandSideOfForOf(node.expression, node.awaitModifier); - - // There may be a destructuring assignment on the left side - if (varExpr.kind === SyntaxKind.ArrayLiteralExpression || varExpr.kind === SyntaxKind.ObjectLiteralExpression) { - // iteratedType may be undefined. In this case, we still want to check the structure of - // varExpr, in particular making sure it's a valid LeftHandSideExpression. But we'd like - // to short circuit the type relation checking as much as possible, so we pass the unknownType. - checkDestructuringAssignment(varExpr, iteratedType || errorType); - } - else { - const leftType = checkExpression(varExpr); - checkReferenceExpression( - varExpr, - Diagnostics.The_left_hand_side_of_a_for_of_statement_must_be_a_variable_or_a_property_access, - Diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_an_optional_property_access); - - // iteratedType will be undefined if the rightType was missing properties/signatures - // required to get its iteratedType (like [Symbol.iterator] or next). This may be - // because we accessed properties from anyType, or it may have led to an error inside - // getElementTypeOfIterable. - if (iteratedType) { - checkTypeAssignableToAndOptionallyElaborate(iteratedType, leftType, varExpr, node.expression); + // From within an async function you can return either a non-promise value or a promise. Any + // Promise/A+ compatible implementation will always assimilate any foreign promise, so we + // should not be checking assignability of a promise to the return type. Instead, we need to + // check assignability of the awaited type of the expression body against the promised type of + // its return type annotation. + const exprType = checkExpression(node.body); + const returnOrPromisedType = getReturnOrPromisedType(returnType, functionFlags); + if (returnOrPromisedType) { + if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { // Async function + const awaitedType = checkAwaitedType(exprType, node.body, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + checkTypeAssignableToAndOptionallyElaborate(awaitedType, returnOrPromisedType, node.body, node.body); + } + else { // Normal function + checkTypeAssignableToAndOptionallyElaborate(exprType, returnOrPromisedType, node.body, node.body); } } } - - checkSourceElement(node.statement); - if (node.locals) { - registerForUnusedIdentifiersCheck(node); - } } - - function checkForInStatement(node: ForInStatement) { - // Grammar checking - checkGrammarForInOrForOfStatement(node); - - const rightType = getNonNullableTypeIfNeeded(checkExpression(node.expression)); - // TypeScript 1.0 spec (April 2014): 5.4 - // In a 'for-in' statement of the form - // for (let VarDecl in Expr) Statement - // VarDecl must be a variable declaration without a type annotation that declares a variable of type Any, - // and Expr must be an expression of type Any, an object type, or a type parameter type. - if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { - const variable = (node.initializer).declarations[0]; - if (variable && isBindingPattern(variable.name)) { - error(variable.name, Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_be_a_destructuring_pattern); - } - checkForInOrForOfVariableDeclaration(node); + } + function checkArithmeticOperandType(operand: Node, type: ts.Type, diagnostic: DiagnosticMessage, isAwaitValid = false): boolean { + if (!isTypeAssignableTo(type, numberOrBigIntType)) { + const awaitedType = isAwaitValid && getAwaitedTypeOfPromise(type); + errorAndMaybeSuggestAwait(operand, !!awaitedType && isTypeAssignableTo(awaitedType, numberOrBigIntType), diagnostic); + return false; + } + return true; + } + function isReadonlyAssignmentDeclaration(d: Declaration) { + if (!isCallExpression(d)) { + return false; + } + if (!isBindableObjectDefinePropertyCall(d)) { + return false; + } + const objectLitType = checkExpressionCached(d.arguments[2]); + const valueType = getTypeOfPropertyOfType(objectLitType, ("value" as __String)); + if (valueType) { + const writableProp = getPropertyOfType(objectLitType, ("writable" as __String)); + const writableType = writableProp && getTypeOfSymbol(writableProp); + if (!writableType || writableType === falseType || writableType === regularFalseType) { + return true; } - else { - // In a 'for-in' statement of the form - // for (Var in Expr) Statement - // Var must be an expression classified as a reference of type Any or the String primitive type, - // and Expr must be an expression of type Any, an object type, or a type parameter type. - const varExpr = node.initializer; - const leftType = checkExpression(varExpr); - if (varExpr.kind === SyntaxKind.ArrayLiteralExpression || varExpr.kind === SyntaxKind.ObjectLiteralExpression) { - error(varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_be_a_destructuring_pattern); + // We include this definition whereupon we walk back and check the type at the declaration because + // The usual definition of `Object.defineProperty` will _not_ cause literal types to be preserved in the + // argument types, should the type be contextualized by the call itself. + if (writableProp && writableProp.valueDeclaration && isPropertyAssignment(writableProp.valueDeclaration)) { + const initializer = writableProp.valueDeclaration.initializer; + const rawOriginalType = checkExpression(initializer); + if (rawOriginalType === falseType || rawOriginalType === regularFalseType) { + return true; } - else if (!isTypeAssignableTo(getIndexTypeOrString(rightType), leftType)) { - error(varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_of_type_string_or_any); + } + return false; + } + const setProp = getPropertyOfType(objectLitType, ("set" as __String)); + return !setProp; + } + function isReadonlySymbol(symbol: ts.Symbol): boolean { + // The following symbols are considered read-only: + // Properties with a 'readonly' modifier + // Variables declared with 'const' + // Get accessors without matching set accessors + // Enum members + // Object.defineProperty assignments with writable false or no setter + // Unions and intersections of the above (unions and intersections eagerly set isReadonly on creation) + return !!(getCheckFlags(symbol) & CheckFlags.Readonly || + symbol.flags & SymbolFlags.Property && getDeclarationModifierFlagsFromSymbol(symbol) & ModifierFlags.Readonly || + symbol.flags & SymbolFlags.Variable && getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const || + symbol.flags & SymbolFlags.Accessor && !(symbol.flags & SymbolFlags.SetAccessor) || + symbol.flags & SymbolFlags.EnumMember || + some(symbol.declarations, isReadonlyAssignmentDeclaration)); + } + function isReferenceToReadonlyEntity(expr: Expression, symbol: ts.Symbol): boolean { + if (isReadonlySymbol(symbol)) { + // Allow assignments to readonly properties within constructors of the same class declaration. + if (symbol.flags & SymbolFlags.Property && + (expr.kind === SyntaxKind.PropertyAccessExpression || expr.kind === SyntaxKind.ElementAccessExpression) && + (expr as AccessExpression).expression.kind === SyntaxKind.ThisKeyword) { + // Look for if this is the constructor for the class that `symbol` is a property of. + const func = getContainingFunction(expr); + if (!(func && func.kind === SyntaxKind.Constructor)) { + return true; } - else { - // run check only former check succeeded to avoid cascading errors - checkReferenceExpression( - varExpr, - Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_a_variable_or_a_property_access, - Diagnostics.The_left_hand_side_of_a_for_in_statement_may_not_be_an_optional_property_access); + // If func.parent is a class and symbol is a (readonly) property of that class, or + // if func is a constructor and symbol is a (readonly) parameter property declared in it, + // then symbol is writeable here. + return !symbol.valueDeclaration || !(func.parent === symbol.valueDeclaration.parent || func === symbol.valueDeclaration.parent); + } + return true; + } + return false; + } + function isReferenceThroughNamespaceImport(expr: Expression): boolean { + if (expr.kind === SyntaxKind.PropertyAccessExpression || expr.kind === SyntaxKind.ElementAccessExpression) { + const node = skipParentheses((expr as AccessExpression).expression); + if (node.kind === SyntaxKind.Identifier) { + const symbol = getNodeLinks(node).resolvedSymbol!; + if (symbol.flags & SymbolFlags.Alias) { + const declaration = getDeclarationOfAliasSymbol(symbol); + return !!declaration && declaration.kind === SyntaxKind.NamespaceImport; } } - - // unknownType is returned i.e. if node.expression is identifier whose name cannot be resolved - // in this case error about missing name is already reported - do not report extra one - if (rightType === neverType || !isTypeAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive)) { - error(node.expression, Diagnostics.The_right_hand_side_of_a_for_in_statement_must_be_of_type_any_an_object_type_or_a_type_parameter_but_here_has_type_0, typeToString(rightType)); + } + return false; + } + function checkReferenceExpression(expr: Expression, invalidReferenceMessage: DiagnosticMessage, invalidOptionalChainMessage: DiagnosticMessage): boolean { + // References are combinations of identifiers, parentheses, and property accesses. + const node = skipOuterExpressions(expr, OuterExpressionKinds.Assertions | OuterExpressionKinds.Parentheses); + if (node.kind !== SyntaxKind.Identifier && node.kind !== SyntaxKind.PropertyAccessExpression && node.kind !== SyntaxKind.ElementAccessExpression) { + error(expr, invalidReferenceMessage); + return false; + } + if (node.flags & NodeFlags.OptionalChain) { + error(expr, invalidOptionalChainMessage); + return false; + } + return true; + } + function checkDeleteExpression(node: DeleteExpression): ts.Type { + checkExpression(node.expression); + const expr = skipParentheses(node.expression); + if (expr.kind !== SyntaxKind.PropertyAccessExpression && expr.kind !== SyntaxKind.ElementAccessExpression) { + error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_a_property_reference); + return booleanType; + } + const links = getNodeLinks(expr); + const symbol = getExportSymbolOfValueSymbolIfExported(links.resolvedSymbol); + if (symbol && isReadonlySymbol(symbol)) { + error(expr, Diagnostics.The_operand_of_a_delete_operator_cannot_be_a_read_only_property); + } + return booleanType; + } + function checkTypeOfExpression(node: TypeOfExpression): ts.Type { + checkExpression(node.expression); + return typeofType; + } + function checkVoidExpression(node: VoidExpression): ts.Type { + checkExpression(node.expression); + return undefinedWideningType; + } + function checkAwaitExpression(node: AwaitExpression): ts.Type { + // Grammar checking + if (produceDiagnostics) { + if (!(node.flags & NodeFlags.AwaitContext)) { + // use of 'await' in non-async function + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const span = getSpanOfTokenAtPosition(sourceFile, node.pos); + const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.await_expression_is_only_allowed_within_an_async_function); + const func = getContainingFunction(node); + if (func && func.kind !== SyntaxKind.Constructor && (getFunctionFlags(func) & FunctionFlags.Async) === 0) { + const relatedInfo = createDiagnosticForNode(func, Diagnostics.Did_you_mean_to_mark_this_function_as_async); + addRelatedInfo(diagnostic, relatedInfo); + } + diagnostics.add(diagnostic); + } } - - checkSourceElement(node.statement); - if (node.locals) { - registerForUnusedIdentifiersCheck(node); + if (isInParameterInitializerBeforeContainingFunction(node)) { + error(node, Diagnostics.await_expressions_cannot_be_used_in_a_parameter_initializer); } } - - function checkForInOrForOfVariableDeclaration(iterationStatement: ForInOrOfStatement): void { - const variableDeclarationList = iterationStatement.initializer; - // checkGrammarForInOrForOfStatement will check that there is exactly one declaration. - if (variableDeclarationList.declarations.length >= 1) { - const decl = variableDeclarationList.declarations[0]; - checkVariableDeclaration(decl); + const operandType = checkExpression(node.expression); + const awaitedType = checkAwaitedType(operandType, node, Diagnostics.Type_of_await_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + if (awaitedType === operandType && awaitedType !== errorType && !(operandType.flags & TypeFlags.AnyOrUnknown)) { + addErrorOrSuggestion(/*isError*/ false, createDiagnosticForNode(node, Diagnostics.await_has_no_effect_on_the_type_of_this_expression)); + } + return awaitedType; + } + function checkPrefixUnaryExpression(node: PrefixUnaryExpression): ts.Type { + const operandType = checkExpression(node.operand); + if (operandType === silentNeverType) { + return silentNeverType; + } + switch (node.operand.kind) { + case SyntaxKind.NumericLiteral: + switch (node.operator) { + case SyntaxKind.MinusToken: + return getFreshTypeOfLiteralType(getLiteralType(-(node.operand as NumericLiteral).text)); + case SyntaxKind.PlusToken: + return getFreshTypeOfLiteralType(getLiteralType(+(node.operand as NumericLiteral).text)); + } + break; + case SyntaxKind.BigIntLiteral: + if (node.operator === SyntaxKind.MinusToken) { + return getFreshTypeOfLiteralType(getLiteralType({ + negative: true, + base10Value: parsePseudoBigInt((node.operand as BigIntLiteral).text) + })); + } + } + switch (node.operator) { + case SyntaxKind.PlusToken: + case SyntaxKind.MinusToken: + case SyntaxKind.TildeToken: + checkNonNullType(operandType, node.operand); + if (maybeTypeOfKind(operandType, TypeFlags.ESSymbolLike)) { + error(node.operand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(node.operator)); + } + if (node.operator === SyntaxKind.PlusToken) { + if (maybeTypeOfKind(operandType, TypeFlags.BigIntLike)) { + error(node.operand, Diagnostics.Operator_0_cannot_be_applied_to_type_1, tokenToString(node.operator), typeToString(getBaseTypeOfLiteralType(operandType))); + } + return numberType; + } + return getUnaryResultType(operandType); + case SyntaxKind.ExclamationToken: + checkTruthinessExpression(node.operand); + const facts = getTypeFacts(operandType) & (TypeFacts.Truthy | TypeFacts.Falsy); + return facts === TypeFacts.Truthy ? falseType : + facts === TypeFacts.Falsy ? trueType : + booleanType; + case SyntaxKind.PlusPlusToken: + case SyntaxKind.MinusMinusToken: + const ok = checkArithmeticOperandType(node.operand, checkNonNullType(operandType, node.operand), Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type); + if (ok) { + // run check only if former checks succeeded to avoid reporting cascading errors + checkReferenceExpression(node.operand, Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, Diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access); + } + return getUnaryResultType(operandType); + } + return errorType; + } + function checkPostfixUnaryExpression(node: PostfixUnaryExpression): ts.Type { + const operandType = checkExpression(node.operand); + if (operandType === silentNeverType) { + return silentNeverType; + } + const ok = checkArithmeticOperandType(node.operand, checkNonNullType(operandType, node.operand), Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type); + if (ok) { + // run check only if former checks succeeded to avoid reporting cascading errors + checkReferenceExpression(node.operand, Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, Diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access); + } + return getUnaryResultType(operandType); + } + function getUnaryResultType(operandType: ts.Type): ts.Type { + if (maybeTypeOfKind(operandType, TypeFlags.BigIntLike)) { + return isTypeAssignableToKind(operandType, TypeFlags.AnyOrUnknown) || maybeTypeOfKind(operandType, TypeFlags.NumberLike) + ? numberOrBigIntType + : bigintType; + } + // If it's not a bigint type, implicit coercion will result in a number + return numberType; + } + // Return true if type might be of the given kind. A union or intersection type might be of a given + // kind if at least one constituent type is of the given kind. + function maybeTypeOfKind(type: ts.Type, kind: TypeFlags): boolean { + if (type.flags & kind & ~TypeFlags.GenericMappedType || kind & TypeFlags.GenericMappedType && isGenericMappedType(type)) { + return true; + } + if (type.flags & TypeFlags.UnionOrIntersection) { + const types = (type).types; + for (const t of types) { + if (maybeTypeOfKind(t, kind)) { + return true; + } } } - - function checkRightHandSideOfForOf(rhsExpression: Expression, awaitModifier: AwaitKeywordToken | undefined): Type { - const expressionType = checkNonNullExpression(rhsExpression); - const use = awaitModifier ? IterationUse.ForAwaitOf : IterationUse.ForOf; - return checkIteratedTypeOrElementType(use, expressionType, undefinedType, rhsExpression); + return false; + } + function isTypeAssignableToKind(source: ts.Type, kind: TypeFlags, strict?: boolean): boolean { + if (source.flags & kind) { + return true; + } + if (strict && source.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null)) { + return false; + } + return !!(kind & TypeFlags.NumberLike) && isTypeAssignableTo(source, numberType) || + !!(kind & TypeFlags.BigIntLike) && isTypeAssignableTo(source, bigintType) || + !!(kind & TypeFlags.StringLike) && isTypeAssignableTo(source, stringType) || + !!(kind & TypeFlags.BooleanLike) && isTypeAssignableTo(source, booleanType) || + !!(kind & TypeFlags.Void) && isTypeAssignableTo(source, voidType) || + !!(kind & TypeFlags.Never) && isTypeAssignableTo(source, neverType) || + !!(kind & TypeFlags.Null) && isTypeAssignableTo(source, nullType) || + !!(kind & TypeFlags.Undefined) && isTypeAssignableTo(source, undefinedType) || + !!(kind & TypeFlags.ESSymbol) && isTypeAssignableTo(source, esSymbolType) || + !!(kind & TypeFlags.NonPrimitive) && isTypeAssignableTo(source, nonPrimitiveType); + } + function allTypesAssignableToKind(source: ts.Type, kind: TypeFlags, strict?: boolean): boolean { + return source.flags & TypeFlags.Union ? + every((source as UnionType).types, subType => allTypesAssignableToKind(subType, kind, strict)) : + isTypeAssignableToKind(source, kind, strict); + } + function isConstEnumObjectType(type: ts.Type): boolean { + return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && !!type.symbol && isConstEnumSymbol(type.symbol); + } + function isConstEnumSymbol(symbol: ts.Symbol): boolean { + return (symbol.flags & SymbolFlags.ConstEnum) !== 0; + } + function checkInstanceOfExpression(left: Expression, right: Expression, leftType: ts.Type, rightType: ts.Type): ts.Type { + if (leftType === silentNeverType || rightType === silentNeverType) { + return silentNeverType; + } + // TypeScript 1.0 spec (April 2014): 4.15.4 + // The instanceof operator requires the left operand to be of type Any, an object type, or a type parameter type, + // and the right operand to be of type Any, a subtype of the 'Function' interface type, or have a call or construct signature. + // The result is always of the Boolean primitive type. + // NOTE: do not raise error if leftType is unknown as related error was already reported + if (!isTypeAny(leftType) && + allTypesAssignableToKind(leftType, TypeFlags.Primitive)) { + error(left, Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter); + } + // NOTE: do not raise error if right is unknown as related error was already reported + if (!(isTypeAny(rightType) || typeHasCallOrConstructSignatures(rightType) || isTypeSubtypeOf(rightType, globalFunctionType))) { + error(right, Diagnostics.The_right_hand_side_of_an_instanceof_expression_must_be_of_type_any_or_of_a_type_assignable_to_the_Function_interface_type); + } + return booleanType; + } + function checkInExpression(left: Expression, right: Expression, leftType: ts.Type, rightType: ts.Type): ts.Type { + if (leftType === silentNeverType || rightType === silentNeverType) { + return silentNeverType; + } + leftType = checkNonNullType(leftType, left); + rightType = checkNonNullType(rightType, right); + // TypeScript 1.0 spec (April 2014): 4.15.5 + // The in operator requires the left operand to be of type Any, the String primitive type, or the Number primitive type, + // and the right operand to be of type Any, an object type, or a type parameter type. + // The result is always of the Boolean primitive type. + if (!(isTypeComparableTo(leftType, stringType) || isTypeAssignableToKind(leftType, TypeFlags.NumberLike | TypeFlags.ESSymbolLike))) { + error(left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_of_type_any_string_number_or_symbol); + } + if (!allTypesAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive)) { + error(right, Diagnostics.The_right_hand_side_of_an_in_expression_must_be_of_type_any_an_object_type_or_a_type_parameter); + } + return booleanType; + } + function checkObjectLiteralAssignment(node: ObjectLiteralExpression, sourceType: ts.Type, rightIsThis?: boolean): ts.Type { + const properties = node.properties; + if (strictNullChecks && properties.length === 0) { + return checkNonNullType(sourceType, node); + } + for (let i = 0; i < properties.length; i++) { + checkObjectLiteralDestructuringPropertyAssignment(node, sourceType, i, properties, rightIsThis); } - - function checkIteratedTypeOrElementType(use: IterationUse, inputType: Type, sentType: Type, errorNode: Node | undefined): Type { - if (isTypeAny(inputType)) { - return inputType; + return sourceType; + } + /** Note: If property cannot be a SpreadAssignment, then allProperties does not need to be provided */ + function checkObjectLiteralDestructuringPropertyAssignment(node: ObjectLiteralExpression, objectLiteralType: ts.Type, propertyIndex: number, allProperties?: NodeArray, rightIsThis = false) { + const properties = node.properties; + const property = properties[propertyIndex]; + if (property.kind === SyntaxKind.PropertyAssignment || property.kind === SyntaxKind.ShorthandPropertyAssignment) { + const name = property.name; + const exprType = getLiteralTypeFromPropertyName(name); + if (isTypeUsableAsPropertyName(exprType)) { + const text = getPropertyNameFromType(exprType); + const prop = getPropertyOfType(objectLiteralType, text); + if (prop) { + markPropertyAsReferenced(prop, property, rightIsThis); + checkPropertyAccessibility(property, /*isSuper*/ false, objectLiteralType, prop); + } } - - return getIteratedTypeOrElementType(use, inputType, sentType, errorNode, /*checkAssignability*/ true) || anyType; + const elementType = getIndexedAccessType(objectLiteralType, exprType, name); + const type = getFlowTypeOfDestructuring(property, elementType); + return checkDestructuringAssignment(property.kind === SyntaxKind.ShorthandPropertyAssignment ? property : property.initializer, type); } - - /** - * When consuming an iterable type in a for..of, spread, or iterator destructuring assignment - * we want to get the iterated type of an iterable for ES2015 or later, or the iterated type - * of a iterable (if defined globally) or element type of an array like for ES2015 or earlier. - */ - function getIteratedTypeOrElementType(use: IterationUse, inputType: Type, sentType: Type, errorNode: Node | undefined, checkAssignability: boolean): Type | undefined { - const allowAsyncIterables = (use & IterationUse.AllowsAsyncIterablesFlag) !== 0; - if (inputType === neverType) { - reportTypeNotIterableError(errorNode!, inputType, allowAsyncIterables); // TODO: GH#18217 - return undefined; - } - - const uplevelIteration = languageVersion >= ScriptTarget.ES2015; - const downlevelIteration = !uplevelIteration && compilerOptions.downlevelIteration; - - // Get the iterated type of an `Iterable` or `IterableIterator` only in ES2015 - // or higher, when inside of an async generator or for-await-if, or when - // downlevelIteration is requested. - if (uplevelIteration || downlevelIteration || allowAsyncIterables) { - // We only report errors for an invalid iterable type in ES2015 or higher. - const iterationTypes = getIterationTypesOfIterable(inputType, use, uplevelIteration ? errorNode : undefined); - if (checkAssignability) { - if (iterationTypes) { - const diagnostic = - use & IterationUse.ForOfFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_for_of_will_always_send_0 : - use & IterationUse.SpreadFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_array_spread_will_always_send_0 : - use & IterationUse.DestructuringFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_array_destructuring_will_always_send_0 : - use & IterationUse.YieldStarFlag ? Diagnostics.Cannot_delegate_iteration_to_value_because_the_next_method_of_its_iterator_expects_type_1_but_the_containing_generator_will_always_send_0 : - undefined; - if (diagnostic) { - checkTypeAssignableTo(sentType, iterationTypes.nextType, errorNode, diagnostic); - } - } - } - if (iterationTypes || uplevelIteration) { - return iterationTypes && iterationTypes.yieldType; - } + else if (property.kind === SyntaxKind.SpreadAssignment) { + if (propertyIndex < properties.length - 1) { + error(property, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); } - - let arrayType = inputType; - let reportedError = false; - let hasStringConstituent = false; - - // If strings are permitted, remove any string-like constituents from the array type. - // This allows us to find other non-string element types from an array unioned with - // a string. - if (use & IterationUse.AllowsStringInputFlag) { - if (arrayType.flags & TypeFlags.Union) { - // After we remove all types that are StringLike, we will know if there was a string constituent - // based on whether the result of filter is a new array. - const arrayTypes = (inputType).types; - const filteredTypes = filter(arrayTypes, t => !(t.flags & TypeFlags.StringLike)); - if (filteredTypes !== arrayTypes) { - arrayType = getUnionType(filteredTypes, UnionReduction.Subtype); - } - } - else if (arrayType.flags & TypeFlags.StringLike) { - arrayType = neverType; + else { + if (languageVersion < ScriptTarget.ESNext) { + checkExternalEmitHelpers(property, ExternalEmitHelpers.Rest); } - - hasStringConstituent = arrayType !== inputType; - if (hasStringConstituent) { - if (languageVersion < ScriptTarget.ES5) { - if (errorNode) { - error(errorNode, Diagnostics.Using_a_string_in_a_for_of_statement_is_only_supported_in_ECMAScript_5_and_higher); - reportedError = true; + const nonRestNames: PropertyName[] = []; + if (allProperties) { + for (const otherProperty of allProperties) { + if (!isSpreadAssignment(otherProperty)) { + nonRestNames.push(otherProperty.name); } } - - // Now that we've removed all the StringLike types, if no constituents remain, then the entire - // arrayOrStringType was a string. - if (arrayType.flags & TypeFlags.Never) { - return stringType; - } - } - } - - if (!isArrayLikeType(arrayType)) { - if (errorNode && !reportedError) { - // Which error we report depends on whether we allow strings or if there was a - // string constituent. For example, if the input type is number | string, we - // want to say that number is not an array type. But if the input was just - // number and string input is allowed, we want to say that number is not an - // array type or a string type. - const yieldType = getIterationTypeOfIterable(use, IterationTypeKind.Yield, inputType, /*errorNode*/ undefined); - const [defaultDiagnostic, maybeMissingAwait]: [DiagnosticMessage, boolean] = !(use & IterationUse.AllowsStringInputFlag) || hasStringConstituent - ? downlevelIteration - ? [Diagnostics.Type_0_is_not_an_array_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator, true] - : yieldType - ? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_Use_compiler_option_downlevelIteration_to_allow_iterating_of_iterators, false] - : [Diagnostics.Type_0_is_not_an_array_type, true] - : downlevelIteration - ? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator, true] - : yieldType - ? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_Use_compiler_option_downlevelIteration_to_allow_iterating_of_iterators, false] - : [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type, true]; - errorAndMaybeSuggestAwait( - errorNode, - maybeMissingAwait && !!getAwaitedTypeOfPromise(arrayType), - defaultDiagnostic, - typeToString(arrayType)); - } - return hasStringConstituent ? stringType : undefined; - } - - const arrayElementType = getIndexTypeOfType(arrayType, IndexKind.Number); - if (hasStringConstituent && arrayElementType) { - // This is just an optimization for the case where arrayOrStringType is string | string[] - if (arrayElementType.flags & TypeFlags.StringLike) { - return stringType; } - - return getUnionType([arrayElementType, stringType], UnionReduction.Subtype); + const type = getRestType(objectLiteralType, nonRestNames, objectLiteralType.symbol); + checkGrammarForDisallowedTrailingComma(allProperties, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); + return checkDestructuringAssignment(property.expression, type); } - - return arrayElementType; } - - /** - * Gets the requested "iteration type" from an `Iterable`-like or `AsyncIterable`-like type. - */ - function getIterationTypeOfIterable(use: IterationUse, typeKind: IterationTypeKind, inputType: Type, errorNode: Node | undefined): Type | undefined { - if (isTypeAny(inputType)) { - return undefined; - } - - const iterationTypes = getIterationTypesOfIterable(inputType, use, errorNode); - return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(typeKind)]; + else { + error(property, Diagnostics.Property_assignment_expected); } - - function createIterationTypes(yieldType: Type = neverType, returnType: Type = neverType, nextType: Type = unknownType): IterationTypes { - // `yieldType` and `returnType` are defaulted to `neverType` they each will be combined - // via `getUnionType` when merging iteration types. `nextType` is defined as `unknownType` - // as it is combined via `getIntersectionType` when merging iteration types. - - // Use the cache only for intrinsic types to keep it small as they are likely to be - // more frequently created (i.e. `Iterator`). Iteration types - // are also cached on the type they are requested for, so we shouldn't need to maintain - // the cache for less-frequently used types. - if (yieldType.flags & TypeFlags.Intrinsic && - returnType.flags & (TypeFlags.Any | TypeFlags.Never | TypeFlags.Unknown | TypeFlags.Void | TypeFlags.Undefined) && - nextType.flags & (TypeFlags.Any | TypeFlags.Never | TypeFlags.Unknown | TypeFlags.Void | TypeFlags.Undefined)) { - const id = getTypeListId([yieldType, returnType, nextType]); - let iterationTypes = iterationTypesCache.get(id); - if (!iterationTypes) { - iterationTypes = { yieldType, returnType, nextType }; - iterationTypesCache.set(id, iterationTypes); - } - return iterationTypes; + } + function checkArrayLiteralAssignment(node: ArrayLiteralExpression, sourceType: ts.Type, checkMode?: CheckMode): ts.Type { + const elements = node.elements; + if (languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Read); + } + // This elementType will be used if the specific property corresponding to this index is not + // present (aka the tuple element property). This call also checks that the parentType is in + // fact an iterable or array (depending on target language). + const elementType = checkIteratedTypeOrElementType(IterationUse.Destructuring, sourceType, undefinedType, node) || errorType; + for (let i = 0; i < elements.length; i++) { + checkArrayLiteralDestructuringElementAssignment(node, sourceType, i, elementType, checkMode); + } + return sourceType; + } + function checkArrayLiteralDestructuringElementAssignment(node: ArrayLiteralExpression, sourceType: ts.Type, elementIndex: number, elementType: ts.Type, checkMode?: CheckMode) { + const elements = node.elements; + const element = elements[elementIndex]; + if (element.kind !== SyntaxKind.OmittedExpression) { + if (element.kind !== SyntaxKind.SpreadElement) { + const indexType = getLiteralType(elementIndex); + if (isArrayLikeType(sourceType)) { + // We create a synthetic expression so that getIndexedAccessType doesn't get confused + // when the element is a SyntaxKind.ElementAccessExpression. + const accessFlags = hasDefaultValue(element) ? AccessFlags.NoTupleBoundsCheck : 0; + const elementType = getIndexedAccessTypeOrUndefined(sourceType, indexType, createSyntheticExpression(element, indexType), accessFlags) || errorType; + const assignedType = hasDefaultValue(element) ? getTypeWithFacts(elementType, TypeFacts.NEUndefined) : elementType; + const type = getFlowTypeOfDestructuring(element, assignedType); + return checkDestructuringAssignment(element, type, checkMode); + } + return checkDestructuringAssignment(element, elementType, checkMode); + } + if (elementIndex < elements.length - 1) { + error(element, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); } - return { yieldType, returnType, nextType }; - } - - /** - * Combines multiple `IterationTypes` records. - * - * If `array` is empty or all elements are missing or are references to `noIterationTypes`, - * then `noIterationTypes` is returned. Otherwise, an `IterationTypes` record is returned - * for the combined iteration types. - */ - function combineIterationTypes(array: (IterationTypes | undefined)[]) { - let yieldTypes: Type[] | undefined; - let returnTypes: Type[] | undefined; - let nextTypes: Type[] | undefined; - for (const iterationTypes of array) { - if (iterationTypes === undefined || iterationTypes === noIterationTypes) { - continue; + else { + const restExpression = (element).expression; + if (restExpression.kind === SyntaxKind.BinaryExpression && (restExpression).operatorToken.kind === SyntaxKind.EqualsToken) { + error((restExpression).operatorToken, Diagnostics.A_rest_element_cannot_have_an_initializer); } - if (iterationTypes === anyIterationTypes) { - return anyIterationTypes; + else { + checkGrammarForDisallowedTrailingComma(node.elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); + const type = everyType(sourceType, isTupleType) ? + mapType(sourceType, t => sliceTupleType((t), elementIndex)) : + createArrayType(elementType); + return checkDestructuringAssignment(restExpression, type, checkMode); } - yieldTypes = append(yieldTypes, iterationTypes.yieldType); - returnTypes = append(returnTypes, iterationTypes.returnType); - nextTypes = append(nextTypes, iterationTypes.nextType); } - if (yieldTypes || returnTypes || nextTypes) { - return createIterationTypes( - yieldTypes && getUnionType(yieldTypes), - returnTypes && getUnionType(returnTypes), - nextTypes && getIntersectionType(nextTypes)); - } - return noIterationTypes; } - - /** - * Gets the *yield*, *return*, and *next* types from an `Iterable`-like or `AsyncIterable`-like type. - * - * At every level that involves analyzing return types of signatures, we union the return types of all the signatures. - * - * Another thing to note is that at any step of this process, we could run into a dead end, - * meaning either the property is missing, or we run into the anyType. If either of these things - * happens, we return `undefined` to signal that we could not find the iteration type. If a property - * is missing, and the previous step did not result in `any`, then we also give an error if the - * caller requested it. Then the caller can decide what to do in the case where there is no iterated - * type. - * - * For a **for-of** statement, `yield*` (in a normal generator), spread, array - * destructuring, or normal generator we will only ever look for a `[Symbol.iterator]()` - * method. - * - * For an async generator we will only ever look at the `[Symbol.asyncIterator]()` method. - * - * For a **for-await-of** statement or a `yield*` in an async generator we will look for - * the `[Symbol.asyncIterator]()` method first, and then the `[Symbol.iterator]()` method. - */ - function getIterationTypesOfIterable(type: Type, use: IterationUse, errorNode: Node | undefined) { - if (isTypeAny(type)) { - return anyIterationTypes; - } - - if (!(type.flags & TypeFlags.Union)) { - const iterationTypes = getIterationTypesOfIterableWorker(type, use, errorNode); - if (iterationTypes === noIterationTypes) { - if (errorNode) { - reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag)); - } - return undefined; + return undefined; + } + function checkDestructuringAssignment(exprOrAssignment: Expression | ShorthandPropertyAssignment, sourceType: ts.Type, checkMode?: CheckMode, rightIsThis?: boolean): ts.Type { + let target: Expression; + if (exprOrAssignment.kind === SyntaxKind.ShorthandPropertyAssignment) { + const prop = (exprOrAssignment); + if (prop.objectAssignmentInitializer) { + // In strict null checking mode, if a default value of a non-undefined type is specified, remove + // undefined from the final type. + if (strictNullChecks && + !(getFalsyFlags(checkExpression(prop.objectAssignmentInitializer)) & TypeFlags.Undefined)) { + sourceType = getTypeWithFacts(sourceType, TypeFacts.NEUndefined); } - return iterationTypes; + checkBinaryLikeExpression(prop.name, prop.equalsToken!, prop.objectAssignmentInitializer, checkMode); } - - const cacheKey = use & IterationUse.AllowsAsyncIterablesFlag ? "iterationTypesOfAsyncIterable" : "iterationTypesOfIterable"; - const cachedTypes = (type as IterableOrIteratorType)[cacheKey]; - if (cachedTypes) return cachedTypes === noIterationTypes ? undefined : cachedTypes; - - let allIterationTypes: IterationTypes[] | undefined; - for (const constituent of (type as UnionType).types) { - const iterationTypes = getIterationTypesOfIterableWorker(constituent, use, errorNode); - if (iterationTypes === noIterationTypes) { - if (errorNode) { - reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag)); - errorNode = undefined; - } + target = (exprOrAssignment).name; + } + else { + target = exprOrAssignment; + } + if (target.kind === SyntaxKind.BinaryExpression && (target).operatorToken.kind === SyntaxKind.EqualsToken) { + checkBinaryExpression((target), checkMode); + target = (target).left; + } + if (target.kind === SyntaxKind.ObjectLiteralExpression) { + return checkObjectLiteralAssignment((target), sourceType, rightIsThis); + } + if (target.kind === SyntaxKind.ArrayLiteralExpression) { + return checkArrayLiteralAssignment((target), sourceType, checkMode); + } + return checkReferenceAssignment(target, sourceType, checkMode); + } + function checkReferenceAssignment(target: Expression, sourceType: ts.Type, checkMode?: CheckMode): ts.Type { + const targetType = checkExpression(target, checkMode); + const error = target.parent.kind === SyntaxKind.SpreadAssignment ? + Diagnostics.The_target_of_an_object_rest_assignment_must_be_a_variable_or_a_property_access : + Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access; + const optionalError = target.parent.kind === SyntaxKind.SpreadAssignment ? + Diagnostics.The_target_of_an_object_rest_assignment_may_not_be_an_optional_property_access : + Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access; + if (checkReferenceExpression(target, error, optionalError)) { + checkTypeAssignableToAndOptionallyElaborate(sourceType, targetType, target, target); + } + return sourceType; + } + /** + * This is a *shallow* check: An expression is side-effect-free if the + * evaluation of the expression *itself* cannot produce side effects. + * For example, x++ / 3 is side-effect free because the / operator + * does not have side effects. + * The intent is to "smell test" an expression for correctness in positions where + * its value is discarded (e.g. the left side of the comma operator). + */ + function isSideEffectFree(node: Node): boolean { + node = skipParentheses(node); + switch (node.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.StringLiteral: + case SyntaxKind.RegularExpressionLiteral: + case SyntaxKind.TaggedTemplateExpression: + case SyntaxKind.TemplateExpression: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.UndefinedKeyword: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ClassExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.TypeOfExpression: + case SyntaxKind.NonNullExpression: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxElement: + return true; + case SyntaxKind.ConditionalExpression: + return isSideEffectFree((node as ConditionalExpression).whenTrue) && + isSideEffectFree((node as ConditionalExpression).whenFalse); + case SyntaxKind.BinaryExpression: + if (isAssignmentOperator((node as BinaryExpression).operatorToken.kind)) { + return false; } - else { - allIterationTypes = append(allIterationTypes, iterationTypes); + return isSideEffectFree((node as BinaryExpression).left) && + isSideEffectFree((node as BinaryExpression).right); + case SyntaxKind.PrefixUnaryExpression: + case SyntaxKind.PostfixUnaryExpression: + // Unary operators ~, !, +, and - have no side effects. + // The rest do. + switch ((node as PrefixUnaryExpression).operator) { + case SyntaxKind.ExclamationToken: + case SyntaxKind.PlusToken: + case SyntaxKind.MinusToken: + case SyntaxKind.TildeToken: + return true; } - } - - const iterationTypes = allIterationTypes ? combineIterationTypes(allIterationTypes) : noIterationTypes; - (type as IterableOrIteratorType)[cacheKey] = iterationTypes; - return iterationTypes === noIterationTypes ? undefined : iterationTypes; + return false; + // Some forms listed here for clarity + case SyntaxKind.VoidExpression: // Explicit opt-out + case SyntaxKind.TypeAssertionExpression: // Not SEF, but can produce useful type warnings + case SyntaxKind.AsExpression: // Not SEF, but can produce useful type warnings + default: + return false; } - - function getAsyncFromSyncIterationTypes(iterationTypes: IterationTypes, errorNode: Node | undefined) { - if (iterationTypes === noIterationTypes) return noIterationTypes; - if (iterationTypes === anyIterationTypes) return anyIterationTypes; - const { yieldType, returnType, nextType } = iterationTypes; - return createIterationTypes( - getAwaitedType(yieldType, errorNode) || anyType, - getAwaitedType(returnType, errorNode) || anyType, - nextType); + } + function isTypeEqualityComparableTo(source: ts.Type, target: ts.Type) { + return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target); + } + function checkBinaryExpression(node: BinaryExpression, checkMode?: CheckMode) { + if (isInJSFile(node) && getAssignedExpandoInitializer(node)) { + return checkExpression(node.right, checkMode); } - - /** - * Gets the *yield*, *return*, and *next* types from a non-union type. - * - * If we are unable to find the *yield*, *return*, and *next* types, `noIterationTypes` is - * returned to indicate to the caller that it should report an error. Otherwise, an - * `IterationTypes` record is returned. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterable` instead. - */ - function getIterationTypesOfIterableWorker(type: Type, use: IterationUse, errorNode: Node | undefined) { - if (isTypeAny(type)) { - return anyIterationTypes; + checkGrammarNullishCoalesceWithLogicalExpression(node); + return checkBinaryLikeExpression(node.left, node.operatorToken, node.right, checkMode, node); + } + function checkGrammarNullishCoalesceWithLogicalExpression(node: BinaryExpression) { + const { left, operatorToken, right } = node; + if (operatorToken.kind === SyntaxKind.QuestionQuestionToken) { + if (isBinaryExpression(left) && (left.operatorToken.kind === SyntaxKind.BarBarToken || left.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) { + grammarErrorOnNode(left, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(left.operatorToken.kind), tokenToString(operatorToken.kind)); } - - if (use & IterationUse.AllowsAsyncIterablesFlag) { - const iterationTypes = - getIterationTypesOfIterableCached(type, asyncIterationTypesResolver) || - getIterationTypesOfIterableFast(type, asyncIterationTypesResolver); - if (iterationTypes) { - return iterationTypes; - } + if (isBinaryExpression(right) && (right.operatorToken.kind === SyntaxKind.BarBarToken || right.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) { + grammarErrorOnNode(right, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(right.operatorToken.kind), tokenToString(operatorToken.kind)); } - - if (use & IterationUse.AllowsSyncIterablesFlag) { - const iterationTypes = - getIterationTypesOfIterableCached(type, syncIterationTypesResolver) || - getIterationTypesOfIterableFast(type, syncIterationTypesResolver); - if (iterationTypes) { - if (use & IterationUse.AllowsAsyncIterablesFlag) { - // for a sync iterable in an async context, only use the cached types if they are valid. - if (iterationTypes !== noIterationTypes) { - return (type as IterableOrIteratorType).iterationTypesOfAsyncIterable = getAsyncFromSyncIterationTypes(iterationTypes, errorNode); + } + } + function checkBinaryLikeExpression(left: Expression, operatorToken: Node, right: Expression, checkMode?: CheckMode, errorNode?: Node): ts.Type { + const operator = operatorToken.kind; + if (operator === SyntaxKind.EqualsToken && (left.kind === SyntaxKind.ObjectLiteralExpression || left.kind === SyntaxKind.ArrayLiteralExpression)) { + return checkDestructuringAssignment(left, checkExpression(right, checkMode), checkMode, right.kind === SyntaxKind.ThisKeyword); + } + let leftType: ts.Type; + if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) { + leftType = checkTruthinessExpression(left, checkMode); + } + else { + leftType = checkExpression(left, checkMode); + } + let rightType = checkExpression(right, checkMode); + switch (operator) { + case SyntaxKind.AsteriskToken: + case SyntaxKind.AsteriskAsteriskToken: + case SyntaxKind.AsteriskEqualsToken: + case SyntaxKind.AsteriskAsteriskEqualsToken: + case SyntaxKind.SlashToken: + case SyntaxKind.SlashEqualsToken: + case SyntaxKind.PercentToken: + case SyntaxKind.PercentEqualsToken: + case SyntaxKind.MinusToken: + case SyntaxKind.MinusEqualsToken: + case SyntaxKind.LessThanLessThanToken: + case SyntaxKind.LessThanLessThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanToken: + case SyntaxKind.GreaterThanGreaterThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: + case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: + case SyntaxKind.BarToken: + case SyntaxKind.BarEqualsToken: + case SyntaxKind.CaretToken: + case SyntaxKind.CaretEqualsToken: + case SyntaxKind.AmpersandToken: + case SyntaxKind.AmpersandEqualsToken: + if (leftType === silentNeverType || rightType === silentNeverType) { + return silentNeverType; + } + leftType = checkNonNullType(leftType, left); + rightType = checkNonNullType(rightType, right); + let suggestedOperator: SyntaxKind | undefined; + // if a user tries to apply a bitwise operator to 2 boolean operands + // try and return them a helpful suggestion + if ((leftType.flags & TypeFlags.BooleanLike) && + (rightType.flags & TypeFlags.BooleanLike) && + (suggestedOperator = getSuggestedBooleanOperator(operatorToken.kind)) !== undefined) { + error(errorNode || operatorToken, Diagnostics.The_0_operator_is_not_allowed_for_boolean_types_Consider_using_1_instead, tokenToString(operatorToken.kind), tokenToString(suggestedOperator)); + return numberType; + } + else { + // otherwise just check each operand separately and report errors as normal + const leftOk = checkArithmeticOperandType(left, leftType, Diagnostics.The_left_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type, /*isAwaitValid*/ true); + const rightOk = checkArithmeticOperandType(right, rightType, Diagnostics.The_right_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type, /*isAwaitValid*/ true); + let resultType: ts.Type; + // If both are any or unknown, allow operation; assume it will resolve to number + if ((isTypeAssignableToKind(leftType, TypeFlags.AnyOrUnknown) && isTypeAssignableToKind(rightType, TypeFlags.AnyOrUnknown)) || + // Or, if neither could be bigint, implicit coercion results in a number result + !(maybeTypeOfKind(leftType, TypeFlags.BigIntLike) || maybeTypeOfKind(rightType, TypeFlags.BigIntLike))) { + resultType = numberType; + } + // At least one is assignable to bigint, so check that both are + else if (bothAreBigIntLike(leftType, rightType)) { + switch (operator) { + case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: + case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: + reportOperatorError(); } + resultType = bigintType; } + // Exactly one of leftType/rightType is assignable to bigint else { - return iterationTypes; + reportOperatorError(bothAreBigIntLike); + resultType = errorType; } + if (leftOk && rightOk) { + checkAssignmentOperator(resultType); + } + return resultType; } - } - - if (use & IterationUse.AllowsAsyncIterablesFlag) { - const iterationTypes = getIterationTypesOfIterableSlow(type, asyncIterationTypesResolver, errorNode); - if (iterationTypes !== noIterationTypes) { - return iterationTypes; + case SyntaxKind.PlusToken: + case SyntaxKind.PlusEqualsToken: + if (leftType === silentNeverType || rightType === silentNeverType) { + return silentNeverType; } - } - - if (use & IterationUse.AllowsSyncIterablesFlag) { - const iterationTypes = getIterationTypesOfIterableSlow(type, syncIterationTypesResolver, errorNode); - if (iterationTypes !== noIterationTypes) { - if (use & IterationUse.AllowsAsyncIterablesFlag) { - return (type as IterableOrIteratorType).iterationTypesOfAsyncIterable = iterationTypes - ? getAsyncFromSyncIterationTypes(iterationTypes, errorNode) - : noIterationTypes; + if (!isTypeAssignableToKind(leftType, TypeFlags.StringLike) && !isTypeAssignableToKind(rightType, TypeFlags.StringLike)) { + leftType = checkNonNullType(leftType, left); + rightType = checkNonNullType(rightType, right); + } + let resultType: ts.Type | undefined; + if (isTypeAssignableToKind(leftType, TypeFlags.NumberLike, /*strict*/ true) && isTypeAssignableToKind(rightType, TypeFlags.NumberLike, /*strict*/ true)) { + // Operands of an enum type are treated as having the primitive type Number. + // If both operands are of the Number primitive type, the result is of the Number primitive type. + resultType = numberType; + } + else if (isTypeAssignableToKind(leftType, TypeFlags.BigIntLike, /*strict*/ true) && isTypeAssignableToKind(rightType, TypeFlags.BigIntLike, /*strict*/ true)) { + // If both operands are of the BigInt primitive type, the result is of the BigInt primitive type. + resultType = bigintType; + } + else if (isTypeAssignableToKind(leftType, TypeFlags.StringLike, /*strict*/ true) || isTypeAssignableToKind(rightType, TypeFlags.StringLike, /*strict*/ true)) { + // If one or both operands are of the String primitive type, the result is of the String primitive type. + resultType = stringType; + } + else if (isTypeAny(leftType) || isTypeAny(rightType)) { + // Otherwise, the result is of type Any. + // NOTE: unknown type here denotes error type. Old compiler treated this case as any type so do we. + resultType = leftType === errorType || rightType === errorType ? errorType : anyType; + } + // Symbols are not allowed at all in arithmetic expressions + if (resultType && !checkForDisallowedESSymbolOperand(operator)) { + return resultType; + } + if (!resultType) { + // Types that have a reasonably good chance of being a valid operand type. + // If both types have an awaited type of one of these, we'll assume the user + // might be missing an await without doing an exhaustive check that inserting + // await(s) will actually be a completely valid binary expression. + const closeEnoughKind = TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.AnyOrUnknown; + reportOperatorError((left, right) => isTypeAssignableToKind(left, closeEnoughKind) && + isTypeAssignableToKind(right, closeEnoughKind)); + return anyType; + } + if (operator === SyntaxKind.PlusEqualsToken) { + checkAssignmentOperator(resultType); + } + return resultType; + case SyntaxKind.LessThanToken: + case SyntaxKind.GreaterThanToken: + case SyntaxKind.LessThanEqualsToken: + case SyntaxKind.GreaterThanEqualsToken: + if (checkForDisallowedESSymbolOperand(operator)) { + leftType = getBaseTypeOfLiteralType(checkNonNullType(leftType, left)); + rightType = getBaseTypeOfLiteralType(checkNonNullType(rightType, right)); + reportOperatorErrorUnless((left, right) => isTypeComparableTo(left, right) || isTypeComparableTo(right, left) || (isTypeAssignableTo(left, numberOrBigIntType) && isTypeAssignableTo(right, numberOrBigIntType))); + } + return booleanType; + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left)); + return booleanType; + case SyntaxKind.InstanceOfKeyword: + return checkInstanceOfExpression(left, right, leftType, rightType); + case SyntaxKind.InKeyword: + return checkInExpression(left, right, leftType, rightType); + case SyntaxKind.AmpersandAmpersandToken: + return getTypeFacts(leftType) & TypeFacts.Truthy ? + getUnionType([extractDefinitelyFalsyTypes(strictNullChecks ? leftType : getBaseTypeOfLiteralType(rightType)), rightType]) : + leftType; + case SyntaxKind.BarBarToken: + return getTypeFacts(leftType) & TypeFacts.Falsy ? + getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], UnionReduction.Subtype) : + leftType; + case SyntaxKind.QuestionQuestionToken: + return getTypeFacts(leftType) & TypeFacts.EQUndefinedOrNull ? + getUnionType([getNonNullableType(leftType), rightType], UnionReduction.Subtype) : + leftType; + case SyntaxKind.EqualsToken: + const declKind = isBinaryExpression(left.parent) ? getAssignmentDeclarationKind(left.parent) : AssignmentDeclarationKind.None; + checkAssignmentDeclaration(declKind, rightType); + if (isAssignmentDeclaration(declKind)) { + if (!(rightType.flags & TypeFlags.Object) || + declKind !== AssignmentDeclarationKind.ModuleExports && + declKind !== AssignmentDeclarationKind.Prototype && + !isEmptyObjectType(rightType) && + !isFunctionObjectType((rightType as ObjectType)) && + !(getObjectFlags(rightType) & ObjectFlags.Class)) { + // don't check assignability of module.exports=, C.prototype=, or expando types because they will necessarily be incomplete + checkAssignmentOperator(rightType); } - else { - return iterationTypes; + return leftType; + } + else { + checkAssignmentOperator(rightType); + return getRegularTypeOfObjectLiteral(rightType); + } + case SyntaxKind.CommaToken: + if (!compilerOptions.allowUnreachableCode && isSideEffectFree(left) && !isEvalNode(right)) { + error(left, Diagnostics.Left_side_of_comma_operator_is_unused_and_has_no_side_effects); + } + return rightType; + default: + return Debug.fail(); + } + function bothAreBigIntLike(left: ts.Type, right: ts.Type): boolean { + return isTypeAssignableToKind(left, TypeFlags.BigIntLike) && isTypeAssignableToKind(right, TypeFlags.BigIntLike); + } + function checkAssignmentDeclaration(kind: AssignmentDeclarationKind, rightType: ts.Type) { + if (kind === AssignmentDeclarationKind.ModuleExports) { + for (const prop of getPropertiesOfObjectType(rightType)) { + const propType = getTypeOfSymbol(prop); + if (propType.symbol && propType.symbol.flags & SymbolFlags.Class) { + const name = prop.escapedName; + const symbol = resolveName(prop.valueDeclaration, name, SymbolFlags.Type, undefined, name, /*isUse*/ false); + if (symbol && symbol.declarations.some(isJSDocTypedefTag)) { + grammarErrorOnNode(symbol.declarations[0], Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name)); + return grammarErrorOnNode(prop.valueDeclaration, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name)); + } } } } - - return noIterationTypes; - } - - /** - * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or - * `AsyncIterable`-like type from the cache. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterable` instead. - */ - function getIterationTypesOfIterableCached(type: Type, resolver: IterationTypesResolver) { - return (type as IterableOrIteratorType)[resolver.iterableCacheKey]; } - - function getIterationTypesOfGlobalIterableType(globalType: Type, resolver: IterationTypesResolver) { - const globalIterationTypes = - getIterationTypesOfIterableCached(globalType, resolver) || - getIterationTypesOfIterableSlow(globalType, resolver, /*errorNode*/ undefined); - return globalIterationTypes === noIterationTypes ? defaultIterationTypes : globalIterationTypes; + function isEvalNode(node: Expression) { + return node.kind === SyntaxKind.Identifier && (node as Identifier).escapedText === "eval"; } - - /** - * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or `AsyncIterable`-like - * type from from common heuristics. - * - * If we previously analyzed this type and found no iteration types, `noIterationTypes` is - * returned. If we found iteration types, an `IterationTypes` record is returned. - * Otherwise, we return `undefined` to indicate to the caller it should perform a more - * exhaustive analysis. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterable` instead. - */ - function getIterationTypesOfIterableFast(type: Type, resolver: IterationTypesResolver) { - // As an optimization, if the type is an instantiation of one of the following global types, then - // just grab its related type argument: - // - `Iterable` or `AsyncIterable` - // - `IterableIterator` or `AsyncIterableIterator` - let globalType: Type; - if (isReferenceToType(type, globalType = resolver.getGlobalIterableType(/*reportErrors*/ false)) || - isReferenceToType(type, globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false))) { - const [yieldType] = getTypeArguments(type as GenericType); - // The "return" and "next" types of `Iterable` and `IterableIterator` are defined by the - // iteration types of their `[Symbol.iterator]()` method. The same is true for their async cousins. - // While we define these as `any` and `undefined` in our libs by default, a custom lib *could* use - // different definitions. - const { returnType, nextType } = getIterationTypesOfGlobalIterableType(globalType, resolver); - return (type as IterableOrIteratorType)[resolver.iterableCacheKey] = createIterationTypes(yieldType, returnType, nextType); - } - - // As an optimization, if the type is an instantiation of the following global type, then - // just grab its related type arguments: - // - `Generator` or `AsyncGenerator` - if (isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))) { - const [yieldType, returnType, nextType] = getTypeArguments(type as GenericType); - return (type as IterableOrIteratorType)[resolver.iterableCacheKey] = createIterationTypes(yieldType, returnType, nextType); + // Return true if there was no error, false if there was an error. + function checkForDisallowedESSymbolOperand(operator: SyntaxKind): boolean { + const offendingSymbolOperand = maybeTypeOfKind(leftType, TypeFlags.ESSymbolLike) ? left : + maybeTypeOfKind(rightType, TypeFlags.ESSymbolLike) ? right : + undefined; + if (offendingSymbolOperand) { + error(offendingSymbolOperand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(operator)); + return false; } + return true; } - - /** - * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or `AsyncIterable`-like - * type from its members. - * - * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` - * record is returned. Otherwise, `noIterationTypes` is returned. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterable` instead. - */ - function getIterationTypesOfIterableSlow(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined) { - const method = getPropertyOfType(type, getPropertyNameForKnownSymbolName(resolver.iteratorSymbolName)); - const methodType = method && !(method.flags & SymbolFlags.Optional) ? getTypeOfSymbol(method) : undefined; - if (isTypeAny(methodType)) { - return (type as IterableOrIteratorType)[resolver.iterableCacheKey] = anyIterationTypes; - } - - const signatures = methodType ? getSignaturesOfType(methodType, SignatureKind.Call) : undefined; - if (!some(signatures)) { - return (type as IterableOrIteratorType)[resolver.iterableCacheKey] = noIterationTypes; + function getSuggestedBooleanOperator(operator: SyntaxKind): SyntaxKind | undefined { + switch (operator) { + case SyntaxKind.BarToken: + case SyntaxKind.BarEqualsToken: + return SyntaxKind.BarBarToken; + case SyntaxKind.CaretToken: + case SyntaxKind.CaretEqualsToken: + return SyntaxKind.ExclamationEqualsEqualsToken; + case SyntaxKind.AmpersandToken: + case SyntaxKind.AmpersandEqualsToken: + return SyntaxKind.AmpersandAmpersandToken; + default: + return undefined; } - - const iteratorType = getUnionType(map(signatures, getReturnTypeOfSignature), UnionReduction.Subtype); - const iterationTypes = getIterationTypesOfIterator(iteratorType, resolver, errorNode) || noIterationTypes; - return (type as IterableOrIteratorType)[resolver.iterableCacheKey] = iterationTypes; - } - - function reportTypeNotIterableError(errorNode: Node, type: Type, allowAsyncIterables: boolean): void { - const message = allowAsyncIterables - ? Diagnostics.Type_0_must_have_a_Symbol_asyncIterator_method_that_returns_an_async_iterator - : Diagnostics.Type_0_must_have_a_Symbol_iterator_method_that_returns_an_iterator; - errorAndMaybeSuggestAwait(errorNode, !!getAwaitedTypeOfPromise(type), message, typeToString(type)); } - - /** - * Gets the *yield*, *return*, and *next* types from an `Iterator`-like or `AsyncIterator`-like type. - * - * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` - * record is returned. Otherwise, `undefined` is returned. - */ - function getIterationTypesOfIterator(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined) { - if (isTypeAny(type)) { - return anyIterationTypes; + function checkAssignmentOperator(valueType: ts.Type): void { + if (produceDiagnostics && isAssignmentOperator(operator)) { + // TypeScript 1.0 spec (April 2014): 4.17 + // An assignment of the form + // VarExpr = ValueExpr + // requires VarExpr to be classified as a reference + // A compound assignment furthermore requires VarExpr to be classified as a reference (section 4.1) + // and the type of the non-compound operation to be assignable to the type of VarExpr. + if (checkReferenceExpression(left, Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access, Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access) + && (!isIdentifier(left) || unescapeLeadingUnderscores(left.escapedText) !== "exports")) { + // to avoid cascading errors check assignability only if 'isReference' check succeeded and no errors were reported + checkTypeAssignableToAndOptionallyElaborate(valueType, leftType, left, right); + } } - - const iterationTypes = - getIterationTypesOfIteratorCached(type, resolver) || - getIterationTypesOfIteratorFast(type, resolver) || - getIterationTypesOfIteratorSlow(type, resolver, errorNode); - return iterationTypes === noIterationTypes ? undefined : iterationTypes; } - - /** - * Gets the iteration types of an `Iterator`-like or `AsyncIterator`-like type from the - * cache. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterator` instead. - */ - function getIterationTypesOfIteratorCached(type: Type, resolver: IterationTypesResolver) { - return (type as IterableOrIteratorType)[resolver.iteratorCacheKey]; + function isAssignmentDeclaration(kind: AssignmentDeclarationKind) { + switch (kind) { + case AssignmentDeclarationKind.ModuleExports: + return true; + case AssignmentDeclarationKind.ExportsProperty: + case AssignmentDeclarationKind.Property: + case AssignmentDeclarationKind.Prototype: + case AssignmentDeclarationKind.PrototypeProperty: + case AssignmentDeclarationKind.ThisProperty: + const symbol = getSymbolOfNode(left); + const init = getAssignedExpandoInitializer(right); + return init && isObjectLiteralExpression(init) && + symbol && hasEntries(symbol.exports); + default: + return false; + } } - /** - * Gets the iteration types of an `Iterator`-like or `AsyncIterator`-like type from the - * cache or from common heuristics. - * - * If we previously analyzed this type and found no iteration types, `noIterationTypes` is - * returned. If we found iteration types, an `IterationTypes` record is returned. - * Otherwise, we return `undefined` to indicate to the caller it should perform a more - * exhaustive analysis. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterator` instead. + * Returns true if an error is reported */ - function getIterationTypesOfIteratorFast(type: Type, resolver: IterationTypesResolver) { - // As an optimization, if the type is an instantiation of one of the following global types, - // then just grab its related type argument: - // - `IterableIterator` or `AsyncIterableIterator` - // - `Iterator` or `AsyncIterator` - // - `Generator` or `AsyncGenerator` - const globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false); - if (isReferenceToType(type, globalType)) { - const [yieldType] = getTypeArguments(type as GenericType); - // The "return" and "next" types of `IterableIterator` and `AsyncIterableIterator` are defined by the - // iteration types of their `next`, `return`, and `throw` methods. While we define these as `any` - // and `undefined` in our libs by default, a custom lib *could* use different definitions. - const globalIterationTypes = - getIterationTypesOfIteratorCached(globalType, resolver) || - getIterationTypesOfIteratorSlow(globalType, resolver, /*errorNode*/ undefined); - const { returnType, nextType } = globalIterationTypes === noIterationTypes ? defaultIterationTypes : globalIterationTypes; - return (type as IterableOrIteratorType)[resolver.iteratorCacheKey] = createIterationTypes(yieldType, returnType, nextType); - } - if (isReferenceToType(type, resolver.getGlobalIteratorType(/*reportErrors*/ false)) || - isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))) { - const [yieldType, returnType, nextType] = getTypeArguments(type as GenericType); - return (type as IterableOrIteratorType)[resolver.iteratorCacheKey] = createIterationTypes(yieldType, returnType, nextType); + function reportOperatorErrorUnless(typesAreCompatible: (left: ts.Type, right: ts.Type) => boolean): boolean { + if (!typesAreCompatible(leftType, rightType)) { + reportOperatorError(typesAreCompatible); + return true; } + return false; } - - function isIteratorResult(type: Type, kind: IterationTypeKind.Yield | IterationTypeKind.Return) { - // From https://tc39.github.io/ecma262/#sec-iteratorresult-interface: - // > [done] is the result status of an iterator `next` method call. If the end of the iterator was reached `done` is `true`. - // > If the end was not reached `done` is `false` and a value is available. - // > If a `done` property (either own or inherited) does not exist, it is consider to have the value `false`. - const doneType = getTypeOfPropertyOfType(type, "done" as __String) || falseType; - return isTypeAssignableTo(kind === IterationTypeKind.Yield ? falseType : trueType, doneType); - } - - function isYieldIteratorResult(type: Type) { - return isIteratorResult(type, IterationTypeKind.Yield); - } - - function isReturnIteratorResult(type: Type) { - return isIteratorResult(type, IterationTypeKind.Return); + function reportOperatorError(isRelated?: (left: ts.Type, right: ts.Type) => boolean) { + let wouldWorkWithAwait = false; + const errNode = errorNode || operatorToken; + if (isRelated) { + const awaitedLeftType = getAwaitedType(leftType); + const awaitedRightType = getAwaitedType(rightType); + wouldWorkWithAwait = !(awaitedLeftType === leftType && awaitedRightType === rightType) + && !!(awaitedLeftType && awaitedRightType) + && isRelated(awaitedLeftType, awaitedRightType); + } + let effectiveLeft = leftType; + let effectiveRight = rightType; + if (!wouldWorkWithAwait && isRelated) { + [effectiveLeft, effectiveRight] = getBaseTypesIfUnrelated(leftType, rightType, isRelated); + } + const [leftStr, rightStr] = getTypeNamesForErrorDisplay(effectiveLeft, effectiveRight); + if (!tryGiveBetterPrimaryError(errNode, wouldWorkWithAwait, leftStr, rightStr)) { + errorAndMaybeSuggestAwait(errNode, wouldWorkWithAwait, Diagnostics.Operator_0_cannot_be_applied_to_types_1_and_2, tokenToString(operatorToken.kind), leftStr, rightStr); + } } - - /** - * Gets the *yield* and *return* types of an `IteratorResult`-like type. - * - * If we are unable to determine a *yield* or a *return* type, `noIterationTypes` is - * returned to indicate to the caller that it should handle the error. Otherwise, an - * `IterationTypes` record is returned. - */ - function getIterationTypesOfIteratorResult(type: Type) { - if (isTypeAny(type)) { - return anyIterationTypes; + function tryGiveBetterPrimaryError(errNode: Node, maybeMissingAwait: boolean, leftStr: string, rightStr: string) { + let typeName: string | undefined; + switch (operatorToken.kind) { + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.EqualsEqualsToken: + typeName = "false"; + break; + case SyntaxKind.ExclamationEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + typeName = "true"; } - - const cachedTypes = (type as IterableOrIteratorType).iterationTypesOfIteratorResult; - if (cachedTypes) { - return cachedTypes; + if (typeName) { + return errorAndMaybeSuggestAwait(errNode, maybeMissingAwait, Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap, typeName, leftStr, rightStr); } - - // As an optimization, if the type is an instantiation of one of the global `IteratorYieldResult` - // or `IteratorReturnResult` types, then just grab its type argument. - if (isReferenceToType(type, getGlobalIteratorYieldResultType(/*reportErrors*/ false))) { - const yieldType = getTypeArguments(type as GenericType)[0]; - return (type as IterableOrIteratorType).iterationTypesOfIteratorResult = createIterationTypes(yieldType, /*returnType*/ undefined, /*nextType*/ undefined); + return undefined; + } + } + function getBaseTypesIfUnrelated(leftType: ts.Type, rightType: ts.Type, isRelated: (left: ts.Type, right: ts.Type) => boolean): [ts.Type, ts.Type] { + let effectiveLeft = leftType; + let effectiveRight = rightType; + const leftBase = getBaseTypeOfLiteralType(leftType); + const rightBase = getBaseTypeOfLiteralType(rightType); + if (!isRelated(leftBase, rightBase)) { + effectiveLeft = leftBase; + effectiveRight = rightBase; + } + return [effectiveLeft, effectiveRight]; + } + function checkYieldExpression(node: YieldExpression): ts.Type { + // Grammar checking + if (produceDiagnostics) { + if (!(node.flags & NodeFlags.YieldContext)) { + grammarErrorOnFirstToken(node, Diagnostics.A_yield_expression_is_only_allowed_in_a_generator_body); } - if (isReferenceToType(type, getGlobalIteratorReturnResultType(/*reportErrors*/ false))) { - const returnType = getTypeArguments(type as GenericType)[0]; - return (type as IterableOrIteratorType).iterationTypesOfIteratorResult = createIterationTypes(/*yieldType*/ undefined, returnType, /*nextType*/ undefined); + if (isInParameterInitializerBeforeContainingFunction(node)) { + error(node, Diagnostics.yield_expressions_cannot_be_used_in_a_parameter_initializer); } - - // Choose any constituents that can produce the requested iteration type. - const yieldIteratorResult = filterType(type, isYieldIteratorResult); - const yieldType = yieldIteratorResult !== neverType ? getTypeOfPropertyOfType(yieldIteratorResult, "value" as __String) : undefined; - - const returnIteratorResult = filterType(type, isReturnIteratorResult); - const returnType = returnIteratorResult !== neverType ? getTypeOfPropertyOfType(returnIteratorResult, "value" as __String) : undefined; - - if (!yieldType && !returnType) { - return (type as IterableOrIteratorType).iterationTypesOfIteratorResult = noIterationTypes; + } + const func = getContainingFunction(node); + if (!func) + return anyType; + const functionFlags = getFunctionFlags(func); + if (!(functionFlags & FunctionFlags.Generator)) { + // If the user's code is syntactically correct, the func should always have a star. After all, we are in a yield context. + return anyType; + } + const isAsync = (functionFlags & FunctionFlags.Async) !== 0; + if (node.asteriskToken) { + // Async generator functions prior to ESNext require the __await, __asyncDelegator, + // and __asyncValues helpers + if (isAsync && languageVersion < ScriptTarget.ESNext) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncDelegatorIncludes); + } + // Generator functions prior to ES2015 require the __values helper + if (!isAsync && languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Values); + } + } + // There is no point in doing an assignability check if the function + // has no explicit return type because the return type is directly computed + // from the yield expressions. + const returnType = getReturnTypeFromAnnotation(func); + const iterationTypes = returnType && getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsync); + const signatureYieldType = iterationTypes && iterationTypes.yieldType || anyType; + const signatureNextType = iterationTypes && iterationTypes.nextType || anyType; + const resolvedSignatureNextType = isAsync ? getAwaitedType(signatureNextType) || anyType : signatureNextType; + const yieldExpressionType = node.expression ? checkExpression(node.expression) : undefinedWideningType; + const yieldedType = getYieldedTypeOfYieldExpression(node, yieldExpressionType, resolvedSignatureNextType, isAsync); + if (returnType && yieldedType) { + checkTypeAssignableToAndOptionallyElaborate(yieldedType, signatureYieldType, node.expression || node, node.expression); + } + if (node.asteriskToken) { + const use = isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar; + return getIterationTypeOfIterable(use, IterationTypeKind.Return, yieldExpressionType, node.expression) + || anyType; + } + else if (returnType) { + return getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, isAsync) + || anyType; + } + return getContextualIterationType(IterationTypeKind.Next, func) || anyType; + } + function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): ts.Type { + checkTruthinessExpression(node.condition); + const type1 = checkExpression(node.whenTrue, checkMode); + const type2 = checkExpression(node.whenFalse, checkMode); + return getUnionType([type1, type2], UnionReduction.Subtype); + } + function checkTemplateExpression(node: TemplateExpression): ts.Type { + // We just want to check each expressions, but we are unconcerned with + // the type of each expression, as any value may be coerced into a string. + // It is worth asking whether this is what we really want though. + // A place where we actually *are* concerned with the expressions' types are + // in tagged templates. + forEach(node.templateSpans, templateSpan => { + if (maybeTypeOfKind(checkExpression(templateSpan.expression), TypeFlags.ESSymbolLike)) { + error(templateSpan.expression, Diagnostics.Implicit_conversion_of_a_symbol_to_a_string_will_fail_at_runtime_Consider_wrapping_this_expression_in_String); } - - // From https://tc39.github.io/ecma262/#sec-iteratorresult-interface - // > ... If the iterator does not have a return value, `value` is `undefined`. In that case, the - // > `value` property may be absent from the conforming object if it does not inherit an explicit - // > `value` property. - return (type as IterableOrIteratorType).iterationTypesOfIteratorResult = createIterationTypes(yieldType, returnType || voidType, /*nextType*/ undefined); + }); + return stringType; + } + function getContextNode(node: Expression): Node { + if (node.kind === SyntaxKind.JsxAttributes && !isJsxSelfClosingElement(node.parent)) { + return node.parent.parent; // Needs to be the root JsxElement, so it encompasses the attributes _and_ the children (which are essentially part of the attributes) } - - /** - * Gets the *yield*, *return*, and *next* types of a the `next()`, `return()`, or - * `throw()` method of an `Iterator`-like or `AsyncIterator`-like type. - * - * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` - * record is returned. Otherwise, we return `undefined`. - */ - function getIterationTypesOfMethod(type: Type, resolver: IterationTypesResolver, methodName: "next" | "return" | "throw", errorNode: Node | undefined): IterationTypes | undefined { - const method = getPropertyOfType(type, methodName as __String); - - // Ignore 'return' or 'throw' if they are missing. - if (!method && methodName !== "next") { - return undefined; + return node; + } + function checkExpressionWithContextualType(node: Expression, contextualType: ts.Type, inferenceContext: InferenceContext | undefined, checkMode: CheckMode): ts.Type { + const context = getContextNode(node); + const saveContextualType = context.contextualType; + const saveInferenceContext = context.inferenceContext; + context.contextualType = contextualType; + context.inferenceContext = inferenceContext; + const type = checkExpression(node, checkMode | CheckMode.Contextual | (inferenceContext ? CheckMode.Inferential : 0)); + // We strip literal freshness when an appropriate contextual type is present such that contextually typed + // literals always preserve their literal types (otherwise they might widen during type inference). An alternative + // here would be to not mark contextually typed literals as fresh in the first place. + const result = maybeTypeOfKind(type, TypeFlags.Literal) && isLiteralOfContextualType(type, instantiateContextualType(contextualType, node)) ? + getRegularTypeOfLiteralType(type) : type; + context.contextualType = saveContextualType; + context.inferenceContext = saveInferenceContext; + return result; + } + function checkExpressionCached(node: Expression | QualifiedName, checkMode?: CheckMode): ts.Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + if (checkMode && checkMode !== CheckMode.Normal) { + return checkExpression(node, checkMode); + } + // When computing a type that we're going to cache, we need to ignore any ongoing control flow + // analysis because variables may have transient types in indeterminable states. Moving flowLoopStart + // to the top of the stack ensures all transient types are computed from a known point. + const saveFlowLoopStart = flowLoopStart; + const saveFlowTypeCache = flowTypeCache; + flowLoopStart = flowLoopCount; + flowTypeCache = undefined; + links.resolvedType = checkExpression(node, checkMode); + flowTypeCache = saveFlowTypeCache; + flowLoopStart = saveFlowLoopStart; + } + return links.resolvedType; + } + function isTypeAssertion(node: Expression) { + node = skipParentheses(node); + return node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression; + } + function checkDeclarationInitializer(declaration: HasExpressionInitializer) { + const initializer = (getEffectiveInitializer(declaration)!); + const type = getQuickTypeOfExpression(initializer) || checkExpressionCached(initializer); + const padded = isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern && + isTupleType(type) && !type.target.hasRestElement && getTypeReferenceArity(type) < declaration.name.elements.length ? + padTupleType(type, declaration.name) : type; + const widened = getCombinedNodeFlags(declaration) & NodeFlags.Const || + isDeclarationReadonly(declaration) || + isTypeAssertion(initializer) || + isLiteralOfContextualType(padded, getContextualType(initializer)) ? padded : getWidenedLiteralType(padded); + if (isInJSFile(declaration)) { + if (widened.flags & TypeFlags.Nullable) { + reportImplicitAny(declaration, anyType); + return anyType; } - - const methodType = method && !(methodName === "next" && (method.flags & SymbolFlags.Optional)) - ? methodName === "next" ? getTypeOfSymbol(method) : getTypeWithFacts(getTypeOfSymbol(method), TypeFacts.NEUndefinedOrNull) - : undefined; - - if (isTypeAny(methodType)) { - // `return()` and `throw()` don't provide a *next* type. - return methodName === "next" ? anyIterationTypes : anyIterationTypesExceptNext; + else if (isEmptyArrayLiteralType(widened)) { + reportImplicitAny(declaration, anyArrayType); + return anyArrayType; } - - // Both async and non-async iterators *must* have a `next` method. - const methodSignatures = methodType ? getSignaturesOfType(methodType, SignatureKind.Call) : emptyArray; - if (methodSignatures.length === 0) { - if (errorNode) { - const diagnostic = methodName === "next" - ? resolver.mustHaveANextMethodDiagnostic - : resolver.mustBeAMethodDiagnostic; - error(errorNode, diagnostic, methodName); + } + return widened; + } + function padTupleType(type: TupleTypeReference, pattern: ArrayBindingPattern) { + const patternElements = pattern.elements; + const arity = getTypeReferenceArity(type); + const elementTypes = arity ? getTypeArguments(type).slice() : []; + for (let i = arity; i < patternElements.length; i++) { + const e = patternElements[i]; + if (i < patternElements.length - 1 || !(e.kind === SyntaxKind.BindingElement && e.dotDotDotToken)) { + elementTypes.push(!isOmittedExpression(e) && hasDefaultValue(e) ? getTypeFromBindingElement(e, /*includePatternInType*/ false, /*reportErrors*/ false) : anyType); + if (!isOmittedExpression(e) && !hasDefaultValue(e)) { + reportImplicitAny(e, anyType); } - return methodName === "next" ? anyIterationTypes : undefined; } - - // Extract the first parameter and return type of each signature. - let methodParameterTypes: Type[] | undefined; - let methodReturnTypes: Type[] | undefined; - for (const signature of methodSignatures) { - if (methodName !== "throw" && some(signature.parameters)) { - methodParameterTypes = append(methodParameterTypes, getTypeAtPosition(signature, 0)); + } + return createTupleType(elementTypes, type.target.minLength, /*hasRestElement*/ false, type.target.readonly); + } + function isLiteralOfContextualType(candidateType: ts.Type, contextualType: ts.Type | undefined): boolean { + if (contextualType) { + if (contextualType.flags & TypeFlags.UnionOrIntersection) { + const types = (contextualType).types; + return some(types, t => isLiteralOfContextualType(candidateType, t)); + } + if (contextualType.flags & TypeFlags.InstantiableNonPrimitive) { + // If the contextual type is a type variable constrained to a primitive type, consider + // this a literal context for literals of that primitive type. For example, given a + // type parameter 'T extends string', infer string literal types for T. + const constraint = getBaseConstraintOfType(contextualType) || unknownType; + return maybeTypeOfKind(constraint, TypeFlags.String) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) || + maybeTypeOfKind(constraint, TypeFlags.Number) && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) || + maybeTypeOfKind(constraint, TypeFlags.BigInt) && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) || + maybeTypeOfKind(constraint, TypeFlags.ESSymbol) && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol) || + isLiteralOfContextualType(candidateType, constraint); + } + // If the contextual type is a literal of a particular primitive type, we consider this a + // literal context for all literals of that primitive type. + return !!(contextualType.flags & (TypeFlags.StringLiteral | TypeFlags.Index) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) || + contextualType.flags & TypeFlags.NumberLiteral && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) || + contextualType.flags & TypeFlags.BigIntLiteral && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) || + contextualType.flags & TypeFlags.BooleanLiteral && maybeTypeOfKind(candidateType, TypeFlags.BooleanLiteral) || + contextualType.flags & TypeFlags.UniqueESSymbol && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol)); + } + return false; + } + function isConstContext(node: Expression): boolean { + const parent = node.parent; + return isAssertionExpression(parent) && isConstTypeReference(parent.type) || + (isParenthesizedExpression(parent) || isArrayLiteralExpression(parent) || isSpreadElement(parent)) && isConstContext(parent) || + (isPropertyAssignment(parent) || isShorthandPropertyAssignment(parent)) && isConstContext(parent.parent); + } + function checkExpressionForMutableLocation(node: Expression, checkMode: CheckMode | undefined, contextualType?: ts.Type, forceTuple?: boolean): ts.Type { + const type = checkExpression(node, checkMode, forceTuple); + return isConstContext(node) ? getRegularTypeOfLiteralType(type) : + isTypeAssertion(node) ? type : + getWidenedLiteralLikeTypeForContextualType(type, instantiateContextualType(arguments.length === 2 ? getContextualType(node) : contextualType, node)); + } + function checkPropertyAssignment(node: PropertyAssignment, checkMode?: CheckMode): ts.Type { + // Do not use hasDynamicName here, because that returns false for well known symbols. + // We want to perform checkComputedPropertyName for all computed properties, including + // well known symbols. + if (node.name.kind === SyntaxKind.ComputedPropertyName) { + checkComputedPropertyName(node.name); + } + return checkExpressionForMutableLocation(node.initializer, checkMode); + } + function checkObjectLiteralMethod(node: MethodDeclaration, checkMode?: CheckMode): ts.Type { + // Grammar checking + checkGrammarMethod(node); + // Do not use hasDynamicName here, because that returns false for well known symbols. + // We want to perform checkComputedPropertyName for all computed properties, including + // well known symbols. + if (node.name.kind === SyntaxKind.ComputedPropertyName) { + checkComputedPropertyName(node.name); + } + const uninstantiatedType = checkFunctionExpressionOrObjectLiteralMethod(node, checkMode); + return instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode); + } + function instantiateTypeWithSingleGenericCallSignature(node: Expression | MethodDeclaration | QualifiedName, type: ts.Type, checkMode?: CheckMode) { + if (checkMode && checkMode & (CheckMode.Inferential | CheckMode.SkipGenericFunctions)) { + const callSignature = getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ true); + const constructSignature = getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ true); + const signature = callSignature || constructSignature; + if (signature && signature.typeParameters) { + const contextualType = getApparentTypeOfContextualType((node), ContextFlags.NoConstraints); + if (contextualType) { + const contextualSignature = getSingleSignature(getNonNullableType(contextualType), callSignature ? SignatureKind.Call : SignatureKind.Construct, /*allowMembers*/ false); + if (contextualSignature && !contextualSignature.typeParameters) { + if (checkMode & CheckMode.SkipGenericFunctions) { + skippedGenericFunction(node, checkMode); + return anyFunctionType; + } + const context = getInferenceContext(node)!; + // We have an expression that is an argument of a generic function for which we are performing + // type argument inference. The expression is of a function type with a single generic call + // signature and a contextual function type with a single non-generic call signature. Now check + // if the outer function returns a function type with a single non-generic call signature and + // if some of the outer function type parameters have no inferences so far. If so, we can + // potentially add inferred type parameters to the outer function return type. + const returnType = context.signature && getReturnTypeOfSignature(context.signature); + const returnSignature = returnType && getSingleCallOrConstructSignature(returnType); + if (returnSignature && !returnSignature.typeParameters && !every(context.inferences, hasInferenceCandidates)) { + // Instantiate the signature with its own type parameters as type arguments, possibly + // renaming the type parameters to ensure they have unique names. + const uniqueTypeParameters = getUniqueTypeParameters(context, signature.typeParameters); + const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, uniqueTypeParameters); + // Infer from the parameters of the instantiated signature to the parameters of the + // contextual signature starting with an empty set of inference candidates. + const inferences = map(context.inferences, info => createInferenceInfo(info.typeParameter)); + applyToParameterTypes(instantiatedSignature, contextualSignature, (source, target) => { + inferTypes(inferences, source, target, /*priority*/ 0, /*contravariant*/ true); + }); + if (some(inferences, hasInferenceCandidates)) { + // We have inference candidates, indicating that one or more type parameters are referenced + // in the parameter types of the contextual signature. Now also infer from the return type. + applyToReturnTypes(instantiatedSignature, contextualSignature, (source, target) => { + inferTypes(inferences, source, target); + }); + // If the type parameters for which we produced candidates do not have any inferences yet, + // we adopt the new inference candidates and add the type parameters of the expression type + // to the set of inferred type parameters for the outer function return type. + if (!hasOverlappingInferences(context.inferences, inferences)) { + mergeInferences(context.inferences, inferences); + context.inferredTypeParameters = concatenate(context.inferredTypeParameters, uniqueTypeParameters); + return getOrCreateTypeFromSignature(instantiatedSignature); + } + } + } + return getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, context)); + } } - methodReturnTypes = append(methodReturnTypes, getReturnTypeOfSignature(signature)); } - - // Resolve the *next* or *return* type from the first parameter of a `next()` or - // `return()` method, respectively. - let returnTypes: Type[] | undefined; - let nextType: Type | undefined; - if (methodName !== "throw") { - const methodParameterType = methodParameterTypes ? getUnionType(methodParameterTypes) : unknownType; - if (methodName === "next") { - // The value of `next(value)` is *not* awaited by async generators - nextType = methodParameterType; - } - else if (methodName === "return") { - // The value of `return(value)` *is* awaited by async generators - const resolvedMethodParameterType = resolver.resolveIterationType(methodParameterType, errorNode) || anyType; - returnTypes = append(returnTypes, resolvedMethodParameterType); - } + } + return type; + } + function skippedGenericFunction(node: Node, checkMode: CheckMode) { + if (checkMode & CheckMode.Inferential) { + // We have skipped a generic function during inferential typing. Obtain the inference context and + // indicate this has occurred such that we know a second pass of inference is be needed. + const context = getInferenceContext(node)!; + context.flags |= InferenceFlags.SkippedGenericFunction; + } + } + function hasInferenceCandidates(info: InferenceInfo) { + return !!(info.candidates || info.contraCandidates); + } + function hasOverlappingInferences(a: InferenceInfo[], b: InferenceInfo[]) { + for (let i = 0; i < a.length; i++) { + if (hasInferenceCandidates(a[i]) && hasInferenceCandidates(b[i])) { + return true; } - - // Resolve the *yield* and *return* types from the return type of the method (i.e. `IteratorResult`) - let yieldType: Type; - const methodReturnType = methodReturnTypes ? getUnionType(methodReturnTypes, UnionReduction.Subtype) : neverType; - const resolvedMethodReturnType = resolver.resolveIterationType(methodReturnType, errorNode) || anyType; - const iterationTypes = getIterationTypesOfIteratorResult(resolvedMethodReturnType); - if (iterationTypes === noIterationTypes) { - if (errorNode) { - error(errorNode, resolver.mustHaveAValueDiagnostic, methodName); - } - yieldType = anyType; - returnTypes = append(returnTypes, anyType); + } + return false; + } + function mergeInferences(target: InferenceInfo[], source: InferenceInfo[]) { + for (let i = 0; i < target.length; i++) { + if (!hasInferenceCandidates(target[i]) && hasInferenceCandidates(source[i])) { + target[i] = source[i]; + } + } + } + function getUniqueTypeParameters(context: InferenceContext, typeParameters: readonly TypeParameter[]): readonly TypeParameter[] { + const result: TypeParameter[] = []; + let oldTypeParameters: TypeParameter[] | undefined; + let newTypeParameters: TypeParameter[] | undefined; + for (const tp of typeParameters) { + const name = tp.symbol.escapedName; + if (hasTypeParameterByName(context.inferredTypeParameters, name) || hasTypeParameterByName(result, name)) { + const newName = getUniqueTypeParameterName(concatenate(context.inferredTypeParameters, result), name); + const symbol = createSymbol(SymbolFlags.TypeParameter, newName); + const newTypeParameter = createTypeParameter(symbol); + newTypeParameter.target = tp; + oldTypeParameters = append(oldTypeParameters, tp); + newTypeParameters = append(newTypeParameters, newTypeParameter); + result.push(newTypeParameter); } else { - yieldType = iterationTypes.yieldType; - returnTypes = append(returnTypes, iterationTypes.returnType); + result.push(tp); } - - return createIterationTypes(yieldType, getUnionType(returnTypes), nextType); } - - /** - * Gets the *yield*, *return*, and *next* types of an `Iterator`-like or `AsyncIterator`-like - * type from its members. - * - * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` - * record is returned. Otherwise, `noIterationTypes` is returned. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterator` instead. - */ - function getIterationTypesOfIteratorSlow(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined) { - const iterationTypes = combineIterationTypes([ - getIterationTypesOfMethod(type, resolver, "next", errorNode), - getIterationTypesOfMethod(type, resolver, "return", errorNode), - getIterationTypesOfMethod(type, resolver, "throw", errorNode), - ]); - return (type as IterableOrIteratorType)[resolver.iteratorCacheKey] = iterationTypes; + if (newTypeParameters) { + const mapper = createTypeMapper(oldTypeParameters!, newTypeParameters); + for (const tp of newTypeParameters) { + tp.mapper = mapper; + } } - - /** - * Gets the requested "iteration type" from a type that is either `Iterable`-like, `Iterator`-like, - * `IterableIterator`-like, or `Generator`-like (for a non-async generator); or `AsyncIterable`-like, - * `AsyncIterator`-like, `AsyncIterableIterator`-like, or `AsyncGenerator`-like (for an async generator). - */ - function getIterationTypeOfGeneratorFunctionReturnType(kind: IterationTypeKind, returnType: Type, isAsyncGenerator: boolean): Type | undefined { - if (isTypeAny(returnType)) { - return undefined; + return result; + } + function hasTypeParameterByName(typeParameters: readonly TypeParameter[] | undefined, name: __String) { + return some(typeParameters, tp => tp.symbol.escapedName === name); + } + function getUniqueTypeParameterName(typeParameters: readonly TypeParameter[], baseName: __String) { + let len = (baseName).length; + while (len > 1 && (baseName).charCodeAt(len - 1) >= CharacterCodes._0 && (baseName).charCodeAt(len - 1) <= CharacterCodes._9) + len--; + const s = (baseName).slice(0, len); + for (let index = 1; true; index++) { + const augmentedName = (<__String>(s + index)); + if (!hasTypeParameterByName(typeParameters, augmentedName)) { + return augmentedName; } - - const iterationTypes = getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsyncGenerator); - return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(kind)]; } - - function getIterationTypesOfGeneratorFunctionReturnType(type: Type, isAsyncGenerator: boolean) { - if (isTypeAny(type)) { - return anyIterationTypes; + } + function getReturnTypeOfSingleNonGenericCallSignature(funcType: ts.Type) { + const signature = getSingleCallSignature(funcType); + if (signature && !signature.typeParameters) { + return getReturnTypeOfSignature(signature); + } + } + function getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr: CallChain) { + const funcType = checkExpression(expr.expression); + const nonOptionalType = getOptionalExpressionType(funcType, expr.expression); + const returnType = getReturnTypeOfSingleNonGenericCallSignature(funcType); + return returnType && propagateOptionalTypeMarker(returnType, expr, nonOptionalType !== funcType); + } + /** + * Returns the type of an expression. Unlike checkExpression, this function is simply concerned + * with computing the type and may not fully check all contained sub-expressions for errors. + */ + function getTypeOfExpression(node: Expression) { + // Don't bother caching types that require no flow analysis and are quick to compute. + const quickType = getQuickTypeOfExpression(node); + if (quickType) { + return quickType; + } + // If a type has been cached for the node, return it. + if (node.flags & NodeFlags.TypeCached && flowTypeCache) { + const cachedType = flowTypeCache[getNodeId(node)]; + if (cachedType) { + return cachedType; + } + } + const startInvocationCount = flowInvocationCount; + const type = checkExpression(node); + // If control flow analysis was required to determine the type, it is worth caching. + if (flowInvocationCount !== startInvocationCount) { + const cache = flowTypeCache || (flowTypeCache = []); + cache[getNodeId(node)] = type; + node.flags |= NodeFlags.TypeCached; + } + return type; + } + function getQuickTypeOfExpression(node: Expression) { + const expr = skipParentheses(node); + // Optimize for the common case of a call to a function with a single non-generic call + // signature where we can just fetch the return type without checking the arguments. + if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) { + const type = isCallChain(expr) ? getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr) : + getReturnTypeOfSingleNonGenericCallSignature(checkNonNullExpression(expr.expression)); + if (type) { + return type; } - - const use = isAsyncGenerator ? IterationUse.AsyncGeneratorReturnType : IterationUse.GeneratorReturnType; - const resolver = isAsyncGenerator ? asyncIterationTypesResolver : syncIterationTypesResolver; - return getIterationTypesOfIterable(type, use, /*errorNode*/ undefined) || - getIterationTypesOfIterator(type, resolver, /*errorNode*/ undefined); } - - function checkBreakOrContinueStatement(node: BreakOrContinueStatement) { - // Grammar checking - if (!checkGrammarStatementInAmbientContext(node)) checkGrammarBreakOrContinueStatement(node); - - // TODO: Check that target label is valid + else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) { + return getTypeFromTypeNode((expr).type); } - - function unwrapReturnType(returnType: Type, functionFlags: FunctionFlags) { - const isGenerator = !!(functionFlags & FunctionFlags.Generator); - const isAsync = !!(functionFlags & FunctionFlags.Async); - return isGenerator ? getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, isAsync) || errorType : - isAsync ? getPromisedTypeOfPromise(returnType) || errorType : - returnType; + else if (node.kind === SyntaxKind.NumericLiteral || node.kind === SyntaxKind.StringLiteral || + node.kind === SyntaxKind.TrueKeyword || node.kind === SyntaxKind.FalseKeyword) { + return checkExpression(node); } - - function isUnwrappedReturnTypeVoidOrAny(func: SignatureDeclaration, returnType: Type): boolean { - const unwrappedReturnType = unwrapReturnType(returnType, getFunctionFlags(func)); - return !!unwrappedReturnType && maybeTypeOfKind(unwrappedReturnType, TypeFlags.Void | TypeFlags.AnyOrUnknown); + return undefined; + } + /** + * Returns the type of an expression. Unlike checkExpression, this function is simply concerned + * with computing the type and may not fully check all contained sub-expressions for errors. + * It is intended for uses where you know there is no contextual type, + * and requesting the contextual type might cause a circularity or other bad behaviour. + * It sets the contextual type of the node to any before calling getTypeOfExpression. + */ + function getContextFreeTypeOfExpression(node: Expression) { + const links = getNodeLinks(node); + if (links.contextFreeType) { + return links.contextFreeType; + } + const saveContextualType = node.contextualType; + node.contextualType = anyType; + const type = links.contextFreeType = checkExpression(node, CheckMode.SkipContextSensitive); + node.contextualType = saveContextualType; + return type; + } + function checkExpression(node: Expression | QualifiedName, checkMode?: CheckMode, forceTuple?: boolean): ts.Type { + const saveCurrentNode = currentNode; + currentNode = node; + instantiationCount = 0; + const uninstantiatedType = checkExpressionWorker(node, checkMode, forceTuple); + const type = instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode); + if (isConstEnumObjectType(type)) { + checkConstEnumAccess(node, type); + } + currentNode = saveCurrentNode; + return type; + } + function checkConstEnumAccess(node: Expression | QualifiedName, type: ts.Type) { + // enum object type for const enums are only permitted in: + // - 'left' in property access + // - 'object' in indexed access + // - target in rhs of import statement + const ok = (node.parent.kind === SyntaxKind.PropertyAccessExpression && (node.parent).expression === node) || + (node.parent.kind === SyntaxKind.ElementAccessExpression && (node.parent).expression === node) || + ((node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName) && isInRightSideOfImportOrExportAssignment((node)) || + (node.parent.kind === SyntaxKind.TypeQuery && (node.parent).exprName === node)) || + (node.parent.kind === SyntaxKind.ExportSpecifier); // We allow reexporting const enums + if (!ok) { + error(node, Diagnostics.const_enums_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment_or_type_query); + } + if (compilerOptions.isolatedModules) { + Debug.assert(!!(type.symbol.flags & SymbolFlags.ConstEnum)); + const constEnumDeclaration = (type.symbol.valueDeclaration as EnumDeclaration); + if (constEnumDeclaration.flags & NodeFlags.Ambient) { + error(node, Diagnostics.Cannot_access_ambient_const_enums_when_the_isolatedModules_flag_is_provided); + } } - - function checkReturnStatement(node: ReturnStatement) { - // Grammar checking - if (checkGrammarStatementInAmbientContext(node)) { - return; + } + function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): ts.Type { + const tag = isInJSFile(node) ? getJSDocTypeTag(node) : undefined; + if (tag) { + return checkAssertionWorker(tag, tag.typeExpression.type, node.expression, checkMode); + } + return checkExpression(node.expression, checkMode); + } + function checkExpressionWorker(node: Expression | QualifiedName, checkMode: CheckMode | undefined, forceTuple?: boolean): ts.Type { + const kind = node.kind; + if (cancellationToken) { + // Only bother checking on a few construct kinds. We don't want to be excessively + // hitting the cancellation token on every node we check. + switch (kind) { + case SyntaxKind.ClassExpression: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + cancellationToken.throwIfCancellationRequested(); } - - const func = getContainingFunction(node); - if (!func) { - grammarErrorOnFirstToken(node, Diagnostics.A_return_statement_can_only_be_used_within_a_function_body); - return; + } + switch (kind) { + case SyntaxKind.Identifier: + return checkIdentifier((node)); + case SyntaxKind.ThisKeyword: + return checkThisExpression(node); + case SyntaxKind.SuperKeyword: + return checkSuperExpression(node); + case SyntaxKind.NullKeyword: + return nullWideningType; + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.StringLiteral: + return getFreshTypeOfLiteralType(getLiteralType((node as StringLiteralLike).text)); + case SyntaxKind.NumericLiteral: + checkGrammarNumericLiteral((node as NumericLiteral)); + return getFreshTypeOfLiteralType(getLiteralType(+(node as NumericLiteral).text)); + case SyntaxKind.BigIntLiteral: + checkGrammarBigIntLiteral((node as BigIntLiteral)); + return getFreshTypeOfLiteralType(getBigIntLiteralType((node as BigIntLiteral))); + case SyntaxKind.TrueKeyword: + return trueType; + case SyntaxKind.FalseKeyword: + return falseType; + case SyntaxKind.TemplateExpression: + return checkTemplateExpression((node)); + case SyntaxKind.RegularExpressionLiteral: + return globalRegExpType; + case SyntaxKind.ArrayLiteralExpression: + return checkArrayLiteral((node), checkMode, forceTuple); + case SyntaxKind.ObjectLiteralExpression: + return checkObjectLiteral((node), checkMode); + case SyntaxKind.PropertyAccessExpression: + return checkPropertyAccessExpression((node)); + case SyntaxKind.QualifiedName: + return checkQualifiedName((node)); + case SyntaxKind.ElementAccessExpression: + return checkIndexedAccess((node)); + case SyntaxKind.CallExpression: + if ((node).expression.kind === SyntaxKind.ImportKeyword) { + return checkImportCallExpression((node)); + } + // falls through + case SyntaxKind.NewExpression: + return checkCallExpression((node), checkMode); + case SyntaxKind.TaggedTemplateExpression: + return checkTaggedTemplateExpression((node)); + case SyntaxKind.ParenthesizedExpression: + return checkParenthesizedExpression((node), checkMode); + case SyntaxKind.ClassExpression: + return checkClassExpression((node)); + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return checkFunctionExpressionOrObjectLiteralMethod((node), checkMode); + case SyntaxKind.TypeOfExpression: + return checkTypeOfExpression((node)); + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + return checkAssertion((node)); + case SyntaxKind.NonNullExpression: + return checkNonNullAssertion((node)); + case SyntaxKind.MetaProperty: + return checkMetaProperty((node)); + case SyntaxKind.DeleteExpression: + return checkDeleteExpression((node)); + case SyntaxKind.VoidExpression: + return checkVoidExpression((node)); + case SyntaxKind.AwaitExpression: + return checkAwaitExpression((node)); + case SyntaxKind.PrefixUnaryExpression: + return checkPrefixUnaryExpression((node)); + case SyntaxKind.PostfixUnaryExpression: + return checkPostfixUnaryExpression((node)); + case SyntaxKind.BinaryExpression: + return checkBinaryExpression((node), checkMode); + case SyntaxKind.ConditionalExpression: + return checkConditionalExpression((node), checkMode); + case SyntaxKind.SpreadElement: + return checkSpreadExpression((node), checkMode); + case SyntaxKind.OmittedExpression: + return undefinedWideningType; + case SyntaxKind.YieldExpression: + return checkYieldExpression((node)); + case SyntaxKind.SyntheticExpression: + return (node).type; + case SyntaxKind.JsxExpression: + return checkJsxExpression((node), checkMode); + case SyntaxKind.JsxElement: + return checkJsxElement((node), checkMode); + case SyntaxKind.JsxSelfClosingElement: + return checkJsxSelfClosingElement((node), checkMode); + case SyntaxKind.JsxFragment: + return checkJsxFragment((node)); + case SyntaxKind.JsxAttributes: + return checkJsxAttributes((node), checkMode); + case SyntaxKind.JsxOpeningElement: + Debug.fail("Shouldn't ever directly check a JsxOpeningElement"); + } + return errorType; + } + // DECLARATION AND STATEMENT TYPE CHECKING + function checkTypeParameter(node: TypeParameterDeclaration) { + // Grammar Checking + if (node.expression) { + grammarErrorOnFirstToken(node.expression, Diagnostics.Type_expected); + } + checkSourceElement(node.constraint); + checkSourceElement(node.default); + const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node)); + // Resolve base constraint to reveal circularity errors + getBaseConstraintOfType(typeParameter); + if (!hasNonCircularTypeParameterDefault(typeParameter)) { + error(node.default, Diagnostics.Type_parameter_0_has_a_circular_default, typeToString(typeParameter)); + } + const constraintType = getConstraintOfTypeParameter(typeParameter); + const defaultType = getDefaultFromTypeParameter(typeParameter); + if (constraintType && defaultType) { + checkTypeAssignableTo(defaultType, getTypeWithThisArgument(instantiateType(constraintType, makeUnaryTypeMapper(typeParameter, defaultType)), defaultType), node.default, Diagnostics.Type_0_does_not_satisfy_the_constraint_1); + } + if (produceDiagnostics) { + checkTypeNameIsReserved(node.name, Diagnostics.Type_parameter_name_cannot_be_0); + } + } + function checkParameter(node: ParameterDeclaration) { + // Grammar checking + // It is a SyntaxError if the Identifier "eval" or the Identifier "arguments" occurs as the + // Identifier in a PropertySetParameterList of a PropertyAssignment that is contained in strict code + // or if its FunctionBody is strict code(11.1.5). + checkGrammarDecoratorsAndModifiers(node); + checkVariableLikeDeclaration(node); + const func = (getContainingFunction(node)!); + if (hasModifier(node, ModifierFlags.ParameterPropertyModifier)) { + if (!(func.kind === SyntaxKind.Constructor && nodeIsPresent(func.body))) { + error(node, Diagnostics.A_parameter_property_is_only_allowed_in_a_constructor_implementation); } - - const signature = getSignatureFromDeclaration(func); - const returnType = getReturnTypeOfSignature(signature); - const functionFlags = getFunctionFlags(func); - if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) { - const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; - if (func.kind === SyntaxKind.SetAccessor) { - if (node.expression) { - error(node, Diagnostics.Setters_cannot_return_a_value); - } + } + if (node.questionToken && isBindingPattern(node.name) && (func as FunctionLikeDeclaration).body) { + error(node, Diagnostics.A_binding_pattern_parameter_cannot_be_optional_in_an_implementation_signature); + } + if (node.name && isIdentifier(node.name) && (node.name.escapedText === "this" || node.name.escapedText === "new")) { + if (func.parameters.indexOf(node) !== 0) { + error(node, Diagnostics.A_0_parameter_must_be_the_first_parameter, (node.name.escapedText as string)); + } + if (func.kind === SyntaxKind.Constructor || func.kind === SyntaxKind.ConstructSignature || func.kind === SyntaxKind.ConstructorType) { + error(node, Diagnostics.A_constructor_cannot_have_a_this_parameter); + } + if (func.kind === SyntaxKind.ArrowFunction) { + error(node, Diagnostics.An_arrow_function_cannot_have_a_this_parameter); + } + } + // Only check rest parameter type if it's not a binding pattern. Since binding patterns are + // not allowed in a rest parameter, we already have an error from checkGrammarParameterList. + if (node.dotDotDotToken && !isBindingPattern(node.name) && !isTypeAssignableTo(getTypeOfSymbol(node.symbol), anyReadonlyArrayType)) { + error(node, Diagnostics.A_rest_parameter_must_be_of_an_array_type); + } + } + function checkTypePredicate(node: TypePredicateNode): void { + const parent = getTypePredicateParent(node); + if (!parent) { + // The parent must not be valid. + error(node, Diagnostics.A_type_predicate_is_only_allowed_in_return_type_position_for_functions_and_methods); + return; + } + const signature = getSignatureFromDeclaration(parent); + const typePredicate = getTypePredicateOfSignature(signature); + if (!typePredicate) { + return; + } + checkSourceElement(node.type); + const { parameterName } = node; + if (typePredicate.kind === TypePredicateKind.This || typePredicate.kind === TypePredicateKind.AssertsThis) { + getTypeFromThisTypeNode((parameterName as ThisTypeNode)); + } + else { + if (typePredicate.parameterIndex >= 0) { + if (signatureHasRestParameter(signature) && typePredicate.parameterIndex === signature.parameters.length - 1) { + error(parameterName, Diagnostics.A_type_predicate_cannot_reference_a_rest_parameter); } - else if (func.kind === SyntaxKind.Constructor) { - if (node.expression && !checkTypeAssignableToAndOptionallyElaborate(exprType, returnType, node, node.expression)) { - error(node, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class); + else { + if (typePredicate.type) { + const leadingError = () => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.A_type_predicate_s_type_must_be_assignable_to_its_parameter_s_type); + checkTypeAssignableTo(typePredicate.type, getTypeOfSymbol(signature.parameters[typePredicate.parameterIndex]), node.type, + /*headMessage*/ undefined, leadingError); } } - else if (getReturnTypeFromAnnotation(func)) { - const unwrappedReturnType = unwrapReturnType(returnType, functionFlags); - const unwrappedExprType = functionFlags & FunctionFlags.Async - ? checkAwaitedType(exprType, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) - : exprType; - if (unwrappedReturnType) { - // If the function has a return type, but promisedType is - // undefined, an error will be reported in checkAsyncFunctionReturnType - // so we don't need to report one here. - checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, node, node.expression); + } + else if (parameterName) { + let hasReportedError = false; + for (const { name } of parent.parameters) { + if (isBindingPattern(name) && + checkIfTypePredicateVariableIsDeclaredInBindingPattern(name, parameterName, typePredicate.parameterName)) { + hasReportedError = true; + break; } } - } - else if (func.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeVoidOrAny(func, returnType)) { - // The function has a return type, but the return statement doesn't have an expression. - error(node, Diagnostics.Not_all_code_paths_return_a_value); + if (!hasReportedError) { + error(node.parameterName, Diagnostics.Cannot_find_parameter_0, typePredicate.parameterName); + } } } - - function checkWithStatement(node: WithStatement) { - // Grammar checking for withStatement - if (!checkGrammarStatementInAmbientContext(node)) { - if (node.flags & NodeFlags.AwaitContext) { - grammarErrorOnFirstToken(node, Diagnostics.with_statements_are_not_allowed_in_an_async_function_block); + } + function getTypePredicateParent(node: Node): SignatureDeclaration | undefined { + switch (node.parent.kind) { + case SyntaxKind.ArrowFunction: + case SyntaxKind.CallSignature: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionType: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + const parent = (node.parent); + if (node === parent.type) { + return parent; } + } + } + function checkIfTypePredicateVariableIsDeclaredInBindingPattern(pattern: BindingPattern, predicateVariableNode: Node, predicateVariableName: string) { + for (const element of pattern.elements) { + if (isOmittedExpression(element)) { + continue; + } + const name = element.name; + if (name.kind === SyntaxKind.Identifier && name.escapedText === predicateVariableName) { + error(predicateVariableNode, Diagnostics.A_type_predicate_cannot_reference_element_0_in_a_binding_pattern, predicateVariableName); + return true; } - - checkExpression(node.expression); - - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - const start = getSpanOfTokenAtPosition(sourceFile, node.pos).start; - const end = node.statement.pos; - grammarErrorAtPos(sourceFile, start, end - start, Diagnostics.The_with_statement_is_not_supported_All_symbols_in_a_with_block_will_have_type_any); + else if (name.kind === SyntaxKind.ArrayBindingPattern || name.kind === SyntaxKind.ObjectBindingPattern) { + if (checkIfTypePredicateVariableIsDeclaredInBindingPattern(name, predicateVariableNode, predicateVariableName)) { + return true; + } } } - - function checkSwitchStatement(node: SwitchStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); - - let firstDefaultClause: CaseOrDefaultClause; - let hasDuplicateDefaultClause = false; - - const expressionType = checkExpression(node.expression); - const expressionIsLiteral = isLiteralType(expressionType); - forEach(node.caseBlock.clauses, clause => { - // Grammar check for duplicate default clauses, skip if we already report duplicate default clause - if (clause.kind === SyntaxKind.DefaultClause && !hasDuplicateDefaultClause) { - if (firstDefaultClause === undefined) { - firstDefaultClause = clause; + } + function checkSignatureDeclaration(node: SignatureDeclaration) { + // Grammar checking + if (node.kind === SyntaxKind.IndexSignature) { + checkGrammarIndexSignature((node)); + } + // TODO (yuisu): Remove this check in else-if when SyntaxKind.Construct is moved and ambient context is handled + else if (node.kind === SyntaxKind.FunctionType || node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.ConstructorType || + node.kind === SyntaxKind.CallSignature || node.kind === SyntaxKind.Constructor || + node.kind === SyntaxKind.ConstructSignature) { + checkGrammarFunctionLikeDeclaration((node)); + } + const functionFlags = getFunctionFlags((node)); + if (!(functionFlags & FunctionFlags.Invalid)) { + // Async generators prior to ESNext require the __await and __asyncGenerator helpers + if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.AsyncGenerator && languageVersion < ScriptTarget.ESNext) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncGeneratorIncludes); + } + // Async functions prior to ES2017 require the __awaiter helper + if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async && languageVersion < ScriptTarget.ES2017) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Awaiter); + } + // Generator functions, Async functions, and Async Generator functions prior to + // ES2015 require the __generator helper + if ((functionFlags & FunctionFlags.AsyncGenerator) !== FunctionFlags.Normal && languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Generator); + } + } + checkTypeParameters(node.typeParameters); + forEach(node.parameters, checkParameter); + // TODO(rbuckton): Should we start checking JSDoc types? + if (node.type) { + checkSourceElement(node.type); + } + if (produceDiagnostics) { + checkCollisionWithArgumentsInGeneratedCode(node); + const returnTypeNode = getEffectiveReturnTypeNode(node); + if (noImplicitAny && !returnTypeNode) { + switch (node.kind) { + case SyntaxKind.ConstructSignature: + error(node, Diagnostics.Construct_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type); + break; + case SyntaxKind.CallSignature: + error(node, Diagnostics.Call_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type); + break; + } + } + if (returnTypeNode) { + const functionFlags = getFunctionFlags((node)); + if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Generator)) === FunctionFlags.Generator) { + const returnType = getTypeFromTypeNode(returnTypeNode); + if (returnType === voidType) { + error(returnTypeNode, Diagnostics.A_generator_cannot_have_a_void_type_annotation); } else { - grammarErrorOnNode(clause, Diagnostics.A_default_clause_cannot_appear_more_than_once_in_a_switch_statement); - hasDuplicateDefaultClause = true; + // Naively, one could check that Generator is assignable to the return type annotation. + // However, that would not catch the error in the following case. + // + // interface BadGenerator extends Iterable, Iterator { } + // function* g(): BadGenerator { } // Iterable and Iterator have different types! + // + const generatorYieldType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, returnType, (functionFlags & FunctionFlags.Async) !== 0) || anyType; + const generatorReturnType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, (functionFlags & FunctionFlags.Async) !== 0) || generatorYieldType; + const generatorNextType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, (functionFlags & FunctionFlags.Async) !== 0) || unknownType; + const generatorInstantiation = createGeneratorReturnType(generatorYieldType, generatorReturnType, generatorNextType, !!(functionFlags & FunctionFlags.Async)); + checkTypeAssignableTo(generatorInstantiation, returnType, returnTypeNode); } } - - if (produceDiagnostics && clause.kind === SyntaxKind.CaseClause) { - // TypeScript 1.0 spec (April 2014): 5.9 - // In a 'switch' statement, each 'case' expression must be of a type that is comparable - // to or from the type of the 'switch' expression. - let caseType = checkExpression(clause.expression); - const caseIsLiteral = isLiteralType(caseType); - let comparedExpressionType = expressionType; - if (!caseIsLiteral || !expressionIsLiteral) { - caseType = caseIsLiteral ? getBaseTypeOfLiteralType(caseType) : caseType; - comparedExpressionType = getBaseTypeOfLiteralType(expressionType); - } - if (!isTypeEqualityComparableTo(comparedExpressionType, caseType)) { - // expressionType is not comparable to caseType, try the reversed check and report errors if it fails - checkTypeComparableTo(caseType, comparedExpressionType, clause.expression, /*headMessage*/ undefined); - } - } - forEach(clause.statements, checkSourceElement); - if (compilerOptions.noFallthroughCasesInSwitch && clause.fallthroughFlowNode && isReachableFlowNode(clause.fallthroughFlowNode)) { - error(clause, Diagnostics.Fallthrough_case_in_switch); + else if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { + checkAsyncFunctionReturnType((node), returnTypeNode); } - }); - if (node.caseBlock.locals) { - registerForUnusedIdentifiersCheck(node.caseBlock); } - } - - function checkLabeledStatement(node: LabeledStatement) { - // Grammar checking - if (!checkGrammarStatementInAmbientContext(node)) { - findAncestor(node.parent, current => { - if (isFunctionLike(current)) { - return "quit"; - } - if (current.kind === SyntaxKind.LabeledStatement && (current).label.escapedText === node.label.escapedText) { - grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0, getTextOfNode(node.label)); - return true; - } - return false; - }); + if (node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.JSDocFunctionType) { + registerForUnusedIdentifiersCheck(node); } - - // ensure that label is unique - checkSourceElement(node.statement); } - - function checkThrowStatement(node: ThrowStatement) { - // Grammar checking - if (!checkGrammarStatementInAmbientContext(node)) { - if (node.expression === undefined) { - grammarErrorAfterFirstToken(node, Diagnostics.Line_break_not_permitted_here); + } + function checkClassForDuplicateDeclarations(node: ClassLikeDeclaration) { + const instanceNames = createUnderscoreEscapedMap(); + const staticNames = createUnderscoreEscapedMap(); + for (const member of node.members) { + if (member.kind === SyntaxKind.Constructor) { + for (const param of (member as ConstructorDeclaration).parameters) { + if (isParameterPropertyDeclaration(param, member) && !isBindingPattern(param.name)) { + addName(instanceNames, param.name, param.name.escapedText, DeclarationMeaning.GetOrSetAccessor); + } } } - - if (node.expression) { - checkExpression(node.expression); - } - } - - function checkTryStatement(node: TryStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); - - checkBlock(node.tryBlock); - const catchClause = node.catchClause; - if (catchClause) { - // Grammar checking - if (catchClause.variableDeclaration) { - if (catchClause.variableDeclaration.type) { - grammarErrorOnFirstToken(catchClause.variableDeclaration.type, Diagnostics.Catch_clause_variable_cannot_have_a_type_annotation); - } - else if (catchClause.variableDeclaration.initializer) { - grammarErrorOnFirstToken(catchClause.variableDeclaration.initializer, Diagnostics.Catch_clause_variable_cannot_have_an_initializer); - } - else { - const blockLocals = catchClause.block.locals; - if (blockLocals) { - forEachKey(catchClause.locals!, caughtName => { - const blockLocal = blockLocals.get(caughtName); - if (blockLocal && (blockLocal.flags & SymbolFlags.BlockScopedVariable) !== 0) { - grammarErrorOnNode(blockLocal.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, caughtName); - } - }); - } + else { + const isStatic = hasModifier(member, ModifierFlags.Static); + const names = isStatic ? staticNames : instanceNames; + const name = member.name; + const memberName = name && getPropertyNameForPropertyNameNode(name); + if (name && memberName) { + switch (member.kind) { + case SyntaxKind.GetAccessor: + addName(names, name, memberName, DeclarationMeaning.GetAccessor); + break; + case SyntaxKind.SetAccessor: + addName(names, name, memberName, DeclarationMeaning.SetAccessor); + break; + case SyntaxKind.PropertyDeclaration: + addName(names, name, memberName, DeclarationMeaning.GetOrSetAccessor); + break; + case SyntaxKind.MethodDeclaration: + addName(names, name, memberName, DeclarationMeaning.Method); + break; } } - - checkBlock(catchClause.block); - } - - if (node.finallyBlock) { - checkBlock(node.finallyBlock); } } - - function checkIndexConstraints(type: Type) { - const declaredNumberIndexer = getIndexDeclarationOfSymbol(type.symbol, IndexKind.Number); - const declaredStringIndexer = getIndexDeclarationOfSymbol(type.symbol, IndexKind.String); - - const stringIndexType = getIndexTypeOfType(type, IndexKind.String); - const numberIndexType = getIndexTypeOfType(type, IndexKind.Number); - - if (stringIndexType || numberIndexType) { - forEach(getPropertiesOfObjectType(type), prop => { - const propType = getTypeOfSymbol(prop); - checkIndexConstraintForProperty(prop, propType, type, declaredStringIndexer, stringIndexType, IndexKind.String); - checkIndexConstraintForProperty(prop, propType, type, declaredNumberIndexer, numberIndexType, IndexKind.Number); - }); - - const classDeclaration = type.symbol.valueDeclaration; - if (getObjectFlags(type) & ObjectFlags.Class && isClassLike(classDeclaration)) { - for (const member of classDeclaration.members) { - // Only process instance properties with computed names here. - // Static properties cannot be in conflict with indexers, - // and properties with literal names were already checked. - if (!hasModifier(member, ModifierFlags.Static) && hasNonBindableDynamicName(member)) { - const symbol = getSymbolOfNode(member); - const propType = getTypeOfSymbol(symbol); - checkIndexConstraintForProperty(symbol, propType, type, declaredStringIndexer, stringIndexType, IndexKind.String); - checkIndexConstraintForProperty(symbol, propType, type, declaredNumberIndexer, numberIndexType, IndexKind.Number); - } + function addName(names: UnderscoreEscapedMap, location: Node, name: __String, meaning: DeclarationMeaning) { + const prev = names.get(name); + if (prev) { + if (prev & DeclarationMeaning.Method) { + if (meaning !== DeclarationMeaning.Method) { + error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); } } - } - - let errorNode: Node | undefined; - if (stringIndexType && numberIndexType) { - errorNode = declaredNumberIndexer || declaredStringIndexer; - // condition 'errorNode === undefined' may appear if types does not declare nor string neither number indexer - if (!errorNode && (getObjectFlags(type) & ObjectFlags.Interface)) { - const someBaseTypeHasBothIndexers = forEach(getBaseTypes(type), base => getIndexTypeOfType(base, IndexKind.String) && getIndexTypeOfType(base, IndexKind.Number)); - errorNode = someBaseTypeHasBothIndexers ? undefined : type.symbol.declarations[0]; + else if (prev & meaning) { + error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); + } + else { + names.set(name, prev | meaning); } } - - if (errorNode && !isTypeAssignableTo(numberIndexType!, stringIndexType!)) { // TODO: GH#18217 - error(errorNode, Diagnostics.Numeric_index_type_0_is_not_assignable_to_string_index_type_1, - typeToString(numberIndexType!), typeToString(stringIndexType!)); + else { + names.set(name, meaning); } - - function checkIndexConstraintForProperty( - prop: Symbol, - propertyType: Type, - containingType: Type, - indexDeclaration: Declaration | undefined, - indexType: Type | undefined, - indexKind: IndexKind): void { - - // ESSymbol properties apply to neither string nor numeric indexers. - if (!indexType || isKnownSymbol(prop)) { - return; + } + } + /** + * Static members being set on a constructor function may conflict with built-in properties + * of Function. Esp. in ECMAScript 5 there are non-configurable and non-writable + * built-in properties. This check issues a transpile error when a class has a static + * member with the same name as a non-writable built-in property. + * + * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.3 + * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.5 + * @see http://www.ecma-international.org/ecma-262/6.0/#sec-properties-of-the-function-constructor + * @see http://www.ecma-international.org/ecma-262/6.0/#sec-function-instances + */ + function checkClassForStaticPropertyNameConflicts(node: ClassLikeDeclaration) { + for (const member of node.members) { + const memberNameNode = member.name; + const isStatic = hasModifier(member, ModifierFlags.Static); + if (isStatic && memberNameNode) { + const memberName = getPropertyNameForPropertyNameNode(memberNameNode); + switch (memberName) { + case "name": + case "length": + case "caller": + case "arguments": + case "prototype": + const message = Diagnostics.Static_property_0_conflicts_with_built_in_property_Function_0_of_constructor_function_1; + const className = getNameOfSymbolAsWritten(getSymbolOfNode(node)); + error(memberNameNode, message, memberName, className); + break; } - - const propDeclaration = prop.valueDeclaration; - const name = propDeclaration && getNameOfDeclaration(propDeclaration); - - // index is numeric and property name is not valid numeric literal - if (indexKind === IndexKind.Number && !(name ? isNumericName(name) : isNumericLiteralName(prop.escapedName))) { - return; + } + } + } + function checkObjectTypeForDuplicateDeclarations(node: TypeLiteralNode | InterfaceDeclaration) { + const names = createMap(); + for (const member of node.members) { + if (member.kind === SyntaxKind.PropertySignature) { + let memberName: string; + const name = member.name!; + switch (name.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + memberName = name.text; + break; + case SyntaxKind.Identifier: + memberName = idText(name); + break; + default: + continue; } - - // perform property check if property or indexer is declared in 'type' - // this allows us to rule out cases when both property and indexer are inherited from the base class - let errorNode: Node | undefined; - if (propDeclaration && name && - (propDeclaration.kind === SyntaxKind.BinaryExpression || - name.kind === SyntaxKind.ComputedPropertyName || - prop.parent === containingType.symbol)) { - errorNode = propDeclaration; - } - else if (indexDeclaration) { - errorNode = indexDeclaration; - } - else if (getObjectFlags(containingType) & ObjectFlags.Interface) { - // for interfaces property and indexer might be inherited from different bases - // check if any base class already has both property and indexer. - // check should be performed only if 'type' is the first type that brings property\indexer together - const someBaseClassHasBothPropertyAndIndexer = forEach(getBaseTypes(containingType), base => getPropertyOfObjectType(base, prop.escapedName) && getIndexTypeOfType(base, indexKind)); - errorNode = someBaseClassHasBothPropertyAndIndexer ? undefined : containingType.symbol.declarations[0]; + if (names.get(memberName)) { + error(getNameOfDeclaration(member.symbol.valueDeclaration), Diagnostics.Duplicate_identifier_0, memberName); + error(member.name, Diagnostics.Duplicate_identifier_0, memberName); } - - if (errorNode && !isTypeAssignableTo(propertyType, indexType)) { - const errorMessage = - indexKind === IndexKind.String - ? Diagnostics.Property_0_of_type_1_is_not_assignable_to_string_index_type_2 - : Diagnostics.Property_0_of_type_1_is_not_assignable_to_numeric_index_type_2; - error(errorNode, errorMessage, symbolToString(prop), typeToString(propertyType), typeToString(indexType)); + else { + names.set(memberName, true); } } } - - function checkTypeNameIsReserved(name: Identifier, message: DiagnosticMessage): void { - // TS 1.0 spec (April 2014): 3.6.1 - // The predefined type keywords are reserved and cannot be used as names of user defined types. - switch (name.escapedText) { - case "any": - case "unknown": - case "number": - case "bigint": - case "boolean": - case "string": - case "symbol": - case "void": - case "object": - error(name, message, name.escapedText as string); - } - } - - /** - * The name cannot be used as 'Object' of user defined types with special target. - */ - function checkClassNameCollisionWithObject(name: Identifier): void { - if (languageVersion === ScriptTarget.ES5 && name.escapedText === "Object" - && moduleKind !== ModuleKind.ES2015 && moduleKind !== ModuleKind.ESNext) { - error(name, Diagnostics.Class_name_cannot_be_Object_when_targeting_ES5_with_module_0, ModuleKind[moduleKind]); // https://github.com/Microsoft/TypeScript/issues/17494 + } + function checkTypeForDuplicateIndexSignatures(node: Node) { + if (node.kind === SyntaxKind.InterfaceDeclaration) { + const nodeSymbol = getSymbolOfNode((node as InterfaceDeclaration)); + // in case of merging interface declaration it is possible that we'll enter this check procedure several times for every declaration + // to prevent this run check only for the first declaration of a given kind + if (nodeSymbol.declarations.length > 0 && nodeSymbol.declarations[0] !== node) { + return; } } - - /** - * Check each type parameter and check that type parameters have no duplicate type parameter declarations - */ - function checkTypeParameters(typeParameterDeclarations: readonly TypeParameterDeclaration[] | undefined) { - if (typeParameterDeclarations) { - let seenDefault = false; - for (let i = 0; i < typeParameterDeclarations.length; i++) { - const node = typeParameterDeclarations[i]; - checkTypeParameter(node); - - if (produceDiagnostics) { - if (node.default) { - seenDefault = true; - checkTypeParametersNotReferenced(node.default, typeParameterDeclarations, i); - } - else if (seenDefault) { - error(node, Diagnostics.Required_type_parameters_may_not_follow_optional_type_parameters); - } - for (let j = 0; j < i; j++) { - if (typeParameterDeclarations[j].symbol === node.symbol) { - error(node.name, Diagnostics.Duplicate_identifier_0, declarationNameToString(node.name)); + // TypeScript 1.0 spec (April 2014) + // 3.7.4: An object type can contain at most one string index signature and one numeric index signature. + // 8.5: A class declaration can have at most one string index member declaration and one numeric index member declaration + const indexSymbol = getIndexSymbol(getSymbolOfNode(node)!); + if (indexSymbol) { + let seenNumericIndexer = false; + let seenStringIndexer = false; + for (const decl of indexSymbol.declarations) { + const declaration = (decl); + if (declaration.parameters.length === 1 && declaration.parameters[0].type) { + switch (declaration.parameters[0].type.kind) { + case SyntaxKind.StringKeyword: + if (!seenStringIndexer) { + seenStringIndexer = true; } - } - } - } - } - } - - /** Check that type parameter defaults only reference previously declared type parameters */ - function checkTypeParametersNotReferenced(root: TypeNode, typeParameters: readonly TypeParameterDeclaration[], index: number) { - visit(root); - function visit(node: Node) { - if (node.kind === SyntaxKind.TypeReference) { - const type = getTypeFromTypeReference(node); - if (type.flags & TypeFlags.TypeParameter) { - for (let i = index; i < typeParameters.length; i++) { - if (type.symbol === getSymbolOfNode(typeParameters[i])) { - error(node, Diagnostics.Type_parameter_defaults_can_only_reference_previously_declared_type_parameters); + else { + error(declaration, Diagnostics.Duplicate_string_index_signature); } - } + break; + case SyntaxKind.NumberKeyword: + if (!seenNumericIndexer) { + seenNumericIndexer = true; + } + else { + error(declaration, Diagnostics.Duplicate_number_index_signature); + } + break; } } - forEachChild(node, visit); } } - - /** Check that type parameter lists are identical across multiple declarations */ - function checkTypeParameterListsIdentical(symbol: Symbol) { - if (symbol.declarations.length === 1) { - return; - } - - const links = getSymbolLinks(symbol); - if (!links.typeParametersChecked) { - links.typeParametersChecked = true; - const declarations = getClassOrInterfaceDeclarationsOfSymbol(symbol); - if (declarations.length <= 1) { - return; - } - - const type = getDeclaredTypeOfSymbol(symbol); - if (!areTypeParametersIdentical(declarations, type.localTypeParameters!)) { - // Report an error on every conflicting declaration. - const name = symbolToString(symbol); - for (const declaration of declarations) { - error(declaration.name, Diagnostics.All_declarations_of_0_must_have_identical_type_parameters, name); + } + function checkPropertyDeclaration(node: PropertyDeclaration | PropertySignature) { + // Grammar checking + if (!checkGrammarDecoratorsAndModifiers(node) && !checkGrammarProperty(node)) + checkGrammarComputedPropertyName(node.name); + checkVariableLikeDeclaration(node); + } + function checkMethodDeclaration(node: MethodDeclaration | MethodSignature) { + // Grammar checking + if (!checkGrammarMethod(node)) + checkGrammarComputedPropertyName(node.name); + // Grammar checking for modifiers is done inside the function checkGrammarFunctionLikeDeclaration + checkFunctionOrMethodDeclaration(node); + // Abstract methods cannot have an implementation. + // Extra checks are to avoid reporting multiple errors relating to the "abstractness" of the node. + if (hasModifier(node, ModifierFlags.Abstract) && node.kind === SyntaxKind.MethodDeclaration && node.body) { + error(node, Diagnostics.Method_0_cannot_have_an_implementation_because_it_is_marked_abstract, declarationNameToString(node.name)); + } + } + function checkConstructorDeclaration(node: ConstructorDeclaration) { + // Grammar check on signature of constructor and modifier of the constructor is done in checkSignatureDeclaration function. + checkSignatureDeclaration(node); + // Grammar check for checking only related to constructorDeclaration + if (!checkGrammarConstructorTypeParameters(node)) + checkGrammarConstructorTypeAnnotation(node); + checkSourceElement(node.body); + const symbol = getSymbolOfNode(node); + const firstDeclaration = getDeclarationOfKind(symbol, node.kind); + // Only type check the symbol once + if (node === firstDeclaration) { + checkFunctionOrConstructorSymbol(symbol); + } + // exit early in the case of signature - super checks are not relevant to them + if (nodeIsMissing(node.body)) { + return; + } + if (!produceDiagnostics) { + return; + } + function isInstancePropertyWithInitializer(n: Node): boolean { + return n.kind === SyntaxKind.PropertyDeclaration && + !hasModifier(n, ModifierFlags.Static) && + !!(n).initializer; + } + // TS 1.0 spec (April 2014): 8.3.2 + // Constructors of classes with no extends clause may not contain super calls, whereas + // constructors of derived classes must contain at least one super call somewhere in their function body. + const containingClassDecl = (node.parent); + if (getClassExtendsHeritageElement(containingClassDecl)) { + captureLexicalThis(node.parent, containingClassDecl); + const classExtendsNull = classDeclarationExtendsNull(containingClassDecl); + const superCall = getSuperCallInConstructor(node); + if (superCall) { + if (classExtendsNull) { + error(superCall, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null); + } + // The first statement in the body of a constructor (excluding prologue directives) must be a super call + // if both of the following are true: + // - The containing class is a derived class. + // - The constructor declares parameter properties + // or the containing class declares instance member variables with initializers. + const superCallShouldBeFirst = some((node.parent).members, isInstancePropertyWithInitializer) || + some(node.parameters, p => hasModifier(p, ModifierFlags.ParameterPropertyModifier)); + // Skip past any prologue directives to find the first statement + // to ensure that it was a super call. + if (superCallShouldBeFirst) { + const statements = node.body!.statements; + let superCallStatement: ExpressionStatement | undefined; + for (const statement of statements) { + if (statement.kind === SyntaxKind.ExpressionStatement && isSuperCall((statement).expression)) { + superCallStatement = (statement); + break; + } + if (!isPrologueDirective(statement)) { + break; + } + } + if (!superCallStatement) { + error(node, Diagnostics.A_super_call_must_be_the_first_statement_in_the_constructor_when_a_class_contains_initialized_properties_or_has_parameter_properties); } } } + else if (!classExtendsNull) { + error(node, Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call); + } } - - function areTypeParametersIdentical(declarations: readonly (ClassDeclaration | InterfaceDeclaration)[], targetParameters: TypeParameter[]) { - const maxTypeArgumentCount = length(targetParameters); - const minTypeArgumentCount = getMinTypeArgumentCount(targetParameters); - - for (const declaration of declarations) { - // If this declaration has too few or too many type parameters, we report an error - const sourceParameters = getEffectiveTypeParameterDeclarations(declaration); - const numTypeParameters = sourceParameters.length; - if (numTypeParameters < minTypeArgumentCount || numTypeParameters > maxTypeArgumentCount) { - return false; - } - - for (let i = 0; i < numTypeParameters; i++) { - const source = sourceParameters[i]; - const target = targetParameters[i]; - - // If the type parameter node does not have the same as the resolved type - // parameter at this position, we report an error. - if (source.name.escapedText !== target.symbol.escapedName) { - return false; + } + function checkAccessorDeclaration(node: AccessorDeclaration) { + if (produceDiagnostics) { + // Grammar checking accessors + if (!checkGrammarFunctionLikeDeclaration(node) && !checkGrammarAccessor(node)) + checkGrammarComputedPropertyName(node.name); + checkDecorators(node); + checkSignatureDeclaration(node); + if (node.kind === SyntaxKind.GetAccessor) { + if (!(node.flags & NodeFlags.Ambient) && nodeIsPresent(node.body) && (node.flags & NodeFlags.HasImplicitReturn)) { + if (!(node.flags & NodeFlags.HasExplicitReturn)) { + error(node.name, Diagnostics.A_get_accessor_must_return_a_value); } - - // If the type parameter node does not have an identical constraint as the resolved - // type parameter at this position, we report an error. - const constraint = getEffectiveConstraintOfTypeParameter(source); - const sourceConstraint = constraint && getTypeFromTypeNode(constraint); - const targetConstraint = getConstraintOfTypeParameter(target); - // relax check if later interface augmentation has no constraint, it's more broad and is OK to merge with - // a more constrained interface (this could be generalized to a full heirarchy check, but that's maybe overkill) - if (sourceConstraint && targetConstraint && !isTypeIdenticalTo(sourceConstraint, targetConstraint)) { - return false; + } + } + // Do not use hasDynamicName here, because that returns false for well known symbols. + // We want to perform checkComputedPropertyName for all computed properties, including + // well known symbols. + if (node.name.kind === SyntaxKind.ComputedPropertyName) { + checkComputedPropertyName(node.name); + } + if (!hasNonBindableDynamicName(node)) { + // TypeScript 1.0 spec (April 2014): 8.4.3 + // Accessors for the same member name must specify the same accessibility. + const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; + const otherAccessor = getDeclarationOfKind(getSymbolOfNode(node), otherKind); + if (otherAccessor) { + const nodeFlags = getModifierFlags(node); + const otherFlags = getModifierFlags(otherAccessor); + if ((nodeFlags & ModifierFlags.AccessibilityModifier) !== (otherFlags & ModifierFlags.AccessibilityModifier)) { + error(node.name, Diagnostics.Getter_and_setter_accessors_do_not_agree_in_visibility); } - - // If the type parameter node has a default and it is not identical to the default - // for the type parameter at this position, we report an error. - const sourceDefault = source.default && getTypeFromTypeNode(source.default); - const targetDefault = getDefaultFromTypeParameter(target); - if (sourceDefault && targetDefault && !isTypeIdenticalTo(sourceDefault, targetDefault)) { - return false; + if ((nodeFlags & ModifierFlags.Abstract) !== (otherFlags & ModifierFlags.Abstract)) { + error(node.name, Diagnostics.Accessors_must_both_be_abstract_or_non_abstract); } + // TypeScript 1.0 spec (April 2014): 4.5 + // If both accessors include type annotations, the specified types must be identical. + checkAccessorDeclarationTypesIdentical(node, otherAccessor, getAnnotatedAccessorType, Diagnostics.get_and_set_accessor_must_have_the_same_type); + checkAccessorDeclarationTypesIdentical(node, otherAccessor, getThisTypeOfDeclaration, Diagnostics.get_and_set_accessor_must_have_the_same_this_type); } } - - return true; + const returnType = getTypeOfAccessors(getSymbolOfNode(node)); + if (node.kind === SyntaxKind.GetAccessor) { + checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); + } } - - function checkClassExpression(node: ClassExpression): Type { - checkClassLikeDeclaration(node); - checkNodeDeferred(node); - return getTypeOfSymbol(getSymbolOfNode(node)); + checkSourceElement(node.body); + } + function checkAccessorDeclarationTypesIdentical(first: AccessorDeclaration, second: AccessorDeclaration, getAnnotatedType: (a: AccessorDeclaration) => ts.Type | undefined, message: DiagnosticMessage) { + const firstType = getAnnotatedType(first); + const secondType = getAnnotatedType(second); + if (firstType && secondType && !isTypeIdenticalTo(firstType, secondType)) { + error(first, message); } - - function checkClassExpressionDeferred(node: ClassExpression) { - forEach(node.members, checkSourceElement); - registerForUnusedIdentifiersCheck(node); + } + function checkMissingDeclaration(node: Node) { + checkDecorators(node); + } + function getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[]): ts.Type[] { + return fillMissingTypeArguments(map((node.typeArguments!), getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(node)); + } + function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[]): boolean { + let typeArguments: ts.Type[] | undefined; + let mapper: TypeMapper | undefined; + let result = true; + for (let i = 0; i < typeParameters.length; i++) { + const constraint = getConstraintOfTypeParameter(typeParameters[i]); + if (constraint) { + if (!typeArguments) { + typeArguments = getEffectiveTypeArguments(node, typeParameters); + mapper = createTypeMapper(typeParameters, typeArguments); + } + result = result && checkTypeAssignableTo(typeArguments[i], instantiateType(constraint, mapper), node.typeArguments![i], Diagnostics.Type_0_does_not_satisfy_the_constraint_1); + } } - - function checkClassDeclaration(node: ClassDeclaration) { - if (!node.name && !hasModifier(node, ModifierFlags.Default)) { - grammarErrorOnFirstToken(node, Diagnostics.A_class_declaration_without_the_default_modifier_must_have_a_name); + return result; + } + function getTypeParametersForTypeReference(node: TypeReferenceNode | ExpressionWithTypeArguments) { + const type = getTypeFromTypeReference(node); + if (type !== errorType) { + const symbol = getNodeLinks(node).resolvedSymbol; + if (symbol) { + return symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters || + (getObjectFlags(type) & ObjectFlags.Reference ? (type).target.localTypeParameters : undefined); } - checkClassLikeDeclaration(node); - forEach(node.members, checkSourceElement); - - registerForUnusedIdentifiersCheck(node); } - - function checkClassLikeDeclaration(node: ClassLikeDeclaration) { - checkGrammarClassLikeDeclaration(node); - checkDecorators(node); - if (node.name) { - checkTypeNameIsReserved(node.name, Diagnostics.Class_name_cannot_be_0); - checkCollisionWithRequireExportsInGeneratedCode(node, node.name); - checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name); - if (!(node.flags & NodeFlags.Ambient)) { - checkClassNameCollisionWithObject(node.name); + return undefined; + } + function checkTypeReferenceNode(node: TypeReferenceNode | ExpressionWithTypeArguments) { + checkGrammarTypeArguments(node, node.typeArguments); + if (node.kind === SyntaxKind.TypeReference && node.typeName.jsdocDotPos !== undefined && !isInJSFile(node) && !isInJSDoc(node)) { + grammarErrorAtPos(node, node.typeName.jsdocDotPos, 1, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); + } + forEach(node.typeArguments, checkSourceElement); + const type = getTypeFromTypeReference(node); + if (type !== errorType) { + if (node.typeArguments && produceDiagnostics) { + const typeParameters = getTypeParametersForTypeReference(node); + if (typeParameters) { + checkTypeArgumentConstraints(node, typeParameters); } } - checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); - checkExportsOnMergedDeclarations(node); - const symbol = getSymbolOfNode(node); - const type = getDeclaredTypeOfSymbol(symbol); - const typeWithThis = getTypeWithThisArgument(type); - const staticType = getTypeOfSymbol(symbol); - checkTypeParameterListsIdentical(symbol); - checkClassForDuplicateDeclarations(node); - - // Only check for reserved static identifiers on non-ambient context. - if (!(node.flags & NodeFlags.Ambient)) { - checkClassForStaticPropertyNameConflicts(node); + if (type.flags & TypeFlags.Enum && getNodeLinks(node).resolvedSymbol!.flags & SymbolFlags.EnumMember) { + error(node, Diagnostics.Enum_type_0_has_members_with_initializers_that_are_not_literals, typeToString(type)); } - - const baseTypeNode = getEffectiveBaseTypeNode(node); - if (baseTypeNode) { - forEach(baseTypeNode.typeArguments, checkSourceElement); - if (languageVersion < ScriptTarget.ES2015) { - checkExternalEmitHelpers(baseTypeNode.parent, ExternalEmitHelpers.Extends); - } - // check both @extends and extends if both are specified. - const extendsNode = getClassExtendsHeritageElement(node); - if (extendsNode && extendsNode !== baseTypeNode) { - checkExpression(extendsNode.expression); + } + } + function getTypeArgumentConstraint(node: TypeNode): ts.Type | undefined { + const typeReferenceNode = tryCast(node.parent, isTypeReferenceType); + if (!typeReferenceNode) + return undefined; + const typeParameters = getTypeParametersForTypeReference(typeReferenceNode)!; // TODO: GH#18217 + const constraint = getConstraintOfTypeParameter(typeParameters[typeReferenceNode.typeArguments!.indexOf(node)]); + return constraint && instantiateType(constraint, createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReferenceNode, typeParameters))); + } + function checkTypeQuery(node: TypeQueryNode) { + getTypeFromTypeQueryNode(node); + } + function checkTypeLiteral(node: TypeLiteralNode) { + forEach(node.members, checkSourceElement); + if (produceDiagnostics) { + const type = getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); + checkIndexConstraints(type); + checkTypeForDuplicateIndexSignatures(node); + checkObjectTypeForDuplicateDeclarations(node); + } + } + function checkArrayType(node: ArrayTypeNode) { + checkSourceElement(node.elementType); + } + function checkTupleType(node: TupleTypeNode) { + const elementTypes = node.elementTypes; + let seenOptionalElement = false; + for (let i = 0; i < elementTypes.length; i++) { + const e = elementTypes[i]; + if (e.kind === SyntaxKind.RestType) { + if (i !== elementTypes.length - 1) { + grammarErrorOnNode(e, Diagnostics.A_rest_element_must_be_last_in_a_tuple_type); + break; } - - const baseTypes = getBaseTypes(type); - if (baseTypes.length && produceDiagnostics) { - const baseType = baseTypes[0]; - const baseConstructorType = getBaseConstructorTypeOfClass(type); - const staticBaseType = getApparentType(baseConstructorType); - checkBaseTypeAccessibility(staticBaseType, baseTypeNode); - checkSourceElement(baseTypeNode.expression); - if (some(baseTypeNode.typeArguments)) { - forEach(baseTypeNode.typeArguments, checkSourceElement); - for (const constructor of getConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode)) { - if (!checkTypeArgumentConstraints(baseTypeNode, constructor.typeParameters!)) { - break; - } - } - } - const baseWithThis = getTypeWithThisArgument(baseType, type.thisType); - if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) { - issueMemberSpecificError(node, typeWithThis, baseWithThis, Diagnostics.Class_0_incorrectly_extends_base_class_1); - } - else { - // Report static side error only when instance type is assignable - checkTypeAssignableTo(staticType, getTypeWithoutSignatures(staticBaseType), node.name || node, - Diagnostics.Class_static_side_0_incorrectly_extends_base_class_static_side_1); - } - if (baseConstructorType.flags & TypeFlags.TypeVariable && !isMixinConstructorType(staticType)) { - error(node.name || node, Diagnostics.A_mixin_class_must_have_a_constructor_with_a_single_rest_parameter_of_type_any); - } - - if (!(staticBaseType.symbol && staticBaseType.symbol.flags & SymbolFlags.Class) && !(baseConstructorType.flags & TypeFlags.TypeVariable)) { - // When the static base type is a "class-like" constructor function (but not actually a class), we verify - // that all instantiated base constructor signatures return the same type. - const constructors = getInstantiatedConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode); - if (forEach(constructors, sig => !isJSConstructor(sig.declaration) && !isTypeIdenticalTo(getReturnTypeOfSignature(sig), baseType))) { - error(baseTypeNode.expression, Diagnostics.Base_constructors_must_all_have_the_same_return_type); - } - } - checkKindsOfPropertyMemberOverrides(type, baseType); + if (!isArrayType(getTypeFromTypeNode((e).type))) { + error(e, Diagnostics.A_rest_element_type_must_be_an_array_type); } } - - const implementedTypeNodes = getClassImplementsHeritageClauseElements(node); - if (implementedTypeNodes) { - for (const typeRefNode of implementedTypeNodes) { - if (!isEntityNameExpression(typeRefNode.expression)) { - error(typeRefNode.expression, Diagnostics.A_class_can_only_implement_an_identifier_Slashqualified_name_with_optional_type_arguments); - } - checkTypeReferenceNode(typeRefNode); - if (produceDiagnostics) { - const t = getTypeFromTypeNode(typeRefNode); - if (t !== errorType) { - if (isValidBaseType(t)) { - const genericDiag = t.symbol && t.symbol.flags & SymbolFlags.Class ? - Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass : - Diagnostics.Class_0_incorrectly_implements_interface_1; - const baseWithThis = getTypeWithThisArgument(t, type.thisType); - if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) { - issueMemberSpecificError(node, typeWithThis, baseWithThis, genericDiag); - } - } - else { - error(typeRefNode, Diagnostics.A_class_can_only_implement_an_object_type_or_intersection_of_object_types_with_statically_known_members); - } - } - } - } + else if (e.kind === SyntaxKind.OptionalType) { + seenOptionalElement = true; } - - if (produceDiagnostics) { - checkIndexConstraints(type); - checkTypeForDuplicateIndexSignatures(node); - checkPropertyInitialization(node); + else if (seenOptionalElement) { + grammarErrorOnNode(e, Diagnostics.A_required_element_cannot_follow_an_optional_element); + break; } } - - function issueMemberSpecificError(node: ClassLikeDeclaration, typeWithThis: Type, baseWithThis: Type, broadDiag: DiagnosticMessage) { - // iterate over all implemented properties and issue errors on each one which isn't compatible, rather than the class as a whole, if possible - let issuedMemberError = false; - for (const member of node.members) { - if (hasStaticModifier(member)) { - continue; - } - const declaredProp = member.name && getSymbolAtLocation(member.name) || getSymbolAtLocation(member); - if (declaredProp) { - const prop = getPropertyOfType(typeWithThis, declaredProp.escapedName); - const baseProp = getPropertyOfType(baseWithThis, declaredProp.escapedName); - if (prop && baseProp) { - const rootChain = () => chainDiagnosticMessages( - /*details*/ undefined, - Diagnostics.Property_0_in_type_1_is_not_assignable_to_the_same_property_in_base_type_2, - symbolToString(declaredProp), - typeToString(typeWithThis), - typeToString(baseWithThis) - ); - if (!checkTypeAssignableTo(getTypeOfSymbol(prop), getTypeOfSymbol(baseProp), member.name || member, /*message*/ undefined, rootChain)) { - issuedMemberError = true; - } - } - } - } - if (!issuedMemberError) { - // check again with diagnostics to generate a less-specific error - checkTypeAssignableTo(typeWithThis, baseWithThis, node.name || node, broadDiag); + forEach(node.elementTypes, checkSourceElement); + } + function checkUnionOrIntersectionType(node: UnionOrIntersectionTypeNode) { + forEach(node.types, checkSourceElement); + } + function checkIndexedAccessIndexType(type: ts.Type, accessNode: IndexedAccessTypeNode | ElementAccessExpression) { + if (!(type.flags & TypeFlags.IndexedAccess)) { + return type; + } + // Check if the index type is assignable to 'keyof T' for the object type. + const objectType = (type).objectType; + const indexType = (type).indexType; + if (isTypeAssignableTo(indexType, getIndexType(objectType, /*stringsOnly*/ false))) { + if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && + getObjectFlags(objectType) & ObjectFlags.Mapped && getMappedTypeModifiers((objectType)) & MappedTypeModifiers.IncludeReadonly) { + error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); } + return type; } - - function checkBaseTypeAccessibility(type: Type, node: ExpressionWithTypeArguments) { - const signatures = getSignaturesOfType(type, SignatureKind.Construct); - if (signatures.length) { - const declaration = signatures[0].declaration; - if (declaration && hasModifier(declaration, ModifierFlags.Private)) { - const typeClassDeclaration = getClassLikeDeclarationOfSymbol(type.symbol)!; - if (!isNodeWithinClass(node, typeClassDeclaration)) { - error(node, Diagnostics.Cannot_extend_a_class_0_Class_constructor_is_marked_as_private, getFullyQualifiedName(type.symbol)); - } + // Check if we're indexing with a numeric type and if either object or index types + // is a generic type with a constraint that has a numeric index signature. + const apparentObjectType = getApparentType(objectType); + if (getIndexInfoOfType(apparentObjectType, IndexKind.Number) && isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) { + return type; + } + if (isGenericObjectType(objectType)) { + const propertyName = getPropertyNameFromIndex(indexType, accessNode); + if (propertyName) { + const propertySymbol = forEachType(apparentObjectType, t => getPropertyOfType(t, propertyName)); + if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.NonPublicAccessibilityModifier) { + error(accessNode, Diagnostics.Private_or_protected_member_0_cannot_be_accessed_on_a_type_parameter, unescapeLeadingUnderscores(propertyName)); + return errorType; } } } - - function getTargetSymbol(s: Symbol) { - // if symbol is instantiated its flags are not copied from the 'target' - // so we'll need to get back original 'target' symbol to work with correct set of flags - return getCheckFlags(s) & CheckFlags.Instantiated ? (s).target! : s; + error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType)); + return errorType; + } + function checkIndexedAccessType(node: IndexedAccessTypeNode) { + checkSourceElement(node.objectType); + checkSourceElement(node.indexType); + checkIndexedAccessIndexType(getTypeFromIndexedAccessTypeNode(node), node); + } + function checkMappedType(node: MappedTypeNode) { + checkSourceElement(node.typeParameter); + checkSourceElement(node.type); + if (!node.type) { + reportImplicitAny(node, anyType); + } + const type = (getTypeFromMappedTypeNode(node)); + const constraintType = getConstraintTypeFromMappedType(type); + checkTypeAssignableTo(constraintType, keyofConstraintType, getEffectiveConstraintOfTypeParameter(node.typeParameter)); + } + function checkThisType(node: ThisTypeNode) { + getTypeFromThisTypeNode(node); + } + function checkTypeOperator(node: TypeOperatorNode) { + checkGrammarTypeOperatorNode(node); + checkSourceElement(node.type); + } + function checkConditionalType(node: ConditionalTypeNode) { + forEachChild(node, checkSourceElement); + } + function checkInferType(node: InferTypeNode) { + if (!findAncestor(node, n => n.parent && n.parent.kind === SyntaxKind.ConditionalType && (n.parent).extendsType === n)) { + grammarErrorOnNode(node, Diagnostics.infer_declarations_are_only_permitted_in_the_extends_clause_of_a_conditional_type); } - - function getClassOrInterfaceDeclarationsOfSymbol(symbol: Symbol) { - return filter(symbol.declarations, (d: Declaration): d is ClassDeclaration | InterfaceDeclaration => - d.kind === SyntaxKind.ClassDeclaration || d.kind === SyntaxKind.InterfaceDeclaration); + checkSourceElement(node.typeParameter); + registerForUnusedIdentifiersCheck(node); + } + function checkImportType(node: ImportTypeNode) { + checkSourceElement(node.argument); + getTypeFromTypeNode(node); + } + function isPrivateWithinAmbient(node: Node): boolean { + return hasModifier(node, ModifierFlags.Private) && !!(node.flags & NodeFlags.Ambient); + } + function getEffectiveDeclarationFlags(n: Declaration, flagsToCheck: ModifierFlags): ModifierFlags { + let flags = getCombinedModifierFlags(n); + // children of classes (even ambient classes) should not be marked as ambient or export + // because those flags have no useful semantics there. + if (n.parent.kind !== SyntaxKind.InterfaceDeclaration && + n.parent.kind !== SyntaxKind.ClassDeclaration && + n.parent.kind !== SyntaxKind.ClassExpression && + n.flags & NodeFlags.Ambient) { + if (!(flags & ModifierFlags.Ambient) && !(isModuleBlock(n.parent) && isModuleDeclaration(n.parent.parent) && isGlobalScopeAugmentation(n.parent.parent))) { + // It is nested in an ambient context, which means it is automatically exported + flags |= ModifierFlags.Export; + } + flags |= ModifierFlags.Ambient; + } + return flags & flagsToCheck; + } + function checkFunctionOrConstructorSymbol(symbol: ts.Symbol): void { + if (!produceDiagnostics) { + return; + } + function getCanonicalOverload(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined): Declaration { + // Consider the canonical set of flags to be the flags of the bodyDeclaration or the first declaration + // Error on all deviations from this canonical set of flags + // The caveat is that if some overloads are defined in lib.d.ts, we don't want to + // report the errors on those. To achieve this, we will say that the implementation is + // the canonical signature only if it is in the same container as the first overload + const implementationSharesContainerWithFirstOverload = implementation !== undefined && implementation.parent === overloads[0].parent; + return implementationSharesContainerWithFirstOverload ? implementation! : overloads[0]; + } + function checkFlagAgreementBetweenOverloads(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined, flagsToCheck: ModifierFlags, someOverloadFlags: ModifierFlags, allOverloadFlags: ModifierFlags): void { + // Error if some overloads have a flag that is not shared by all overloads. To find the + // deviations, we XOR someOverloadFlags with allOverloadFlags + const someButNotAllOverloadFlags = someOverloadFlags ^ allOverloadFlags; + if (someButNotAllOverloadFlags !== 0) { + const canonicalFlags = getEffectiveDeclarationFlags(getCanonicalOverload(overloads, implementation), flagsToCheck); + forEach(overloads, o => { + const deviation = getEffectiveDeclarationFlags(o, flagsToCheck) ^ canonicalFlags; + if (deviation & ModifierFlags.Export) { + error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_exported_or_non_exported); + } + else if (deviation & ModifierFlags.Ambient) { + error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_ambient_or_non_ambient); + } + else if (deviation & (ModifierFlags.Private | ModifierFlags.Protected)) { + error(getNameOfDeclaration(o) || o, Diagnostics.Overload_signatures_must_all_be_public_private_or_protected); + } + else if (deviation & ModifierFlags.Abstract) { + error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_abstract_or_non_abstract); + } + }); + } } - - function checkKindsOfPropertyMemberOverrides(type: InterfaceType, baseType: BaseType): void { - // TypeScript 1.0 spec (April 2014): 8.2.3 - // A derived class inherits all members from its base class it doesn't override. - // Inheritance means that a derived class implicitly contains all non - overridden members of the base class. - // Both public and private property members are inherited, but only public property members can be overridden. - // A property member in a derived class is said to override a property member in a base class - // when the derived class property member has the same name and kind(instance or static) - // as the base class property member. - // The type of an overriding property member must be assignable(section 3.8.4) - // to the type of the overridden property member, or otherwise a compile - time error occurs. - // Base class instance member functions can be overridden by derived class instance member functions, - // but not by other kinds of members. - // Base class instance member variables and accessors can be overridden by - // derived class instance member variables and accessors, but not by other kinds of members. - - // NOTE: assignability is checked in checkClassDeclaration - const baseProperties = getPropertiesOfType(baseType); - basePropertyCheck: for (const baseProperty of baseProperties) { - const base = getTargetSymbol(baseProperty); - - if (base.flags & SymbolFlags.Prototype) { - continue; - } - - const derived = getTargetSymbol(getPropertyOfObjectType(type, base.escapedName)!); // TODO: GH#18217 - const baseDeclarationFlags = getDeclarationModifierFlagsFromSymbol(base); - - Debug.assert(!!derived, "derived should point to something, even if it is the base class' declaration."); - - // In order to resolve whether the inherited method was overridden in the base class or not, - // we compare the Symbols obtained. Since getTargetSymbol returns the symbol on the *uninstantiated* - // type declaration, derived and base resolve to the same symbol even in the case of generic classes. - if (derived === base) { - // derived class inherits base without override/redeclaration - const derivedClassDecl = getClassLikeDeclarationOfSymbol(type.symbol)!; - - // It is an error to inherit an abstract member without implementing it or being declared abstract. - // If there is no declaration for the derived class (as in the case of class expressions), - // then the class cannot be declared abstract. - if (baseDeclarationFlags & ModifierFlags.Abstract && (!derivedClassDecl || !hasModifier(derivedClassDecl, ModifierFlags.Abstract))) { - // Searches other base types for a declaration that would satisfy the inherited abstract member. - // (The class may have more than one base type via declaration merging with an interface with the - // same name.) - for (const otherBaseType of getBaseTypes(type)) { - if (otherBaseType === baseType) continue; - const baseSymbol = getPropertyOfObjectType(otherBaseType, base.escapedName); - const derivedElsewhere = baseSymbol && getTargetSymbol(baseSymbol); - if (derivedElsewhere && derivedElsewhere !== base) { - continue basePropertyCheck; - } - } - - if (derivedClassDecl.kind === SyntaxKind.ClassExpression) { - error(derivedClassDecl, Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1, - symbolToString(baseProperty), typeToString(baseType)); - } - else { - error(derivedClassDecl, Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2, - typeToString(type), symbolToString(baseProperty), typeToString(baseType)); - } + function checkQuestionTokenAgreementBetweenOverloads(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined, someHaveQuestionToken: boolean, allHaveQuestionToken: boolean): void { + if (someHaveQuestionToken !== allHaveQuestionToken) { + const canonicalHasQuestionToken = hasQuestionToken(getCanonicalOverload(overloads, implementation)); + forEach(overloads, o => { + const deviation = hasQuestionToken(o) !== canonicalHasQuestionToken; + if (deviation) { + error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_optional_or_required); } + }); + } + } + const flagsToCheck: ModifierFlags = ModifierFlags.Export | ModifierFlags.Ambient | ModifierFlags.Private | ModifierFlags.Protected | ModifierFlags.Abstract; + let someNodeFlags: ModifierFlags = ModifierFlags.None; + let allNodeFlags = flagsToCheck; + let someHaveQuestionToken = false; + let allHaveQuestionToken = true; + let hasOverloads = false; + let bodyDeclaration: FunctionLikeDeclaration | undefined; + let lastSeenNonAmbientDeclaration: FunctionLikeDeclaration | undefined; + let previousDeclaration: SignatureDeclaration | undefined; + const declarations = symbol.declarations; + const isConstructor = (symbol.flags & SymbolFlags.Constructor) !== 0; + function reportImplementationExpectedError(node: SignatureDeclaration): void { + if (node.name && nodeIsMissing(node.name)) { + return; + } + let seen = false; + const subsequentNode = forEachChild(node.parent, c => { + if (seen) { + return c; } else { - // derived overrides base. - const derivedDeclarationFlags = getDeclarationModifierFlagsFromSymbol(derived); - if (baseDeclarationFlags & ModifierFlags.Private || derivedDeclarationFlags & ModifierFlags.Private) { - // either base or derived property is private - not override, skip it - continue; - } - - let errorMessage: DiagnosticMessage; - const basePropertyFlags = base.flags & SymbolFlags.PropertyOrAccessor; - const derivedPropertyFlags = derived.flags & SymbolFlags.PropertyOrAccessor; - if (basePropertyFlags && derivedPropertyFlags) { - // property/accessor is overridden with property/accessor - if (!compilerOptions.useDefineForClassFields - || baseDeclarationFlags & ModifierFlags.Abstract && !(base.valueDeclaration && isPropertyDeclaration(base.valueDeclaration) && base.valueDeclaration.initializer) - || base.valueDeclaration && base.valueDeclaration.parent.kind === SyntaxKind.InterfaceDeclaration - || derived.valueDeclaration && isBinaryExpression(derived.valueDeclaration)) { - // when the base property is abstract or from an interface, base/derived flags don't need to match - // same when the derived property is from an assignment - continue; - } - - const overriddenInstanceProperty = basePropertyFlags !== SymbolFlags.Property && derivedPropertyFlags === SymbolFlags.Property; - const overriddenInstanceAccessor = basePropertyFlags === SymbolFlags.Property && derivedPropertyFlags !== SymbolFlags.Property; - if (overriddenInstanceProperty || overriddenInstanceAccessor) { - const errorMessage = overriddenInstanceProperty ? - Diagnostics._0_is_defined_as_an_accessor_in_class_1_but_is_overridden_here_in_2_as_an_instance_property : - Diagnostics._0_is_defined_as_a_property_in_class_1_but_is_overridden_here_in_2_as_an_accessor; - error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, symbolToString(base), typeToString(baseType), typeToString(type)); - } - else { - const uninitialized = find(derived.declarations, d => d.kind === SyntaxKind.PropertyDeclaration && !(d as PropertyDeclaration).initializer); - if (uninitialized - && !(derived.flags & SymbolFlags.Transient) - && !(baseDeclarationFlags & ModifierFlags.Abstract) - && !(derivedDeclarationFlags & ModifierFlags.Abstract) - && !derived.declarations.some(d => d.flags & NodeFlags.Ambient)) { - const constructor = findConstructorDeclaration(getClassLikeDeclarationOfSymbol(type.symbol)!); - const propName = (uninitialized as PropertyDeclaration).name; - if ((uninitialized as PropertyDeclaration).exclamationToken - || !constructor - || !isIdentifier(propName) - || !strictNullChecks - || !isPropertyInitializedInConstructor(propName, type, constructor)) { - const errorMessage = Diagnostics.Property_0_will_overwrite_the_base_property_in_1_If_this_is_intentional_add_an_initializer_Otherwise_add_a_declare_modifier_or_remove_the_redundant_declaration; - error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, symbolToString(base), typeToString(baseType)); - } - } + seen = c === node; + } + }); + // We may be here because of some extra nodes between overloads that could not be parsed into a valid node. + // In this case the subsequent node is not really consecutive (.pos !== node.end), and we must ignore it here. + if (subsequentNode && subsequentNode.pos === node.end) { + if (subsequentNode.kind === node.kind) { + const errorNode: Node = (subsequentNode).name || subsequentNode; + // TODO: GH#17345: These are methods, so handle computed name case. (`Always allowing computed property names is *not* the correct behavior!) + const subsequentName = (subsequentNode).name; + if (node.name && subsequentName && + (isComputedPropertyName(node.name) && isComputedPropertyName(subsequentName) || + !isComputedPropertyName(node.name) && !isComputedPropertyName(subsequentName) && getEscapedTextOfIdentifierOrLiteral(node.name) === getEscapedTextOfIdentifierOrLiteral(subsequentName))) { + const reportError = (node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature) && + hasModifier(node, ModifierFlags.Static) !== hasModifier(subsequentNode, ModifierFlags.Static); + // we can get here in two cases + // 1. mixed static and instance class members + // 2. something with the same name was defined before the set of overloads that prevents them from merging + // here we'll report error only for the first case since for second we should already report error in binder + if (reportError) { + const diagnostic = hasModifier(node, ModifierFlags.Static) ? Diagnostics.Function_overload_must_be_static : Diagnostics.Function_overload_must_not_be_static; + error(errorNode, diagnostic); } - - // correct case - continue; + return; } - else if (isPrototypeProperty(base)) { - if (isPrototypeProperty(derived) || derived.flags & SymbolFlags.Property) { - // method is overridden with method or property -- correct case - continue; - } - else { - Debug.assert(!!(derived.flags & SymbolFlags.Accessor)); - errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_accessor; - } + else if (nodeIsPresent((subsequentNode).body)) { + error(errorNode, Diagnostics.Function_implementation_name_must_be_0, declarationNameToString(node.name)); + return; } - else if (base.flags & SymbolFlags.Accessor) { - errorMessage = Diagnostics.Class_0_defines_instance_member_accessor_1_but_extended_class_2_defines_it_as_instance_member_function; + } + } + const errorNode: Node = node.name || node; + if (isConstructor) { + error(errorNode, Diagnostics.Constructor_implementation_is_missing); + } + else { + // Report different errors regarding non-consecutive blocks of declarations depending on whether + // the node in question is abstract. + if (hasModifier(node, ModifierFlags.Abstract)) { + error(errorNode, Diagnostics.All_declarations_of_an_abstract_method_must_be_consecutive); + } + else { + error(errorNode, Diagnostics.Function_implementation_is_missing_or_not_immediately_following_the_declaration); + } + } + } + let duplicateFunctionDeclaration = false; + let multipleConstructorImplementation = false; + let hasNonAmbientClass = false; + for (const current of declarations) { + const node = (current); + const inAmbientContext = node.flags & NodeFlags.Ambient; + const inAmbientContextOrInterface = node.parent.kind === SyntaxKind.InterfaceDeclaration || node.parent.kind === SyntaxKind.TypeLiteral || inAmbientContext; + if (inAmbientContextOrInterface) { + // check if declarations are consecutive only if they are non-ambient + // 1. ambient declarations can be interleaved + // i.e. this is legal + // declare function foo(); + // declare function bar(); + // declare function foo(); + // 2. mixing ambient and non-ambient declarations is a separate error that will be reported - do not want to report an extra one + previousDeclaration = undefined; + } + if ((node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression) && !inAmbientContext) { + hasNonAmbientClass = true; + } + if (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature || node.kind === SyntaxKind.Constructor) { + const currentNodeFlags = getEffectiveDeclarationFlags(node, flagsToCheck); + someNodeFlags |= currentNodeFlags; + allNodeFlags &= currentNodeFlags; + someHaveQuestionToken = someHaveQuestionToken || hasQuestionToken(node); + allHaveQuestionToken = allHaveQuestionToken && hasQuestionToken(node); + if (nodeIsPresent((node as FunctionLikeDeclaration).body) && bodyDeclaration) { + if (isConstructor) { + multipleConstructorImplementation = true; } else { - errorMessage = Diagnostics.Class_0_defines_instance_member_property_1_but_extended_class_2_defines_it_as_instance_member_function; + duplicateFunctionDeclaration = true; + } + } + else if (previousDeclaration && previousDeclaration.parent === node.parent && previousDeclaration.end !== node.pos) { + reportImplementationExpectedError(previousDeclaration); + } + if (nodeIsPresent((node as FunctionLikeDeclaration).body)) { + if (!bodyDeclaration) { + bodyDeclaration = (node as FunctionLikeDeclaration); } - - error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, typeToString(baseType), symbolToString(base), typeToString(type)); + } + else { + hasOverloads = true; + } + previousDeclaration = node; + if (!inAmbientContextOrInterface) { + lastSeenNonAmbientDeclaration = (node as FunctionLikeDeclaration); } } } - - function checkInheritedPropertiesAreIdentical(type: InterfaceType, typeNode: Node): boolean { - const baseTypes = getBaseTypes(type); - if (baseTypes.length < 2) { - return true; - } - - interface InheritanceInfoMap { prop: Symbol; containingType: Type; } - const seen = createUnderscoreEscapedMap(); - forEach(resolveDeclaredMembers(type).declaredProperties, p => { seen.set(p.escapedName, { prop: p, containingType: type }); }); - let ok = true; - - for (const base of baseTypes) { - const properties = getPropertiesOfType(getTypeWithThisArgument(base, type.thisType)); - for (const prop of properties) { - const existing = seen.get(prop.escapedName); - if (!existing) { - seen.set(prop.escapedName, { prop, containingType: base }); - } - else { - const isInheritedProperty = existing.containingType !== type; - if (isInheritedProperty && !isPropertyIdenticalTo(existing.prop, prop)) { - ok = false; - - const typeName1 = typeToString(existing.containingType); - const typeName2 = typeToString(base); - - let errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Named_property_0_of_types_1_and_2_are_not_identical, symbolToString(prop), typeName1, typeName2); - errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Interface_0_cannot_simultaneously_extend_types_1_and_2, typeToString(type), typeName1, typeName2); - diagnostics.add(createDiagnosticForNodeFromMessageChain(typeNode, errorInfo)); - } + if (multipleConstructorImplementation) { + forEach(declarations, declaration => { + error(declaration, Diagnostics.Multiple_constructor_implementations_are_not_allowed); + }); + } + if (duplicateFunctionDeclaration) { + forEach(declarations, declaration => { + error(getNameOfDeclaration(declaration), Diagnostics.Duplicate_function_implementation); + }); + } + if (hasNonAmbientClass && !isConstructor && symbol.flags & SymbolFlags.Function) { + // A non-ambient class cannot be an implementation for a non-constructor function/class merge + // TODO: The below just replicates our older error from when classes and functions were + // entirely unable to merge - a more helpful message like "Class declaration cannot implement overload list" + // might be warranted. :shrug: + forEach(declarations, declaration => { + addDuplicateDeclarationError(getNameOfDeclaration(declaration) || declaration, Diagnostics.Duplicate_identifier_0, symbolName(symbol), filter(declarations, d => d !== declaration)); + }); + } + // Abstract methods can't have an implementation -- in particular, they don't need one. + if (lastSeenNonAmbientDeclaration && !lastSeenNonAmbientDeclaration.body && + !hasModifier(lastSeenNonAmbientDeclaration, ModifierFlags.Abstract) && !lastSeenNonAmbientDeclaration.questionToken) { + reportImplementationExpectedError(lastSeenNonAmbientDeclaration); + } + if (hasOverloads) { + checkFlagAgreementBetweenOverloads(declarations, bodyDeclaration, flagsToCheck, someNodeFlags, allNodeFlags); + checkQuestionTokenAgreementBetweenOverloads(declarations, bodyDeclaration, someHaveQuestionToken, allHaveQuestionToken); + if (bodyDeclaration) { + const signatures = getSignaturesOfSymbol(symbol); + const bodySignature = getSignatureFromDeclaration(bodyDeclaration); + for (const signature of signatures) { + if (!isImplementationCompatibleWithOverload(bodySignature, signature)) { + addRelatedInfo(error(signature.declaration, Diagnostics.This_overload_signature_is_not_compatible_with_its_implementation_signature), createDiagnosticForNode(bodyDeclaration, Diagnostics.The_implementation_signature_is_declared_here)); + break; } } } - - return ok; } - - function checkPropertyInitialization(node: ClassLikeDeclaration) { - if (!strictNullChecks || !strictPropertyInitialization || node.flags & NodeFlags.Ambient) { + } + function checkExportsOnMergedDeclarations(node: Node): void { + if (!produceDiagnostics) { + return; + } + // if localSymbol is defined on node then node itself is exported - check is required + let symbol = node.localSymbol; + if (!symbol) { + // local symbol is undefined => this declaration is non-exported. + // however symbol might contain other declarations that are exported + symbol = getSymbolOfNode(node)!; + if (!symbol.exportSymbol) { + // this is a pure local symbol (all declarations are non-exported) - no need to check anything return; } - const constructor = findConstructorDeclaration(node); - for (const member of node.members) { - if (getModifierFlags(member) & ModifierFlags.Ambient) { - continue; + } + // run the check only for the first declaration in the list + if (getDeclarationOfKind(symbol, node.kind) !== node) { + return; + } + let exportedDeclarationSpaces = DeclarationSpaces.None; + let nonExportedDeclarationSpaces = DeclarationSpaces.None; + let defaultExportedDeclarationSpaces = DeclarationSpaces.None; + for (const d of symbol.declarations) { + const declarationSpaces = getDeclarationSpaces(d); + const effectiveDeclarationFlags = getEffectiveDeclarationFlags(d, ModifierFlags.Export | ModifierFlags.Default); + if (effectiveDeclarationFlags & ModifierFlags.Export) { + if (effectiveDeclarationFlags & ModifierFlags.Default) { + defaultExportedDeclarationSpaces |= declarationSpaces; } - if (isInstancePropertyWithoutInitializer(member)) { - const propName = (member).name; - if (isIdentifier(propName)) { - const type = getTypeOfSymbol(getSymbolOfNode(member)); - if (!(type.flags & TypeFlags.AnyOrUnknown || getFalsyFlags(type) & TypeFlags.Undefined)) { - if (!constructor || !isPropertyInitializedInConstructor(propName, type, constructor)) { - error(member.name, Diagnostics.Property_0_has_no_initializer_and_is_not_definitely_assigned_in_the_constructor, declarationNameToString(propName)); - } - } - } + else { + exportedDeclarationSpaces |= declarationSpaces; } } + else { + nonExportedDeclarationSpaces |= declarationSpaces; + } } - - function isInstancePropertyWithoutInitializer(node: Node) { - return node.kind === SyntaxKind.PropertyDeclaration && - !hasModifier(node, ModifierFlags.Static | ModifierFlags.Abstract) && - !(node).exclamationToken && - !(node).initializer; - } - - function isPropertyInitializedInConstructor(propName: Identifier, propType: Type, constructor: ConstructorDeclaration) { - const reference = createPropertyAccess(createThis(), propName); - reference.expression.parent = reference; - reference.parent = constructor; - reference.flowNode = constructor.returnFlowNode; - const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); - return !(getFalsyFlags(flowType) & TypeFlags.Undefined); + // Spaces for anything not declared a 'default export'. + const nonDefaultExportedDeclarationSpaces = exportedDeclarationSpaces | nonExportedDeclarationSpaces; + const commonDeclarationSpacesForExportsAndLocals = exportedDeclarationSpaces & nonExportedDeclarationSpaces; + const commonDeclarationSpacesForDefaultAndNonDefault = defaultExportedDeclarationSpaces & nonDefaultExportedDeclarationSpaces; + if (commonDeclarationSpacesForExportsAndLocals || commonDeclarationSpacesForDefaultAndNonDefault) { + // declaration spaces for exported and non-exported declarations intersect + for (const d of symbol.declarations) { + const declarationSpaces = getDeclarationSpaces(d); + const name = getNameOfDeclaration(d); + // Only error on the declarations that contributed to the intersecting spaces. + if (declarationSpaces & commonDeclarationSpacesForDefaultAndNonDefault) { + error(name, Diagnostics.Merged_declaration_0_cannot_include_a_default_export_declaration_Consider_adding_a_separate_export_default_0_declaration_instead, declarationNameToString(name)); + } + else if (declarationSpaces & commonDeclarationSpacesForExportsAndLocals) { + error(name, Diagnostics.Individual_declarations_in_merged_declaration_0_must_be_all_exported_or_all_local, declarationNameToString(name)); + } + } } - - function checkInterfaceDeclaration(node: InterfaceDeclaration) { - // Grammar checking - if (!checkGrammarDecoratorsAndModifiers(node)) checkGrammarInterfaceDeclaration(node); - - checkTypeParameters(node.typeParameters); - if (produceDiagnostics) { - checkTypeNameIsReserved(node.name, Diagnostics.Interface_name_cannot_be_0); - - checkExportsOnMergedDeclarations(node); - const symbol = getSymbolOfNode(node); - checkTypeParameterListsIdentical(symbol); - - // Only check this symbol once - const firstInterfaceDecl = getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration); - if (node === firstInterfaceDecl) { - const type = getDeclaredTypeOfSymbol(symbol); - const typeWithThis = getTypeWithThisArgument(type); - // run subsequent checks only if first set succeeded - if (checkInheritedPropertiesAreIdentical(type, node.name)) { - for (const baseType of getBaseTypes(type)) { - checkTypeAssignableTo(typeWithThis, getTypeWithThisArgument(baseType, type.thisType), node.name, Diagnostics.Interface_0_incorrectly_extends_interface_1); - } - checkIndexConstraints(type); + function getDeclarationSpaces(decl: Declaration): DeclarationSpaces { + let d = (decl as Node); + switch (d.kind) { + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + // A jsdoc typedef and callback are, by definition, type aliases. + // falls through + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocEnumTag: + return DeclarationSpaces.ExportType; + case SyntaxKind.ModuleDeclaration: + return isAmbientModule((d as ModuleDeclaration)) || getModuleInstanceState((d as ModuleDeclaration)) !== ModuleInstanceState.NonInstantiated + ? DeclarationSpaces.ExportNamespace | DeclarationSpaces.ExportValue + : DeclarationSpaces.ExportNamespace; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.EnumDeclaration: + return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue; + case SyntaxKind.SourceFile: + return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue | DeclarationSpaces.ExportNamespace; + case SyntaxKind.ExportAssignment: + // Export assigned entity name expressions act as aliases and should fall through, otherwise they export values + if (!isEntityNameExpression((d as ExportAssignment).expression)) { + return DeclarationSpaces.ExportValue; } - } - checkObjectTypeForDuplicateDeclarations(node); + d = (d as ExportAssignment).expression; + // The below options all declare an Alias, which is allowed to merge with other values within the importing module. + // falls through + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportClause: + let result = DeclarationSpaces.None; + const target = resolveAlias(getSymbolOfNode(d)!); + forEach(target.declarations, d => { result |= getDeclarationSpaces(d); }); + return result; + case SyntaxKind.VariableDeclaration: + case SyntaxKind.BindingElement: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ImportSpecifier: // https://github.com/Microsoft/TypeScript/pull/7591 + return DeclarationSpaces.ExportValue; + default: + return Debug.failBadSyntaxKind(d); } - forEach(getInterfaceBaseTypeNodes(node), heritageElement => { - if (!isEntityNameExpression(heritageElement.expression)) { - error(heritageElement.expression, Diagnostics.An_interface_can_only_extend_an_identifier_Slashqualified_name_with_optional_type_arguments); - } - checkTypeReferenceNode(heritageElement); - }); - - forEach(node.members, checkSourceElement); - - if (produceDiagnostics) { - checkTypeForDuplicateIndexSignatures(node); - registerForUnusedIdentifiersCheck(node); + } + } + function getAwaitedTypeOfPromise(type: ts.Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): ts.Type | undefined { + const promisedType = getPromisedTypeOfPromise(type, errorNode); + return promisedType && getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0); + } + /** + * Gets the "promised type" of a promise. + * @param type The type of the promise. + * @remarks The "promised type" of a type is the type of the "value" parameter of the "onfulfilled" callback. + */ + function getPromisedTypeOfPromise(promise: ts.Type, errorNode?: Node): ts.Type | undefined { + // + // { // promise + // then( // thenFunction + // onfulfilled: ( // onfulfilledParameterType + // value: T // valueParameterType + // ) => any + // ): any; + // } + // + if (isTypeAny(promise)) { + return undefined; + } + const typeAsPromise = (promise); + if (typeAsPromise.promisedTypeOfPromise) { + return typeAsPromise.promisedTypeOfPromise; + } + if (isReferenceToType(promise, getGlobalPromiseType(/*reportErrors*/ false))) { + return typeAsPromise.promisedTypeOfPromise = getTypeArguments((promise))[0]; + } + const thenFunction = (getTypeOfPropertyOfType(promise, ("then" as __String))!); // TODO: GH#18217 + if (isTypeAny(thenFunction)) { + return undefined; + } + const thenSignatures = thenFunction ? getSignaturesOfType(thenFunction, SignatureKind.Call) : emptyArray; + if (thenSignatures.length === 0) { + if (errorNode) { + error(errorNode, Diagnostics.A_promise_must_have_a_then_method); } + return undefined; } - - function checkTypeAliasDeclaration(node: TypeAliasDeclaration) { - // Grammar checking - checkGrammarDecoratorsAndModifiers(node); - - checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); - checkTypeParameters(node.typeParameters); - checkSourceElement(node.type); - registerForUnusedIdentifiersCheck(node); + const onfulfilledParameterType = getTypeWithFacts(getUnionType(map(thenSignatures, getTypeOfFirstParameterOfSignature)), TypeFacts.NEUndefinedOrNull); + if (isTypeAny(onfulfilledParameterType)) { + return undefined; } - - function computeEnumMemberValues(node: EnumDeclaration) { - const nodeLinks = getNodeLinks(node); - if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) { - nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed; - let autoValue: number | undefined = 0; - for (const member of node.members) { - const value = computeMemberValue(member, autoValue); - getNodeLinks(member).enumMemberValue = value; - autoValue = typeof value === "number" ? value + 1 : undefined; - } + const onfulfilledParameterSignatures = getSignaturesOfType(onfulfilledParameterType, SignatureKind.Call); + if (onfulfilledParameterSignatures.length === 0) { + if (errorNode) { + error(errorNode, Diagnostics.The_first_parameter_of_the_then_method_of_a_promise_must_be_a_callback); } + return undefined; } - - function computeMemberValue(member: EnumMember, autoValue: number | undefined) { - if (isComputedNonLiteralName(member.name)) { - error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums); + return typeAsPromise.promisedTypeOfPromise = getUnionType(map(onfulfilledParameterSignatures, getTypeOfFirstParameterOfSignature), UnionReduction.Subtype); + } + /** + * Gets the "awaited type" of a type. + * @param type The type to await. + * @remarks The "awaited type" of an expression is its "promised type" if the expression is a + * Promise-like type; otherwise, it is the type of the expression. This is used to reflect + * The runtime behavior of the `await` keyword. + */ + function checkAwaitedType(type: ts.Type, errorNode: Node, diagnosticMessage: DiagnosticMessage, arg0?: string | number): ts.Type { + const awaitedType = getAwaitedType(type, errorNode, diagnosticMessage, arg0); + return awaitedType || errorType; + } + function getAwaitedType(type: ts.Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): ts.Type | undefined { + const typeAsAwaitable = (type); + if (typeAsAwaitable.awaitedTypeOfType) { + return typeAsAwaitable.awaitedTypeOfType; + } + if (isTypeAny(type)) { + return typeAsAwaitable.awaitedTypeOfType = type; + } + if (type.flags & TypeFlags.Union) { + let types: ts.Type[] | undefined; + for (const constituentType of (type).types) { + types = append(types, getAwaitedType(constituentType, errorNode, diagnosticMessage, arg0)); } - else { - const text = getTextOfPropertyName(member.name); - if (isNumericLiteralName(text) && !isInfinityOrNaNString(text)) { - error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name); - } + if (!types) { + return undefined; } - if (member.initializer) { - return computeConstantValue(member); + return typeAsAwaitable.awaitedTypeOfType = getUnionType(types); + } + const promisedType = getPromisedTypeOfPromise(type); + if (promisedType) { + if (type.id === promisedType.id || awaitedTypeStack.indexOf(promisedType.id) >= 0) { + // Verify that we don't have a bad actor in the form of a promise whose + // promised type is the same as the promise type, or a mutually recursive + // promise. If so, we return undefined as we cannot guess the shape. If this + // were the actual case in the JavaScript, this Promise would never resolve. + // + // An example of a bad actor with a singly-recursive promise type might + // be: + // + // interface BadPromise { + // then( + // onfulfilled: (value: BadPromise) => any, + // onrejected: (error: any) => any): BadPromise; + // } + // The above interface will pass the PromiseLike check, and return a + // promised type of `BadPromise`. Since this is a self reference, we + // don't want to keep recursing ad infinitum. + // + // An example of a bad actor in the form of a mutually-recursive + // promise type might be: + // + // interface BadPromiseA { + // then( + // onfulfilled: (value: BadPromiseB) => any, + // onrejected: (error: any) => any): BadPromiseB; + // } + // + // interface BadPromiseB { + // then( + // onfulfilled: (value: BadPromiseA) => any, + // onrejected: (error: any) => any): BadPromiseA; + // } + // + if (errorNode) { + error(errorNode, Diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method); + } + return undefined; } - // In ambient non-const numeric enum declarations, enum members without initializers are - // considered computed members (as opposed to having auto-incremented values). - if (member.parent.flags & NodeFlags.Ambient && !isEnumConst(member.parent) && getEnumKind(getSymbolOfNode(member.parent)) === EnumKind.Numeric) { + // Keep track of the type we're about to unwrap to avoid bad recursive promise types. + // See the comments above for more information. + awaitedTypeStack.push(type.id); + const awaitedType = getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0); + awaitedTypeStack.pop(); + if (!awaitedType) { return undefined; } - // If the member declaration specifies no value, the member is considered a constant enum member. - // If the member is the first member in the enum declaration, it is assigned the value zero. - // Otherwise, it is assigned the value of the immediately preceding member plus one, and an error - // occurs if the immediately preceding member is not a constant enum member. - if (autoValue !== undefined) { - return autoValue; + return typeAsAwaitable.awaitedTypeOfType = awaitedType; + } + // The type was not a promise, so it could not be unwrapped any further. + // As long as the type does not have a callable "then" property, it is + // safe to return the type; otherwise, an error will be reported in + // the call to getNonThenableType and we will return undefined. + // + // An example of a non-promise "thenable" might be: + // + // await { then(): void {} } + // + // The "thenable" does not match the minimal definition for a promise. When + // a Promise/A+-compatible or ES6 promise tries to adopt this value, the promise + // will never settle. We treat this as an error to help flag an early indicator + // of a runtime problem. If the user wants to return this value from an async + // function, they would need to wrap it in some other value. If they want it to + // be treated as a promise, they can cast to . + const thenFunction = getTypeOfPropertyOfType(type, ("then" as __String)); + if (thenFunction && getSignaturesOfType(thenFunction, SignatureKind.Call).length > 0) { + if (errorNode) { + if (!diagnosticMessage) + return Debug.fail(); + error(errorNode, diagnosticMessage, arg0); } - error(member.name, Diagnostics.Enum_member_must_have_initializer); return undefined; } - - function computeConstantValue(member: EnumMember): string | number | undefined { - const enumKind = getEnumKind(getSymbolOfNode(member.parent)); - const isConstEnum = isEnumConst(member.parent); - const initializer = member.initializer!; - const value = enumKind === EnumKind.Literal && !isLiteralEnumMember(member) ? undefined : evaluate(initializer); - if (value !== undefined) { - if (isConstEnum && typeof value === "number" && !isFinite(value)) { - error(initializer, isNaN(value) ? - Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN : - Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value); - } - } - else if (enumKind === EnumKind.Literal) { - error(initializer, Diagnostics.Computed_values_are_not_permitted_in_an_enum_with_string_valued_members); - return 0; + return typeAsAwaitable.awaitedTypeOfType = type; + } + /** + * Checks the return type of an async function to ensure it is a compatible + * Promise implementation. + * + * This checks that an async function has a valid Promise-compatible return type. + * An async function has a valid Promise-compatible return type if the resolved value + * of the return type has a construct signature that takes in an `initializer` function + * that in turn supplies a `resolve` function as one of its arguments and results in an + * object with a callable `then` signature. + * + * @param node The signature to check + */ + function checkAsyncFunctionReturnType(node: FunctionLikeDeclaration | MethodSignature, returnTypeNode: TypeNode) { + // As part of our emit for an async function, we will need to emit the entity name of + // the return type annotation as an expression. To meet the necessary runtime semantics + // for __awaiter, we must also check that the type of the declaration (e.g. the static + // side or "constructor" of the promise type) is compatible `PromiseConstructorLike`. + // + // An example might be (from lib.es6.d.ts): + // + // interface Promise { ... } + // interface PromiseConstructor { + // new (...): Promise; + // } + // declare var Promise: PromiseConstructor; + // + // When an async function declares a return type annotation of `Promise`, we + // need to get the type of the `Promise` variable declaration above, which would + // be `PromiseConstructor`. + // + // The same case applies to a class: + // + // declare class Promise { + // constructor(...); + // then(...): Promise; + // } + // + const returnType = getTypeFromTypeNode(returnTypeNode); + if (languageVersion >= ScriptTarget.ES2015) { + if (returnType === errorType) { + return; } - else if (isConstEnum) { - error(initializer, Diagnostics.const_enum_member_initializers_can_only_contain_literal_values_and_other_computed_enum_values); + const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true); + if (globalPromiseType !== emptyGenericType && !isReferenceToType(returnType, globalPromiseType)) { + // The promise type was not a valid type reference to the global promise type, so we + // report an error and return the unknown type. + error(returnTypeNode, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type); + return; } - else if (member.parent.flags & NodeFlags.Ambient) { - error(initializer, Diagnostics.In_ambient_enum_declarations_member_initializer_must_be_constant_expression); + } + else { + // Always mark the type node as referenced if it points to a value + markTypeNodeAsReferenced(returnTypeNode); + if (returnType === errorType) { + return; } - else { - // Only here do we need to check that the initializer is assignable to the enum type. - checkTypeAssignableTo(checkExpression(initializer), getDeclaredTypeOfSymbol(getSymbolOfNode(member.parent)), initializer, /*headMessage*/ undefined); + const promiseConstructorName = getEntityNameFromTypeNode(returnTypeNode); + if (promiseConstructorName === undefined) { + error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, typeToString(returnType)); + return; } - return value; - - function evaluate(expr: Expression): string | number | undefined { - switch (expr.kind) { - case SyntaxKind.PrefixUnaryExpression: - const value = evaluate((expr).operand); - if (typeof value === "number") { - switch ((expr).operator) { - case SyntaxKind.PlusToken: return value; - case SyntaxKind.MinusToken: return -value; - case SyntaxKind.TildeToken: return ~value; - } - } - break; - case SyntaxKind.BinaryExpression: - const left = evaluate((expr).left); - const right = evaluate((expr).right); - if (typeof left === "number" && typeof right === "number") { - switch ((expr).operatorToken.kind) { - case SyntaxKind.BarToken: return left | right; - case SyntaxKind.AmpersandToken: return left & right; - case SyntaxKind.GreaterThanGreaterThanToken: return left >> right; - case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: return left >>> right; - case SyntaxKind.LessThanLessThanToken: return left << right; - case SyntaxKind.CaretToken: return left ^ right; - case SyntaxKind.AsteriskToken: return left * right; - case SyntaxKind.SlashToken: return left / right; - case SyntaxKind.PlusToken: return left + right; - case SyntaxKind.MinusToken: return left - right; - case SyntaxKind.PercentToken: return left % right; - case SyntaxKind.AsteriskAsteriskToken: return left ** right; - } - } - else if (typeof left === "string" && typeof right === "string" && (expr).operatorToken.kind === SyntaxKind.PlusToken) { - return left + right; - } - break; - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - return (expr).text; - case SyntaxKind.NumericLiteral: - checkGrammarNumericLiteral(expr); - return +(expr).text; - case SyntaxKind.ParenthesizedExpression: - return evaluate((expr).expression); - case SyntaxKind.Identifier: - const identifier = expr; - if (isInfinityOrNaNString(identifier.escapedText)) { - return +(identifier.escapedText); - } - return nodeIsMissing(expr) ? 0 : evaluateEnumMember(expr, getSymbolOfNode(member.parent), identifier.escapedText); - case SyntaxKind.ElementAccessExpression: - case SyntaxKind.PropertyAccessExpression: - const ex = expr; - if (isConstantMemberAccess(ex)) { - const type = getTypeOfExpression(ex.expression); - if (type.symbol && type.symbol.flags & SymbolFlags.Enum) { - let name: __String; - if (ex.kind === SyntaxKind.PropertyAccessExpression) { - name = ex.name.escapedText; - } - else { - name = escapeLeadingUnderscores(cast(ex.argumentExpression, isLiteralExpression).text); - } - return evaluateEnumMember(expr, type.symbol, name); - } - } - break; + const promiseConstructorSymbol = resolveEntityName(promiseConstructorName, SymbolFlags.Value, /*ignoreErrors*/ true); + const promiseConstructorType = promiseConstructorSymbol ? getTypeOfSymbol(promiseConstructorSymbol) : errorType; + if (promiseConstructorType === errorType) { + if (promiseConstructorName.kind === SyntaxKind.Identifier && promiseConstructorName.escapedText === "Promise" && getTargetType(returnType) === getGlobalPromiseType(/*reportErrors*/ false)) { + error(returnTypeNode, Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option); } - return undefined; - } - - function evaluateEnumMember(expr: Expression, enumSymbol: Symbol, name: __String) { - const memberSymbol = enumSymbol.exports!.get(name); - if (memberSymbol) { - const declaration = memberSymbol.valueDeclaration; - if (declaration !== member) { - if (isBlockScopedNameDeclaredBeforeUse(declaration, member)) { - return getEnumMemberValue(declaration as EnumMember); - } - error(expr, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums); - return 0; - } + else { + error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName)); } - return undefined; + return; } - } - - function isConstantMemberAccess(node: Expression): boolean { - return node.kind === SyntaxKind.Identifier || - node.kind === SyntaxKind.PropertyAccessExpression && isConstantMemberAccess((node).expression) || - node.kind === SyntaxKind.ElementAccessExpression && isConstantMemberAccess((node).expression) && - isStringLiteralLike((node).argumentExpression); - } - - function checkEnumDeclaration(node: EnumDeclaration) { - if (!produceDiagnostics) { + const globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType(/*reportErrors*/ true); + if (globalPromiseConstructorLikeType === emptyObjectType) { + // If we couldn't resolve the global PromiseConstructorLike type we cannot verify + // compatibility with __awaiter. + error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName)); return; } - - // Grammar checking - checkGrammarDecoratorsAndModifiers(node); - - checkTypeNameIsReserved(node.name, Diagnostics.Enum_name_cannot_be_0); - checkCollisionWithRequireExportsInGeneratedCode(node, node.name); - checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name); - checkExportsOnMergedDeclarations(node); - - computeEnumMemberValues(node); - - // Spec 2014 - Section 9.3: - // It isn't possible for one enum declaration to continue the automatic numbering sequence of another, - // and when an enum type has multiple declarations, only one declaration is permitted to omit a value - // for the first member. - // - // Only perform this check once per symbol - const enumSymbol = getSymbolOfNode(node); - const firstDeclaration = getDeclarationOfKind(enumSymbol, node.kind); - if (node === firstDeclaration) { - if (enumSymbol.declarations.length > 1) { - const enumIsConst = isEnumConst(node); - // check that const is placed\omitted on all enum declarations - forEach(enumSymbol.declarations, decl => { - if (isEnumDeclaration(decl) && isEnumConst(decl) !== enumIsConst) { - error(getNameOfDeclaration(decl), Diagnostics.Enum_declarations_must_all_be_const_or_non_const); - } - }); - } - - let seenEnumMissingInitialInitializer = false; - forEach(enumSymbol.declarations, declaration => { - // return true if we hit a violation of the rule, false otherwise - if (declaration.kind !== SyntaxKind.EnumDeclaration) { - return false; - } - - const enumDeclaration = declaration; - if (!enumDeclaration.members.length) { - return false; - } - - const firstEnumMember = enumDeclaration.members[0]; - if (!firstEnumMember.initializer) { - if (seenEnumMissingInitialInitializer) { - error(firstEnumMember.name, Diagnostics.In_an_enum_with_multiple_declarations_only_one_declaration_can_omit_an_initializer_for_its_first_enum_element); - } - else { - seenEnumMissingInitialInitializer = true; - } - } - }); + if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value)) { + return; + } + // Verify there is no local declaration that could collide with the promise constructor. + const rootName = promiseConstructorName && getFirstIdentifier(promiseConstructorName); + const collidingSymbol = getSymbol((node.locals!), rootName.escapedText, SymbolFlags.Value); + if (collidingSymbol) { + error(collidingSymbol.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions, idText(rootName), entityNameToString(promiseConstructorName)); + return; } } - - function getFirstNonAmbientClassOrFunctionDeclaration(symbol: Symbol): Declaration | undefined { - const declarations = symbol.declarations; - for (const declaration of declarations) { - if ((declaration.kind === SyntaxKind.ClassDeclaration || - (declaration.kind === SyntaxKind.FunctionDeclaration && nodeIsPresent((declaration).body))) && - !(declaration.flags & NodeFlags.Ambient)) { - return declaration; - } + checkAwaitedType(returnType, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + } + /** Check a decorator */ + function checkDecorator(node: Decorator): void { + const signature = getResolvedSignature(node); + const returnType = getReturnTypeOfSignature(signature); + if (returnType.flags & TypeFlags.Any) { + return; + } + let expectedReturnType: ts.Type; + const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node); + let errorInfo: DiagnosticMessageChain | undefined; + switch (node.parent.kind) { + case SyntaxKind.ClassDeclaration: + const classSymbol = getSymbolOfNode(node.parent); + const classConstructorType = getTypeOfSymbol(classSymbol); + expectedReturnType = getUnionType([classConstructorType, voidType]); + break; + case SyntaxKind.Parameter: + expectedReturnType = voidType; + errorInfo = chainDiagnosticMessages( + /*details*/ undefined, Diagnostics.The_return_type_of_a_parameter_decorator_function_must_be_either_void_or_any); + break; + case SyntaxKind.PropertyDeclaration: + expectedReturnType = voidType; + errorInfo = chainDiagnosticMessages( + /*details*/ undefined, Diagnostics.The_return_type_of_a_property_decorator_function_must_be_either_void_or_any); + break; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + const methodType = getTypeOfNode(node.parent); + const descriptorType = createTypedPropertyDescriptorType(methodType); + expectedReturnType = getUnionType([descriptorType, voidType]); + break; + default: + return Debug.fail(); + } + checkTypeAssignableTo(returnType, expectedReturnType, node, headMessage, () => errorInfo); + } + /** + * If a TypeNode can be resolved to a value symbol imported from an external module, it is + * marked as referenced to prevent import elision. + */ + function markTypeNodeAsReferenced(node: TypeNode) { + markEntityNameOrEntityExpressionAsReference(node && getEntityNameFromTypeNode(node)); + } + function markEntityNameOrEntityExpressionAsReference(typeName: EntityNameOrEntityNameExpression | undefined) { + if (!typeName) + return; + const rootName = getFirstIdentifier(typeName); + const meaning = (typeName.kind === SyntaxKind.Identifier ? SymbolFlags.Type : SymbolFlags.Namespace) | SymbolFlags.Alias; + const rootSymbol = resolveName(rootName, rootName.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isRefernce*/ true); + if (rootSymbol + && rootSymbol.flags & SymbolFlags.Alias + && symbolIsValue(rootSymbol) + && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol))) { + markAliasSymbolAsReferenced(rootSymbol); + } + } + /** + * This function marks the type used for metadata decorator as referenced if it is import + * from external module. + * This is different from markTypeNodeAsReferenced because it tries to simplify type nodes in + * union and intersection type + * @param node + */ + function markDecoratorMedataDataTypeNodeAsReferenced(node: TypeNode | undefined): void { + const entityName = getEntityNameForDecoratorMetadata(node); + if (entityName && isEntityName(entityName)) { + markEntityNameOrEntityExpressionAsReference(entityName); + } + } + function getEntityNameForDecoratorMetadata(node: TypeNode | undefined): EntityName | undefined { + if (node) { + switch (node.kind) { + case SyntaxKind.IntersectionType: + case SyntaxKind.UnionType: + return getEntityNameForDecoratorMetadataFromTypeList((node).types); + case SyntaxKind.ConditionalType: + return getEntityNameForDecoratorMetadataFromTypeList([(node).trueType, (node).falseType]); + case SyntaxKind.ParenthesizedType: + return getEntityNameForDecoratorMetadata((node).type); + case SyntaxKind.TypeReference: + return (node).typeName; } - return undefined; } - - function inSameLexicalScope(node1: Node, node2: Node) { - const container1 = getEnclosingBlockScopeContainer(node1); - const container2 = getEnclosingBlockScopeContainer(node2); - if (isGlobalSourceFile(container1)) { - return isGlobalSourceFile(container2); + } + function getEntityNameForDecoratorMetadataFromTypeList(types: readonly TypeNode[]): EntityName | undefined { + let commonEntityName: EntityName | undefined; + for (let typeNode of types) { + while (typeNode.kind === SyntaxKind.ParenthesizedType) { + typeNode = (typeNode as ParenthesizedTypeNode).type; // Skip parens if need be + } + if (typeNode.kind === SyntaxKind.NeverKeyword) { + continue; // Always elide `never` from the union/intersection if possible + } + if (!strictNullChecks && (typeNode.kind === SyntaxKind.NullKeyword || typeNode.kind === SyntaxKind.UndefinedKeyword)) { + continue; // Elide null and undefined from unions for metadata, just like what we did prior to the implementation of strict null checks + } + const individualEntityName = getEntityNameForDecoratorMetadata(typeNode); + if (!individualEntityName) { + // Individual is something like string number + // So it would be serialized to either that type or object + // Safe to return here + return undefined; } - else if (isGlobalSourceFile(container2)) { - return false; + if (commonEntityName) { + // Note this is in sync with the transformation that happens for type node. + // Keep this in sync with serializeUnionOrIntersectionType + // Verify if they refer to same entity and is identifier + // return undefined if they dont match because we would emit object + if (!isIdentifier(commonEntityName) || + !isIdentifier(individualEntityName) || + commonEntityName.escapedText !== individualEntityName.escapedText) { + return undefined; + } } else { - return container1 === container2; + commonEntityName = individualEntityName; } } - - function checkModuleDeclaration(node: ModuleDeclaration) { - if (produceDiagnostics) { - // Grammar checking - const isGlobalAugmentation = isGlobalScopeAugmentation(node); - const inAmbientContext = node.flags & NodeFlags.Ambient; - if (isGlobalAugmentation && !inAmbientContext) { - error(node.name, Diagnostics.Augmentations_for_the_global_scope_should_have_declare_modifier_unless_they_appear_in_already_ambient_context); - } - - const isAmbientExternalModule = isAmbientModule(node); - const contextErrorMessage = isAmbientExternalModule - ? Diagnostics.An_ambient_module_declaration_is_only_allowed_at_the_top_level_in_a_file - : Diagnostics.A_namespace_declaration_is_only_allowed_in_a_namespace_or_module; - if (checkGrammarModuleElementContext(node, contextErrorMessage)) { - // If we hit a module declaration in an illegal context, just bail out to avoid cascading errors. - return; - } - - if (!checkGrammarDecoratorsAndModifiers(node)) { - if (!inAmbientContext && node.name.kind === SyntaxKind.StringLiteral) { - grammarErrorOnNode(node.name, Diagnostics.Only_ambient_modules_can_use_quoted_names); - } - } - - if (isIdentifier(node.name)) { - checkCollisionWithRequireExportsInGeneratedCode(node, node.name); - checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name); - } - - checkExportsOnMergedDeclarations(node); - const symbol = getSymbolOfNode(node); - - // The following checks only apply on a non-ambient instantiated module declaration. - if (symbol.flags & SymbolFlags.ValueModule - && !inAmbientContext - && symbol.declarations.length > 1 - && isInstantiatedModule(node, !!compilerOptions.preserveConstEnums || !!compilerOptions.isolatedModules)) { - const firstNonAmbientClassOrFunc = getFirstNonAmbientClassOrFunctionDeclaration(symbol); - if (firstNonAmbientClassOrFunc) { - if (getSourceFileOfNode(node) !== getSourceFileOfNode(firstNonAmbientClassOrFunc)) { - error(node.name, Diagnostics.A_namespace_declaration_cannot_be_in_a_different_file_from_a_class_or_function_with_which_it_is_merged); - } - else if (node.pos < firstNonAmbientClassOrFunc.pos) { - error(node.name, Diagnostics.A_namespace_declaration_cannot_be_located_prior_to_a_class_or_function_with_which_it_is_merged); + return commonEntityName; + } + function getParameterTypeNodeForDecoratorCheck(node: ParameterDeclaration): TypeNode | undefined { + const typeNode = getEffectiveTypeAnnotationNode(node); + return isRestParameter(node) ? getRestParameterElementType(typeNode) : typeNode; + } + /** Check the decorators of a node */ + function checkDecorators(node: Node): void { + if (!node.decorators) { + return; + } + // skip this check for nodes that cannot have decorators. These should have already had an error reported by + // checkGrammarDecorators. + if (!nodeCanBeDecorated(node, node.parent, node.parent.parent)) { + return; + } + if (!compilerOptions.experimentalDecorators) { + error(node, Diagnostics.Experimental_support_for_decorators_is_a_feature_that_is_subject_to_change_in_a_future_release_Set_the_experimentalDecorators_option_in_your_tsconfig_or_jsconfig_to_remove_this_warning); + } + const firstDecorator = node.decorators[0]; + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Decorate); + if (node.kind === SyntaxKind.Parameter) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Param); + } + if (compilerOptions.emitDecoratorMetadata) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Metadata); + // we only need to perform these checks if we are emitting serialized type metadata for the target of a decorator. + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + const constructor = getFirstConstructorWithBody((node)); + if (constructor) { + for (const parameter of constructor.parameters) { + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); } } - - // if the module merges with a class declaration in the same lexical scope, - // we need to track this to ensure the correct emit. - const mergedClass = getDeclarationOfKind(symbol, SyntaxKind.ClassDeclaration); - if (mergedClass && - inSameLexicalScope(node, mergedClass)) { - getNodeLinks(node).flags |= NodeCheckFlags.LexicalModuleMergesWithClass; + break; + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; + const otherAccessor = getDeclarationOfKind(getSymbolOfNode((node as AccessorDeclaration)), otherKind); + markDecoratorMedataDataTypeNodeAsReferenced(getAnnotatedAccessorTypeNode((node as AccessorDeclaration)) || otherAccessor && getAnnotatedAccessorTypeNode(otherAccessor)); + break; + case SyntaxKind.MethodDeclaration: + for (const parameter of (node).parameters) { + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); } - } - - if (isAmbientExternalModule) { - if (isExternalModuleAugmentation(node)) { - // body of the augmentation should be checked for consistency only if augmentation was applied to its target (either global scope or module) - // otherwise we'll be swamped in cascading errors. - // We can detect if augmentation was applied using following rules: - // - augmentation for a global scope is always applied - // - augmentation for some external module is applied if symbol for augmentation is merged (it was combined with target module). - const checkBody = isGlobalAugmentation || (getSymbolOfNode(node).flags & SymbolFlags.Transient); - if (checkBody && node.body) { - for (const statement of node.body.statements) { - checkModuleAugmentationElement(statement, isGlobalAugmentation); - } - } + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode((node))); + break; + case SyntaxKind.PropertyDeclaration: + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveTypeAnnotationNode((node))); + break; + case SyntaxKind.Parameter: + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck((node))); + const containingSignature = (node as ParameterDeclaration).parent; + for (const parameter of containingSignature.parameters) { + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); } - else if (isGlobalSourceFile(node.parent)) { - if (isGlobalAugmentation) { - error(node.name, Diagnostics.Augmentations_for_the_global_scope_can_only_be_directly_nested_in_external_modules_or_ambient_module_declarations); - } - else if (isExternalModuleNameRelative(getTextOfIdentifierOrLiteral(node.name))) { - error(node.name, Diagnostics.Ambient_module_declaration_cannot_specify_relative_module_name); - } + break; + } + } + forEach(node.decorators, checkDecorator); + } + function checkFunctionDeclaration(node: FunctionDeclaration): void { + if (produceDiagnostics) { + checkFunctionOrMethodDeclaration(node); + checkGrammarForGenerator(node); + checkCollisionWithRequireExportsInGeneratedCode(node, node.name!); + checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name!); + } + } + function checkJSDocTypeAliasTag(node: JSDocTypedefTag | JSDocCallbackTag) { + if (!node.typeExpression) { + // If the node had `@property` tags, `typeExpression` would have been set to the first property tag. + error(node.name, Diagnostics.JSDoc_typedef_tag_should_either_have_a_type_annotation_or_be_followed_by_property_or_member_tags); + } + if (node.name) { + checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); + } + checkSourceElement(node.typeExpression); + } + function checkJSDocTemplateTag(node: JSDocTemplateTag): void { + checkSourceElement(node.constraint); + for (const tp of node.typeParameters) { + checkSourceElement(tp); + } + } + function checkJSDocTypeTag(node: JSDocTypeTag) { + checkSourceElement(node.typeExpression); + } + function checkJSDocParameterTag(node: JSDocParameterTag) { + checkSourceElement(node.typeExpression); + if (!getParameterSymbolFromJSDoc(node)) { + const decl = getHostSignatureFromJSDoc(node); + // don't issue an error for invalid hosts -- just functions -- + // and give a better error message when the host function mentions `arguments` + // but the tag doesn't have an array type + if (decl) { + const i = getJSDocTags(decl).filter(isJSDocParameterTag).indexOf(node); + if (i > -1 && i < decl.parameters.length && isBindingPattern(decl.parameters[i].name)) { + return; + } + if (!containsArgumentsReference(decl)) { + if (isQualifiedName(node.name)) { + error(node.name, Diagnostics.Qualified_name_0_is_not_allowed_without_a_leading_param_object_1, entityNameToString(node.name), entityNameToString(node.name.left)); } else { - if (isGlobalAugmentation) { - error(node.name, Diagnostics.Augmentations_for_the_global_scope_can_only_be_directly_nested_in_external_modules_or_ambient_module_declarations); - } - else { - // Node is not an augmentation and is not located on the script level. - // This means that this is declaration of ambient module that is located in other module or namespace which is prohibited. - error(node.name, Diagnostics.Ambient_modules_cannot_be_nested_in_other_modules_or_namespaces); - } + error(node.name, Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name, idText(node.name)); } } + else if (findLast(getJSDocTags(decl), isJSDocParameterTag) === node && + node.typeExpression && node.typeExpression.type && + !isArrayType(getTypeFromTypeNode(node.typeExpression.type))) { + error(node.name, Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name_It_would_match_arguments_if_it_had_an_array_type, idText(node.name.kind === SyntaxKind.QualifiedName ? node.name.right : node.name)); + } } - - if (node.body) { - checkSourceElement(node.body); - if (!isGlobalScopeAugmentation(node)) { - registerForUnusedIdentifiersCheck(node); + } + } + function checkJSDocFunctionType(node: JSDocFunctionType): void { + if (produceDiagnostics && !node.type && !isJSDocConstructSignature(node)) { + reportImplicitAny(node, anyType); + } + checkSignatureDeclaration(node); + } + function checkJSDocAugmentsTag(node: JSDocAugmentsTag): void { + const classLike = getJSDocHost(node); + if (!isClassDeclaration(classLike) && !isClassExpression(classLike)) { + error(classLike, Diagnostics.JSDoc_0_is_not_attached_to_a_class, idText(node.tagName)); + return; + } + const augmentsTags = getJSDocTags(classLike).filter(isJSDocAugmentsTag); + Debug.assert(augmentsTags.length > 0); + if (augmentsTags.length > 1) { + error(augmentsTags[1], Diagnostics.Class_declarations_cannot_have_more_than_one_augments_or_extends_tag); + } + const name = getIdentifierFromEntityNameExpression(node.class.expression); + const extend = getClassExtendsHeritageElement(classLike); + if (extend) { + const className = getIdentifierFromEntityNameExpression(extend.expression); + if (className && name.escapedText !== className.escapedText) { + error(name, Diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, idText(node.tagName), idText(name), idText(className)); + } + } + } + function getIdentifierFromEntityNameExpression(node: Identifier | PropertyAccessExpression): Identifier; + function getIdentifierFromEntityNameExpression(node: Expression): Identifier | undefined; + function getIdentifierFromEntityNameExpression(node: Expression): Identifier | undefined { + switch (node.kind) { + case SyntaxKind.Identifier: + return node as Identifier; + case SyntaxKind.PropertyAccessExpression: + return (node as PropertyAccessExpression).name; + default: + return undefined; + } + } + function checkFunctionOrMethodDeclaration(node: FunctionDeclaration | MethodDeclaration | MethodSignature): void { + checkDecorators(node); + checkSignatureDeclaration(node); + const functionFlags = getFunctionFlags(node); + // Do not use hasDynamicName here, because that returns false for well known symbols. + // We want to perform checkComputedPropertyName for all computed properties, including + // well known symbols. + if (node.name && node.name.kind === SyntaxKind.ComputedPropertyName) { + // This check will account for methods in class/interface declarations, + // as well as accessors in classes/object literals + checkComputedPropertyName(node.name); + } + if (!hasNonBindableDynamicName(node)) { + // first we want to check the local symbol that contain this declaration + // - if node.localSymbol !== undefined - this is current declaration is exported and localSymbol points to the local symbol + // - if node.localSymbol === undefined - this node is non-exported so we can just pick the result of getSymbolOfNode + const symbol = getSymbolOfNode(node); + const localSymbol = node.localSymbol || symbol; + // Since the javascript won't do semantic analysis like typescript, + // if the javascript file comes before the typescript file and both contain same name functions, + // checkFunctionOrConstructorSymbol wouldn't be called if we didnt ignore javascript function. + const firstDeclaration = find(localSymbol.declarations, + // Get first non javascript function declaration + declaration => declaration.kind === node.kind && !(declaration.flags & NodeFlags.JavaScriptFile)); + // Only type check the symbol once + if (node === firstDeclaration) { + checkFunctionOrConstructorSymbol(localSymbol); + } + if (symbol.parent) { + // run check once for the first declaration + if (getDeclarationOfKind(symbol, node.kind) === node) { + // run check on export symbol to check that modifiers agree across all exported declarations + checkFunctionOrConstructorSymbol(symbol); } } } - - function checkModuleAugmentationElement(node: Node, isGlobalAugmentation: boolean): void { + const body = node.kind === SyntaxKind.MethodSignature ? undefined : node.body; + checkSourceElement(body); + checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, getReturnTypeFromAnnotation(node)); + if (produceDiagnostics && !getEffectiveReturnTypeNode(node)) { + // Report an implicit any error if there is no body, no explicit return type, and node is not a private method + // in an ambient context + if (nodeIsMissing(body) && !isPrivateWithinAmbient(node)) { + reportImplicitAny(node, anyType); + } + if (functionFlags & FunctionFlags.Generator && nodeIsPresent(body)) { + // A generator with a body and no type annotation can still cause errors. It can error if the + // yielded values have no common supertype, or it can give an implicit any error if it has no + // yielded values. The only way to trigger these errors is to try checking its return type. + getReturnTypeOfSignature(getSignatureFromDeclaration(node)); + } + } + // A js function declaration can have a @type tag instead of a return type node, but that type must have a call signature + if (isInJSFile(node)) { + const typeTag = getJSDocTypeTag(node); + if (typeTag && typeTag.typeExpression && !getContextualCallSignature(getTypeFromTypeNode(typeTag.typeExpression), node)) { + error(typeTag, Diagnostics.The_type_of_a_function_declaration_must_match_the_function_s_signature); + } + } + } + function registerForUnusedIdentifiersCheck(node: PotentiallyUnusedIdentifier): void { + // May be in a call such as getTypeOfNode that happened to call this. But potentiallyUnusedIdentifiers is only defined in the scope of `checkSourceFile`. + if (produceDiagnostics) { + const sourceFile = getSourceFileOfNode(node); + let potentiallyUnusedIdentifiers = allPotentiallyUnusedIdentifiers.get(sourceFile.path); + if (!potentiallyUnusedIdentifiers) { + potentiallyUnusedIdentifiers = []; + allPotentiallyUnusedIdentifiers.set(sourceFile.path, potentiallyUnusedIdentifiers); + } + // TODO: GH#22580 + // Debug.assert(addToSeen(seenPotentiallyUnusedIdentifiers, getNodeId(node)), "Adding potentially-unused identifier twice"); + potentiallyUnusedIdentifiers.push(node); + } + } + type PotentiallyUnusedIdentifier = SourceFile | ModuleDeclaration | ClassLikeDeclaration | InterfaceDeclaration | Block | CaseBlock | ForStatement | ForInStatement | ForOfStatement | Exclude | TypeAliasDeclaration | InferTypeNode; + function checkUnusedIdentifiers(potentiallyUnusedIdentifiers: readonly PotentiallyUnusedIdentifier[], addDiagnostic: AddUnusedDiagnostic) { + for (const node of potentiallyUnusedIdentifiers) { switch (node.kind) { - case SyntaxKind.VariableStatement: - // error each individual name in variable statement instead of marking the entire variable statement - for (const decl of (node).declarationList.declarations) { - checkModuleAugmentationElement(decl, isGlobalAugmentation); + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + checkUnusedClassMembers(node, addDiagnostic); + checkUnusedTypeParameters(node, addDiagnostic); + break; + case SyntaxKind.SourceFile: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.Block: + case SyntaxKind.CaseBlock: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + checkUnusedLocalsAndParameters(node, addDiagnostic); + break; + case SyntaxKind.Constructor: + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ArrowFunction: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + if (node.body) { // Don't report unused parameters in overloads + checkUnusedLocalsAndParameters(node, addDiagnostic); } + checkUnusedTypeParameters(node, addDiagnostic); break; - case SyntaxKind.ExportAssignment: - case SyntaxKind.ExportDeclaration: - grammarErrorOnFirstToken(node, Diagnostics.Exports_and_export_assignments_are_not_permitted_in_module_augmentations); + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.InterfaceDeclaration: + checkUnusedTypeParameters(node, addDiagnostic); break; - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ImportDeclaration: - grammarErrorOnFirstToken(node, Diagnostics.Imports_are_not_permitted_in_module_augmentations_Consider_moving_them_to_the_enclosing_external_module); + case SyntaxKind.InferType: + checkUnusedInferTypeParameter(node, addDiagnostic); break; - case SyntaxKind.BindingElement: - case SyntaxKind.VariableDeclaration: - const name = (node).name; - if (isBindingPattern(name)) { - for (const el of name.elements) { - // mark individual names in binding pattern - checkModuleAugmentationElement(el, isGlobalAugmentation); - } + default: + Debug.assertNever(node, "Node should not have been registered for unused identifiers check"); + } + } + } + function errorUnusedLocal(declaration: Declaration, name: string, addDiagnostic: AddUnusedDiagnostic) { + const node = getNameOfDeclaration(declaration) || declaration; + const message = isTypeDeclaration(declaration) ? Diagnostics._0_is_declared_but_never_used : Diagnostics._0_is_declared_but_its_value_is_never_read; + addDiagnostic(declaration, UnusedKind.Local, createDiagnosticForNode(node, message, name)); + } + function isIdentifierThatStartsWithUnderscore(node: Node) { + return isIdentifier(node) && idText(node).charCodeAt(0) === CharacterCodes._; + } + function checkUnusedClassMembers(node: ClassDeclaration | ClassExpression, addDiagnostic: AddUnusedDiagnostic): void { + for (const member of node.members) { + switch (member.kind) { + case SyntaxKind.MethodDeclaration: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + if (member.kind === SyntaxKind.SetAccessor && member.symbol.flags & SymbolFlags.GetAccessor) { + // Already would have reported an error on the getter. break; } - // falls through - case SyntaxKind.ClassDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.TypeAliasDeclaration: - if (isGlobalAugmentation) { - return; + const symbol = getSymbolOfNode(member); + if (!symbol.isReferenced && hasModifier(member, ModifierFlags.Private)) { + addDiagnostic(member, UnusedKind.Local, createDiagnosticForNode((member.name!), Diagnostics._0_is_declared_but_its_value_is_never_read, symbolToString(symbol))); } - const symbol = getSymbolOfNode(node); - if (symbol) { - // module augmentations cannot introduce new names on the top level scope of the module - // this is done it two steps - // 1. quick check - if symbol for node is not merged - this is local symbol to this augmentation - report error - // 2. main check - report error if value declaration of the parent symbol is module augmentation) - let reportError = !(symbol.flags & SymbolFlags.Transient); - if (!reportError) { - // symbol should not originate in augmentation - reportError = !!symbol.parent && isExternalModuleAugmentation(symbol.parent.declarations[0]); + break; + case SyntaxKind.Constructor: + for (const parameter of (member).parameters) { + if (!parameter.symbol.isReferenced && hasModifier(parameter, ModifierFlags.Private)) { + addDiagnostic(parameter, UnusedKind.Local, createDiagnosticForNode(parameter.name, Diagnostics.Property_0_is_declared_but_its_value_is_never_read, symbolName(parameter.symbol))); } } break; + case SyntaxKind.IndexSignature: + case SyntaxKind.SemicolonClassElement: + // Can't be private + break; + default: + Debug.fail(); } } - - function getFirstNonModuleExportsIdentifier(node: EntityNameOrEntityNameExpression): Identifier { - switch (node.kind) { - case SyntaxKind.Identifier: - return node; - case SyntaxKind.QualifiedName: - do { - node = node.left; - } while (node.kind !== SyntaxKind.Identifier); - return node; - case SyntaxKind.PropertyAccessExpression: - do { - if (isModuleExportsAccessExpression(node.expression)) { - return node.name; - } - node = node.expression; - } while (node.kind !== SyntaxKind.Identifier); - return node; - } + } + function checkUnusedInferTypeParameter(node: InferTypeNode, addDiagnostic: AddUnusedDiagnostic): void { + const { typeParameter } = node; + if (isTypeParameterUnused(typeParameter)) { + addDiagnostic(node, UnusedKind.Parameter, createDiagnosticForNode(node, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(typeParameter.name))); } - - function checkExternalImportOrExportDeclaration(node: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration): boolean { - const moduleName = getExternalModuleName(node); - if (!moduleName || nodeIsMissing(moduleName)) { - // Should be a parse error. - return false; - } - if (!isStringLiteral(moduleName)) { - error(moduleName, Diagnostics.String_literal_expected); - return false; - } - const inAmbientExternalModule = node.parent.kind === SyntaxKind.ModuleBlock && isAmbientModule(node.parent.parent); - if (node.parent.kind !== SyntaxKind.SourceFile && !inAmbientExternalModule) { - error(moduleName, node.kind === SyntaxKind.ExportDeclaration ? - Diagnostics.Export_declarations_are_not_permitted_in_a_namespace : - Diagnostics.Import_declarations_in_a_namespace_cannot_reference_a_module); - return false; - } - if (inAmbientExternalModule && isExternalModuleNameRelative(moduleName.text)) { - // we have already reported errors on top level imports\exports in external module augmentations in checkModuleDeclaration - // no need to do this again. - if (!isTopLevelInExternalModuleAugmentation(node)) { - // TypeScript 1.0 spec (April 2013): 12.1.6 - // An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference - // other external modules only through top - level external module names. - // Relative external module names are not permitted. - error(node, Diagnostics.Import_or_export_declaration_in_an_ambient_module_declaration_cannot_reference_module_through_relative_module_name); - return false; + } + function checkUnusedTypeParameters(node: ClassLikeDeclaration | SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration, addDiagnostic: AddUnusedDiagnostic): void { + // Only report errors on the last declaration for the type parameter container; + // this ensures that all uses have been accounted for. + if (last(getSymbolOfNode(node).declarations) !== node) + return; + const typeParameters = getEffectiveTypeParameterDeclarations(node); + const seenParentsWithEveryUnused = new NodeSet(); + for (const typeParameter of typeParameters) { + if (!isTypeParameterUnused(typeParameter)) + continue; + const name = idText(typeParameter.name); + const { parent } = typeParameter; + if (parent.kind !== SyntaxKind.InferType && parent.typeParameters!.every(isTypeParameterUnused)) { + if (seenParentsWithEveryUnused.tryAdd(parent)) { + const range = isJSDocTemplateTag(parent) + // Whole @template tag + ? rangeOfNode(parent) + // Include the `<>` in the error message + : rangeOfTypeParameters((parent.typeParameters!)); + const only = typeParameters.length === 1; + const message = only ? Diagnostics._0_is_declared_but_its_value_is_never_read : Diagnostics.All_type_parameters_are_unused; + const arg0 = only ? name : undefined; + addDiagnostic(typeParameter, UnusedKind.Parameter, createFileDiagnostic(getSourceFileOfNode(parent), range.pos, range.end - range.pos, message, arg0)); } } - return true; - } - - function checkAliasSymbol(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportSpecifier | ExportSpecifier) { - let symbol = getSymbolOfNode(node); - const target = resolveAlias(symbol); - - const shouldSkipWithJSExpandoTargets = symbol.flags & SymbolFlags.Assignment; - if (!shouldSkipWithJSExpandoTargets && target !== unknownSymbol) { - // For external modules symbol represents local symbol for an alias. - // This local symbol will merge any other local declarations (excluding other aliases) - // and symbol.flags will contains combined representation for all merged declaration. - // Based on symbol.flags we can compute a set of excluded meanings (meaning that resolved alias should not have, - // otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export* - // in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names). - symbol = getMergedSymbol(symbol.exportSymbol || symbol); - const excludedMeanings = - (symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue) ? SymbolFlags.Value : 0) | - (symbol.flags & SymbolFlags.Type ? SymbolFlags.Type : 0) | - (symbol.flags & SymbolFlags.Namespace ? SymbolFlags.Namespace : 0); - if (target.flags & excludedMeanings) { - const message = node.kind === SyntaxKind.ExportSpecifier ? - Diagnostics.Export_declaration_conflicts_with_exported_declaration_of_0 : - Diagnostics.Import_declaration_conflicts_with_local_declaration_of_0; - error(node, message, symbolToString(symbol)); - } - - // Don't allow to re-export something with no value side when `--isolatedModules` is set. - if (compilerOptions.isolatedModules - && node.kind === SyntaxKind.ExportSpecifier - && !(target.flags & SymbolFlags.Value) - && !(node.flags & NodeFlags.Ambient)) { - error(node, Diagnostics.Cannot_re_export_a_type_when_the_isolatedModules_flag_is_provided); - } + else { + addDiagnostic(typeParameter, UnusedKind.Parameter, createDiagnosticForNode(typeParameter, Diagnostics._0_is_declared_but_its_value_is_never_read, name)); } } - - function checkImportBinding(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportSpecifier) { - checkCollisionWithRequireExportsInGeneratedCode(node, node.name!); - checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name!); - checkAliasSymbol(node); + } + function isTypeParameterUnused(typeParameter: TypeParameterDeclaration): boolean { + return !((getMergedSymbol(typeParameter.symbol).isReferenced!) & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderscore(typeParameter.name); + } + function addToGroup(map: ts.Map<[K, V[]]>, key: K, value: V, getKey: (key: K) => number | string): void { + const keyString = String(getKey(key)); + const group = map.get(keyString); + if (group) { + group[1].push(value); } - - function checkImportDeclaration(node: ImportDeclaration) { - if (checkGrammarModuleElementContext(node, Diagnostics.An_import_declaration_can_only_be_used_in_a_namespace_or_module)) { - // If we hit an import declaration in an illegal context, just bail out to avoid cascading errors. - return; - } - if (!checkGrammarDecoratorsAndModifiers(node) && hasModifiers(node)) { - grammarErrorOnFirstToken(node, Diagnostics.An_import_declaration_cannot_have_modifiers); - } - if (checkExternalImportOrExportDeclaration(node)) { - const importClause = node.importClause; - if (importClause) { - if (importClause.name) { - checkImportBinding(importClause); - } - if (importClause.namedBindings) { - if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { - checkImportBinding(importClause.namedBindings); - } - else { - const moduleExisted = resolveExternalModuleName(node, node.moduleSpecifier); - if (moduleExisted) { - forEach(importClause.namedBindings.elements, checkImportBinding); - } - } - } - } - } + else { + map.set(keyString, [key, [value]]); } - - function checkImportEqualsDeclaration(node: ImportEqualsDeclaration) { - if (checkGrammarModuleElementContext(node, Diagnostics.An_import_declaration_can_only_be_used_in_a_namespace_or_module)) { - // If we hit an import declaration in an illegal context, just bail out to avoid cascading errors. + } + function tryGetRootParameterDeclaration(node: Node): ParameterDeclaration | undefined { + return tryCast(getRootDeclaration(node), isParameter); + } + function checkUnusedLocalsAndParameters(nodeWithLocals: Node, addDiagnostic: AddUnusedDiagnostic): void { + // Ideally we could use the ImportClause directly as a key, but must wait until we have full ES6 maps. So must store key along with value. + const unusedImports = createMap<[ImportClause, ImportedDeclaration[]]>(); + const unusedDestructures = createMap<[ObjectBindingPattern, BindingElement[]]>(); + const unusedVariables = createMap<[VariableDeclarationList, VariableDeclaration[]]>(); + nodeWithLocals.locals!.forEach(local => { + // If it's purely a type parameter, ignore, will be checked in `checkUnusedTypeParameters`. + // If it's a type parameter merged with a parameter, check if the parameter-side is used. + if (local.flags & SymbolFlags.TypeParameter ? !(local.flags & SymbolFlags.Variable && !((local.isReferenced!) & SymbolFlags.Variable)) : local.isReferenced || local.exportSymbol) { return; } - - checkGrammarDecoratorsAndModifiers(node); - if (isInternalModuleImportEqualsDeclaration(node) || checkExternalImportOrExportDeclaration(node)) { - checkImportBinding(node); - if (hasModifier(node, ModifierFlags.Export)) { - markExportAsReferenced(node); - } - if (node.moduleReference.kind !== SyntaxKind.ExternalModuleReference) { - const target = resolveAlias(getSymbolOfNode(node)); - if (target !== unknownSymbol) { - if (target.flags & SymbolFlags.Value) { - // Target is a value symbol, check that it is not hidden by a local declaration with the same name - const moduleName = getFirstIdentifier(node.moduleReference); - if (!(resolveEntityName(moduleName, SymbolFlags.Value | SymbolFlags.Namespace)!.flags & SymbolFlags.Namespace)) { - error(moduleName, Diagnostics.Module_0_is_hidden_by_a_local_declaration_with_the_same_name, declarationNameToString(moduleName)); - } - } - if (target.flags & SymbolFlags.Type) { - checkTypeNameIsReserved(node.name, Diagnostics.Import_name_cannot_be_0); - } - } + for (const declaration of local.declarations) { + if (isAmbientModule(declaration) || + (isVariableDeclaration(declaration) && isForInOrOfStatement(declaration.parent.parent) || isImportedDeclaration(declaration)) && isIdentifierThatStartsWithUnderscore(declaration.name!)) { + continue; } - else { - if (moduleKind >= ModuleKind.ES2015 && !(node.flags & NodeFlags.Ambient)) { - // Import equals declaration is deprecated in es6 or above - grammarErrorOnNode(node, Diagnostics.Import_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_import_Asterisk_as_ns_from_mod_import_a_from_mod_import_d_from_mod_or_another_module_format_instead); - } + if (isImportedDeclaration(declaration)) { + addToGroup(unusedImports, importClauseFromImported(declaration), declaration, getNodeId); } - } - } - - function checkExportDeclaration(node: ExportDeclaration) { - if (checkGrammarModuleElementContext(node, Diagnostics.An_export_declaration_can_only_be_used_in_a_module)) { - // If we hit an export in an illegal context, just bail out to avoid cascading errors. - return; - } - - if (!checkGrammarDecoratorsAndModifiers(node) && hasModifiers(node)) { - grammarErrorOnFirstToken(node, Diagnostics.An_export_declaration_cannot_have_modifiers); - } - - if (!node.moduleSpecifier || checkExternalImportOrExportDeclaration(node)) { - if (node.exportClause) { - // export { x, y } - // export { x, y } from "foo" - forEach(node.exportClause.elements, checkExportSpecifier); - - const inAmbientExternalModule = node.parent.kind === SyntaxKind.ModuleBlock && isAmbientModule(node.parent.parent); - const inAmbientNamespaceDeclaration = !inAmbientExternalModule && node.parent.kind === SyntaxKind.ModuleBlock && - !node.moduleSpecifier && node.flags & NodeFlags.Ambient; - if (node.parent.kind !== SyntaxKind.SourceFile && !inAmbientExternalModule && !inAmbientNamespaceDeclaration) { - error(node, Diagnostics.Export_declarations_are_not_permitted_in_a_namespace); + else if (isBindingElement(declaration) && isObjectBindingPattern(declaration.parent)) { + // In `{ a, ...b }, `a` is considered used since it removes a property from `b`. `b` may still be unused though. + const lastElement = last(declaration.parent.elements); + if (declaration === lastElement || !last(declaration.parent.elements).dotDotDotToken) { + addToGroup(unusedDestructures, declaration.parent, declaration, getNodeId); } } + else if (isVariableDeclaration(declaration)) { + addToGroup(unusedVariables, declaration.parent, declaration, getNodeId); + } else { - // export * from "foo" - const moduleSymbol = resolveExternalModuleName(node, node.moduleSpecifier!); - if (moduleSymbol && hasExportAssignmentSymbol(moduleSymbol)) { - error(node.moduleSpecifier, Diagnostics.Module_0_uses_export_and_cannot_be_used_with_export_Asterisk, symbolToString(moduleSymbol)); + const parameter = local.valueDeclaration && tryGetRootParameterDeclaration(local.valueDeclaration); + const name = local.valueDeclaration && getNameOfDeclaration(local.valueDeclaration); + if (parameter && name) { + if (!isParameterPropertyDeclaration(parameter, parameter.parent) && !parameterIsThisKeyword(parameter) && !isIdentifierThatStartsWithUnderscore(name)) { + addDiagnostic(parameter, UnusedKind.Parameter, createDiagnosticForNode(name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(local))); + } } - - if (moduleKind !== ModuleKind.System && moduleKind !== ModuleKind.ES2015 && moduleKind !== ModuleKind.ESNext) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.ExportStar); + else { + errorUnusedLocal(declaration, symbolName(local), addDiagnostic); } } } - } - - function checkGrammarModuleElementContext(node: Statement, errorMessage: DiagnosticMessage): boolean { - const isInAppropriateContext = node.parent.kind === SyntaxKind.SourceFile || node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.ModuleDeclaration; - if (!isInAppropriateContext) { - grammarErrorOnFirstToken(node, errorMessage); + }); + unusedImports.forEach(([importClause, unuseds]) => { + const importDecl = importClause.parent; + const nDeclarations = (importClause.name ? 1 : 0) + + (importClause.namedBindings ? + (importClause.namedBindings.kind === SyntaxKind.NamespaceImport ? 1 : importClause.namedBindings.elements.length) + : 0); + if (nDeclarations === unuseds.length) { + addDiagnostic(importDecl, UnusedKind.Local, unuseds.length === 1 + ? createDiagnosticForNode(importDecl, Diagnostics._0_is_declared_but_its_value_is_never_read, idText((first(unuseds).name!))) + : createDiagnosticForNode(importDecl, Diagnostics.All_imports_in_import_declaration_are_unused)); } - return !isInAppropriateContext; - } - - function checkExportSpecifier(node: ExportSpecifier) { - checkAliasSymbol(node); - if (getEmitDeclarations(compilerOptions)) { - collectLinkedAliases(node.propertyName || node.name, /*setVisibility*/ true); + else { + for (const unused of unuseds) + errorUnusedLocal(unused, idText((unused.name!)), addDiagnostic); } - if (!node.parent.parent.moduleSpecifier) { - const exportedName = node.propertyName || node.name; - // find immediate value referenced by exported name (SymbolFlags.Alias is set so we don't chase down aliases) - const symbol = resolveName(exportedName, exportedName.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, - /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); - if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) { - error(exportedName, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, idText(exportedName)); + }); + unusedDestructures.forEach(([bindingPattern, bindingElements]) => { + const kind = tryGetRootParameterDeclaration(bindingPattern.parent) ? UnusedKind.Parameter : UnusedKind.Local; + if (bindingPattern.elements.length === bindingElements.length) { + if (bindingElements.length === 1 && bindingPattern.parent.kind === SyntaxKind.VariableDeclaration && bindingPattern.parent.parent.kind === SyntaxKind.VariableDeclarationList) { + addToGroup(unusedVariables, bindingPattern.parent.parent, bindingPattern.parent, getNodeId); } else { - markExportAsReferenced(node); - const target = symbol && (symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol); - if (!target || target === unknownSymbol || target.flags & SymbolFlags.Value) { - checkExpressionCached(node.propertyName || node.name); - } + addDiagnostic(bindingPattern, kind, bindingElements.length === 1 + ? createDiagnosticForNode(bindingPattern, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(first(bindingElements).name)) + : createDiagnosticForNode(bindingPattern, Diagnostics.All_destructured_elements_are_unused)); } } - } - - function checkExportAssignment(node: ExportAssignment) { - if (checkGrammarModuleElementContext(node, Diagnostics.An_export_assignment_can_only_be_used_in_a_module)) { - // If we hit an export assignment in an illegal context, just bail out to avoid cascading errors. - return; - } - - const container = node.parent.kind === SyntaxKind.SourceFile ? node.parent : node.parent.parent; - if (container.kind === SyntaxKind.ModuleDeclaration && !isAmbientModule(container)) { - if (node.isExportEquals) { - error(node, Diagnostics.An_export_assignment_cannot_be_used_in_a_namespace); - } - else { - error(node, Diagnostics.A_default_export_can_only_be_used_in_an_ECMAScript_style_module); + else { + for (const e of bindingElements) { + addDiagnostic(e, kind, createDiagnosticForNode(e, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(e.name))); } - - return; - } - // Grammar checking - if (!checkGrammarDecoratorsAndModifiers(node) && hasModifiers(node)) { - grammarErrorOnFirstToken(node, Diagnostics.An_export_assignment_cannot_have_modifiers); } - if (node.expression.kind === SyntaxKind.Identifier) { - const id = node.expression as Identifier; - const sym = resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, node); - if (sym) { - markAliasReferenced(sym, id); - // If not a value, we're interpreting the identifier as a type export, along the lines of (`export { Id as default }`) - const target = sym.flags & SymbolFlags.Alias ? resolveAlias(sym) : sym; - if (target === unknownSymbol || target.flags & SymbolFlags.Value) { - // However if it is a value, we need to check it's being used correctly - checkExpressionCached(node.expression); - } - } - - if (getEmitDeclarations(compilerOptions)) { - collectLinkedAliases(node.expression as Identifier, /*setVisibility*/ true); - } + }); + unusedVariables.forEach(([declarationList, declarations]) => { + if (declarationList.declarations.length === declarations.length) { + addDiagnostic(declarationList, UnusedKind.Local, declarations.length === 1 + ? createDiagnosticForNode(first(declarations).name, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(first(declarations).name)) + : createDiagnosticForNode(declarationList.parent.kind === SyntaxKind.VariableStatement ? declarationList.parent : declarationList, Diagnostics.All_variables_are_unused)); } else { - checkExpressionCached(node.expression); - } - - checkExternalModuleExports(container); - - if ((node.flags & NodeFlags.Ambient) && !isEntityNameExpression(node.expression)) { - grammarErrorOnNode(node.expression, Diagnostics.The_expression_of_an_export_assignment_must_be_an_identifier_or_qualified_name_in_an_ambient_context); - } - - if (node.isExportEquals && !(node.flags & NodeFlags.Ambient)) { - if (moduleKind >= ModuleKind.ES2015) { - // export assignment is not supported in es6 modules - grammarErrorOnNode(node, Diagnostics.Export_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_export_default_or_another_module_format_instead); - } - else if (moduleKind === ModuleKind.System) { - // system modules does not support export assignment - grammarErrorOnNode(node, Diagnostics.Export_assignment_is_not_supported_when_module_flag_is_system); + for (const decl of declarations) { + addDiagnostic(decl, UnusedKind.Local, createDiagnosticForNode(decl, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(decl.name))); } } + }); + } + function bindingNameText(name: BindingName): string { + switch (name.kind) { + case SyntaxKind.Identifier: + return idText(name); + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ObjectBindingPattern: + return bindingNameText(cast(first(name.elements), isBindingElement).name); + default: + return Debug.assertNever(name); } - - function hasExportedMembers(moduleSymbol: Symbol) { - return forEachEntry(moduleSymbol.exports!, (_, id) => id !== "export="); + } + type ImportedDeclaration = ImportClause | ImportSpecifier | NamespaceImport; + function isImportedDeclaration(node: Node): node is ImportedDeclaration { + return node.kind === SyntaxKind.ImportClause || node.kind === SyntaxKind.ImportSpecifier || node.kind === SyntaxKind.NamespaceImport; + } + function importClauseFromImported(decl: ImportedDeclaration): ImportClause { + return decl.kind === SyntaxKind.ImportClause ? decl : decl.kind === SyntaxKind.NamespaceImport ? decl.parent : decl.parent.parent; + } + function checkBlock(node: Block) { + // Grammar checking for SyntaxKind.Block + if (node.kind === SyntaxKind.Block) { + checkGrammarStatementInAmbientContext(node); } - - function checkExternalModuleExports(node: SourceFile | ModuleDeclaration) { - const moduleSymbol = getSymbolOfNode(node); - const links = getSymbolLinks(moduleSymbol); - if (!links.exportsChecked) { - const exportEqualsSymbol = moduleSymbol.exports!.get("export=" as __String); - if (exportEqualsSymbol && hasExportedMembers(moduleSymbol)) { - const declaration = getDeclarationOfAliasSymbol(exportEqualsSymbol) || exportEqualsSymbol.valueDeclaration; - if (!isTopLevelInExternalModuleAugmentation(declaration) && !isInJSFile(declaration)) { - error(declaration, Diagnostics.An_export_assignment_cannot_be_used_in_a_module_with_other_exported_elements); - } - } - // Checks for export * conflicts - const exports = getExportsOfModule(moduleSymbol); - if (exports) { - exports.forEach(({ declarations, flags }, id) => { - if (id === "__export") { - return; - } - // ECMA262: 15.2.1.1 It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries. - // (TS Exceptions: namespaces, function overloads, enums, and interfaces) - if (flags & (SymbolFlags.Namespace | SymbolFlags.Interface | SymbolFlags.Enum)) { - return; - } - const exportedDeclarationsCount = countWhere(declarations, isNotOverloadAndNotAccessor); - if (flags & SymbolFlags.TypeAlias && exportedDeclarationsCount <= 2) { - // it is legal to merge type alias with other values - // so count should be either 1 (just type alias) or 2 (type alias + merged value) - return; - } - if (exportedDeclarationsCount > 1) { - for (const declaration of declarations) { - if (isNotOverload(declaration)) { - diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Cannot_redeclare_exported_variable_0, unescapeLeadingUnderscores(id))); - } - } - } - }); - } - links.exportsChecked = true; - } + if (isFunctionOrModuleBlock(node)) { + const saveFlowAnalysisDisabled = flowAnalysisDisabled; + forEach(node.statements, checkSourceElement); + flowAnalysisDisabled = saveFlowAnalysisDisabled; } - - function checkSourceElement(node: Node | undefined): void { - if (node) { - const saveCurrentNode = currentNode; - currentNode = node; - instantiationCount = 0; - checkSourceElementWorker(node); - currentNode = saveCurrentNode; - } + else { + forEach(node.statements, checkSourceElement); } - - function checkSourceElementWorker(node: Node): void { - if (isInJSFile(node)) { - forEach((node as JSDocContainer).jsDoc, ({ tags }) => forEach(tags, checkSourceElement)); + if (node.locals) { + registerForUnusedIdentifiersCheck(node); + } + } + function checkCollisionWithArgumentsInGeneratedCode(node: SignatureDeclaration) { + // no rest parameters \ declaration context \ overload - no codegen impact + if (languageVersion >= ScriptTarget.ES2015 || compilerOptions.noEmit || !hasRestParameter(node) || node.flags & NodeFlags.Ambient || nodeIsMissing((node).body)) { + return; + } + forEach(node.parameters, p => { + if (p.name && !isBindingPattern(p.name) && p.name.escapedText === argumentsSymbol.escapedName) { + error(p, Diagnostics.Duplicate_identifier_arguments_Compiler_uses_arguments_to_initialize_rest_parameters); } - - const kind = node.kind; - if (cancellationToken) { - // Only bother checking on a few construct kinds. We don't want to be excessively - // hitting the cancellation token on every node we check. - switch (kind) { - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.FunctionDeclaration: - cancellationToken.throwIfCancellationRequested(); + }); + } + function needCollisionCheckForIdentifier(node: Node, identifier: Identifier | undefined, name: string): boolean { + if (!(identifier && identifier.escapedText === name)) { + return false; + } + if (node.kind === SyntaxKind.PropertyDeclaration || + node.kind === SyntaxKind.PropertySignature || + node.kind === SyntaxKind.MethodDeclaration || + node.kind === SyntaxKind.MethodSignature || + node.kind === SyntaxKind.GetAccessor || + node.kind === SyntaxKind.SetAccessor) { + // it is ok to have member named '_super' or '_this' - member access is always qualified + return false; + } + if (node.flags & NodeFlags.Ambient) { + // ambient context - no codegen impact + return false; + } + const root = getRootDeclaration(node); + if (root.kind === SyntaxKind.Parameter && nodeIsMissing((root.parent).body)) { + // just an overload - no codegen impact + return false; + } + return true; + } + // this function will run after checking the source file so 'CaptureThis' is correct for all nodes + function checkIfThisIsCapturedInEnclosingScope(node: Node): void { + findAncestor(node, current => { + if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureThis) { + const isDeclaration = node.kind !== SyntaxKind.Identifier; + if (isDeclaration) { + error(getNameOfDeclaration((node)), Diagnostics.Duplicate_identifier_this_Compiler_uses_variable_declaration_this_to_capture_this_reference); } + else { + error(node, Diagnostics.Expression_resolves_to_variable_declaration_this_that_compiler_uses_to_capture_this_reference); + } + return true; } - if (kind >= SyntaxKind.FirstStatement && kind <= SyntaxKind.LastStatement && node.flowNode && !isReachableFlowNode(node.flowNode)) { - errorOrSuggestion(compilerOptions.allowUnreachableCode === false, node, Diagnostics.Unreachable_code_detected); - } - - switch (kind) { - case SyntaxKind.TypeParameter: - return checkTypeParameter(node); - case SyntaxKind.Parameter: - return checkParameter(node); - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - return checkPropertyDeclaration(node); - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: - return checkSignatureDeclaration(node); - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - return checkMethodDeclaration(node); - case SyntaxKind.Constructor: - return checkConstructorDeclaration(node); - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return checkAccessorDeclaration(node); - case SyntaxKind.TypeReference: - return checkTypeReferenceNode(node); - case SyntaxKind.TypePredicate: - return checkTypePredicate(node); - case SyntaxKind.TypeQuery: - return checkTypeQuery(node); - case SyntaxKind.TypeLiteral: - return checkTypeLiteral(node); - case SyntaxKind.ArrayType: - return checkArrayType(node); - case SyntaxKind.TupleType: - return checkTupleType(node); - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: - return checkUnionOrIntersectionType(node); - case SyntaxKind.ParenthesizedType: - case SyntaxKind.OptionalType: - case SyntaxKind.RestType: - return checkSourceElement((node).type); - case SyntaxKind.ThisType: - return checkThisType(node); - case SyntaxKind.TypeOperator: - return checkTypeOperator(node); - case SyntaxKind.ConditionalType: - return checkConditionalType(node); - case SyntaxKind.InferType: - return checkInferType(node); - case SyntaxKind.ImportType: - return checkImportType(node); - case SyntaxKind.JSDocAugmentsTag: - return checkJSDocAugmentsTag(node as JSDocAugmentsTag); - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocEnumTag: - return checkJSDocTypeAliasTag(node as JSDocTypedefTag); - case SyntaxKind.JSDocTemplateTag: - return checkJSDocTemplateTag(node as JSDocTemplateTag); - case SyntaxKind.JSDocTypeTag: - return checkJSDocTypeTag(node as JSDocTypeTag); - case SyntaxKind.JSDocParameterTag: - return checkJSDocParameterTag(node as JSDocParameterTag); - case SyntaxKind.JSDocFunctionType: - checkJSDocFunctionType(node as JSDocFunctionType); - // falls through - case SyntaxKind.JSDocNonNullableType: - case SyntaxKind.JSDocNullableType: - case SyntaxKind.JSDocAllType: - case SyntaxKind.JSDocUnknownType: - case SyntaxKind.JSDocTypeLiteral: - checkJSDocTypeIsInJsFile(node); - forEachChild(node, checkSourceElement); - return; - case SyntaxKind.JSDocVariadicType: - checkJSDocVariadicType(node as JSDocVariadicType); - return; - case SyntaxKind.JSDocTypeExpression: - return checkSourceElement((node as JSDocTypeExpression).type); - case SyntaxKind.IndexedAccessType: - return checkIndexedAccessType(node); - case SyntaxKind.MappedType: - return checkMappedType(node); - case SyntaxKind.FunctionDeclaration: - return checkFunctionDeclaration(node); - case SyntaxKind.Block: - case SyntaxKind.ModuleBlock: - return checkBlock(node); - case SyntaxKind.VariableStatement: - return checkVariableStatement(node); - case SyntaxKind.ExpressionStatement: - return checkExpressionStatement(node); - case SyntaxKind.IfStatement: - return checkIfStatement(node); - case SyntaxKind.DoStatement: - return checkDoStatement(node); - case SyntaxKind.WhileStatement: - return checkWhileStatement(node); - case SyntaxKind.ForStatement: - return checkForStatement(node); - case SyntaxKind.ForInStatement: - return checkForInStatement(node); - case SyntaxKind.ForOfStatement: - return checkForOfStatement(node); - case SyntaxKind.ContinueStatement: - case SyntaxKind.BreakStatement: - return checkBreakOrContinueStatement(node); - case SyntaxKind.ReturnStatement: - return checkReturnStatement(node); - case SyntaxKind.WithStatement: - return checkWithStatement(node); - case SyntaxKind.SwitchStatement: - return checkSwitchStatement(node); - case SyntaxKind.LabeledStatement: - return checkLabeledStatement(node); - case SyntaxKind.ThrowStatement: - return checkThrowStatement(node); - case SyntaxKind.TryStatement: - return checkTryStatement(node); - case SyntaxKind.VariableDeclaration: - return checkVariableDeclaration(node); - case SyntaxKind.BindingElement: - return checkBindingElement(node); - case SyntaxKind.ClassDeclaration: - return checkClassDeclaration(node); - case SyntaxKind.InterfaceDeclaration: - return checkInterfaceDeclaration(node); - case SyntaxKind.TypeAliasDeclaration: - return checkTypeAliasDeclaration(node); - case SyntaxKind.EnumDeclaration: - return checkEnumDeclaration(node); - case SyntaxKind.ModuleDeclaration: - return checkModuleDeclaration(node); - case SyntaxKind.ImportDeclaration: - return checkImportDeclaration(node); - case SyntaxKind.ImportEqualsDeclaration: - return checkImportEqualsDeclaration(node); - case SyntaxKind.ExportDeclaration: - return checkExportDeclaration(node); - case SyntaxKind.ExportAssignment: - return checkExportAssignment(node); - case SyntaxKind.EmptyStatement: - case SyntaxKind.DebuggerStatement: - checkGrammarStatementInAmbientContext(node); - return; - case SyntaxKind.MissingDeclaration: - return checkMissingDeclaration(node); + return false; + }); + } + function checkIfNewTargetIsCapturedInEnclosingScope(node: Node): void { + findAncestor(node, current => { + if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureNewTarget) { + const isDeclaration = node.kind !== SyntaxKind.Identifier; + if (isDeclaration) { + error(getNameOfDeclaration((node)), Diagnostics.Duplicate_identifier_newTarget_Compiler_uses_variable_declaration_newTarget_to_capture_new_target_meta_property_reference); + } + else { + error(node, Diagnostics.Expression_resolves_to_variable_declaration_newTarget_that_compiler_uses_to_capture_new_target_meta_property_reference); + } + return true; } + return false; + }); + } + function checkCollisionWithRequireExportsInGeneratedCode(node: Node, name: Identifier) { + // No need to check for require or exports for ES6 modules and later + if (moduleKind >= ModuleKind.ES2015 || compilerOptions.noEmit) { + return; } - - function checkJSDocTypeIsInJsFile(node: Node): void { - if (!isInJSFile(node)) { - grammarErrorOnNode(node, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); + if (!needCollisionCheckForIdentifier(node, name, "require") && !needCollisionCheckForIdentifier(node, name, "exports")) { + return; + } + // Uninstantiated modules shouldnt do this check + if (isModuleDeclaration(node) && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { + return; + } + // In case of variable declaration, node.parent is variable statement so look at the variable statement's parent + const parent = getDeclarationContainer(node); + if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule((parent))) { + // If the declaration happens to be in external module, report error that require and exports are reserved keywords + error(name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module, declarationNameToString(name), declarationNameToString(name)); + } + } + function checkCollisionWithGlobalPromiseInGeneratedCode(node: Node, name: Identifier): void { + if (languageVersion >= ScriptTarget.ES2017 || compilerOptions.noEmit || !needCollisionCheckForIdentifier(node, name, "Promise")) { + return; + } + // Uninstantiated modules shouldnt do this check + if (isModuleDeclaration(node) && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { + return; + } + // In case of variable declaration, node.parent is variable statement so look at the variable statement's parent + const parent = getDeclarationContainer(node); + if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule((parent)) && parent.flags & NodeFlags.HasAsyncFunctions) { + // If the declaration happens to be in external module, report error that Promise is a reserved identifier. + error(name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module_containing_async_functions, declarationNameToString(name), declarationNameToString(name)); + } + } + function checkVarDeclaredNamesNotShadowed(node: VariableDeclaration | BindingElement) { + // - ScriptBody : StatementList + // It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList + // also occurs in the VarDeclaredNames of StatementList. + // - Block : { StatementList } + // It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList + // also occurs in the VarDeclaredNames of StatementList. + // Variable declarations are hoisted to the top of their function scope. They can shadow + // block scoped declarations, which bind tighter. this will not be flagged as duplicate definition + // by the binder as the declaration scope is different. + // A non-initialized declaration is a no-op as the block declaration will resolve before the var + // declaration. the problem is if the declaration has an initializer. this will act as a write to the + // block declared value. this is fine for let, but not const. + // Only consider declarations with initializers, uninitialized const declarations will not + // step on a let/const variable. + // Do not consider const and const declarations, as duplicate block-scoped declarations + // are handled by the binder. + // We are only looking for const declarations that step on let\const declarations from a + // different scope. e.g.: + // { + // const x = 0; // localDeclarationSymbol obtained after name resolution will correspond to this declaration + // const x = 0; // symbol for this declaration will be 'symbol' + // } + // skip block-scoped variables and parameters + if ((getCombinedNodeFlags(node) & NodeFlags.BlockScoped) !== 0 || isParameterDeclaration(node)) { + return; + } + // skip variable declarations that don't have initializers + // NOTE: in ES6 spec initializer is required in variable declarations where name is binding pattern + // so we'll always treat binding elements as initialized + if (node.kind === SyntaxKind.VariableDeclaration && !node.initializer) { + return; + } + const symbol = getSymbolOfNode(node); + if (symbol.flags & SymbolFlags.FunctionScopedVariable) { + if (!isIdentifier(node.name)) + return Debug.fail(); + const localDeclarationSymbol = resolveName(node, node.name.escapedText, SymbolFlags.Variable, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); + if (localDeclarationSymbol && + localDeclarationSymbol !== symbol && + localDeclarationSymbol.flags & SymbolFlags.BlockScopedVariable) { + if (getDeclarationNodeFlagsFromSymbol(localDeclarationSymbol) & NodeFlags.BlockScoped) { + const varDeclList = (getAncestor(localDeclarationSymbol.valueDeclaration, SyntaxKind.VariableDeclarationList)!); + const container = varDeclList.parent.kind === SyntaxKind.VariableStatement && varDeclList.parent.parent + ? varDeclList.parent.parent + : undefined; + // names of block-scoped and function scoped variables can collide only + // if block scoped variable is defined in the function\module\source file scope (because of variable hoisting) + const namesShareScope = container && + (container.kind === SyntaxKind.Block && isFunctionLike(container.parent) || + container.kind === SyntaxKind.ModuleBlock || + container.kind === SyntaxKind.ModuleDeclaration || + container.kind === SyntaxKind.SourceFile); + // here we know that function scoped variable is shadowed by block scoped one + // if they are defined in the same scope - binder has already reported redeclaration error + // otherwise if variable has an initializer - show error that initialization will fail + // since LHS will be block scoped name instead of function scoped + if (!namesShareScope) { + const name = symbolToString(localDeclarationSymbol); + error(node, Diagnostics.Cannot_initialize_outer_scoped_variable_0_in_the_same_scope_as_block_scoped_declaration_1, name, name); + } + } } } - - function checkJSDocVariadicType(node: JSDocVariadicType): void { - checkJSDocTypeIsInJsFile(node); + } + function convertAutoToAny(type: ts.Type) { + return type === autoType ? anyType : type === autoArrayType ? anyArrayType : type; + } + // Check variable, parameter, or property declaration + function checkVariableLikeDeclaration(node: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement) { + checkDecorators(node); + if (!isBindingElement(node)) { checkSourceElement(node.type); - - // Only legal location is in the *last* parameter tag or last parameter of a JSDoc function. - const { parent } = node; - if (isParameter(parent) && isJSDocFunctionType(parent.parent)) { - if (last(parent.parent.parameters) !== parent) { - error(node, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); - } - return; + } + // JSDoc `function(string, string): string` syntax results in parameters with no name + if (!node.name) { + return; + } + // For a computed property, just check the initializer and exit + // Do not use hasDynamicName here, because that returns false for well known symbols. + // We want to perform checkComputedPropertyName for all computed properties, including + // well known symbols. + if (node.name.kind === SyntaxKind.ComputedPropertyName) { + checkComputedPropertyName(node.name); + if (node.initializer) { + checkExpressionCached(node.initializer); } - - if (!isJSDocTypeExpression(parent)) { - error(node, Diagnostics.JSDoc_may_only_appear_in_the_last_parameter_of_a_signature); + } + if (node.kind === SyntaxKind.BindingElement) { + if (node.parent.kind === SyntaxKind.ObjectBindingPattern && languageVersion < ScriptTarget.ESNext) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Rest); } - - const paramTag = node.parent.parent; - if (!isJSDocParameterTag(paramTag)) { - error(node, Diagnostics.JSDoc_may_only_appear_in_the_last_parameter_of_a_signature); - return; + // check computed properties inside property names of binding elements + if (node.propertyName && node.propertyName.kind === SyntaxKind.ComputedPropertyName) { + checkComputedPropertyName(node.propertyName); } - - const param = getParameterSymbolFromJSDoc(paramTag); - if (!param) { - // We will error in `checkJSDocParameterTag`. - return; + // check private/protected variable access + const parent = node.parent.parent; + const parentType = getTypeForBindingElementParent(parent); + const name = node.propertyName || node.name; + if (parentType && !isBindingPattern(name)) { + const exprType = getLiteralTypeFromPropertyName(name); + if (isTypeUsableAsPropertyName(exprType)) { + const nameText = getPropertyNameFromType(exprType); + const property = getPropertyOfType(parentType, nameText); + if (property) { + markPropertyAsReferenced(property, /*nodeForCheckWriteOnly*/ undefined, /*isThisAccess*/ false); // A destructuring is never a write-only reference. + checkPropertyAccessibility(parent, !!parent.initializer && parent.initializer.kind === SyntaxKind.SuperKeyword, parentType, property); + } + } } - - const host = getHostSignatureFromJSDoc(paramTag); - if (!host || last(host.parameters).symbol !== param) { - error(node, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); + } + // For a binding pattern, check contained binding elements + if (isBindingPattern(node.name)) { + if (node.name.kind === SyntaxKind.ArrayBindingPattern && languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Read); } + forEach(node.name.elements, checkSourceElement); } - - function getTypeFromJSDocVariadicType(node: JSDocVariadicType): Type { - const type = getTypeFromTypeNode(node.type); - const { parent } = node; - const paramTag = node.parent.parent; - if (isJSDocTypeExpression(node.parent) && isJSDocParameterTag(paramTag)) { - // Else we will add a diagnostic, see `checkJSDocVariadicType`. - const host = getHostSignatureFromJSDoc(paramTag); - if (host) { - /* - Only return an array type if the corresponding parameter is marked as a rest parameter, or if there are no parameters. - So in the following situation we will not create an array type: - /** @param {...number} a * / - function f(a) {} - Because `a` will just be of type `number | undefined`. A synthetic `...args` will also be added, which *will* get an array type. - */ - const lastParamDeclaration = lastOrUndefined(host.parameters); - const symbol = getParameterSymbolFromJSDoc(paramTag); - if (!lastParamDeclaration || - symbol && lastParamDeclaration.symbol === symbol && isRestParameter(lastParamDeclaration)) { - return createArrayType(type); - } - } - } - if (isParameter(parent) && isJSDocFunctionType(parent.parent)) { - return createArrayType(type); - } - return addOptionality(type); + // For a parameter declaration with an initializer, error and exit if the containing function doesn't have a body + if (node.initializer && getRootDeclaration(node).kind === SyntaxKind.Parameter && nodeIsMissing((getContainingFunction(node) as FunctionLikeDeclaration).body)) { + error(node, Diagnostics.A_parameter_initializer_is_only_allowed_in_a_function_or_constructor_implementation); + return; } - - // Function and class expression bodies are checked after all statements in the enclosing body. This is - // to ensure constructs like the following are permitted: - // const foo = function () { - // const s = foo(); - // return "hello"; - // } - // Here, performing a full type check of the body of the function expression whilst in the process of - // determining the type of foo would cause foo to be given type any because of the recursive reference. - // Delaying the type check of the body ensures foo has been assigned a type. - function checkNodeDeferred(node: Node) { - const enclosingFile = getSourceFileOfNode(node); - const links = getNodeLinks(enclosingFile); - if (!(links.flags & NodeCheckFlags.TypeChecked)) { - links.deferredNodes = links.deferredNodes || createMap(); - const id = "" + getNodeId(node); - links.deferredNodes.set(id, node); + // For a binding pattern, validate the initializer and exit + if (isBindingPattern(node.name)) { + const needCheckInitializer = node.initializer && node.parent.parent.kind !== SyntaxKind.ForInStatement; + const needCheckWidenedType = node.name.elements.length === 0; + if (needCheckInitializer || needCheckWidenedType) { + // Don't validate for-in initializer as it is already an error + const widenedType = getWidenedTypeForVariableLikeDeclaration(node); + if (needCheckInitializer) { + const initializerType = checkExpressionCached(node.initializer!); + if (strictNullChecks && needCheckWidenedType) { + checkNonNullNonVoidType(initializerType, node); + } + else { + checkTypeAssignableToAndOptionallyElaborate(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, node.initializer); + } + } + // check the binding pattern with empty elements + if (needCheckWidenedType) { + if (isArrayBindingPattern(node.name)) { + checkIteratedTypeOrElementType(IterationUse.Destructuring, widenedType, undefinedType, node); + } + else if (strictNullChecks) { + checkNonNullNonVoidType(widenedType, node); + } + } } + return; } - - function checkDeferredNodes(context: SourceFile) { - const links = getNodeLinks(context); - if (links.deferredNodes) { - links.deferredNodes.forEach(checkDeferredNode); + const symbol = getSymbolOfNode(node); + const type = convertAutoToAny(getTypeOfSymbol(symbol)); + if (node === symbol.valueDeclaration) { + // Node is the primary declaration of the symbol, just validate the initializer + // Don't validate for-in initializer as it is already an error + const initializer = getEffectiveInitializer(node); + if (initializer) { + const isJSObjectLiteralInitializer = isInJSFile(node) && + isObjectLiteralExpression(initializer) && + (initializer.properties.length === 0 || isPrototypeAccess(node.name)) && + hasEntries(symbol.exports); + if (!isJSObjectLiteralInitializer && node.parent.parent.kind !== SyntaxKind.ForInStatement) { + checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(initializer), type, node, initializer, /*headMessage*/ undefined); + } } - } - - function checkDeferredNode(node: Node) { - const saveCurrentNode = currentNode; - currentNode = node; - instantiationCount = 0; - switch (node.kind) { - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - checkFunctionExpressionOrObjectLiteralMethodDeferred(node); - break; - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - checkAccessorDeclaration(node); - break; - case SyntaxKind.ClassExpression: - checkClassExpressionDeferred(node); - break; - case SyntaxKind.JsxSelfClosingElement: - checkJsxSelfClosingElementDeferred(node); - break; - case SyntaxKind.JsxElement: - checkJsxElementDeferred(node); - break; + if (symbol.declarations.length > 1) { + if (some(symbol.declarations, d => d !== node && isVariableLike(d) && !areDeclarationFlagsIdentical(d, node))) { + error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name)); + } } - currentNode = saveCurrentNode; } - - function checkSourceFile(node: SourceFile) { - performance.mark("beforeCheck"); - checkSourceFileWorker(node); - performance.mark("afterCheck"); - performance.measure("Check", "beforeCheck", "afterCheck"); - } - - function unusedIsError(kind: UnusedKind, isAmbient: boolean): boolean { - if (isAmbient) { - return false; + else { + // Node is a secondary declaration, check that type is identical to primary declaration and check that + // initializer is consistent with type associated with the node + const declarationType = convertAutoToAny(getWidenedTypeForVariableLikeDeclaration(node)); + if (type !== errorType && declarationType !== errorType && + !isTypeIdenticalTo(type, declarationType) && + !(symbol.flags & SymbolFlags.Assignment)) { + errorNextVariableOrPropertyDeclarationMustHaveSameType(symbol.valueDeclaration, type, node, declarationType); } - switch (kind) { - case UnusedKind.Local: - return !!compilerOptions.noUnusedLocals; - case UnusedKind.Parameter: - return !!compilerOptions.noUnusedParameters; - default: - return Debug.assertNever(kind); + if (node.initializer) { + checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(node.initializer), declarationType, node, node.initializer, /*headMessage*/ undefined); + } + if (!areDeclarationFlagsIdentical(node, symbol.valueDeclaration)) { + error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name)); } } - - function getPotentiallyUnusedIdentifiers(sourceFile: SourceFile): readonly PotentiallyUnusedIdentifier[] { - return allPotentiallyUnusedIdentifiers.get(sourceFile.path) || emptyArray; + if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature) { + // We know we don't have a binding pattern or computed name here + checkExportsOnMergedDeclarations(node); + if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { + checkVarDeclaredNamesNotShadowed(node); + } + checkCollisionWithRequireExportsInGeneratedCode(node, (node.name)); + checkCollisionWithGlobalPromiseInGeneratedCode(node, (node.name)); } - - // Fully type check a source file and collect the relevant diagnostics. - function checkSourceFileWorker(node: SourceFile) { - const links = getNodeLinks(node); - if (!(links.flags & NodeCheckFlags.TypeChecked)) { - if (skipTypeChecking(node, compilerOptions, host)) { - return; - } - - // Grammar checking - checkGrammarSourceFile(node); - - clear(potentialThisCollisions); - clear(potentialNewTargetCollisions); - - forEach(node.statements, checkSourceElement); - checkSourceElement(node.endOfFileToken); - - checkDeferredNodes(node); - - if (isExternalOrCommonJsModule(node)) { - registerForUnusedIdentifiersCheck(node); - } - - if (!node.isDeclarationFile && (compilerOptions.noUnusedLocals || compilerOptions.noUnusedParameters)) { - checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(node), (containingNode, kind, diag) => { - if (!containsParseError(containingNode) && unusedIsError(kind, !!(containingNode.flags & NodeFlags.Ambient))) { - diagnostics.add(diag); - } - }); - } - - if (isExternalOrCommonJsModule(node)) { - checkExternalModuleExports(node); - } - - if (potentialThisCollisions.length) { - forEach(potentialThisCollisions, checkIfThisIsCapturedInEnclosingScope); - clear(potentialThisCollisions); - } - - if (potentialNewTargetCollisions.length) { - forEach(potentialNewTargetCollisions, checkIfNewTargetIsCapturedInEnclosingScope); - clear(potentialNewTargetCollisions); + } + function errorNextVariableOrPropertyDeclarationMustHaveSameType(firstDeclaration: Declaration | undefined, firstType: ts.Type, nextDeclaration: Declaration, nextType: ts.Type): void { + const nextDeclarationName = getNameOfDeclaration(nextDeclaration); + const message = nextDeclaration.kind === SyntaxKind.PropertyDeclaration || nextDeclaration.kind === SyntaxKind.PropertySignature + ? Diagnostics.Subsequent_property_declarations_must_have_the_same_type_Property_0_must_be_of_type_1_but_here_has_type_2 + : Diagnostics.Subsequent_variable_declarations_must_have_the_same_type_Variable_0_must_be_of_type_1_but_here_has_type_2; + const declName = declarationNameToString(nextDeclarationName); + const err = error(nextDeclarationName, message, declName, typeToString(firstType), typeToString(nextType)); + if (firstDeclaration) { + addRelatedInfo(err, createDiagnosticForNode(firstDeclaration, Diagnostics._0_was_also_declared_here, declName)); + } + } + function areDeclarationFlagsIdentical(left: Declaration, right: Declaration) { + if ((left.kind === SyntaxKind.Parameter && right.kind === SyntaxKind.VariableDeclaration) || + (left.kind === SyntaxKind.VariableDeclaration && right.kind === SyntaxKind.Parameter)) { + // Differences in optionality between parameters and variables are allowed. + return true; + } + if (hasQuestionToken(left) !== hasQuestionToken(right)) { + return false; + } + const interestingFlags = ModifierFlags.Private | + ModifierFlags.Protected | + ModifierFlags.Async | + ModifierFlags.Abstract | + ModifierFlags.Readonly | + ModifierFlags.Static; + return getSelectedModifierFlags(left, interestingFlags) === getSelectedModifierFlags(right, interestingFlags); + } + function checkVariableDeclaration(node: VariableDeclaration) { + checkGrammarVariableDeclaration(node); + return checkVariableLikeDeclaration(node); + } + function checkBindingElement(node: BindingElement) { + checkGrammarBindingElement(node); + return checkVariableLikeDeclaration(node); + } + function checkVariableStatement(node: VariableStatement) { + // Grammar checking + if (!checkGrammarDecoratorsAndModifiers(node) && !checkGrammarVariableDeclarationList(node.declarationList)) + checkGrammarForDisallowedLetOrConstStatement(node); + forEach(node.declarationList.declarations, checkSourceElement); + } + function checkExpressionStatement(node: ExpressionStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + checkExpression(node.expression); + } + function checkIfStatement(node: IfStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + const type = checkTruthinessExpression(node.expression); + checkTestingKnownTruthyCallableType(node, type); + checkSourceElement(node.thenStatement); + if (node.thenStatement.kind === SyntaxKind.EmptyStatement) { + error(node.thenStatement, Diagnostics.The_body_of_an_if_statement_cannot_be_the_empty_statement); + } + checkSourceElement(node.elseStatement); + } + function checkTestingKnownTruthyCallableType(ifStatement: IfStatement, type: ts.Type) { + if (!strictNullChecks) { + return; + } + const testedNode = isIdentifier(ifStatement.expression) + ? ifStatement.expression + : isPropertyAccessExpression(ifStatement.expression) + ? ifStatement.expression.name + : undefined; + if (!testedNode) { + return; + } + const possiblyFalsy = getFalsyFlags(type); + if (possiblyFalsy) { + return; + } + // While it technically should be invalid for any known-truthy value + // to be tested, we de-scope to functions unrefenced in the block as a + // heuristic to identify the most common bugs. There are too many + // false positives for values sourced from type definitions without + // strictNullChecks otherwise. + const callSignatures = getSignaturesOfType(type, SignatureKind.Call); + if (callSignatures.length === 0) { + return; + } + const testedFunctionSymbol = getSymbolAtLocation(testedNode); + if (!testedFunctionSymbol) { + return; + } + const functionIsUsedInBody = forEachChild(ifStatement.thenStatement, function check(childNode): boolean | undefined { + if (isIdentifier(childNode)) { + const childSymbol = getSymbolAtLocation(childNode); + if (childSymbol && childSymbol.id === testedFunctionSymbol.id) { + return true; } - - links.flags |= NodeCheckFlags.TypeChecked; } + return forEachChild(childNode, check); + }); + if (!functionIsUsedInBody) { + error(ifStatement.expression, Diagnostics.This_condition_will_always_return_true_since_the_function_is_always_defined_Did_you_mean_to_call_it_instead); } - - function getDiagnostics(sourceFile: SourceFile, ct: CancellationToken): Diagnostic[] { - try { - // Record the cancellation token so it can be checked later on during checkSourceElement. - // Do this in a finally block so we can ensure that it gets reset back to nothing after - // this call is done. - cancellationToken = ct; - return getDiagnosticsWorker(sourceFile); + } + function checkDoStatement(node: DoStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + checkSourceElement(node.statement); + checkTruthinessExpression(node.expression); + } + function checkWhileStatement(node: WhileStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + checkTruthinessExpression(node.expression); + checkSourceElement(node.statement); + } + function checkTruthinessExpression(node: Expression, checkMode?: CheckMode) { + const type = checkExpression(node, checkMode); + if (type.flags & TypeFlags.Void) { + error(node, Diagnostics.An_expression_of_type_void_cannot_be_tested_for_truthiness); + } + return type; + } + function checkForStatement(node: ForStatement) { + // Grammar checking + if (!checkGrammarStatementInAmbientContext(node)) { + if (node.initializer && node.initializer.kind === SyntaxKind.VariableDeclarationList) { + checkGrammarVariableDeclarationList((node.initializer)); } - finally { - cancellationToken = undefined; + } + if (node.initializer) { + if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { + forEach((node.initializer).declarations, checkVariableDeclaration); + } + else { + checkExpression(node.initializer); } } - - function getDiagnosticsWorker(sourceFile: SourceFile): Diagnostic[] { - throwIfNonDiagnosticsProducing(); - if (sourceFile) { - // Some global diagnostics are deferred until they are needed and - // may not be reported in the first call to getGlobalDiagnostics. - // We should catch these changes and report them. - const previousGlobalDiagnostics = diagnostics.getGlobalDiagnostics(); - const previousGlobalDiagnosticsSize = previousGlobalDiagnostics.length; - - checkSourceFile(sourceFile); - - const semanticDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName); - const currentGlobalDiagnostics = diagnostics.getGlobalDiagnostics(); - if (currentGlobalDiagnostics !== previousGlobalDiagnostics) { - // If the arrays are not the same reference, new diagnostics were added. - const deferredGlobalDiagnostics = relativeComplement(previousGlobalDiagnostics, currentGlobalDiagnostics, compareDiagnostics); - return concatenate(deferredGlobalDiagnostics, semanticDiagnostics); - } - else if (previousGlobalDiagnosticsSize === 0 && currentGlobalDiagnostics.length > 0) { - // If the arrays are the same reference, but the length has changed, a single - // new diagnostic was added as DiagnosticCollection attempts to reuse the - // same array. - return concatenate(currentGlobalDiagnostics, semanticDiagnostics); + if (node.condition) + checkTruthinessExpression(node.condition); + if (node.incrementor) + checkExpression(node.incrementor); + checkSourceElement(node.statement); + if (node.locals) { + registerForUnusedIdentifiersCheck(node); + } + } + function checkForOfStatement(node: ForOfStatement): void { + checkGrammarForInOrForOfStatement(node); + if (node.awaitModifier) { + const functionFlags = getFunctionFlags(getContainingFunction(node)); + if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Async)) === FunctionFlags.Async && languageVersion < ScriptTarget.ESNext) { + // for..await..of in an async function or async generator function prior to ESNext requires the __asyncValues helper + checkExternalEmitHelpers(node, ExternalEmitHelpers.ForAwaitOfIncludes); + } + } + else if (compilerOptions.downlevelIteration && languageVersion < ScriptTarget.ES2015) { + // for..of prior to ES2015 requires the __values helper when downlevelIteration is enabled + checkExternalEmitHelpers(node, ExternalEmitHelpers.ForOfIncludes); + } + // Check the LHS and RHS + // If the LHS is a declaration, just check it as a variable declaration, which will in turn check the RHS + // via checkRightHandSideOfForOf. + // If the LHS is an expression, check the LHS, as a destructuring assignment or as a reference. + // Then check that the RHS is assignable to it. + if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { + checkForInOrForOfVariableDeclaration(node); + } + else { + const varExpr = node.initializer; + const iteratedType = checkRightHandSideOfForOf(node.expression, node.awaitModifier); + // There may be a destructuring assignment on the left side + if (varExpr.kind === SyntaxKind.ArrayLiteralExpression || varExpr.kind === SyntaxKind.ObjectLiteralExpression) { + // iteratedType may be undefined. In this case, we still want to check the structure of + // varExpr, in particular making sure it's a valid LeftHandSideExpression. But we'd like + // to short circuit the type relation checking as much as possible, so we pass the unknownType. + checkDestructuringAssignment(varExpr, iteratedType || errorType); + } + else { + const leftType = checkExpression(varExpr); + checkReferenceExpression(varExpr, Diagnostics.The_left_hand_side_of_a_for_of_statement_must_be_a_variable_or_a_property_access, Diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_an_optional_property_access); + // iteratedType will be undefined if the rightType was missing properties/signatures + // required to get its iteratedType (like [Symbol.iterator] or next). This may be + // because we accessed properties from anyType, or it may have led to an error inside + // getElementTypeOfIterable. + if (iteratedType) { + checkTypeAssignableToAndOptionallyElaborate(iteratedType, leftType, varExpr, node.expression); } - - return semanticDiagnostics; } - - // Global diagnostics are always added when a file is not provided to - // getDiagnostics - forEach(host.getSourceFiles(), checkSourceFile); - return diagnostics.getDiagnostics(); } - - function getGlobalDiagnostics(): Diagnostic[] { - throwIfNonDiagnosticsProducing(); - return diagnostics.getGlobalDiagnostics(); + checkSourceElement(node.statement); + if (node.locals) { + registerForUnusedIdentifiersCheck(node); } - - function throwIfNonDiagnosticsProducing() { - if (!produceDiagnostics) { - throw new Error("Trying to get diagnostics from a type checker that does not produce them."); + } + function checkForInStatement(node: ForInStatement) { + // Grammar checking + checkGrammarForInOrForOfStatement(node); + const rightType = getNonNullableTypeIfNeeded(checkExpression(node.expression)); + // TypeScript 1.0 spec (April 2014): 5.4 + // In a 'for-in' statement of the form + // for (let VarDecl in Expr) Statement + // VarDecl must be a variable declaration without a type annotation that declares a variable of type Any, + // and Expr must be an expression of type Any, an object type, or a type parameter type. + if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { + const variable = (node.initializer).declarations[0]; + if (variable && isBindingPattern(variable.name)) { + error(variable.name, Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_be_a_destructuring_pattern); + } + checkForInOrForOfVariableDeclaration(node); + } + else { + // In a 'for-in' statement of the form + // for (Var in Expr) Statement + // Var must be an expression classified as a reference of type Any or the String primitive type, + // and Expr must be an expression of type Any, an object type, or a type parameter type. + const varExpr = node.initializer; + const leftType = checkExpression(varExpr); + if (varExpr.kind === SyntaxKind.ArrayLiteralExpression || varExpr.kind === SyntaxKind.ObjectLiteralExpression) { + error(varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_be_a_destructuring_pattern); } - } - - // Language service support - - function getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[] { - if (location.flags & NodeFlags.InWithStatement) { - // We cannot answer semantic questions within a with block, do not proceed any further - return []; + else if (!isTypeAssignableTo(getIndexTypeOrString(rightType), leftType)) { + error(varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_of_type_string_or_any); } - - const symbols = createSymbolTable(); - let isStatic = false; - - populateSymbols(); - - symbols.delete(InternalSymbolName.This); // Not a symbol, a keyword - return symbolsToArray(symbols); - - function populateSymbols() { - while (location) { - if (location.locals && !isGlobalSourceFile(location)) { - copySymbols(location.locals, meaning); - } - - switch (location.kind) { - case SyntaxKind.SourceFile: - if (!isExternalOrCommonJsModule(location)) break; - // falls through - case SyntaxKind.ModuleDeclaration: - copySymbols(getSymbolOfNode(location as ModuleDeclaration | SourceFile).exports!, meaning & SymbolFlags.ModuleMember); - break; - case SyntaxKind.EnumDeclaration: - copySymbols(getSymbolOfNode(location as EnumDeclaration).exports!, meaning & SymbolFlags.EnumMember); - break; - case SyntaxKind.ClassExpression: - const className = (location as ClassExpression).name; - if (className) { - copySymbol(location.symbol, meaning); - } - - // this fall-through is necessary because we would like to handle - // type parameter inside class expression similar to how we handle it in classDeclaration and interface Declaration. - // falls through - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - // If we didn't come from static member of class or interface, - // add the type parameters into the symbol table - // (type parameters of classDeclaration/classExpression and interface are in member property of the symbol. - // Note: that the memberFlags come from previous iteration. - if (!isStatic) { - copySymbols(getMembersOfSymbol(getSymbolOfNode(location as ClassDeclaration | InterfaceDeclaration)), meaning & SymbolFlags.Type); - } - break; - case SyntaxKind.FunctionExpression: - const funcName = (location as FunctionExpression).name; - if (funcName) { - copySymbol(location.symbol, meaning); - } - break; - } - - if (introducesArgumentsExoticObject(location)) { - copySymbol(argumentsSymbol, meaning); + else { + // run check only former check succeeded to avoid cascading errors + checkReferenceExpression(varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_a_variable_or_a_property_access, Diagnostics.The_left_hand_side_of_a_for_in_statement_may_not_be_an_optional_property_access); + } + } + // unknownType is returned i.e. if node.expression is identifier whose name cannot be resolved + // in this case error about missing name is already reported - do not report extra one + if (rightType === neverType || !isTypeAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive)) { + error(node.expression, Diagnostics.The_right_hand_side_of_a_for_in_statement_must_be_of_type_any_an_object_type_or_a_type_parameter_but_here_has_type_0, typeToString(rightType)); + } + checkSourceElement(node.statement); + if (node.locals) { + registerForUnusedIdentifiersCheck(node); + } + } + function checkForInOrForOfVariableDeclaration(iterationStatement: ForInOrOfStatement): void { + const variableDeclarationList = (iterationStatement.initializer); + // checkGrammarForInOrForOfStatement will check that there is exactly one declaration. + if (variableDeclarationList.declarations.length >= 1) { + const decl = variableDeclarationList.declarations[0]; + checkVariableDeclaration(decl); + } + } + function checkRightHandSideOfForOf(rhsExpression: Expression, awaitModifier: AwaitKeywordToken | undefined): ts.Type { + const expressionType = checkNonNullExpression(rhsExpression); + const use = awaitModifier ? IterationUse.ForAwaitOf : IterationUse.ForOf; + return checkIteratedTypeOrElementType(use, expressionType, undefinedType, rhsExpression); + } + function checkIteratedTypeOrElementType(use: IterationUse, inputType: ts.Type, sentType: ts.Type, errorNode: Node | undefined): ts.Type { + if (isTypeAny(inputType)) { + return inputType; + } + return getIteratedTypeOrElementType(use, inputType, sentType, errorNode, /*checkAssignability*/ true) || anyType; + } + /** + * When consuming an iterable type in a for..of, spread, or iterator destructuring assignment + * we want to get the iterated type of an iterable for ES2015 or later, or the iterated type + * of a iterable (if defined globally) or element type of an array like for ES2015 or earlier. + */ + function getIteratedTypeOrElementType(use: IterationUse, inputType: ts.Type, sentType: ts.Type, errorNode: Node | undefined, checkAssignability: boolean): ts.Type | undefined { + const allowAsyncIterables = (use & IterationUse.AllowsAsyncIterablesFlag) !== 0; + if (inputType === neverType) { + reportTypeNotIterableError(errorNode!, inputType, allowAsyncIterables); // TODO: GH#18217 + return undefined; + } + const uplevelIteration = languageVersion >= ScriptTarget.ES2015; + const downlevelIteration = !uplevelIteration && compilerOptions.downlevelIteration; + // Get the iterated type of an `Iterable` or `IterableIterator` only in ES2015 + // or higher, when inside of an async generator or for-await-if, or when + // downlevelIteration is requested. + if (uplevelIteration || downlevelIteration || allowAsyncIterables) { + // We only report errors for an invalid iterable type in ES2015 or higher. + const iterationTypes = getIterationTypesOfIterable(inputType, use, uplevelIteration ? errorNode : undefined); + if (checkAssignability) { + if (iterationTypes) { + const diagnostic = use & IterationUse.ForOfFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_for_of_will_always_send_0 : + use & IterationUse.SpreadFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_array_spread_will_always_send_0 : + use & IterationUse.DestructuringFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_array_destructuring_will_always_send_0 : + use & IterationUse.YieldStarFlag ? Diagnostics.Cannot_delegate_iteration_to_value_because_the_next_method_of_its_iterator_expects_type_1_but_the_containing_generator_will_always_send_0 : + undefined; + if (diagnostic) { + checkTypeAssignableTo(sentType, iterationTypes.nextType, errorNode, diagnostic); } - - isStatic = hasModifier(location, ModifierFlags.Static); - location = location.parent; } - - copySymbols(globals, meaning); } - - /** - * Copy the given symbol into symbol tables if the symbol has the given meaning - * and it doesn't already existed in the symbol table - * @param key a key for storing in symbol table; if undefined, use symbol.name - * @param symbol the symbol to be added into symbol table - * @param meaning meaning of symbol to filter by before adding to symbol table - */ - function copySymbol(symbol: Symbol, meaning: SymbolFlags): void { - if (getCombinedLocalAndExportSymbolFlags(symbol) & meaning) { - const id = symbol.escapedName; - // We will copy all symbol regardless of its reserved name because - // symbolsToArray will check whether the key is a reserved name and - // it will not copy symbol with reserved name to the array - if (!symbols.has(id)) { - symbols.set(id, symbol); - } + if (iterationTypes || uplevelIteration) { + return iterationTypes && iterationTypes.yieldType; + } + } + let arrayType = inputType; + let reportedError = false; + let hasStringConstituent = false; + // If strings are permitted, remove any string-like constituents from the array type. + // This allows us to find other non-string element types from an array unioned with + // a string. + if (use & IterationUse.AllowsStringInputFlag) { + if (arrayType.flags & TypeFlags.Union) { + // After we remove all types that are StringLike, we will know if there was a string constituent + // based on whether the result of filter is a new array. + const arrayTypes = (inputType).types; + const filteredTypes = filter(arrayTypes, t => !(t.flags & TypeFlags.StringLike)); + if (filteredTypes !== arrayTypes) { + arrayType = getUnionType(filteredTypes, UnionReduction.Subtype); } } - - function copySymbols(source: SymbolTable, meaning: SymbolFlags): void { - if (meaning) { - source.forEach(symbol => { - copySymbol(symbol, meaning); - }); + else if (arrayType.flags & TypeFlags.StringLike) { + arrayType = neverType; + } + hasStringConstituent = arrayType !== inputType; + if (hasStringConstituent) { + if (languageVersion < ScriptTarget.ES5) { + if (errorNode) { + error(errorNode, Diagnostics.Using_a_string_in_a_for_of_statement_is_only_supported_in_ECMAScript_5_and_higher); + reportedError = true; + } + } + // Now that we've removed all the StringLike types, if no constituents remain, then the entire + // arrayOrStringType was a string. + if (arrayType.flags & TypeFlags.Never) { + return stringType; } } } - - function isTypeDeclarationName(name: Node): boolean { - return name.kind === SyntaxKind.Identifier && - isTypeDeclaration(name.parent) && - (name.parent).name === name; + if (!isArrayLikeType(arrayType)) { + if (errorNode && !reportedError) { + // Which error we report depends on whether we allow strings or if there was a + // string constituent. For example, if the input type is number | string, we + // want to say that number is not an array type. But if the input was just + // number and string input is allowed, we want to say that number is not an + // array type or a string type. + const yieldType = getIterationTypeOfIterable(use, IterationTypeKind.Yield, inputType, /*errorNode*/ undefined); + const [defaultDiagnostic, maybeMissingAwait]: [DiagnosticMessage, boolean] = !(use & IterationUse.AllowsStringInputFlag) || hasStringConstituent + ? downlevelIteration + ? [Diagnostics.Type_0_is_not_an_array_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator, true] + : yieldType + ? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_Use_compiler_option_downlevelIteration_to_allow_iterating_of_iterators, false] + : [Diagnostics.Type_0_is_not_an_array_type, true] + : downlevelIteration + ? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator, true] + : yieldType + ? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_Use_compiler_option_downlevelIteration_to_allow_iterating_of_iterators, false] + : [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type, true]; + errorAndMaybeSuggestAwait(errorNode, maybeMissingAwait && !!getAwaitedTypeOfPromise(arrayType), defaultDiagnostic, typeToString(arrayType)); + } + return hasStringConstituent ? stringType : undefined; + } + const arrayElementType = getIndexTypeOfType(arrayType, IndexKind.Number); + if (hasStringConstituent && arrayElementType) { + // This is just an optimization for the case where arrayOrStringType is string | string[] + if (arrayElementType.flags & TypeFlags.StringLike) { + return stringType; + } + return getUnionType([arrayElementType, stringType], UnionReduction.Subtype); } - - function isTypeDeclaration(node: Node): node is TypeParameterDeclaration | ClassDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumDeclaration { - switch (node.kind) { - case SyntaxKind.TypeParameter: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.EnumDeclaration: - return true; - default: - return false; + return arrayElementType; + } + /** + * Gets the requested "iteration type" from an `Iterable`-like or `AsyncIterable`-like type. + */ + function getIterationTypeOfIterable(use: IterationUse, typeKind: IterationTypeKind, inputType: ts.Type, errorNode: Node | undefined): ts.Type | undefined { + if (isTypeAny(inputType)) { + return undefined; + } + const iterationTypes = getIterationTypesOfIterable(inputType, use, errorNode); + return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(typeKind)]; + } + function createIterationTypes(yieldType: ts.Type = neverType, returnType: ts.Type = neverType, nextType: ts.Type = unknownType): IterationTypes { + // `yieldType` and `returnType` are defaulted to `neverType` they each will be combined + // via `getUnionType` when merging iteration types. `nextType` is defined as `unknownType` + // as it is combined via `getIntersectionType` when merging iteration types. + // Use the cache only for intrinsic types to keep it small as they are likely to be + // more frequently created (i.e. `Iterator`). Iteration types + // are also cached on the type they are requested for, so we shouldn't need to maintain + // the cache for less-frequently used types. + if (yieldType.flags & TypeFlags.Intrinsic && + returnType.flags & (TypeFlags.Any | TypeFlags.Never | TypeFlags.Unknown | TypeFlags.Void | TypeFlags.Undefined) && + nextType.flags & (TypeFlags.Any | TypeFlags.Never | TypeFlags.Unknown | TypeFlags.Void | TypeFlags.Undefined)) { + const id = getTypeListId([yieldType, returnType, nextType]); + let iterationTypes = iterationTypesCache.get(id); + if (!iterationTypes) { + iterationTypes = { yieldType, returnType, nextType }; + iterationTypesCache.set(id, iterationTypes); + } + return iterationTypes; + } + return { yieldType, returnType, nextType }; + } + /** + * Combines multiple `IterationTypes` records. + * + * If `array` is empty or all elements are missing or are references to `noIterationTypes`, + * then `noIterationTypes` is returned. Otherwise, an `IterationTypes` record is returned + * for the combined iteration types. + */ + function combineIterationTypes(array: (IterationTypes | undefined)[]) { + let yieldTypes: ts.Type[] | undefined; + let returnTypes: ts.Type[] | undefined; + let nextTypes: ts.Type[] | undefined; + for (const iterationTypes of array) { + if (iterationTypes === undefined || iterationTypes === noIterationTypes) { + continue; + } + if (iterationTypes === anyIterationTypes) { + return anyIterationTypes; } + yieldTypes = append(yieldTypes, iterationTypes.yieldType); + returnTypes = append(returnTypes, iterationTypes.returnType); + nextTypes = append(nextTypes, iterationTypes.nextType); } - - // True if the given identifier is part of a type reference - function isTypeReferenceIdentifier(node: EntityName): boolean { - while (node.parent.kind === SyntaxKind.QualifiedName) { - node = node.parent as QualifiedName; + if (yieldTypes || returnTypes || nextTypes) { + return createIterationTypes(yieldTypes && getUnionType(yieldTypes), returnTypes && getUnionType(returnTypes), nextTypes && getIntersectionType(nextTypes)); + } + return noIterationTypes; + } + /** + * Gets the *yield*, *return*, and *next* types from an `Iterable`-like or `AsyncIterable`-like type. + * + * At every level that involves analyzing return types of signatures, we union the return types of all the signatures. + * + * Another thing to note is that at any step of this process, we could run into a dead end, + * meaning either the property is missing, or we run into the anyType. If either of these things + * happens, we return `undefined` to signal that we could not find the iteration type. If a property + * is missing, and the previous step did not result in `any`, then we also give an error if the + * caller requested it. Then the caller can decide what to do in the case where there is no iterated + * type. + * + * For a **for-of** statement, `yield*` (in a normal generator), spread, array + * destructuring, or normal generator we will only ever look for a `[Symbol.iterator]()` + * method. + * + * For an async generator we will only ever look at the `[Symbol.asyncIterator]()` method. + * + * For a **for-await-of** statement or a `yield*` in an async generator we will look for + * the `[Symbol.asyncIterator]()` method first, and then the `[Symbol.iterator]()` method. + */ + function getIterationTypesOfIterable(type: ts.Type, use: IterationUse, errorNode: Node | undefined) { + if (isTypeAny(type)) { + return anyIterationTypes; + } + if (!(type.flags & TypeFlags.Union)) { + const iterationTypes = getIterationTypesOfIterableWorker(type, use, errorNode); + if (iterationTypes === noIterationTypes) { + if (errorNode) { + reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag)); + } + return undefined; } - - return node.parent.kind === SyntaxKind.TypeReference; + return iterationTypes; } - - function isHeritageClauseElementIdentifier(node: Node): boolean { - while (node.parent.kind === SyntaxKind.PropertyAccessExpression) { - node = node.parent; + const cacheKey = use & IterationUse.AllowsAsyncIterablesFlag ? "iterationTypesOfAsyncIterable" : "iterationTypesOfIterable"; + const cachedTypes = (type as IterableOrIteratorType)[cacheKey]; + if (cachedTypes) + return cachedTypes === noIterationTypes ? undefined : cachedTypes; + let allIterationTypes: IterationTypes[] | undefined; + for (const constituent of (type as UnionType).types) { + const iterationTypes = getIterationTypesOfIterableWorker(constituent, use, errorNode); + if (iterationTypes === noIterationTypes) { + if (errorNode) { + reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag)); + errorNode = undefined; + } + } + else { + allIterationTypes = append(allIterationTypes, iterationTypes); } - - return node.parent.kind === SyntaxKind.ExpressionWithTypeArguments; } - - function forEachEnclosingClass(node: Node, callback: (node: Node) => T | undefined): T | undefined { - let result: T | undefined; - - while (true) { - node = getContainingClass(node)!; - if (!node) break; - if (result = callback(node)) break; + const iterationTypes = allIterationTypes ? combineIterationTypes(allIterationTypes) : noIterationTypes; + (type as IterableOrIteratorType)[cacheKey] = iterationTypes; + return iterationTypes === noIterationTypes ? undefined : iterationTypes; + } + function getAsyncFromSyncIterationTypes(iterationTypes: IterationTypes, errorNode: Node | undefined) { + if (iterationTypes === noIterationTypes) + return noIterationTypes; + if (iterationTypes === anyIterationTypes) + return anyIterationTypes; + const { yieldType, returnType, nextType } = iterationTypes; + return createIterationTypes(getAwaitedType(yieldType, errorNode) || anyType, getAwaitedType(returnType, errorNode) || anyType, nextType); + } + /** + * Gets the *yield*, *return*, and *next* types from a non-union type. + * + * If we are unable to find the *yield*, *return*, and *next* types, `noIterationTypes` is + * returned to indicate to the caller that it should report an error. Otherwise, an + * `IterationTypes` record is returned. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterable` instead. + */ + function getIterationTypesOfIterableWorker(type: ts.Type, use: IterationUse, errorNode: Node | undefined) { + if (isTypeAny(type)) { + return anyIterationTypes; + } + if (use & IterationUse.AllowsAsyncIterablesFlag) { + const iterationTypes = getIterationTypesOfIterableCached(type, asyncIterationTypesResolver) || + getIterationTypesOfIterableFast(type, asyncIterationTypesResolver); + if (iterationTypes) { + return iterationTypes; } - - return result; } - - function isNodeUsedDuringClassInitialization(node: Node) { - return !!findAncestor(node, element => { - if (isConstructorDeclaration(element) && nodeIsPresent(element.body) || isPropertyDeclaration(element)) { - return true; + if (use & IterationUse.AllowsSyncIterablesFlag) { + const iterationTypes = getIterationTypesOfIterableCached(type, syncIterationTypesResolver) || + getIterationTypesOfIterableFast(type, syncIterationTypesResolver); + if (iterationTypes) { + if (use & IterationUse.AllowsAsyncIterablesFlag) { + // for a sync iterable in an async context, only use the cached types if they are valid. + if (iterationTypes !== noIterationTypes) { + return (type as IterableOrIteratorType).iterationTypesOfAsyncIterable = getAsyncFromSyncIterationTypes(iterationTypes, errorNode); + } } - else if (isClassLike(element) || isFunctionLikeDeclaration(element)) { - return "quit"; + else { + return iterationTypes; } - - return false; - }); - } - - function isNodeWithinClass(node: Node, classDeclaration: ClassLikeDeclaration) { - return !!forEachEnclosingClass(node, n => n === classDeclaration); - } - - function getLeftSideOfImportEqualsOrExportAssignment(nodeOnRightSide: EntityName): ImportEqualsDeclaration | ExportAssignment | undefined { - while (nodeOnRightSide.parent.kind === SyntaxKind.QualifiedName) { - nodeOnRightSide = nodeOnRightSide.parent; } - - if (nodeOnRightSide.parent.kind === SyntaxKind.ImportEqualsDeclaration) { - return (nodeOnRightSide.parent).moduleReference === nodeOnRightSide ? nodeOnRightSide.parent : undefined; + } + if (use & IterationUse.AllowsAsyncIterablesFlag) { + const iterationTypes = getIterationTypesOfIterableSlow(type, asyncIterationTypesResolver, errorNode); + if (iterationTypes !== noIterationTypes) { + return iterationTypes; } - - if (nodeOnRightSide.parent.kind === SyntaxKind.ExportAssignment) { - return (nodeOnRightSide.parent).expression === nodeOnRightSide ? nodeOnRightSide.parent : undefined; + } + if (use & IterationUse.AllowsSyncIterablesFlag) { + const iterationTypes = getIterationTypesOfIterableSlow(type, syncIterationTypesResolver, errorNode); + if (iterationTypes !== noIterationTypes) { + if (use & IterationUse.AllowsAsyncIterablesFlag) { + return (type as IterableOrIteratorType).iterationTypesOfAsyncIterable = iterationTypes + ? getAsyncFromSyncIterationTypes(iterationTypes, errorNode) + : noIterationTypes; + } + else { + return iterationTypes; + } } - - return undefined; } - - function isInRightSideOfImportOrExportAssignment(node: EntityName) { - return getLeftSideOfImportEqualsOrExportAssignment(node) !== undefined; + return noIterationTypes; + } + /** + * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or + * `AsyncIterable`-like type from the cache. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterable` instead. + */ + function getIterationTypesOfIterableCached(type: ts.Type, resolver: IterationTypesResolver) { + return (type as IterableOrIteratorType)[resolver.iterableCacheKey]; + } + function getIterationTypesOfGlobalIterableType(globalType: ts.Type, resolver: IterationTypesResolver) { + const globalIterationTypes = getIterationTypesOfIterableCached(globalType, resolver) || + getIterationTypesOfIterableSlow(globalType, resolver, /*errorNode*/ undefined); + return globalIterationTypes === noIterationTypes ? defaultIterationTypes : globalIterationTypes; + } + /** + * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or `AsyncIterable`-like + * type from from common heuristics. + * + * If we previously analyzed this type and found no iteration types, `noIterationTypes` is + * returned. If we found iteration types, an `IterationTypes` record is returned. + * Otherwise, we return `undefined` to indicate to the caller it should perform a more + * exhaustive analysis. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterable` instead. + */ + function getIterationTypesOfIterableFast(type: ts.Type, resolver: IterationTypesResolver) { + // As an optimization, if the type is an instantiation of one of the following global types, then + // just grab its related type argument: + // - `Iterable` or `AsyncIterable` + // - `IterableIterator` or `AsyncIterableIterator` + let globalType: ts.Type; + if (isReferenceToType(type, globalType = resolver.getGlobalIterableType(/*reportErrors*/ false)) || + isReferenceToType(type, globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false))) { + const [yieldType] = getTypeArguments((type as GenericType)); + // The "return" and "next" types of `Iterable` and `IterableIterator` are defined by the + // iteration types of their `[Symbol.iterator]()` method. The same is true for their async cousins. + // While we define these as `any` and `undefined` in our libs by default, a custom lib *could* use + // different definitions. + const { returnType, nextType } = getIterationTypesOfGlobalIterableType(globalType, resolver); + return (type as IterableOrIteratorType)[resolver.iterableCacheKey] = createIterationTypes(yieldType, returnType, nextType); + } + // As an optimization, if the type is an instantiation of the following global type, then + // just grab its related type arguments: + // - `Generator` or `AsyncGenerator` + if (isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))) { + const [yieldType, returnType, nextType] = getTypeArguments((type as GenericType)); + return (type as IterableOrIteratorType)[resolver.iterableCacheKey] = createIterationTypes(yieldType, returnType, nextType); } - - function getSpecialPropertyAssignmentSymbolFromEntityName(entityName: EntityName | PropertyAccessExpression) { - const specialPropertyAssignmentKind = getAssignmentDeclarationKind(entityName.parent.parent as BinaryExpression); - switch (specialPropertyAssignmentKind) { - case AssignmentDeclarationKind.ExportsProperty: - case AssignmentDeclarationKind.PrototypeProperty: - return getSymbolOfNode(entityName.parent); - case AssignmentDeclarationKind.ThisProperty: - case AssignmentDeclarationKind.ModuleExports: - case AssignmentDeclarationKind.Property: - return getSymbolOfNode(entityName.parent.parent); - } + } + /** + * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or `AsyncIterable`-like + * type from its members. + * + * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` + * record is returned. Otherwise, `noIterationTypes` is returned. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterable` instead. + */ + function getIterationTypesOfIterableSlow(type: ts.Type, resolver: IterationTypesResolver, errorNode: Node | undefined) { + const method = getPropertyOfType(type, getPropertyNameForKnownSymbolName(resolver.iteratorSymbolName)); + const methodType = method && !(method.flags & SymbolFlags.Optional) ? getTypeOfSymbol(method) : undefined; + if (isTypeAny(methodType)) { + return (type as IterableOrIteratorType)[resolver.iterableCacheKey] = anyIterationTypes; + } + const signatures = methodType ? getSignaturesOfType(methodType, SignatureKind.Call) : undefined; + if (!some(signatures)) { + return (type as IterableOrIteratorType)[resolver.iterableCacheKey] = noIterationTypes; + } + const iteratorType = getUnionType(map(signatures, getReturnTypeOfSignature), UnionReduction.Subtype); + const iterationTypes = getIterationTypesOfIterator(iteratorType, resolver, errorNode) || noIterationTypes; + return (type as IterableOrIteratorType)[resolver.iterableCacheKey] = iterationTypes; + } + function reportTypeNotIterableError(errorNode: Node, type: ts.Type, allowAsyncIterables: boolean): void { + const message = allowAsyncIterables + ? Diagnostics.Type_0_must_have_a_Symbol_asyncIterator_method_that_returns_an_async_iterator + : Diagnostics.Type_0_must_have_a_Symbol_iterator_method_that_returns_an_iterator; + errorAndMaybeSuggestAwait(errorNode, !!getAwaitedTypeOfPromise(type), message, typeToString(type)); + } + /** + * Gets the *yield*, *return*, and *next* types from an `Iterator`-like or `AsyncIterator`-like type. + * + * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` + * record is returned. Otherwise, `undefined` is returned. + */ + function getIterationTypesOfIterator(type: ts.Type, resolver: IterationTypesResolver, errorNode: Node | undefined) { + if (isTypeAny(type)) { + return anyIterationTypes; + } + const iterationTypes = getIterationTypesOfIteratorCached(type, resolver) || + getIterationTypesOfIteratorFast(type, resolver) || + getIterationTypesOfIteratorSlow(type, resolver, errorNode); + return iterationTypes === noIterationTypes ? undefined : iterationTypes; + } + /** + * Gets the iteration types of an `Iterator`-like or `AsyncIterator`-like type from the + * cache. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterator` instead. + */ + function getIterationTypesOfIteratorCached(type: ts.Type, resolver: IterationTypesResolver) { + return (type as IterableOrIteratorType)[resolver.iteratorCacheKey]; + } + /** + * Gets the iteration types of an `Iterator`-like or `AsyncIterator`-like type from the + * cache or from common heuristics. + * + * If we previously analyzed this type and found no iteration types, `noIterationTypes` is + * returned. If we found iteration types, an `IterationTypes` record is returned. + * Otherwise, we return `undefined` to indicate to the caller it should perform a more + * exhaustive analysis. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterator` instead. + */ + function getIterationTypesOfIteratorFast(type: ts.Type, resolver: IterationTypesResolver) { + // As an optimization, if the type is an instantiation of one of the following global types, + // then just grab its related type argument: + // - `IterableIterator` or `AsyncIterableIterator` + // - `Iterator` or `AsyncIterator` + // - `Generator` or `AsyncGenerator` + const globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false); + if (isReferenceToType(type, globalType)) { + const [yieldType] = getTypeArguments((type as GenericType)); + // The "return" and "next" types of `IterableIterator` and `AsyncIterableIterator` are defined by the + // iteration types of their `next`, `return`, and `throw` methods. While we define these as `any` + // and `undefined` in our libs by default, a custom lib *could* use different definitions. + const globalIterationTypes = getIterationTypesOfIteratorCached(globalType, resolver) || + getIterationTypesOfIteratorSlow(globalType, resolver, /*errorNode*/ undefined); + const { returnType, nextType } = globalIterationTypes === noIterationTypes ? defaultIterationTypes : globalIterationTypes; + return (type as IterableOrIteratorType)[resolver.iteratorCacheKey] = createIterationTypes(yieldType, returnType, nextType); + } + if (isReferenceToType(type, resolver.getGlobalIteratorType(/*reportErrors*/ false)) || + isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))) { + const [yieldType, returnType, nextType] = getTypeArguments((type as GenericType)); + return (type as IterableOrIteratorType)[resolver.iteratorCacheKey] = createIterationTypes(yieldType, returnType, nextType); } - - function isImportTypeQualifierPart(node: EntityName): ImportTypeNode | undefined { - let parent = node.parent; - while (isQualifiedName(parent)) { - node = parent; - parent = parent.parent; - } - if (parent && parent.kind === SyntaxKind.ImportType && (parent as ImportTypeNode).qualifier === node) { - return parent as ImportTypeNode; - } + } + function isIteratorResult(type: ts.Type, kind: IterationTypeKind.Yield | IterationTypeKind.Return) { + // From https://tc39.github.io/ecma262/#sec-iteratorresult-interface: + // > [done] is the result status of an iterator `next` method call. If the end of the iterator was reached `done` is `true`. + // > If the end was not reached `done` is `false` and a value is available. + // > If a `done` property (either own or inherited) does not exist, it is consider to have the value `false`. + const doneType = getTypeOfPropertyOfType(type, ("done" as __String)) || falseType; + return isTypeAssignableTo(kind === IterationTypeKind.Yield ? falseType : trueType, doneType); + } + function isYieldIteratorResult(type: ts.Type) { + return isIteratorResult(type, IterationTypeKind.Yield); + } + function isReturnIteratorResult(type: ts.Type) { + return isIteratorResult(type, IterationTypeKind.Return); + } + /** + * Gets the *yield* and *return* types of an `IteratorResult`-like type. + * + * If we are unable to determine a *yield* or a *return* type, `noIterationTypes` is + * returned to indicate to the caller that it should handle the error. Otherwise, an + * `IterationTypes` record is returned. + */ + function getIterationTypesOfIteratorResult(type: ts.Type) { + if (isTypeAny(type)) { + return anyIterationTypes; + } + const cachedTypes = (type as IterableOrIteratorType).iterationTypesOfIteratorResult; + if (cachedTypes) { + return cachedTypes; + } + // As an optimization, if the type is an instantiation of one of the global `IteratorYieldResult` + // or `IteratorReturnResult` types, then just grab its type argument. + if (isReferenceToType(type, getGlobalIteratorYieldResultType(/*reportErrors*/ false))) { + const yieldType = getTypeArguments((type as GenericType))[0]; + return (type as IterableOrIteratorType).iterationTypesOfIteratorResult = createIterationTypes(yieldType, /*returnType*/ undefined, /*nextType*/ undefined); + } + if (isReferenceToType(type, getGlobalIteratorReturnResultType(/*reportErrors*/ false))) { + const returnType = getTypeArguments((type as GenericType))[0]; + return (type as IterableOrIteratorType).iterationTypesOfIteratorResult = createIterationTypes(/*yieldType*/ undefined, returnType, /*nextType*/ undefined); + } + // Choose any constituents that can produce the requested iteration type. + const yieldIteratorResult = filterType(type, isYieldIteratorResult); + const yieldType = yieldIteratorResult !== neverType ? getTypeOfPropertyOfType(yieldIteratorResult, ("value" as __String)) : undefined; + const returnIteratorResult = filterType(type, isReturnIteratorResult); + const returnType = returnIteratorResult !== neverType ? getTypeOfPropertyOfType(returnIteratorResult, ("value" as __String)) : undefined; + if (!yieldType && !returnType) { + return (type as IterableOrIteratorType).iterationTypesOfIteratorResult = noIterationTypes; + } + // From https://tc39.github.io/ecma262/#sec-iteratorresult-interface + // > ... If the iterator does not have a return value, `value` is `undefined`. In that case, the + // > `value` property may be absent from the conforming object if it does not inherit an explicit + // > `value` property. + return (type as IterableOrIteratorType).iterationTypesOfIteratorResult = createIterationTypes(yieldType, returnType || voidType, /*nextType*/ undefined); + } + /** + * Gets the *yield*, *return*, and *next* types of a the `next()`, `return()`, or + * `throw()` method of an `Iterator`-like or `AsyncIterator`-like type. + * + * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` + * record is returned. Otherwise, we return `undefined`. + */ + function getIterationTypesOfMethod(type: ts.Type, resolver: IterationTypesResolver, methodName: "next" | "return" | "throw", errorNode: Node | undefined): IterationTypes | undefined { + const method = getPropertyOfType(type, (methodName as __String)); + // Ignore 'return' or 'throw' if they are missing. + if (!method && methodName !== "next") { return undefined; } - - function getSymbolOfEntityNameOrPropertyAccessExpression(entityName: EntityName | PropertyAccessExpression): Symbol | undefined { - if (isDeclarationName(entityName)) { - return getSymbolOfNode(entityName.parent); - } - - if (isInJSFile(entityName) && - entityName.parent.kind === SyntaxKind.PropertyAccessExpression && - entityName.parent === (entityName.parent.parent as BinaryExpression).left) { - // Check if this is a special property assignment - const specialPropertyAssignmentSymbol = getSpecialPropertyAssignmentSymbolFromEntityName(entityName); - if (specialPropertyAssignmentSymbol) { - return specialPropertyAssignmentSymbol; + const methodType = method && !(methodName === "next" && (method.flags & SymbolFlags.Optional)) + ? methodName === "next" ? getTypeOfSymbol(method) : getTypeWithFacts(getTypeOfSymbol(method), TypeFacts.NEUndefinedOrNull) + : undefined; + if (isTypeAny(methodType)) { + // `return()` and `throw()` don't provide a *next* type. + return methodName === "next" ? anyIterationTypes : anyIterationTypesExceptNext; + } + // Both async and non-async iterators *must* have a `next` method. + const methodSignatures = methodType ? getSignaturesOfType(methodType, SignatureKind.Call) : emptyArray; + if (methodSignatures.length === 0) { + if (errorNode) { + const diagnostic = methodName === "next" + ? resolver.mustHaveANextMethodDiagnostic + : resolver.mustBeAMethodDiagnostic; + error(errorNode, diagnostic, methodName); + } + return methodName === "next" ? anyIterationTypes : undefined; + } + // Extract the first parameter and return type of each signature. + let methodParameterTypes: ts.Type[] | undefined; + let methodReturnTypes: ts.Type[] | undefined; + for (const signature of methodSignatures) { + if (methodName !== "throw" && some(signature.parameters)) { + methodParameterTypes = append(methodParameterTypes, getTypeAtPosition(signature, 0)); + } + methodReturnTypes = append(methodReturnTypes, getReturnTypeOfSignature(signature)); + } + // Resolve the *next* or *return* type from the first parameter of a `next()` or + // `return()` method, respectively. + let returnTypes: ts.Type[] | undefined; + let nextType: ts.Type | undefined; + if (methodName !== "throw") { + const methodParameterType = methodParameterTypes ? getUnionType(methodParameterTypes) : unknownType; + if (methodName === "next") { + // The value of `next(value)` is *not* awaited by async generators + nextType = methodParameterType; + } + else if (methodName === "return") { + // The value of `return(value)` *is* awaited by async generators + const resolvedMethodParameterType = resolver.resolveIterationType(methodParameterType, errorNode) || anyType; + returnTypes = append(returnTypes, resolvedMethodParameterType); + } + } + // Resolve the *yield* and *return* types from the return type of the method (i.e. `IteratorResult`) + let yieldType: ts.Type; + const methodReturnType = methodReturnTypes ? getUnionType(methodReturnTypes, UnionReduction.Subtype) : neverType; + const resolvedMethodReturnType = resolver.resolveIterationType(methodReturnType, errorNode) || anyType; + const iterationTypes = getIterationTypesOfIteratorResult(resolvedMethodReturnType); + if (iterationTypes === noIterationTypes) { + if (errorNode) { + error(errorNode, resolver.mustHaveAValueDiagnostic, methodName); + } + yieldType = anyType; + returnTypes = append(returnTypes, anyType); + } + else { + yieldType = iterationTypes.yieldType; + returnTypes = append(returnTypes, iterationTypes.returnType); + } + return createIterationTypes(yieldType, getUnionType(returnTypes), nextType); + } + /** + * Gets the *yield*, *return*, and *next* types of an `Iterator`-like or `AsyncIterator`-like + * type from its members. + * + * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` + * record is returned. Otherwise, `noIterationTypes` is returned. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterator` instead. + */ + function getIterationTypesOfIteratorSlow(type: ts.Type, resolver: IterationTypesResolver, errorNode: Node | undefined) { + const iterationTypes = combineIterationTypes([ + getIterationTypesOfMethod(type, resolver, "next", errorNode), + getIterationTypesOfMethod(type, resolver, "return", errorNode), + getIterationTypesOfMethod(type, resolver, "throw", errorNode), + ]); + return (type as IterableOrIteratorType)[resolver.iteratorCacheKey] = iterationTypes; + } + /** + * Gets the requested "iteration type" from a type that is either `Iterable`-like, `Iterator`-like, + * `IterableIterator`-like, or `Generator`-like (for a non-async generator); or `AsyncIterable`-like, + * `AsyncIterator`-like, `AsyncIterableIterator`-like, or `AsyncGenerator`-like (for an async generator). + */ + function getIterationTypeOfGeneratorFunctionReturnType(kind: IterationTypeKind, returnType: ts.Type, isAsyncGenerator: boolean): ts.Type | undefined { + if (isTypeAny(returnType)) { + return undefined; + } + const iterationTypes = getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsyncGenerator); + return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(kind)]; + } + function getIterationTypesOfGeneratorFunctionReturnType(type: ts.Type, isAsyncGenerator: boolean) { + if (isTypeAny(type)) { + return anyIterationTypes; + } + const use = isAsyncGenerator ? IterationUse.AsyncGeneratorReturnType : IterationUse.GeneratorReturnType; + const resolver = isAsyncGenerator ? asyncIterationTypesResolver : syncIterationTypesResolver; + return getIterationTypesOfIterable(type, use, /*errorNode*/ undefined) || + getIterationTypesOfIterator(type, resolver, /*errorNode*/ undefined); + } + function checkBreakOrContinueStatement(node: BreakOrContinueStatement) { + // Grammar checking + if (!checkGrammarStatementInAmbientContext(node)) + checkGrammarBreakOrContinueStatement(node); + // TODO: Check that target label is valid + } + function unwrapReturnType(returnType: ts.Type, functionFlags: FunctionFlags) { + const isGenerator = !!(functionFlags & FunctionFlags.Generator); + const isAsync = !!(functionFlags & FunctionFlags.Async); + return isGenerator ? getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, isAsync) || errorType : + isAsync ? getPromisedTypeOfPromise(returnType) || errorType : + returnType; + } + function isUnwrappedReturnTypeVoidOrAny(func: SignatureDeclaration, returnType: ts.Type): boolean { + const unwrappedReturnType = unwrapReturnType(returnType, getFunctionFlags(func)); + return !!unwrappedReturnType && maybeTypeOfKind(unwrappedReturnType, TypeFlags.Void | TypeFlags.AnyOrUnknown); + } + function checkReturnStatement(node: ReturnStatement) { + // Grammar checking + if (checkGrammarStatementInAmbientContext(node)) { + return; + } + const func = getContainingFunction(node); + if (!func) { + grammarErrorOnFirstToken(node, Diagnostics.A_return_statement_can_only_be_used_within_a_function_body); + return; + } + const signature = getSignatureFromDeclaration(func); + const returnType = getReturnTypeOfSignature(signature); + const functionFlags = getFunctionFlags(func); + if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) { + const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; + if (func.kind === SyntaxKind.SetAccessor) { + if (node.expression) { + error(node, Diagnostics.Setters_cannot_return_a_value); } } - - if (entityName.parent.kind === SyntaxKind.ExportAssignment && isEntityNameExpression(entityName)) { - // Even an entity name expression that doesn't resolve as an entityname may still typecheck as a property access expression - const success = resolveEntityName(entityName, - /*all meanings*/ SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*ignoreErrors*/ true); - if (success && success !== unknownSymbol) { - return success; + else if (func.kind === SyntaxKind.Constructor) { + if (node.expression && !checkTypeAssignableToAndOptionallyElaborate(exprType, returnType, node, node.expression)) { + error(node, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class); } } - else if (!isPropertyAccessExpression(entityName) && isInRightSideOfImportOrExportAssignment(entityName)) { - // Since we already checked for ExportAssignment, this really could only be an Import - const importEqualsDeclaration = getAncestor(entityName, SyntaxKind.ImportEqualsDeclaration); - Debug.assert(importEqualsDeclaration !== undefined); - return getSymbolOfPartOfRightHandSideOfImportEquals(entityName, /*dontResolveAlias*/ true); - } - - if (!isPropertyAccessExpression(entityName)) { - const possibleImportNode = isImportTypeQualifierPart(entityName); - if (possibleImportNode) { - getTypeFromTypeNode(possibleImportNode); - const sym = getNodeLinks(entityName).resolvedSymbol; - return sym === unknownSymbol ? undefined : sym; + else if (getReturnTypeFromAnnotation(func)) { + const unwrappedReturnType = unwrapReturnType(returnType, functionFlags); + const unwrappedExprType = functionFlags & FunctionFlags.Async + ? checkAwaitedType(exprType, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) + : exprType; + if (unwrappedReturnType) { + // If the function has a return type, but promisedType is + // undefined, an error will be reported in checkAsyncFunctionReturnType + // so we don't need to report one here. + checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, node, node.expression); } } - - while (isRightSideOfQualifiedNameOrPropertyAccess(entityName)) { - entityName = entityName.parent; + } + else if (func.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeVoidOrAny(func, returnType)) { + // The function has a return type, but the return statement doesn't have an expression. + error(node, Diagnostics.Not_all_code_paths_return_a_value); + } + } + function checkWithStatement(node: WithStatement) { + // Grammar checking for withStatement + if (!checkGrammarStatementInAmbientContext(node)) { + if (node.flags & NodeFlags.AwaitContext) { + grammarErrorOnFirstToken(node, Diagnostics.with_statements_are_not_allowed_in_an_async_function_block); } - - if (isHeritageClauseElementIdentifier(entityName)) { - let meaning = SymbolFlags.None; - // In an interface or class, we're definitely interested in a type. - if (entityName.parent.kind === SyntaxKind.ExpressionWithTypeArguments) { - meaning = SymbolFlags.Type; - - // In a class 'extends' clause we are also looking for a value. - if (isExpressionWithTypeArgumentsInClassExtendsClause(entityName.parent)) { - meaning |= SymbolFlags.Value; - } + } + checkExpression(node.expression); + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const start = getSpanOfTokenAtPosition(sourceFile, node.pos).start; + const end = node.statement.pos; + grammarErrorAtPos(sourceFile, start, end - start, Diagnostics.The_with_statement_is_not_supported_All_symbols_in_a_with_block_will_have_type_any); + } + } + function checkSwitchStatement(node: SwitchStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + let firstDefaultClause: CaseOrDefaultClause; + let hasDuplicateDefaultClause = false; + const expressionType = checkExpression(node.expression); + const expressionIsLiteral = isLiteralType(expressionType); + forEach(node.caseBlock.clauses, clause => { + // Grammar check for duplicate default clauses, skip if we already report duplicate default clause + if (clause.kind === SyntaxKind.DefaultClause && !hasDuplicateDefaultClause) { + if (firstDefaultClause === undefined) { + firstDefaultClause = clause; } else { - meaning = SymbolFlags.Namespace; + grammarErrorOnNode(clause, Diagnostics.A_default_clause_cannot_appear_more_than_once_in_a_switch_statement); + hasDuplicateDefaultClause = true; } - - meaning |= SymbolFlags.Alias; - const entityNameSymbol = isEntityNameExpression(entityName) ? resolveEntityName(entityName, meaning) : undefined; - if (entityNameSymbol) { - return entityNameSymbol; + } + if (produceDiagnostics && clause.kind === SyntaxKind.CaseClause) { + // TypeScript 1.0 spec (April 2014): 5.9 + // In a 'switch' statement, each 'case' expression must be of a type that is comparable + // to or from the type of the 'switch' expression. + let caseType = checkExpression(clause.expression); + const caseIsLiteral = isLiteralType(caseType); + let comparedExpressionType = expressionType; + if (!caseIsLiteral || !expressionIsLiteral) { + caseType = caseIsLiteral ? getBaseTypeOfLiteralType(caseType) : caseType; + comparedExpressionType = getBaseTypeOfLiteralType(expressionType); + } + if (!isTypeEqualityComparableTo(comparedExpressionType, caseType)) { + // expressionType is not comparable to caseType, try the reversed check and report errors if it fails + checkTypeComparableTo(caseType, comparedExpressionType, clause.expression, /*headMessage*/ undefined); } } - - if (entityName.parent.kind === SyntaxKind.JSDocParameterTag) { - return getParameterSymbolFromJSDoc(entityName.parent as JSDocParameterTag); + forEach(clause.statements, checkSourceElement); + if (compilerOptions.noFallthroughCasesInSwitch && clause.fallthroughFlowNode && isReachableFlowNode(clause.fallthroughFlowNode)) { + error(clause, Diagnostics.Fallthrough_case_in_switch); } - - if (entityName.parent.kind === SyntaxKind.TypeParameter && entityName.parent.parent.kind === SyntaxKind.JSDocTemplateTag) { - Debug.assert(!isInJSFile(entityName)); // Otherwise `isDeclarationName` would have been true. - const typeParameter = getTypeParameterFromJsDoc(entityName.parent as TypeParameterDeclaration & { parent: JSDocTemplateTag }); - return typeParameter && typeParameter.symbol; + }); + if (node.caseBlock.locals) { + registerForUnusedIdentifiersCheck(node.caseBlock); + } + } + function checkLabeledStatement(node: LabeledStatement) { + // Grammar checking + if (!checkGrammarStatementInAmbientContext(node)) { + findAncestor(node.parent, current => { + if (isFunctionLike(current)) { + return "quit"; + } + if (current.kind === SyntaxKind.LabeledStatement && (current).label.escapedText === node.label.escapedText) { + grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0, getTextOfNode(node.label)); + return true; + } + return false; + }); + } + // ensure that label is unique + checkSourceElement(node.statement); + } + function checkThrowStatement(node: ThrowStatement) { + // Grammar checking + if (!checkGrammarStatementInAmbientContext(node)) { + if (node.expression === undefined) { + grammarErrorAfterFirstToken(node, Diagnostics.Line_break_not_permitted_here); } - - if (isExpressionNode(entityName)) { - if (nodeIsMissing(entityName)) { - // Missing entity name. - return undefined; + } + if (node.expression) { + checkExpression(node.expression); + } + } + function checkTryStatement(node: TryStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + checkBlock(node.tryBlock); + const catchClause = node.catchClause; + if (catchClause) { + // Grammar checking + if (catchClause.variableDeclaration) { + if (catchClause.variableDeclaration.type) { + grammarErrorOnFirstToken(catchClause.variableDeclaration.type, Diagnostics.Catch_clause_variable_cannot_have_a_type_annotation); } - - if (entityName.kind === SyntaxKind.Identifier) { - if (isJSXTagName(entityName) && isJsxIntrinsicIdentifier(entityName)) { - const symbol = getIntrinsicTagSymbol(entityName.parent); - return symbol === unknownSymbol ? undefined : symbol; - } - - return resolveEntityName(entityName, SymbolFlags.Value, /*ignoreErrors*/ false, /*dontResolveAlias*/ true); + else if (catchClause.variableDeclaration.initializer) { + grammarErrorOnFirstToken(catchClause.variableDeclaration.initializer, Diagnostics.Catch_clause_variable_cannot_have_an_initializer); } - else if (entityName.kind === SyntaxKind.PropertyAccessExpression || entityName.kind === SyntaxKind.QualifiedName) { - const links = getNodeLinks(entityName); - if (links.resolvedSymbol) { - return links.resolvedSymbol; - } - - if (entityName.kind === SyntaxKind.PropertyAccessExpression) { - checkPropertyAccessExpression(entityName); - } - else { - checkQualifiedName(entityName); + else { + const blockLocals = catchClause.block.locals; + if (blockLocals) { + forEachKey((catchClause.locals!), caughtName => { + const blockLocal = blockLocals.get(caughtName); + if (blockLocal && (blockLocal.flags & SymbolFlags.BlockScopedVariable) !== 0) { + grammarErrorOnNode(blockLocal.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, caughtName); + } + }); } - return links.resolvedSymbol; } } - else if (isTypeReferenceIdentifier(entityName)) { - const meaning = entityName.parent.kind === SyntaxKind.TypeReference ? SymbolFlags.Type : SymbolFlags.Namespace; - return resolveEntityName(entityName, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true); + checkBlock(catchClause.block); + } + if (node.finallyBlock) { + checkBlock(node.finallyBlock); + } + } + function checkIndexConstraints(type: ts.Type) { + const declaredNumberIndexer = getIndexDeclarationOfSymbol(type.symbol, IndexKind.Number); + const declaredStringIndexer = getIndexDeclarationOfSymbol(type.symbol, IndexKind.String); + const stringIndexType = getIndexTypeOfType(type, IndexKind.String); + const numberIndexType = getIndexTypeOfType(type, IndexKind.Number); + if (stringIndexType || numberIndexType) { + forEach(getPropertiesOfObjectType(type), prop => { + const propType = getTypeOfSymbol(prop); + checkIndexConstraintForProperty(prop, propType, type, declaredStringIndexer, stringIndexType, IndexKind.String); + checkIndexConstraintForProperty(prop, propType, type, declaredNumberIndexer, numberIndexType, IndexKind.Number); + }); + const classDeclaration = type.symbol.valueDeclaration; + if (getObjectFlags(type) & ObjectFlags.Class && isClassLike(classDeclaration)) { + for (const member of classDeclaration.members) { + // Only process instance properties with computed names here. + // Static properties cannot be in conflict with indexers, + // and properties with literal names were already checked. + if (!hasModifier(member, ModifierFlags.Static) && hasNonBindableDynamicName(member)) { + const symbol = getSymbolOfNode(member); + const propType = getTypeOfSymbol(symbol); + checkIndexConstraintForProperty(symbol, propType, type, declaredStringIndexer, stringIndexType, IndexKind.String); + checkIndexConstraintForProperty(symbol, propType, type, declaredNumberIndexer, numberIndexType, IndexKind.Number); + } + } } - - if (entityName.parent.kind === SyntaxKind.TypePredicate) { - return resolveEntityName(entityName, /*meaning*/ SymbolFlags.FunctionScopedVariable); + } + let errorNode: Node | undefined; + if (stringIndexType && numberIndexType) { + errorNode = declaredNumberIndexer || declaredStringIndexer; + // condition 'errorNode === undefined' may appear if types does not declare nor string neither number indexer + if (!errorNode && (getObjectFlags(type) & ObjectFlags.Interface)) { + const someBaseTypeHasBothIndexers = forEach(getBaseTypes((type)), base => getIndexTypeOfType(base, IndexKind.String) && getIndexTypeOfType(base, IndexKind.Number)); + errorNode = someBaseTypeHasBothIndexers ? undefined : type.symbol.declarations[0]; } - - // Do we want to return undefined here? - return undefined; } - - function getSymbolAtLocation(node: Node): Symbol | undefined { - if (node.kind === SyntaxKind.SourceFile) { - return isExternalModule(node) ? getMergedSymbol(node.symbol) : undefined; + if (errorNode && !isTypeAssignableTo(numberIndexType!, stringIndexType!)) { // TODO: GH#18217 + error(errorNode, Diagnostics.Numeric_index_type_0_is_not_assignable_to_string_index_type_1, typeToString(numberIndexType!), typeToString(stringIndexType!)); + } + function checkIndexConstraintForProperty(prop: ts.Symbol, propertyType: ts.Type, containingType: ts.Type, indexDeclaration: Declaration | undefined, indexType: ts.Type | undefined, indexKind: IndexKind): void { + // ESSymbol properties apply to neither string nor numeric indexers. + if (!indexType || isKnownSymbol(prop)) { + return; } - const { parent } = node; - const grandParent = parent.parent; - - if (node.flags & NodeFlags.InWithStatement) { - // We cannot answer semantic questions within a with block, do not proceed any further - return undefined; + const propDeclaration = prop.valueDeclaration; + const name = propDeclaration && getNameOfDeclaration(propDeclaration); + // index is numeric and property name is not valid numeric literal + if (indexKind === IndexKind.Number && !(name ? isNumericName(name) : isNumericLiteralName(prop.escapedName))) { + return; } - - if (isDeclarationNameOrImportPropertyName(node)) { - // This is a declaration, call getSymbolOfNode - const parentSymbol = getSymbolOfNode(parent)!; - return isImportOrExportSpecifier(node.parent) && node.parent.propertyName === node - ? getImmediateAliasedSymbol(parentSymbol) - : parentSymbol; + // perform property check if property or indexer is declared in 'type' + // this allows us to rule out cases when both property and indexer are inherited from the base class + let errorNode: Node | undefined; + if (propDeclaration && name && + (propDeclaration.kind === SyntaxKind.BinaryExpression || + name.kind === SyntaxKind.ComputedPropertyName || + prop.parent === containingType.symbol)) { + errorNode = propDeclaration; } - else if (isLiteralComputedPropertyDeclarationName(node)) { - return getSymbolOfNode(parent.parent); + else if (indexDeclaration) { + errorNode = indexDeclaration; } - - if (node.kind === SyntaxKind.Identifier) { - if (isInRightSideOfImportOrExportAssignment(node)) { - return getSymbolOfEntityNameOrPropertyAccessExpression(node); - } - else if (parent.kind === SyntaxKind.BindingElement && - grandParent.kind === SyntaxKind.ObjectBindingPattern && - node === (parent).propertyName) { - const typeOfPattern = getTypeOfNode(grandParent); - const propertyDeclaration = getPropertyOfType(typeOfPattern, (node).escapedText); - - if (propertyDeclaration) { - return propertyDeclaration; - } - } + else if (getObjectFlags(containingType) & ObjectFlags.Interface) { + // for interfaces property and indexer might be inherited from different bases + // check if any base class already has both property and indexer. + // check should be performed only if 'type' is the first type that brings property\indexer together + const someBaseClassHasBothPropertyAndIndexer = forEach(getBaseTypes((containingType)), base => getPropertyOfObjectType(base, prop.escapedName) && getIndexTypeOfType(base, indexKind)); + errorNode = someBaseClassHasBothPropertyAndIndexer ? undefined : containingType.symbol.declarations[0]; } - - switch (node.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.QualifiedName: - return getSymbolOfEntityNameOrPropertyAccessExpression(node); - - case SyntaxKind.ThisKeyword: - const container = getThisContainer(node, /*includeArrowFunctions*/ false); - if (isFunctionLike(container)) { - const sig = getSignatureFromDeclaration(container); - if (sig.thisParameter) { - return sig.thisParameter; - } - } - if (isInExpressionContext(node)) { - return checkExpression(node as Expression).symbol; + if (errorNode && !isTypeAssignableTo(propertyType, indexType)) { + const errorMessage = indexKind === IndexKind.String + ? Diagnostics.Property_0_of_type_1_is_not_assignable_to_string_index_type_2 + : Diagnostics.Property_0_of_type_1_is_not_assignable_to_numeric_index_type_2; + error(errorNode, errorMessage, symbolToString(prop), typeToString(propertyType), typeToString(indexType)); + } + } + } + function checkTypeNameIsReserved(name: Identifier, message: DiagnosticMessage): void { + // TS 1.0 spec (April 2014): 3.6.1 + // The predefined type keywords are reserved and cannot be used as names of user defined types. + switch (name.escapedText) { + case "any": + case "unknown": + case "number": + case "bigint": + case "boolean": + case "string": + case "symbol": + case "void": + case "object": + error(name, message, name.escapedText as string); + } + } + /** + * The name cannot be used as 'Object' of user defined types with special target. + */ + function checkClassNameCollisionWithObject(name: Identifier): void { + if (languageVersion === ScriptTarget.ES5 && name.escapedText === "Object" + && moduleKind !== ModuleKind.ES2015 && moduleKind !== ModuleKind.ESNext) { + error(name, Diagnostics.Class_name_cannot_be_Object_when_targeting_ES5_with_module_0, ModuleKind[moduleKind]); // https://github.com/Microsoft/TypeScript/issues/17494 + } + } + /** + * Check each type parameter and check that type parameters have no duplicate type parameter declarations + */ + function checkTypeParameters(typeParameterDeclarations: readonly TypeParameterDeclaration[] | undefined) { + if (typeParameterDeclarations) { + let seenDefault = false; + for (let i = 0; i < typeParameterDeclarations.length; i++) { + const node = typeParameterDeclarations[i]; + checkTypeParameter(node); + if (produceDiagnostics) { + if (node.default) { + seenDefault = true; + checkTypeParametersNotReferenced(node.default, typeParameterDeclarations, i); } - // falls through - - case SyntaxKind.ThisType: - return getTypeFromThisTypeNode(node as ThisExpression | ThisTypeNode).symbol; - - case SyntaxKind.SuperKeyword: - return checkExpression(node as Expression).symbol; - - case SyntaxKind.ConstructorKeyword: - // constructor keyword for an overload, should take us to the definition if it exist - const constructorDeclaration = node.parent; - if (constructorDeclaration && constructorDeclaration.kind === SyntaxKind.Constructor) { - return (constructorDeclaration.parent).symbol; + else if (seenDefault) { + error(node, Diagnostics.Required_type_parameters_may_not_follow_optional_type_parameters); } - return undefined; - - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - // 1). import x = require("./mo/*gotToDefinitionHere*/d") - // 2). External module name in an import declaration - // 3). Dynamic import call or require in javascript - // 4). type A = import("./f/*gotToDefinitionHere*/oo") - if ((isExternalModuleImportEqualsDeclaration(node.parent.parent) && getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node) || - ((node.parent.kind === SyntaxKind.ImportDeclaration || node.parent.kind === SyntaxKind.ExportDeclaration) && (node.parent).moduleSpecifier === node) || - ((isInJSFile(node) && isRequireCall(node.parent, /*checkArgumentIsStringLiteralLike*/ false)) || isImportCall(node.parent)) || - (isLiteralTypeNode(node.parent) && isLiteralImportTypeNode(node.parent.parent) && node.parent.parent.argument === node.parent) - ) { - return resolveExternalModuleName(node, node); - } - if (isCallExpression(parent) && isBindableObjectDefinePropertyCall(parent) && parent.arguments[1] === node) { - return getSymbolOfNode(parent); + for (let j = 0; j < i; j++) { + if (typeParameterDeclarations[j].symbol === node.symbol) { + error(node.name, Diagnostics.Duplicate_identifier_0, declarationNameToString(node.name)); + } } - // falls through - - case SyntaxKind.NumericLiteral: - // index access - const objectType = isElementAccessExpression(parent) - ? parent.argumentExpression === node ? getTypeOfExpression(parent.expression) : undefined - : isLiteralTypeNode(parent) && isIndexedAccessTypeNode(grandParent) - ? getTypeFromTypeNode(grandParent.objectType) - : undefined; - return objectType && getPropertyOfType(objectType, escapeLeadingUnderscores((node as StringLiteral | NumericLiteral).text)); - - case SyntaxKind.DefaultKeyword: - case SyntaxKind.FunctionKeyword: - case SyntaxKind.EqualsGreaterThanToken: - case SyntaxKind.ClassKeyword: - return getSymbolOfNode(node.parent); - case SyntaxKind.ImportType: - return isLiteralImportTypeNode(node) ? getSymbolAtLocation(node.argument.literal) : undefined; - - case SyntaxKind.ExportKeyword: - return isExportAssignment(node.parent) ? Debug.assertDefined(node.parent.symbol) : undefined; - - default: - return undefined; + } } } - - function getShorthandAssignmentValueSymbol(location: Node): Symbol | undefined { - if (location && location.kind === SyntaxKind.ShorthandPropertyAssignment) { - return resolveEntityName((location).name, SymbolFlags.Value | SymbolFlags.Alias); + } + /** Check that type parameter defaults only reference previously declared type parameters */ + function checkTypeParametersNotReferenced(root: TypeNode, typeParameters: readonly TypeParameterDeclaration[], index: number) { + visit(root); + function visit(node: Node) { + if (node.kind === SyntaxKind.TypeReference) { + const type = getTypeFromTypeReference((node)); + if (type.flags & TypeFlags.TypeParameter) { + for (let i = index; i < typeParameters.length; i++) { + if (type.symbol === getSymbolOfNode(typeParameters[i])) { + error(node, Diagnostics.Type_parameter_defaults_can_only_reference_previously_declared_type_parameters); + } + } + } } - return undefined; - } - - /** Returns the target of an export specifier without following aliases */ - function getExportSpecifierLocalTargetSymbol(node: ExportSpecifier): Symbol | undefined { - return node.parent.parent.moduleSpecifier ? - getExternalModuleMember(node.parent.parent, node) : - resolveEntityName(node.propertyName || node.name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); + forEachChild(node, visit); } - - function getTypeOfNode(node: Node): Type { - if (node.flags & NodeFlags.InWithStatement) { - // We cannot answer semantic questions within a with block, do not proceed any further - return errorType; - } - - const classDecl = tryGetClassImplementingOrExtendingExpressionWithTypeArguments(node); - const classType = classDecl && getDeclaredTypeOfClassOrInterface(getSymbolOfNode(classDecl.class)); - if (isPartOfTypeNode(node)) { - const typeFromTypeNode = getTypeFromTypeNode(node); - return classType ? getTypeWithThisArgument(typeFromTypeNode, classType.thisType) : typeFromTypeNode; + } + /** Check that type parameter lists are identical across multiple declarations */ + function checkTypeParameterListsIdentical(symbol: ts.Symbol) { + if (symbol.declarations.length === 1) { + return; + } + const links = getSymbolLinks(symbol); + if (!links.typeParametersChecked) { + links.typeParametersChecked = true; + const declarations = getClassOrInterfaceDeclarationsOfSymbol(symbol); + if (declarations.length <= 1) { + return; } - - if (isExpressionNode(node)) { - return getRegularTypeOfExpression(node); + const type = (getDeclaredTypeOfSymbol(symbol)); + if (!areTypeParametersIdentical(declarations, type.localTypeParameters!)) { + // Report an error on every conflicting declaration. + const name = symbolToString(symbol); + for (const declaration of declarations) { + error(declaration.name, Diagnostics.All_declarations_of_0_must_have_identical_type_parameters, name); + } } - - if (classType && !classDecl!.isImplements) { - // A SyntaxKind.ExpressionWithTypeArguments is considered a type node, except when it occurs in the - // extends clause of a class. We handle that case here. - const baseType = firstOrUndefined(getBaseTypes(classType)); - return baseType ? getTypeWithThisArgument(baseType, classType.thisType) : errorType; + } + } + function areTypeParametersIdentical(declarations: readonly (ClassDeclaration | InterfaceDeclaration)[], targetParameters: TypeParameter[]) { + const maxTypeArgumentCount = length(targetParameters); + const minTypeArgumentCount = getMinTypeArgumentCount(targetParameters); + for (const declaration of declarations) { + // If this declaration has too few or too many type parameters, we report an error + const sourceParameters = getEffectiveTypeParameterDeclarations(declaration); + const numTypeParameters = sourceParameters.length; + if (numTypeParameters < minTypeArgumentCount || numTypeParameters > maxTypeArgumentCount) { + return false; } - - if (isTypeDeclaration(node)) { - // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration - const symbol = getSymbolOfNode(node); - return getDeclaredTypeOfSymbol(symbol); + for (let i = 0; i < numTypeParameters; i++) { + const source = sourceParameters[i]; + const target = targetParameters[i]; + // If the type parameter node does not have the same as the resolved type + // parameter at this position, we report an error. + if (source.name.escapedText !== target.symbol.escapedName) { + return false; + } + // If the type parameter node does not have an identical constraint as the resolved + // type parameter at this position, we report an error. + const constraint = getEffectiveConstraintOfTypeParameter(source); + const sourceConstraint = constraint && getTypeFromTypeNode(constraint); + const targetConstraint = getConstraintOfTypeParameter(target); + // relax check if later interface augmentation has no constraint, it's more broad and is OK to merge with + // a more constrained interface (this could be generalized to a full heirarchy check, but that's maybe overkill) + if (sourceConstraint && targetConstraint && !isTypeIdenticalTo(sourceConstraint, targetConstraint)) { + return false; + } + // If the type parameter node has a default and it is not identical to the default + // for the type parameter at this position, we report an error. + const sourceDefault = source.default && getTypeFromTypeNode(source.default); + const targetDefault = getDefaultFromTypeParameter(target); + if (sourceDefault && targetDefault && !isTypeIdenticalTo(sourceDefault, targetDefault)) { + return false; + } } - - if (isTypeDeclarationName(node)) { - const symbol = getSymbolAtLocation(node); - return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType; + } + return true; + } + function checkClassExpression(node: ClassExpression): ts.Type { + checkClassLikeDeclaration(node); + checkNodeDeferred(node); + return getTypeOfSymbol(getSymbolOfNode(node)); + } + function checkClassExpressionDeferred(node: ClassExpression) { + forEach(node.members, checkSourceElement); + registerForUnusedIdentifiersCheck(node); + } + function checkClassDeclaration(node: ClassDeclaration) { + if (!node.name && !hasModifier(node, ModifierFlags.Default)) { + grammarErrorOnFirstToken(node, Diagnostics.A_class_declaration_without_the_default_modifier_must_have_a_name); + } + checkClassLikeDeclaration(node); + forEach(node.members, checkSourceElement); + registerForUnusedIdentifiersCheck(node); + } + function checkClassLikeDeclaration(node: ClassLikeDeclaration) { + checkGrammarClassLikeDeclaration(node); + checkDecorators(node); + if (node.name) { + checkTypeNameIsReserved(node.name, Diagnostics.Class_name_cannot_be_0); + checkCollisionWithRequireExportsInGeneratedCode(node, node.name); + checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name); + if (!(node.flags & NodeFlags.Ambient)) { + checkClassNameCollisionWithObject(node.name); + } + } + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + checkExportsOnMergedDeclarations(node); + const symbol = getSymbolOfNode(node); + const type = (getDeclaredTypeOfSymbol(symbol)); + const typeWithThis = getTypeWithThisArgument(type); + const staticType = (getTypeOfSymbol(symbol)); + checkTypeParameterListsIdentical(symbol); + checkClassForDuplicateDeclarations(node); + // Only check for reserved static identifiers on non-ambient context. + if (!(node.flags & NodeFlags.Ambient)) { + checkClassForStaticPropertyNameConflicts(node); + } + const baseTypeNode = getEffectiveBaseTypeNode(node); + if (baseTypeNode) { + forEach(baseTypeNode.typeArguments, checkSourceElement); + if (languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(baseTypeNode.parent, ExternalEmitHelpers.Extends); } - - if (isDeclaration(node)) { - // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration - const symbol = getSymbolOfNode(node); - return getTypeOfSymbol(symbol); + // check both @extends and extends if both are specified. + const extendsNode = getClassExtendsHeritageElement(node); + if (extendsNode && extendsNode !== baseTypeNode) { + checkExpression(extendsNode.expression); } - - if (isDeclarationNameOrImportPropertyName(node)) { - const symbol = getSymbolAtLocation(node); - return symbol ? getTypeOfSymbol(symbol) : errorType; + const baseTypes = getBaseTypes(type); + if (baseTypes.length && produceDiagnostics) { + const baseType = baseTypes[0]; + const baseConstructorType = getBaseConstructorTypeOfClass(type); + const staticBaseType = getApparentType(baseConstructorType); + checkBaseTypeAccessibility(staticBaseType, baseTypeNode); + checkSourceElement(baseTypeNode.expression); + if (some(baseTypeNode.typeArguments)) { + forEach(baseTypeNode.typeArguments, checkSourceElement); + for (const constructor of getConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode)) { + if (!checkTypeArgumentConstraints(baseTypeNode, constructor.typeParameters!)) { + break; + } + } + } + const baseWithThis = getTypeWithThisArgument(baseType, type.thisType); + if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) { + issueMemberSpecificError(node, typeWithThis, baseWithThis, Diagnostics.Class_0_incorrectly_extends_base_class_1); + } + else { + // Report static side error only when instance type is assignable + checkTypeAssignableTo(staticType, getTypeWithoutSignatures(staticBaseType), node.name || node, Diagnostics.Class_static_side_0_incorrectly_extends_base_class_static_side_1); + } + if (baseConstructorType.flags & TypeFlags.TypeVariable && !isMixinConstructorType(staticType)) { + error(node.name || node, Diagnostics.A_mixin_class_must_have_a_constructor_with_a_single_rest_parameter_of_type_any); + } + if (!(staticBaseType.symbol && staticBaseType.symbol.flags & SymbolFlags.Class) && !(baseConstructorType.flags & TypeFlags.TypeVariable)) { + // When the static base type is a "class-like" constructor function (but not actually a class), we verify + // that all instantiated base constructor signatures return the same type. + const constructors = getInstantiatedConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode); + if (forEach(constructors, sig => !isJSConstructor(sig.declaration) && !isTypeIdenticalTo(getReturnTypeOfSignature(sig), baseType))) { + error(baseTypeNode.expression, Diagnostics.Base_constructors_must_all_have_the_same_return_type); + } + } + checkKindsOfPropertyMemberOverrides(type, baseType); + } + } + const implementedTypeNodes = getClassImplementsHeritageClauseElements(node); + if (implementedTypeNodes) { + for (const typeRefNode of implementedTypeNodes) { + if (!isEntityNameExpression(typeRefNode.expression)) { + error(typeRefNode.expression, Diagnostics.A_class_can_only_implement_an_identifier_Slashqualified_name_with_optional_type_arguments); + } + checkTypeReferenceNode(typeRefNode); + if (produceDiagnostics) { + const t = getTypeFromTypeNode(typeRefNode); + if (t !== errorType) { + if (isValidBaseType(t)) { + const genericDiag = t.symbol && t.symbol.flags & SymbolFlags.Class ? + Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass : + Diagnostics.Class_0_incorrectly_implements_interface_1; + const baseWithThis = getTypeWithThisArgument(t, type.thisType); + if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) { + issueMemberSpecificError(node, typeWithThis, baseWithThis, genericDiag); + } + } + else { + error(typeRefNode, Diagnostics.A_class_can_only_implement_an_object_type_or_intersection_of_object_types_with_statically_known_members); + } + } + } } - - if (isBindingPattern(node)) { - return getTypeForVariableLikeDeclaration(node.parent, /*includeOptionality*/ true) || errorType; + } + if (produceDiagnostics) { + checkIndexConstraints(type); + checkTypeForDuplicateIndexSignatures(node); + checkPropertyInitialization(node); + } + } + function issueMemberSpecificError(node: ClassLikeDeclaration, typeWithThis: ts.Type, baseWithThis: ts.Type, broadDiag: DiagnosticMessage) { + // iterate over all implemented properties and issue errors on each one which isn't compatible, rather than the class as a whole, if possible + let issuedMemberError = false; + for (const member of node.members) { + if (hasStaticModifier(member)) { + continue; } - - if (isInRightSideOfImportOrExportAssignment(node)) { - const symbol = getSymbolAtLocation(node); - if (symbol) { - const declaredType = getDeclaredTypeOfSymbol(symbol); - return declaredType !== errorType ? declaredType : getTypeOfSymbol(symbol); + const declaredProp = member.name && getSymbolAtLocation(member.name) || getSymbolAtLocation(member); + if (declaredProp) { + const prop = getPropertyOfType(typeWithThis, declaredProp.escapedName); + const baseProp = getPropertyOfType(baseWithThis, declaredProp.escapedName); + if (prop && baseProp) { + const rootChain = () => chainDiagnosticMessages( + /*details*/ undefined, Diagnostics.Property_0_in_type_1_is_not_assignable_to_the_same_property_in_base_type_2, symbolToString(declaredProp), typeToString(typeWithThis), typeToString(baseWithThis)); + if (!checkTypeAssignableTo(getTypeOfSymbol(prop), getTypeOfSymbol(baseProp), member.name || member, /*message*/ undefined, rootChain)) { + issuedMemberError = true; + } } } - - return errorType; } - - // Gets the type of object literal or array literal of destructuring assignment. - // { a } from - // for ( { a } of elems) { - // } - // [ a ] from - // [a] = [ some array ...] - function getTypeOfAssignmentPattern(expr: AssignmentPattern): Type | undefined { - Debug.assert(expr.kind === SyntaxKind.ObjectLiteralExpression || expr.kind === SyntaxKind.ArrayLiteralExpression); - // If this is from "for of" - // for ( { a } of elems) { - // } - if (expr.parent.kind === SyntaxKind.ForOfStatement) { - const iteratedType = checkRightHandSideOfForOf((expr.parent).expression, (expr.parent).awaitModifier); - return checkDestructuringAssignment(expr, iteratedType || errorType); - } - // If this is from "for" initializer - // for ({a } = elems[0];.....) { } - if (expr.parent.kind === SyntaxKind.BinaryExpression) { - const iteratedType = getTypeOfExpression((expr.parent).right); - return checkDestructuringAssignment(expr, iteratedType || errorType); - } - // If this is from nested object binding pattern - // for ({ skills: { primary, secondary } } = multiRobot, i = 0; i < 1; i++) { - if (expr.parent.kind === SyntaxKind.PropertyAssignment) { - const node = cast(expr.parent.parent, isObjectLiteralExpression); - const typeOfParentObjectLiteral = getTypeOfAssignmentPattern(node) || errorType; - const propertyIndex = indexOfNode(node.properties, expr.parent); - return checkObjectLiteralDestructuringPropertyAssignment(node, typeOfParentObjectLiteral, propertyIndex); - } - // Array literal assignment - array destructuring pattern - const node = cast(expr.parent, isArrayLiteralExpression); - // [{ property1: p1, property2 }] = elems; - const typeOfArrayLiteral = getTypeOfAssignmentPattern(node) || errorType; - const elementType = checkIteratedTypeOrElementType(IterationUse.Destructuring, typeOfArrayLiteral, undefinedType, expr.parent) || errorType; - return checkArrayLiteralDestructuringElementAssignment(node, typeOfArrayLiteral, node.elements.indexOf(expr), elementType); + if (!issuedMemberError) { + // check again with diagnostics to generate a less-specific error + checkTypeAssignableTo(typeWithThis, baseWithThis, node.name || node, broadDiag); } - - // Gets the property symbol corresponding to the property in destructuring assignment - // 'property1' from - // for ( { property1: a } of elems) { - // } - // 'property1' at location 'a' from: - // [a] = [ property1, property2 ] - function getPropertySymbolOfDestructuringAssignment(location: Identifier) { - // Get the type of the object or array literal and then look for property of given name in the type - const typeOfObjectLiteral = getTypeOfAssignmentPattern(cast(location.parent.parent, isAssignmentPattern)); - return typeOfObjectLiteral && getPropertyOfType(typeOfObjectLiteral, location.escapedText); + } + function checkBaseTypeAccessibility(type: ts.Type, node: ExpressionWithTypeArguments) { + const signatures = getSignaturesOfType(type, SignatureKind.Construct); + if (signatures.length) { + const declaration = signatures[0].declaration; + if (declaration && hasModifier(declaration, ModifierFlags.Private)) { + const typeClassDeclaration = (getClassLikeDeclarationOfSymbol(type.symbol)!); + if (!isNodeWithinClass(node, typeClassDeclaration)) { + error(node, Diagnostics.Cannot_extend_a_class_0_Class_constructor_is_marked_as_private, getFullyQualifiedName(type.symbol)); + } + } } - - function getRegularTypeOfExpression(expr: Expression): Type { - if (isRightSideOfQualifiedNameOrPropertyAccess(expr)) { - expr = expr.parent; + } + function getTargetSymbol(s: ts.Symbol) { + // if symbol is instantiated its flags are not copied from the 'target' + // so we'll need to get back original 'target' symbol to work with correct set of flags + return getCheckFlags(s) & CheckFlags.Instantiated ? (s).target! : s; + } + function getClassOrInterfaceDeclarationsOfSymbol(symbol: ts.Symbol) { + return filter(symbol.declarations, (d: Declaration): d is ClassDeclaration | InterfaceDeclaration => d.kind === SyntaxKind.ClassDeclaration || d.kind === SyntaxKind.InterfaceDeclaration); + } + function checkKindsOfPropertyMemberOverrides(type: InterfaceType, baseType: BaseType): void { + // TypeScript 1.0 spec (April 2014): 8.2.3 + // A derived class inherits all members from its base class it doesn't override. + // Inheritance means that a derived class implicitly contains all non - overridden members of the base class. + // Both public and private property members are inherited, but only public property members can be overridden. + // A property member in a derived class is said to override a property member in a base class + // when the derived class property member has the same name and kind(instance or static) + // as the base class property member. + // The type of an overriding property member must be assignable(section 3.8.4) + // to the type of the overridden property member, or otherwise a compile - time error occurs. + // Base class instance member functions can be overridden by derived class instance member functions, + // but not by other kinds of members. + // Base class instance member variables and accessors can be overridden by + // derived class instance member variables and accessors, but not by other kinds of members. + // NOTE: assignability is checked in checkClassDeclaration + const baseProperties = getPropertiesOfType(baseType); + basePropertyCheck: for (const baseProperty of baseProperties) { + const base = getTargetSymbol(baseProperty); + if (base.flags & SymbolFlags.Prototype) { + continue; + } + const derived = getTargetSymbol(getPropertyOfObjectType(type, base.escapedName)!); // TODO: GH#18217 + const baseDeclarationFlags = getDeclarationModifierFlagsFromSymbol(base); + Debug.assert(!!derived, "derived should point to something, even if it is the base class' declaration."); + // In order to resolve whether the inherited method was overridden in the base class or not, + // we compare the Symbols obtained. Since getTargetSymbol returns the symbol on the *uninstantiated* + // type declaration, derived and base resolve to the same symbol even in the case of generic classes. + if (derived === base) { + // derived class inherits base without override/redeclaration + const derivedClassDecl = (getClassLikeDeclarationOfSymbol(type.symbol)!); + // It is an error to inherit an abstract member without implementing it or being declared abstract. + // If there is no declaration for the derived class (as in the case of class expressions), + // then the class cannot be declared abstract. + if (baseDeclarationFlags & ModifierFlags.Abstract && (!derivedClassDecl || !hasModifier(derivedClassDecl, ModifierFlags.Abstract))) { + // Searches other base types for a declaration that would satisfy the inherited abstract member. + // (The class may have more than one base type via declaration merging with an interface with the + // same name.) + for (const otherBaseType of getBaseTypes(type)) { + if (otherBaseType === baseType) + continue; + const baseSymbol = getPropertyOfObjectType(otherBaseType, base.escapedName); + const derivedElsewhere = baseSymbol && getTargetSymbol(baseSymbol); + if (derivedElsewhere && derivedElsewhere !== base) { + continue basePropertyCheck; + } + } + if (derivedClassDecl.kind === SyntaxKind.ClassExpression) { + error(derivedClassDecl, Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1, symbolToString(baseProperty), typeToString(baseType)); + } + else { + error(derivedClassDecl, Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2, typeToString(type), symbolToString(baseProperty), typeToString(baseType)); + } + } + } + else { + // derived overrides base. + const derivedDeclarationFlags = getDeclarationModifierFlagsFromSymbol(derived); + if (baseDeclarationFlags & ModifierFlags.Private || derivedDeclarationFlags & ModifierFlags.Private) { + // either base or derived property is private - not override, skip it + continue; + } + let errorMessage: DiagnosticMessage; + const basePropertyFlags = base.flags & SymbolFlags.PropertyOrAccessor; + const derivedPropertyFlags = derived.flags & SymbolFlags.PropertyOrAccessor; + if (basePropertyFlags && derivedPropertyFlags) { + // property/accessor is overridden with property/accessor + if (!compilerOptions.useDefineForClassFields + || baseDeclarationFlags & ModifierFlags.Abstract && !(base.valueDeclaration && isPropertyDeclaration(base.valueDeclaration) && base.valueDeclaration.initializer) + || base.valueDeclaration && base.valueDeclaration.parent.kind === SyntaxKind.InterfaceDeclaration + || derived.valueDeclaration && isBinaryExpression(derived.valueDeclaration)) { + // when the base property is abstract or from an interface, base/derived flags don't need to match + // same when the derived property is from an assignment + continue; + } + const overriddenInstanceProperty = basePropertyFlags !== SymbolFlags.Property && derivedPropertyFlags === SymbolFlags.Property; + const overriddenInstanceAccessor = basePropertyFlags === SymbolFlags.Property && derivedPropertyFlags !== SymbolFlags.Property; + if (overriddenInstanceProperty || overriddenInstanceAccessor) { + const errorMessage = overriddenInstanceProperty ? + Diagnostics._0_is_defined_as_an_accessor_in_class_1_but_is_overridden_here_in_2_as_an_instance_property : + Diagnostics._0_is_defined_as_a_property_in_class_1_but_is_overridden_here_in_2_as_an_accessor; + error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, symbolToString(base), typeToString(baseType), typeToString(type)); + } + else { + const uninitialized = find(derived.declarations, d => d.kind === SyntaxKind.PropertyDeclaration && !(d as PropertyDeclaration).initializer); + if (uninitialized + && !(derived.flags & SymbolFlags.Transient) + && !(baseDeclarationFlags & ModifierFlags.Abstract) + && !(derivedDeclarationFlags & ModifierFlags.Abstract) + && !derived.declarations.some(d => d.flags & NodeFlags.Ambient)) { + const constructor = findConstructorDeclaration((getClassLikeDeclarationOfSymbol(type.symbol)!)); + const propName = (uninitialized as PropertyDeclaration).name; + if ((uninitialized as PropertyDeclaration).exclamationToken + || !constructor + || !isIdentifier(propName) + || !strictNullChecks + || !isPropertyInitializedInConstructor(propName, type, constructor)) { + const errorMessage = Diagnostics.Property_0_will_overwrite_the_base_property_in_1_If_this_is_intentional_add_an_initializer_Otherwise_add_a_declare_modifier_or_remove_the_redundant_declaration; + error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, symbolToString(base), typeToString(baseType)); + } + } + } + // correct case + continue; + } + else if (isPrototypeProperty(base)) { + if (isPrototypeProperty(derived) || derived.flags & SymbolFlags.Property) { + // method is overridden with method or property -- correct case + continue; + } + else { + Debug.assert(!!(derived.flags & SymbolFlags.Accessor)); + errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_accessor; + } + } + else if (base.flags & SymbolFlags.Accessor) { + errorMessage = Diagnostics.Class_0_defines_instance_member_accessor_1_but_extended_class_2_defines_it_as_instance_member_function; + } + else { + errorMessage = Diagnostics.Class_0_defines_instance_member_property_1_but_extended_class_2_defines_it_as_instance_member_function; + } + error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, typeToString(baseType), symbolToString(base), typeToString(type)); } - return getRegularTypeOfLiteralType(getTypeOfExpression(expr)); } - - /** - * Gets either the static or instance type of a class element, based on - * whether the element is declared as "static". - */ - function getParentTypeOfClassElement(node: ClassElement) { - const classSymbol = getSymbolOfNode(node.parent)!; - return hasModifier(node, ModifierFlags.Static) - ? getTypeOfSymbol(classSymbol) - : getDeclaredTypeOfSymbol(classSymbol); + } + function checkInheritedPropertiesAreIdentical(type: InterfaceType, typeNode: Node): boolean { + const baseTypes = getBaseTypes(type); + if (baseTypes.length < 2) { + return true; } - - function getClassElementPropertyKeyType(element: ClassElement) { - const name = element.name!; - switch (name.kind) { - case SyntaxKind.Identifier: - return getLiteralType(idText(name)); - case SyntaxKind.NumericLiteral: - case SyntaxKind.StringLiteral: - return getLiteralType(name.text); - case SyntaxKind.ComputedPropertyName: - const nameType = checkComputedPropertyName(name); - return isTypeAssignableToKind(nameType, TypeFlags.ESSymbolLike) ? nameType : stringType; - default: - return Debug.fail("Unsupported property name."); - } + interface InheritanceInfoMap { + prop: ts.Symbol; + containingType: ts.Type; } - - // Return the list of properties of the given type, augmented with properties from Function - // if the type has call or construct signatures - function getAugmentedPropertiesOfType(type: Type): Symbol[] { - type = getApparentType(type); - const propsByName = createSymbolTable(getPropertiesOfType(type)); - const functionType = getSignaturesOfType(type, SignatureKind.Call).length ? globalCallableFunctionType : - getSignaturesOfType(type, SignatureKind.Construct).length ? globalNewableFunctionType : - undefined; - if (functionType) { - forEach(getPropertiesOfType(functionType), p => { - if (!propsByName.has(p.escapedName)) { - propsByName.set(p.escapedName, p); + const seen = createUnderscoreEscapedMap(); + forEach(resolveDeclaredMembers(type).declaredProperties, p => { seen.set(p.escapedName, { prop: p, containingType: type }); }); + let ok = true; + for (const base of baseTypes) { + const properties = getPropertiesOfType(getTypeWithThisArgument(base, type.thisType)); + for (const prop of properties) { + const existing = seen.get(prop.escapedName); + if (!existing) { + seen.set(prop.escapedName, { prop, containingType: base }); + } + else { + const isInheritedProperty = existing.containingType !== type; + if (isInheritedProperty && !isPropertyIdenticalTo(existing.prop, prop)) { + ok = false; + const typeName1 = typeToString(existing.containingType); + const typeName2 = typeToString(base); + let errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Named_property_0_of_types_1_and_2_are_not_identical, symbolToString(prop), typeName1, typeName2); + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Interface_0_cannot_simultaneously_extend_types_1_and_2, typeToString(type), typeName1, typeName2); + diagnostics.add(createDiagnosticForNodeFromMessageChain(typeNode, errorInfo)); } - }); + } } - return getNamedMembers(propsByName); } - - function typeHasCallOrConstructSignatures(type: Type): boolean { - return ts.typeHasCallOrConstructSignatures(type, checker); + return ok; + } + function checkPropertyInitialization(node: ClassLikeDeclaration) { + if (!strictNullChecks || !strictPropertyInitialization || node.flags & NodeFlags.Ambient) { + return; } - - function getRootSymbols(symbol: Symbol): readonly Symbol[] { - const roots = getImmediateRootSymbols(symbol); - return roots ? flatMap(roots, getRootSymbols) : [symbol]; - } - function getImmediateRootSymbols(symbol: Symbol): readonly Symbol[] | undefined { - if (getCheckFlags(symbol) & CheckFlags.Synthetic) { - return mapDefined(getSymbolLinks(symbol).containingType!.types, type => getPropertyOfType(type, symbol.escapedName)); - } - else if (symbol.flags & SymbolFlags.Transient) { - const { leftSpread, rightSpread, syntheticOrigin } = symbol as TransientSymbol; - return leftSpread ? [leftSpread, rightSpread!] - : syntheticOrigin ? [syntheticOrigin] - : singleElementArray(tryGetAliasTarget(symbol)); + const constructor = findConstructorDeclaration(node); + for (const member of node.members) { + if (getModifierFlags(member) & ModifierFlags.Ambient) { + continue; } - return undefined; - } - function tryGetAliasTarget(symbol: Symbol): Symbol | undefined { - let target: Symbol | undefined; - let next: Symbol | undefined = symbol; - while (next = getSymbolLinks(next).target) { - target = next; + if (isInstancePropertyWithoutInitializer(member)) { + const propName = (member).name; + if (isIdentifier(propName)) { + const type = getTypeOfSymbol(getSymbolOfNode(member)); + if (!(type.flags & TypeFlags.AnyOrUnknown || getFalsyFlags(type) & TypeFlags.Undefined)) { + if (!constructor || !isPropertyInitializedInConstructor(propName, type, constructor)) { + error(member.name, Diagnostics.Property_0_has_no_initializer_and_is_not_definitely_assigned_in_the_constructor, declarationNameToString(propName)); + } + } + } } - return target; } - - // Emitter support - - function isArgumentsLocalBinding(nodeIn: Identifier): boolean { - if (!isGeneratedIdentifier(nodeIn)) { - const node = getParseTreeNode(nodeIn, isIdentifier); - if (node) { - const isPropertyName = node.parent.kind === SyntaxKind.PropertyAccessExpression && (node.parent).name === node; - return !isPropertyName && getReferencedValueSymbol(node) === argumentsSymbol; + } + function isInstancePropertyWithoutInitializer(node: Node) { + return node.kind === SyntaxKind.PropertyDeclaration && + !hasModifier(node, ModifierFlags.Static | ModifierFlags.Abstract) && + !(node).exclamationToken && + !(node).initializer; + } + function isPropertyInitializedInConstructor(propName: Identifier, propType: ts.Type, constructor: ConstructorDeclaration) { + const reference = createPropertyAccess(createThis(), propName); + reference.expression.parent = reference; + reference.parent = constructor; + reference.flowNode = constructor.returnFlowNode; + const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); + return !(getFalsyFlags(flowType) & TypeFlags.Undefined); + } + function checkInterfaceDeclaration(node: InterfaceDeclaration) { + // Grammar checking + if (!checkGrammarDecoratorsAndModifiers(node)) + checkGrammarInterfaceDeclaration(node); + checkTypeParameters(node.typeParameters); + if (produceDiagnostics) { + checkTypeNameIsReserved(node.name, Diagnostics.Interface_name_cannot_be_0); + checkExportsOnMergedDeclarations(node); + const symbol = getSymbolOfNode(node); + checkTypeParameterListsIdentical(symbol); + // Only check this symbol once + const firstInterfaceDecl = getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration); + if (node === firstInterfaceDecl) { + const type = (getDeclaredTypeOfSymbol(symbol)); + const typeWithThis = getTypeWithThisArgument(type); + // run subsequent checks only if first set succeeded + if (checkInheritedPropertiesAreIdentical(type, node.name)) { + for (const baseType of getBaseTypes(type)) { + checkTypeAssignableTo(typeWithThis, getTypeWithThisArgument(baseType, type.thisType), node.name, Diagnostics.Interface_0_incorrectly_extends_interface_1); + } + checkIndexConstraints(type); } } - - return false; + checkObjectTypeForDuplicateDeclarations(node); } - - function moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean { - let moduleSymbol = resolveExternalModuleName(moduleReferenceExpression.parent, moduleReferenceExpression); - if (!moduleSymbol || isShorthandAmbientModuleSymbol(moduleSymbol)) { - // If the module is not found or is shorthand, assume that it may export a value. - return true; - } - - const hasExportAssignment = hasExportAssignmentSymbol(moduleSymbol); - // if module has export assignment then 'resolveExternalModuleSymbol' will return resolved symbol for export assignment - // otherwise it will return moduleSymbol itself - moduleSymbol = resolveExternalModuleSymbol(moduleSymbol); - - const symbolLinks = getSymbolLinks(moduleSymbol); - if (symbolLinks.exportsSomeValue === undefined) { - // for export assignments - check if resolved symbol for RHS is itself a value - // otherwise - check if at least one export is value - symbolLinks.exportsSomeValue = hasExportAssignment - ? !!(moduleSymbol.flags & SymbolFlags.Value) - : forEachEntry(getExportsOfModule(moduleSymbol), isValue); + forEach(getInterfaceBaseTypeNodes(node), heritageElement => { + if (!isEntityNameExpression(heritageElement.expression)) { + error(heritageElement.expression, Diagnostics.An_interface_can_only_extend_an_identifier_Slashqualified_name_with_optional_type_arguments); } - - return symbolLinks.exportsSomeValue!; - - function isValue(s: Symbol): boolean { - s = resolveSymbol(s); - return s && !!(s.flags & SymbolFlags.Value); + checkTypeReferenceNode(heritageElement); + }); + forEach(node.members, checkSourceElement); + if (produceDiagnostics) { + checkTypeForDuplicateIndexSignatures(node); + registerForUnusedIdentifiersCheck(node); + } + } + function checkTypeAliasDeclaration(node: TypeAliasDeclaration) { + // Grammar checking + checkGrammarDecoratorsAndModifiers(node); + checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); + checkTypeParameters(node.typeParameters); + checkSourceElement(node.type); + registerForUnusedIdentifiersCheck(node); + } + function computeEnumMemberValues(node: EnumDeclaration) { + const nodeLinks = getNodeLinks(node); + if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) { + nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed; + let autoValue: number | undefined = 0; + for (const member of node.members) { + const value = computeMemberValue(member, autoValue); + getNodeLinks(member).enumMemberValue = value; + autoValue = typeof value === "number" ? value + 1 : undefined; } } - - function isNameOfModuleOrEnumDeclaration(node: Identifier) { - return isModuleOrEnumDeclaration(node.parent) && node === node.parent.name; + } + function computeMemberValue(member: EnumMember, autoValue: number | undefined) { + if (isComputedNonLiteralName(member.name)) { + error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums); } - - // When resolved as an expression identifier, if the given node references an exported entity, return the declaration - // node of the exported entity's container. Otherwise, return undefined. - function getReferencedExportContainer(nodeIn: Identifier, prefixLocals?: boolean): SourceFile | ModuleDeclaration | EnumDeclaration | undefined { - const node = getParseTreeNode(nodeIn, isIdentifier); - if (node) { - // When resolving the export container for the name of a module or enum - // declaration, we need to start resolution at the declaration's container. - // Otherwise, we could incorrectly resolve the export container as the - // declaration if it contains an exported member with the same name. - let symbol = getReferencedValueSymbol(node, /*startInDeclarationContainer*/ isNameOfModuleOrEnumDeclaration(node)); - if (symbol) { - if (symbol.flags & SymbolFlags.ExportValue) { - // If we reference an exported entity within the same module declaration, then whether - // we prefix depends on the kind of entity. SymbolFlags.ExportHasLocal encompasses all the - // kinds that we do NOT prefix. - const exportSymbol = getMergedSymbol(symbol.exportSymbol!); - if (!prefixLocals && exportSymbol.flags & SymbolFlags.ExportHasLocal && !(exportSymbol.flags & SymbolFlags.Variable)) { - return undefined; - } - symbol = exportSymbol; - } - const parentSymbol = getParentOfSymbol(symbol); - if (parentSymbol) { - if (parentSymbol.flags & SymbolFlags.ValueModule && parentSymbol.valueDeclaration.kind === SyntaxKind.SourceFile) { - const symbolFile = parentSymbol.valueDeclaration; - const referenceFile = getSourceFileOfNode(node); - // If `node` accesses an export and that export isn't in the same file, then symbol is a namespace export, so return undefined. - const symbolIsUmdExport = symbolFile !== referenceFile; - return symbolIsUmdExport ? undefined : symbolFile; - } - return findAncestor(node.parent, (n): n is ModuleDeclaration | EnumDeclaration => isModuleOrEnumDeclaration(n) && getSymbolOfNode(n) === parentSymbol); - } - } + else { + const text = getTextOfPropertyName(member.name); + if (isNumericLiteralName(text) && !isInfinityOrNaNString(text)) { + error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name); } } - - // When resolved as an expression identifier, if the given node references an import, return the declaration of - // that import. Otherwise, return undefined. - function getReferencedImportDeclaration(nodeIn: Identifier): Declaration | undefined { - const node = getParseTreeNode(nodeIn, isIdentifier); - if (node) { - const symbol = getReferencedValueSymbol(node); - // We should only get the declaration of an alias if there isn't a local value - // declaration for the symbol - if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value)) { - return getDeclarationOfAliasSymbol(symbol); - } - } - + if (member.initializer) { + return computeConstantValue(member); + } + // In ambient non-const numeric enum declarations, enum members without initializers are + // considered computed members (as opposed to having auto-incremented values). + if (member.parent.flags & NodeFlags.Ambient && !isEnumConst(member.parent) && getEnumKind(getSymbolOfNode(member.parent)) === EnumKind.Numeric) { return undefined; } - - function isSymbolOfDestructuredElementOfCatchBinding(symbol: Symbol) { - return isBindingElement(symbol.valueDeclaration) - && walkUpBindingElementsAndPatterns(symbol.valueDeclaration).parent.kind === SyntaxKind.CatchClause; + // If the member declaration specifies no value, the member is considered a constant enum member. + // If the member is the first member in the enum declaration, it is assigned the value zero. + // Otherwise, it is assigned the value of the immediately preceding member plus one, and an error + // occurs if the immediately preceding member is not a constant enum member. + if (autoValue !== undefined) { + return autoValue; } - - function isSymbolOfDeclarationWithCollidingName(symbol: Symbol): boolean { - if (symbol.flags & SymbolFlags.BlockScoped && !isSourceFile(symbol.valueDeclaration)) { - const links = getSymbolLinks(symbol); - if (links.isDeclarationWithCollidingName === undefined) { - const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration); - if (isStatementWithLocals(container) || isSymbolOfDestructuredElementOfCatchBinding(symbol)) { - const nodeLinks = getNodeLinks(symbol.valueDeclaration); - if (resolveName(container.parent, symbol.escapedName, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false)) { - // redeclaration - always should be renamed - links.isDeclarationWithCollidingName = true; - } - else if (nodeLinks.flags & NodeCheckFlags.CapturedBlockScopedBinding) { - // binding is captured in the function - // should be renamed if: - // - binding is not top level - top level bindings never collide with anything - // AND - // - binding is not declared in loop, should be renamed to avoid name reuse across siblings - // let a, b - // { let x = 1; a = () => x; } - // { let x = 100; b = () => x; } - // console.log(a()); // should print '1' - // console.log(b()); // should print '100' - // OR - // - binding is declared inside loop but not in inside initializer of iteration statement or directly inside loop body - // * variables from initializer are passed to rewritten loop body as parameters so they are not captured directly - // * variables that are declared immediately in loop body will become top level variable after loop is rewritten and thus - // they will not collide with anything - const isDeclaredInLoop = nodeLinks.flags & NodeCheckFlags.BlockScopedBindingInLoop; - const inLoopInitializer = isIterationStatement(container, /*lookInLabeledStatements*/ false); - const inLoopBodyBlock = container.kind === SyntaxKind.Block && isIterationStatement(container.parent, /*lookInLabeledStatements*/ false); - - links.isDeclarationWithCollidingName = !isBlockScopedContainerTopLevel(container) && (!isDeclaredInLoop || (!inLoopInitializer && !inLoopBodyBlock)); + error(member.name, Diagnostics.Enum_member_must_have_initializer); + return undefined; + } + function computeConstantValue(member: EnumMember): string | number | undefined { + const enumKind = getEnumKind(getSymbolOfNode(member.parent)); + const isConstEnum = isEnumConst(member.parent); + const initializer = member.initializer!; + const value = enumKind === EnumKind.Literal && !isLiteralEnumMember(member) ? undefined : evaluate(initializer); + if (value !== undefined) { + if (isConstEnum && typeof value === "number" && !isFinite(value)) { + error(initializer, isNaN(value) ? + Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN : + Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value); + } + } + else if (enumKind === EnumKind.Literal) { + error(initializer, Diagnostics.Computed_values_are_not_permitted_in_an_enum_with_string_valued_members); + return 0; + } + else if (isConstEnum) { + error(initializer, Diagnostics.const_enum_member_initializers_can_only_contain_literal_values_and_other_computed_enum_values); + } + else if (member.parent.flags & NodeFlags.Ambient) { + error(initializer, Diagnostics.In_ambient_enum_declarations_member_initializer_must_be_constant_expression); + } + else { + // Only here do we need to check that the initializer is assignable to the enum type. + checkTypeAssignableTo(checkExpression(initializer), getDeclaredTypeOfSymbol(getSymbolOfNode(member.parent)), initializer, /*headMessage*/ undefined); + } + return value; + function evaluate(expr: Expression): string | number | undefined { + switch (expr.kind) { + case SyntaxKind.PrefixUnaryExpression: + const value = evaluate((expr).operand); + if (typeof value === "number") { + switch ((expr).operator) { + case SyntaxKind.PlusToken: return value; + case SyntaxKind.MinusToken: return -value; + case SyntaxKind.TildeToken: return ~value; } - else { - links.isDeclarationWithCollidingName = false; + } + break; + case SyntaxKind.BinaryExpression: + const left = evaluate((expr).left); + const right = evaluate((expr).right); + if (typeof left === "number" && typeof right === "number") { + switch ((expr).operatorToken.kind) { + case SyntaxKind.BarToken: return left | right; + case SyntaxKind.AmpersandToken: return left & right; + case SyntaxKind.GreaterThanGreaterThanToken: return left >> right; + case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: return left >>> right; + case SyntaxKind.LessThanLessThanToken: return left << right; + case SyntaxKind.CaretToken: return left ^ right; + case SyntaxKind.AsteriskToken: return left * right; + case SyntaxKind.SlashToken: return left / right; + case SyntaxKind.PlusToken: return left + right; + case SyntaxKind.MinusToken: return left - right; + case SyntaxKind.PercentToken: return left % right; + case SyntaxKind.AsteriskAsteriskToken: return left ** right; + } + } + else if (typeof left === "string" && typeof right === "string" && (expr).operatorToken.kind === SyntaxKind.PlusToken) { + return left + right; + } + break; + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + return (expr).text; + case SyntaxKind.NumericLiteral: + checkGrammarNumericLiteral((expr)); + return +(expr).text; + case SyntaxKind.ParenthesizedExpression: + return evaluate((expr).expression); + case SyntaxKind.Identifier: + const identifier = (expr); + if (isInfinityOrNaNString(identifier.escapedText)) { + return +(identifier.escapedText); + } + return nodeIsMissing(expr) ? 0 : evaluateEnumMember(expr, getSymbolOfNode(member.parent), identifier.escapedText); + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.PropertyAccessExpression: + const ex = (expr); + if (isConstantMemberAccess(ex)) { + const type = getTypeOfExpression(ex.expression); + if (type.symbol && type.symbol.flags & SymbolFlags.Enum) { + let name: __String; + if (ex.kind === SyntaxKind.PropertyAccessExpression) { + name = ex.name.escapedText; + } + else { + name = escapeLeadingUnderscores(cast(ex.argumentExpression, isLiteralExpression).text); + } + return evaluateEnumMember(expr, type.symbol, name); } } - } - return links.isDeclarationWithCollidingName!; + break; } - return false; + return undefined; } - - // When resolved as an expression identifier, if the given node references a nested block scoped entity with - // a name that either hides an existing name or might hide it when compiled downlevel, - // return the declaration of that entity. Otherwise, return undefined. - function getReferencedDeclarationWithCollidingName(nodeIn: Identifier): Declaration | undefined { - if (!isGeneratedIdentifier(nodeIn)) { - const node = getParseTreeNode(nodeIn, isIdentifier); - if (node) { - const symbol = getReferencedValueSymbol(node); - if (symbol && isSymbolOfDeclarationWithCollidingName(symbol)) { - return symbol.valueDeclaration; + function evaluateEnumMember(expr: Expression, enumSymbol: ts.Symbol, name: __String) { + const memberSymbol = enumSymbol.exports!.get(name); + if (memberSymbol) { + const declaration = memberSymbol.valueDeclaration; + if (declaration !== member) { + if (isBlockScopedNameDeclaredBeforeUse(declaration, member)) { + return getEnumMemberValue((declaration as EnumMember)); } + error(expr, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums); + return 0; } } - return undefined; } - - // Return true if the given node is a declaration of a nested block scoped entity with a name that either hides an - // existing name or might hide a name when compiled downlevel - function isDeclarationWithCollidingName(nodeIn: Declaration): boolean { - const node = getParseTreeNode(nodeIn, isDeclaration); - if (node) { - const symbol = getSymbolOfNode(node); - if (symbol) { - return isSymbolOfDeclarationWithCollidingName(symbol); - } + } + function isConstantMemberAccess(node: Expression): boolean { + return node.kind === SyntaxKind.Identifier || + node.kind === SyntaxKind.PropertyAccessExpression && isConstantMemberAccess((node).expression) || + node.kind === SyntaxKind.ElementAccessExpression && isConstantMemberAccess((node).expression) && + isStringLiteralLike((node).argumentExpression); + } + function checkEnumDeclaration(node: EnumDeclaration) { + if (!produceDiagnostics) { + return; + } + // Grammar checking + checkGrammarDecoratorsAndModifiers(node); + checkTypeNameIsReserved(node.name, Diagnostics.Enum_name_cannot_be_0); + checkCollisionWithRequireExportsInGeneratedCode(node, node.name); + checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name); + checkExportsOnMergedDeclarations(node); + computeEnumMemberValues(node); + // Spec 2014 - Section 9.3: + // It isn't possible for one enum declaration to continue the automatic numbering sequence of another, + // and when an enum type has multiple declarations, only one declaration is permitted to omit a value + // for the first member. + // + // Only perform this check once per symbol + const enumSymbol = getSymbolOfNode(node); + const firstDeclaration = getDeclarationOfKind(enumSymbol, node.kind); + if (node === firstDeclaration) { + if (enumSymbol.declarations.length > 1) { + const enumIsConst = isEnumConst(node); + // check that const is placed\omitted on all enum declarations + forEach(enumSymbol.declarations, decl => { + if (isEnumDeclaration(decl) && isEnumConst(decl) !== enumIsConst) { + error(getNameOfDeclaration(decl), Diagnostics.Enum_declarations_must_all_be_const_or_non_const); + } + }); } - - return false; + let seenEnumMissingInitialInitializer = false; + forEach(enumSymbol.declarations, declaration => { + // return true if we hit a violation of the rule, false otherwise + if (declaration.kind !== SyntaxKind.EnumDeclaration) { + return false; + } + const enumDeclaration = (declaration); + if (!enumDeclaration.members.length) { + return false; + } + const firstEnumMember = enumDeclaration.members[0]; + if (!firstEnumMember.initializer) { + if (seenEnumMissingInitialInitializer) { + error(firstEnumMember.name, Diagnostics.In_an_enum_with_multiple_declarations_only_one_declaration_can_omit_an_initializer_for_its_first_enum_element); + } + else { + seenEnumMissingInitialInitializer = true; + } + } + }); } - - function isValueAliasDeclaration(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ImportClause: - case SyntaxKind.NamespaceImport: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: - return isAliasResolvedToValue(getSymbolOfNode(node) || unknownSymbol); - case SyntaxKind.ExportDeclaration: - const exportClause = (node).exportClause; - return !!exportClause && some(exportClause.elements, isValueAliasDeclaration); - case SyntaxKind.ExportAssignment: - return (node).expression && (node).expression.kind === SyntaxKind.Identifier ? - isAliasResolvedToValue(getSymbolOfNode(node) || unknownSymbol) : - true; + } + function getFirstNonAmbientClassOrFunctionDeclaration(symbol: ts.Symbol): Declaration | undefined { + const declarations = symbol.declarations; + for (const declaration of declarations) { + if ((declaration.kind === SyntaxKind.ClassDeclaration || + (declaration.kind === SyntaxKind.FunctionDeclaration && nodeIsPresent((declaration).body))) && + !(declaration.flags & NodeFlags.Ambient)) { + return declaration; } - return false; } - - function isTopLevelValueImportEqualsWithEntityName(nodeIn: ImportEqualsDeclaration): boolean { - const node = getParseTreeNode(nodeIn, isImportEqualsDeclaration); - if (node === undefined || node.parent.kind !== SyntaxKind.SourceFile || !isInternalModuleImportEqualsDeclaration(node)) { - // parent is not source file or it is not reference to internal module - return false; - } - - const isValue = isAliasResolvedToValue(getSymbolOfNode(node)); - return isValue && node.moduleReference && !nodeIsMissing(node.moduleReference); + return undefined; + } + function inSameLexicalScope(node1: Node, node2: Node) { + const container1 = getEnclosingBlockScopeContainer(node1); + const container2 = getEnclosingBlockScopeContainer(node2); + if (isGlobalSourceFile(container1)) { + return isGlobalSourceFile(container2); } - - function isAliasResolvedToValue(symbol: Symbol): boolean { - const target = resolveAlias(symbol); - if (target === unknownSymbol) { - return true; - } - // const enums and modules that contain only const enums are not considered values from the emit perspective - // unless 'preserveConstEnums' option is set to true - return !!(target.flags & SymbolFlags.Value) && - (compilerOptions.preserveConstEnums || !isConstEnumOrConstEnumOnlyModule(target)); + else if (isGlobalSourceFile(container2)) { + return false; } - - function isConstEnumOrConstEnumOnlyModule(s: Symbol): boolean { - return isConstEnumSymbol(s) || !!s.constEnumOnlyModule; + else { + return container1 === container2; } - - function isReferencedAliasDeclaration(node: Node, checkChildren?: boolean): boolean { - if (isAliasSymbolDeclaration(node)) { - const symbol = getSymbolOfNode(node); - if (symbol && getSymbolLinks(symbol).referenced) { - return true; + } + function checkModuleDeclaration(node: ModuleDeclaration) { + if (produceDiagnostics) { + // Grammar checking + const isGlobalAugmentation = isGlobalScopeAugmentation(node); + const inAmbientContext = node.flags & NodeFlags.Ambient; + if (isGlobalAugmentation && !inAmbientContext) { + error(node.name, Diagnostics.Augmentations_for_the_global_scope_should_have_declare_modifier_unless_they_appear_in_already_ambient_context); + } + const isAmbientExternalModule = isAmbientModule(node); + const contextErrorMessage = isAmbientExternalModule + ? Diagnostics.An_ambient_module_declaration_is_only_allowed_at_the_top_level_in_a_file + : Diagnostics.A_namespace_declaration_is_only_allowed_in_a_namespace_or_module; + if (checkGrammarModuleElementContext(node, contextErrorMessage)) { + // If we hit a module declaration in an illegal context, just bail out to avoid cascading errors. + return; + } + if (!checkGrammarDecoratorsAndModifiers(node)) { + if (!inAmbientContext && node.name.kind === SyntaxKind.StringLiteral) { + grammarErrorOnNode(node.name, Diagnostics.Only_ambient_modules_can_use_quoted_names); } - const target = getSymbolLinks(symbol!).target; // TODO: GH#18217 - if (target && getModifierFlags(node) & ModifierFlags.Export && - target.flags & SymbolFlags.Value && (compilerOptions.preserveConstEnums || !isConstEnumOrConstEnumOnlyModule(target))) { - // An `export import ... =` of a value symbol is always considered referenced - return true; + } + if (isIdentifier(node.name)) { + checkCollisionWithRequireExportsInGeneratedCode(node, node.name); + checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name); + } + checkExportsOnMergedDeclarations(node); + const symbol = getSymbolOfNode(node); + // The following checks only apply on a non-ambient instantiated module declaration. + if (symbol.flags & SymbolFlags.ValueModule + && !inAmbientContext + && symbol.declarations.length > 1 + && isInstantiatedModule(node, !!compilerOptions.preserveConstEnums || !!compilerOptions.isolatedModules)) { + const firstNonAmbientClassOrFunc = getFirstNonAmbientClassOrFunctionDeclaration(symbol); + if (firstNonAmbientClassOrFunc) { + if (getSourceFileOfNode(node) !== getSourceFileOfNode(firstNonAmbientClassOrFunc)) { + error(node.name, Diagnostics.A_namespace_declaration_cannot_be_in_a_different_file_from_a_class_or_function_with_which_it_is_merged); + } + else if (node.pos < firstNonAmbientClassOrFunc.pos) { + error(node.name, Diagnostics.A_namespace_declaration_cannot_be_located_prior_to_a_class_or_function_with_which_it_is_merged); + } + } + // if the module merges with a class declaration in the same lexical scope, + // we need to track this to ensure the correct emit. + const mergedClass = getDeclarationOfKind(symbol, SyntaxKind.ClassDeclaration); + if (mergedClass && + inSameLexicalScope(node, mergedClass)) { + getNodeLinks(node).flags |= NodeCheckFlags.LexicalModuleMergesWithClass; + } + } + if (isAmbientExternalModule) { + if (isExternalModuleAugmentation(node)) { + // body of the augmentation should be checked for consistency only if augmentation was applied to its target (either global scope or module) + // otherwise we'll be swamped in cascading errors. + // We can detect if augmentation was applied using following rules: + // - augmentation for a global scope is always applied + // - augmentation for some external module is applied if symbol for augmentation is merged (it was combined with target module). + const checkBody = isGlobalAugmentation || (getSymbolOfNode(node).flags & SymbolFlags.Transient); + if (checkBody && node.body) { + for (const statement of node.body.statements) { + checkModuleAugmentationElement(statement, isGlobalAugmentation); + } + } + } + else if (isGlobalSourceFile(node.parent)) { + if (isGlobalAugmentation) { + error(node.name, Diagnostics.Augmentations_for_the_global_scope_can_only_be_directly_nested_in_external_modules_or_ambient_module_declarations); + } + else if (isExternalModuleNameRelative(getTextOfIdentifierOrLiteral(node.name))) { + error(node.name, Diagnostics.Ambient_module_declaration_cannot_specify_relative_module_name); + } + } + else { + if (isGlobalAugmentation) { + error(node.name, Diagnostics.Augmentations_for_the_global_scope_can_only_be_directly_nested_in_external_modules_or_ambient_module_declarations); + } + else { + // Node is not an augmentation and is not located on the script level. + // This means that this is declaration of ambient module that is located in other module or namespace which is prohibited. + error(node.name, Diagnostics.Ambient_modules_cannot_be_nested_in_other_modules_or_namespaces); + } } } - - if (checkChildren) { - return !!forEachChild(node, node => isReferencedAliasDeclaration(node, checkChildren)); + } + if (node.body) { + checkSourceElement(node.body); + if (!isGlobalScopeAugmentation(node)) { + registerForUnusedIdentifiersCheck(node); } - return false; } - - function isImplementationOfOverload(node: SignatureDeclaration) { - if (nodeIsPresent((node as FunctionLikeDeclaration).body)) { - if (isGetAccessor(node) || isSetAccessor(node)) return false; // Get or set accessors can never be overload implementations, but can have up to 2 signatures + } + function checkModuleAugmentationElement(node: Node, isGlobalAugmentation: boolean): void { + switch (node.kind) { + case SyntaxKind.VariableStatement: + // error each individual name in variable statement instead of marking the entire variable statement + for (const decl of (node).declarationList.declarations) { + checkModuleAugmentationElement(decl, isGlobalAugmentation); + } + break; + case SyntaxKind.ExportAssignment: + case SyntaxKind.ExportDeclaration: + grammarErrorOnFirstToken(node, Diagnostics.Exports_and_export_assignments_are_not_permitted_in_module_augmentations); + break; + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ImportDeclaration: + grammarErrorOnFirstToken(node, Diagnostics.Imports_are_not_permitted_in_module_augmentations_Consider_moving_them_to_the_enclosing_external_module); + break; + case SyntaxKind.BindingElement: + case SyntaxKind.VariableDeclaration: + const name = (node).name; + if (isBindingPattern(name)) { + for (const el of name.elements) { + // mark individual names in binding pattern + checkModuleAugmentationElement(el, isGlobalAugmentation); + } + break; + } + // falls through + case SyntaxKind.ClassDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.TypeAliasDeclaration: + if (isGlobalAugmentation) { + return; + } const symbol = getSymbolOfNode(node); - const signaturesOfSymbol = getSignaturesOfSymbol(symbol); - // If this function body corresponds to function with multiple signature, it is implementation of overload - // e.g.: function foo(a: string): string; - // function foo(a: number): number; - // function foo(a: any) { // This is implementation of the overloads - // return a; - // } - return signaturesOfSymbol.length > 1 || - // If there is single signature for the symbol, it is overload if that signature isn't coming from the node - // e.g.: function foo(a: string): string; - // function foo(a: any) { // This is implementation of the overloads - // return a; - // } - (signaturesOfSymbol.length === 1 && signaturesOfSymbol[0].declaration !== node); - } + if (symbol) { + // module augmentations cannot introduce new names on the top level scope of the module + // this is done it two steps + // 1. quick check - if symbol for node is not merged - this is local symbol to this augmentation - report error + // 2. main check - report error if value declaration of the parent symbol is module augmentation) + let reportError = !(symbol.flags & SymbolFlags.Transient); + if (!reportError) { + // symbol should not originate in augmentation + reportError = !!symbol.parent && isExternalModuleAugmentation(symbol.parent.declarations[0]); + } + } + break; + } + } + function getFirstNonModuleExportsIdentifier(node: EntityNameOrEntityNameExpression): Identifier { + switch (node.kind) { + case SyntaxKind.Identifier: + return node; + case SyntaxKind.QualifiedName: + do { + node = node.left; + } while (node.kind !== SyntaxKind.Identifier); + return node; + case SyntaxKind.PropertyAccessExpression: + do { + if (isModuleExportsAccessExpression(node.expression)) { + return node.name; + } + node = node.expression; + } while (node.kind !== SyntaxKind.Identifier); + return node; + } + } + function checkExternalImportOrExportDeclaration(node: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration): boolean { + const moduleName = getExternalModuleName(node); + if (!moduleName || nodeIsMissing(moduleName)) { + // Should be a parse error. return false; } - - function isRequiredInitializedParameter(parameter: ParameterDeclaration | JSDocParameterTag): boolean { - return !!strictNullChecks && - !isOptionalParameter(parameter) && - !isJSDocParameterTag(parameter) && - !!parameter.initializer && - !hasModifier(parameter, ModifierFlags.ParameterPropertyModifier); + if (!isStringLiteral(moduleName)) { + error(moduleName, Diagnostics.String_literal_expected); + return false; } - - function isOptionalUninitializedParameterProperty(parameter: ParameterDeclaration) { - return strictNullChecks && - isOptionalParameter(parameter) && - !parameter.initializer && - hasModifier(parameter, ModifierFlags.ParameterPropertyModifier); + const inAmbientExternalModule = node.parent.kind === SyntaxKind.ModuleBlock && isAmbientModule(node.parent.parent); + if (node.parent.kind !== SyntaxKind.SourceFile && !inAmbientExternalModule) { + error(moduleName, node.kind === SyntaxKind.ExportDeclaration ? + Diagnostics.Export_declarations_are_not_permitted_in_a_namespace : + Diagnostics.Import_declarations_in_a_namespace_cannot_reference_a_module); + return false; } - - function isExpandoFunctionDeclaration(node: Declaration): boolean { - const declaration = getParseTreeNode(node, isFunctionDeclaration); - if (!declaration) { + if (inAmbientExternalModule && isExternalModuleNameRelative(moduleName.text)) { + // we have already reported errors on top level imports\exports in external module augmentations in checkModuleDeclaration + // no need to do this again. + if (!isTopLevelInExternalModuleAugmentation(node)) { + // TypeScript 1.0 spec (April 2013): 12.1.6 + // An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference + // other external modules only through top - level external module names. + // Relative external module names are not permitted. + error(node, Diagnostics.Import_or_export_declaration_in_an_ambient_module_declaration_cannot_reference_module_through_relative_module_name); return false; } - const symbol = getSymbolOfNode(declaration); - if (!symbol || !(symbol.flags & SymbolFlags.Function)) { - return false; - } - return !!forEachEntry(getExportsOfSymbol(symbol), p => p.flags & SymbolFlags.Value && p.valueDeclaration && isPropertyAccessExpression(p.valueDeclaration)); } - - function getPropertiesOfContainerFunction(node: Declaration): Symbol[] { - const declaration = getParseTreeNode(node, isFunctionDeclaration); - if (!declaration) { - return emptyArray; + return true; + } + function checkAliasSymbol(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportSpecifier | ExportSpecifier) { + let symbol = getSymbolOfNode(node); + const target = resolveAlias(symbol); + const shouldSkipWithJSExpandoTargets = symbol.flags & SymbolFlags.Assignment; + if (!shouldSkipWithJSExpandoTargets && target !== unknownSymbol) { + // For external modules symbol represents local symbol for an alias. + // This local symbol will merge any other local declarations (excluding other aliases) + // and symbol.flags will contains combined representation for all merged declaration. + // Based on symbol.flags we can compute a set of excluded meanings (meaning that resolved alias should not have, + // otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export* + // in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names). + symbol = getMergedSymbol(symbol.exportSymbol || symbol); + const excludedMeanings = (symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue) ? SymbolFlags.Value : 0) | + (symbol.flags & SymbolFlags.Type ? SymbolFlags.Type : 0) | + (symbol.flags & SymbolFlags.Namespace ? SymbolFlags.Namespace : 0); + if (target.flags & excludedMeanings) { + const message = node.kind === SyntaxKind.ExportSpecifier ? + Diagnostics.Export_declaration_conflicts_with_exported_declaration_of_0 : + Diagnostics.Import_declaration_conflicts_with_local_declaration_of_0; + error(node, message, symbolToString(symbol)); + } + // Don't allow to re-export something with no value side when `--isolatedModules` is set. + if (compilerOptions.isolatedModules + && node.kind === SyntaxKind.ExportSpecifier + && !(target.flags & SymbolFlags.Value) + && !(node.flags & NodeFlags.Ambient)) { + error(node, Diagnostics.Cannot_re_export_a_type_when_the_isolatedModules_flag_is_provided); } - const symbol = getSymbolOfNode(declaration); - return symbol && getPropertiesOfType(getTypeOfSymbol(symbol)) || emptyArray; - } - - function getNodeCheckFlags(node: Node): NodeCheckFlags { - return getNodeLinks(node).flags || 0; } - - function getEnumMemberValue(node: EnumMember): string | number | undefined { - computeEnumMemberValues(node.parent); - return getNodeLinks(node).enumMemberValue; + } + function checkImportBinding(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportSpecifier) { + checkCollisionWithRequireExportsInGeneratedCode(node, node.name!); + checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name!); + checkAliasSymbol(node); + } + function checkImportDeclaration(node: ImportDeclaration) { + if (checkGrammarModuleElementContext(node, Diagnostics.An_import_declaration_can_only_be_used_in_a_namespace_or_module)) { + // If we hit an import declaration in an illegal context, just bail out to avoid cascading errors. + return; } - - function canHaveConstantValue(node: Node): node is EnumMember | AccessExpression { - switch (node.kind) { - case SyntaxKind.EnumMember: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - return true; - } - return false; + if (!checkGrammarDecoratorsAndModifiers(node) && hasModifiers(node)) { + grammarErrorOnFirstToken(node, Diagnostics.An_import_declaration_cannot_have_modifiers); } - - function getConstantValue(node: EnumMember | AccessExpression): string | number | undefined { - if (node.kind === SyntaxKind.EnumMember) { - return getEnumMemberValue(node); - } - - const symbol = getNodeLinks(node).resolvedSymbol; - if (symbol && (symbol.flags & SymbolFlags.EnumMember)) { - // inline property\index accesses only for const enums - const member = symbol.valueDeclaration as EnumMember; - if (isEnumConst(member.parent)) { - return getEnumMemberValue(member); + if (checkExternalImportOrExportDeclaration(node)) { + const importClause = node.importClause; + if (importClause) { + if (importClause.name) { + checkImportBinding(importClause); + } + if (importClause.namedBindings) { + if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { + checkImportBinding(importClause.namedBindings); + } + else { + const moduleExisted = resolveExternalModuleName(node, node.moduleSpecifier); + if (moduleExisted) { + forEach(importClause.namedBindings.elements, checkImportBinding); + } + } } } - - return undefined; } - - function isFunctionType(type: Type): boolean { - return !!(type.flags & TypeFlags.Object) && getSignaturesOfType(type, SignatureKind.Call).length > 0; + } + function checkImportEqualsDeclaration(node: ImportEqualsDeclaration) { + if (checkGrammarModuleElementContext(node, Diagnostics.An_import_declaration_can_only_be_used_in_a_namespace_or_module)) { + // If we hit an import declaration in an illegal context, just bail out to avoid cascading errors. + return; } - - function getTypeReferenceSerializationKind(typeNameIn: EntityName, location?: Node): TypeReferenceSerializationKind { - // ensure both `typeName` and `location` are parse tree nodes. - const typeName = getParseTreeNode(typeNameIn, isEntityName); - if (!typeName) return TypeReferenceSerializationKind.Unknown; - - if (location) { - location = getParseTreeNode(location); - if (!location) return TypeReferenceSerializationKind.Unknown; - } - - // Resolve the symbol as a value to ensure the type can be reached at runtime during emit. - const valueSymbol = resolveEntityName(typeName, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, location); - - // Resolve the symbol as a type so that we can provide a more useful hint for the type serializer. - const typeSymbol = resolveEntityName(typeName, SymbolFlags.Type, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, location); - if (valueSymbol && valueSymbol === typeSymbol) { - const globalPromiseSymbol = getGlobalPromiseConstructorSymbol(/*reportErrors*/ false); - if (globalPromiseSymbol && valueSymbol === globalPromiseSymbol) { - return TypeReferenceSerializationKind.Promise; - } - - const constructorType = getTypeOfSymbol(valueSymbol); - if (constructorType && isConstructorType(constructorType)) { - return TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue; - } - } - - // We might not be able to resolve type symbol so use unknown type in that case (eg error case) - if (!typeSymbol) { - return TypeReferenceSerializationKind.Unknown; - } - const type = getDeclaredTypeOfSymbol(typeSymbol); - if (type === errorType) { - return TypeReferenceSerializationKind.Unknown; + checkGrammarDecoratorsAndModifiers(node); + if (isInternalModuleImportEqualsDeclaration(node) || checkExternalImportOrExportDeclaration(node)) { + checkImportBinding(node); + if (hasModifier(node, ModifierFlags.Export)) { + markExportAsReferenced(node); } - else if (type.flags & TypeFlags.AnyOrUnknown) { - return TypeReferenceSerializationKind.ObjectType; - } - else if (isTypeAssignableToKind(type, TypeFlags.Void | TypeFlags.Nullable | TypeFlags.Never)) { - return TypeReferenceSerializationKind.VoidNullableOrNeverType; - } - else if (isTypeAssignableToKind(type, TypeFlags.BooleanLike)) { - return TypeReferenceSerializationKind.BooleanType; - } - else if (isTypeAssignableToKind(type, TypeFlags.NumberLike)) { - return TypeReferenceSerializationKind.NumberLikeType; + if (node.moduleReference.kind !== SyntaxKind.ExternalModuleReference) { + const target = resolveAlias(getSymbolOfNode(node)); + if (target !== unknownSymbol) { + if (target.flags & SymbolFlags.Value) { + // Target is a value symbol, check that it is not hidden by a local declaration with the same name + const moduleName = getFirstIdentifier(node.moduleReference); + if (!(resolveEntityName(moduleName, SymbolFlags.Value | SymbolFlags.Namespace)!.flags & SymbolFlags.Namespace)) { + error(moduleName, Diagnostics.Module_0_is_hidden_by_a_local_declaration_with_the_same_name, declarationNameToString(moduleName)); + } + } + if (target.flags & SymbolFlags.Type) { + checkTypeNameIsReserved(node.name, Diagnostics.Import_name_cannot_be_0); + } + } } - else if (isTypeAssignableToKind(type, TypeFlags.BigIntLike)) { - return TypeReferenceSerializationKind.BigIntLikeType; + else { + if (moduleKind >= ModuleKind.ES2015 && !(node.flags & NodeFlags.Ambient)) { + // Import equals declaration is deprecated in es6 or above + grammarErrorOnNode(node, Diagnostics.Import_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_import_Asterisk_as_ns_from_mod_import_a_from_mod_import_d_from_mod_or_another_module_format_instead); + } } - else if (isTypeAssignableToKind(type, TypeFlags.StringLike)) { - return TypeReferenceSerializationKind.StringLikeType; + } + } + function checkExportDeclaration(node: ExportDeclaration) { + if (checkGrammarModuleElementContext(node, Diagnostics.An_export_declaration_can_only_be_used_in_a_module)) { + // If we hit an export in an illegal context, just bail out to avoid cascading errors. + return; + } + if (!checkGrammarDecoratorsAndModifiers(node) && hasModifiers(node)) { + grammarErrorOnFirstToken(node, Diagnostics.An_export_declaration_cannot_have_modifiers); + } + if (!node.moduleSpecifier || checkExternalImportOrExportDeclaration(node)) { + if (node.exportClause) { + // export { x, y } + // export { x, y } from "foo" + forEach(node.exportClause.elements, checkExportSpecifier); + const inAmbientExternalModule = node.parent.kind === SyntaxKind.ModuleBlock && isAmbientModule(node.parent.parent); + const inAmbientNamespaceDeclaration = !inAmbientExternalModule && node.parent.kind === SyntaxKind.ModuleBlock && + !node.moduleSpecifier && node.flags & NodeFlags.Ambient; + if (node.parent.kind !== SyntaxKind.SourceFile && !inAmbientExternalModule && !inAmbientNamespaceDeclaration) { + error(node, Diagnostics.Export_declarations_are_not_permitted_in_a_namespace); + } } - else if (isTupleType(type)) { - return TypeReferenceSerializationKind.ArrayLikeType; + else { + // export * from "foo" + const moduleSymbol = resolveExternalModuleName(node, node.moduleSpecifier!); + if (moduleSymbol && hasExportAssignmentSymbol(moduleSymbol)) { + error(node.moduleSpecifier, Diagnostics.Module_0_uses_export_and_cannot_be_used_with_export_Asterisk, symbolToString(moduleSymbol)); + } + if (moduleKind !== ModuleKind.System && moduleKind !== ModuleKind.ES2015 && moduleKind !== ModuleKind.ESNext) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ExportStar); + } } - else if (isTypeAssignableToKind(type, TypeFlags.ESSymbolLike)) { - return TypeReferenceSerializationKind.ESSymbolType; + } + } + function checkGrammarModuleElementContext(node: Statement, errorMessage: DiagnosticMessage): boolean { + const isInAppropriateContext = node.parent.kind === SyntaxKind.SourceFile || node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.ModuleDeclaration; + if (!isInAppropriateContext) { + grammarErrorOnFirstToken(node, errorMessage); + } + return !isInAppropriateContext; + } + function checkExportSpecifier(node: ExportSpecifier) { + checkAliasSymbol(node); + if (getEmitDeclarations(compilerOptions)) { + collectLinkedAliases(node.propertyName || node.name, /*setVisibility*/ true); + } + if (!node.parent.parent.moduleSpecifier) { + const exportedName = node.propertyName || node.name; + // find immediate value referenced by exported name (SymbolFlags.Alias is set so we don't chase down aliases) + const symbol = resolveName(exportedName, exportedName.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, + /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); + if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) { + error(exportedName, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, idText(exportedName)); } - else if (isFunctionType(type)) { - return TypeReferenceSerializationKind.TypeWithCallSignature; + else { + markExportAsReferenced(node); + const target = symbol && (symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol); + if (!target || target === unknownSymbol || target.flags & SymbolFlags.Value) { + checkExpressionCached(node.propertyName || node.name); + } } - else if (isArrayType(type)) { - return TypeReferenceSerializationKind.ArrayLikeType; + } + } + function checkExportAssignment(node: ExportAssignment) { + if (checkGrammarModuleElementContext(node, Diagnostics.An_export_assignment_can_only_be_used_in_a_module)) { + // If we hit an export assignment in an illegal context, just bail out to avoid cascading errors. + return; + } + const container = node.parent.kind === SyntaxKind.SourceFile ? node.parent : node.parent.parent; + if (container.kind === SyntaxKind.ModuleDeclaration && !isAmbientModule(container)) { + if (node.isExportEquals) { + error(node, Diagnostics.An_export_assignment_cannot_be_used_in_a_namespace); } else { - return TypeReferenceSerializationKind.ObjectType; + error(node, Diagnostics.A_default_export_can_only_be_used_in_an_ECMAScript_style_module); } + return; } - - function createTypeOfDeclaration(declarationIn: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean) { - const declaration = getParseTreeNode(declarationIn, isVariableLikeOrAccessor); - if (!declaration) { - return createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; - } - // Get type of the symbol if this is the valid symbol otherwise get type at location - const symbol = getSymbolOfNode(declaration); - let type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature)) - ? getWidenedLiteralType(getTypeOfSymbol(symbol)) - : errorType; - if (type.flags & TypeFlags.UniqueESSymbol && - type.symbol === symbol) { - flags |= NodeBuilderFlags.AllowUniqueESSymbolType; + // Grammar checking + if (!checkGrammarDecoratorsAndModifiers(node) && hasModifiers(node)) { + grammarErrorOnFirstToken(node, Diagnostics.An_export_assignment_cannot_have_modifiers); + } + if (node.expression.kind === SyntaxKind.Identifier) { + const id = (node.expression as Identifier); + const sym = resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, node); + if (sym) { + markAliasReferenced(sym, id); + // If not a value, we're interpreting the identifier as a type export, along the lines of (`export { Id as default }`) + const target = sym.flags & SymbolFlags.Alias ? resolveAlias(sym) : sym; + if (target === unknownSymbol || target.flags & SymbolFlags.Value) { + // However if it is a value, we need to check it's being used correctly + checkExpressionCached(node.expression); + } } - if (addUndefined) { - type = getOptionalType(type); + if (getEmitDeclarations(compilerOptions)) { + collectLinkedAliases((node.expression as Identifier), /*setVisibility*/ true); } - return nodeBuilder.typeToTypeNode(type, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); } - - function createReturnTypeOfSignatureDeclaration(signatureDeclarationIn: SignatureDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker) { - const signatureDeclaration = getParseTreeNode(signatureDeclarationIn, isFunctionLike); - if (!signatureDeclaration) { - return createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; + else { + checkExpressionCached(node.expression); + } + checkExternalModuleExports(container); + if ((node.flags & NodeFlags.Ambient) && !isEntityNameExpression(node.expression)) { + grammarErrorOnNode(node.expression, Diagnostics.The_expression_of_an_export_assignment_must_be_an_identifier_or_qualified_name_in_an_ambient_context); + } + if (node.isExportEquals && !(node.flags & NodeFlags.Ambient)) { + if (moduleKind >= ModuleKind.ES2015) { + // export assignment is not supported in es6 modules + grammarErrorOnNode(node, Diagnostics.Export_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_export_default_or_another_module_format_instead); + } + else if (moduleKind === ModuleKind.System) { + // system modules does not support export assignment + grammarErrorOnNode(node, Diagnostics.Export_assignment_is_not_supported_when_module_flag_is_system); } - const signature = getSignatureFromDeclaration(signatureDeclaration); - return nodeBuilder.typeToTypeNode(getReturnTypeOfSignature(signature), enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); } - - function createTypeOfExpression(exprIn: Expression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker) { - const expr = getParseTreeNode(exprIn, isExpression); - if (!expr) { - return createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; + } + function hasExportedMembers(moduleSymbol: ts.Symbol) { + return forEachEntry((moduleSymbol.exports!), (_, id) => id !== "export="); + } + function checkExternalModuleExports(node: SourceFile | ModuleDeclaration) { + const moduleSymbol = getSymbolOfNode(node); + const links = getSymbolLinks(moduleSymbol); + if (!links.exportsChecked) { + const exportEqualsSymbol = moduleSymbol.exports!.get(("export=" as __String)); + if (exportEqualsSymbol && hasExportedMembers(moduleSymbol)) { + const declaration = getDeclarationOfAliasSymbol(exportEqualsSymbol) || exportEqualsSymbol.valueDeclaration; + if (!isTopLevelInExternalModuleAugmentation(declaration) && !isInJSFile(declaration)) { + error(declaration, Diagnostics.An_export_assignment_cannot_be_used_in_a_module_with_other_exported_elements); + } + } + // Checks for export * conflicts + const exports = getExportsOfModule(moduleSymbol); + if (exports) { + exports.forEach(({ declarations, flags }, id) => { + if (id === "__export") { + return; + } + // ECMA262: 15.2.1.1 It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries. + // (TS Exceptions: namespaces, function overloads, enums, and interfaces) + if (flags & (SymbolFlags.Namespace | SymbolFlags.Interface | SymbolFlags.Enum)) { + return; + } + const exportedDeclarationsCount = countWhere(declarations, isNotOverloadAndNotAccessor); + if (flags & SymbolFlags.TypeAlias && exportedDeclarationsCount <= 2) { + // it is legal to merge type alias with other values + // so count should be either 1 (just type alias) or 2 (type alias + merged value) + return; + } + if (exportedDeclarationsCount > 1) { + for (const declaration of declarations) { + if (isNotOverload(declaration)) { + diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Cannot_redeclare_exported_variable_0, unescapeLeadingUnderscores(id))); + } + } + } + }); } - const type = getWidenedType(getRegularTypeOfExpression(expr)); - return nodeBuilder.typeToTypeNode(type, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); + links.exportsChecked = true; } - - function hasGlobalName(name: string): boolean { - return globals.has(escapeLeadingUnderscores(name)); + } + function checkSourceElement(node: Node | undefined): void { + if (node) { + const saveCurrentNode = currentNode; + currentNode = node; + instantiationCount = 0; + checkSourceElementWorker(node); + currentNode = saveCurrentNode; } - - function getReferencedValueSymbol(reference: Identifier, startInDeclarationContainer?: boolean): Symbol | undefined { - const resolvedSymbol = getNodeLinks(reference).resolvedSymbol; - if (resolvedSymbol) { - return resolvedSymbol; + } + function checkSourceElementWorker(node: Node): void { + if (isInJSFile(node)) { + forEach((node as JSDocContainer).jsDoc, ({ tags }) => forEach(tags, checkSourceElement)); + } + const kind = node.kind; + if (cancellationToken) { + // Only bother checking on a few construct kinds. We don't want to be excessively + // hitting the cancellation token on every node we check. + switch (kind) { + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.FunctionDeclaration: + cancellationToken.throwIfCancellationRequested(); } - - let location: Node = reference; - if (startInDeclarationContainer) { - // When resolving the name of a declaration as a value, we need to start resolution - // at a point outside of the declaration. - const parent = reference.parent; - if (isDeclaration(parent) && reference === parent.name) { - location = getDeclarationContainer(parent); - } + } + if (kind >= SyntaxKind.FirstStatement && kind <= SyntaxKind.LastStatement && node.flowNode && !isReachableFlowNode(node.flowNode)) { + errorOrSuggestion(compilerOptions.allowUnreachableCode === false, node, Diagnostics.Unreachable_code_detected); + } + switch (kind) { + case SyntaxKind.TypeParameter: + return checkTypeParameter((node)); + case SyntaxKind.Parameter: + return checkParameter((node)); + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + return checkPropertyDeclaration((node)); + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + return checkSignatureDeclaration((node)); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + return checkMethodDeclaration((node)); + case SyntaxKind.Constructor: + return checkConstructorDeclaration((node)); + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return checkAccessorDeclaration((node)); + case SyntaxKind.TypeReference: + return checkTypeReferenceNode((node)); + case SyntaxKind.TypePredicate: + return checkTypePredicate((node)); + case SyntaxKind.TypeQuery: + return checkTypeQuery((node)); + case SyntaxKind.TypeLiteral: + return checkTypeLiteral((node)); + case SyntaxKind.ArrayType: + return checkArrayType((node)); + case SyntaxKind.TupleType: + return checkTupleType((node)); + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + return checkUnionOrIntersectionType((node)); + case SyntaxKind.ParenthesizedType: + case SyntaxKind.OptionalType: + case SyntaxKind.RestType: + return checkSourceElement((node).type); + case SyntaxKind.ThisType: + return checkThisType((node)); + case SyntaxKind.TypeOperator: + return checkTypeOperator((node)); + case SyntaxKind.ConditionalType: + return checkConditionalType((node)); + case SyntaxKind.InferType: + return checkInferType((node)); + case SyntaxKind.ImportType: + return checkImportType((node)); + case SyntaxKind.JSDocAugmentsTag: + return checkJSDocAugmentsTag((node as JSDocAugmentsTag)); + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocEnumTag: + return checkJSDocTypeAliasTag((node as JSDocTypedefTag)); + case SyntaxKind.JSDocTemplateTag: + return checkJSDocTemplateTag((node as JSDocTemplateTag)); + case SyntaxKind.JSDocTypeTag: + return checkJSDocTypeTag((node as JSDocTypeTag)); + case SyntaxKind.JSDocParameterTag: + return checkJSDocParameterTag((node as JSDocParameterTag)); + case SyntaxKind.JSDocFunctionType: + checkJSDocFunctionType((node as JSDocFunctionType)); + // falls through + case SyntaxKind.JSDocNonNullableType: + case SyntaxKind.JSDocNullableType: + case SyntaxKind.JSDocAllType: + case SyntaxKind.JSDocUnknownType: + case SyntaxKind.JSDocTypeLiteral: + checkJSDocTypeIsInJsFile(node); + forEachChild(node, checkSourceElement); + return; + case SyntaxKind.JSDocVariadicType: + checkJSDocVariadicType((node as JSDocVariadicType)); + return; + case SyntaxKind.JSDocTypeExpression: + return checkSourceElement((node as JSDocTypeExpression).type); + case SyntaxKind.IndexedAccessType: + return checkIndexedAccessType((node)); + case SyntaxKind.MappedType: + return checkMappedType((node)); + case SyntaxKind.FunctionDeclaration: + return checkFunctionDeclaration((node)); + case SyntaxKind.Block: + case SyntaxKind.ModuleBlock: + return checkBlock((node)); + case SyntaxKind.VariableStatement: + return checkVariableStatement((node)); + case SyntaxKind.ExpressionStatement: + return checkExpressionStatement((node)); + case SyntaxKind.IfStatement: + return checkIfStatement((node)); + case SyntaxKind.DoStatement: + return checkDoStatement((node)); + case SyntaxKind.WhileStatement: + return checkWhileStatement((node)); + case SyntaxKind.ForStatement: + return checkForStatement((node)); + case SyntaxKind.ForInStatement: + return checkForInStatement((node)); + case SyntaxKind.ForOfStatement: + return checkForOfStatement((node)); + case SyntaxKind.ContinueStatement: + case SyntaxKind.BreakStatement: + return checkBreakOrContinueStatement((node)); + case SyntaxKind.ReturnStatement: + return checkReturnStatement((node)); + case SyntaxKind.WithStatement: + return checkWithStatement((node)); + case SyntaxKind.SwitchStatement: + return checkSwitchStatement((node)); + case SyntaxKind.LabeledStatement: + return checkLabeledStatement((node)); + case SyntaxKind.ThrowStatement: + return checkThrowStatement((node)); + case SyntaxKind.TryStatement: + return checkTryStatement((node)); + case SyntaxKind.VariableDeclaration: + return checkVariableDeclaration((node)); + case SyntaxKind.BindingElement: + return checkBindingElement((node)); + case SyntaxKind.ClassDeclaration: + return checkClassDeclaration((node)); + case SyntaxKind.InterfaceDeclaration: + return checkInterfaceDeclaration((node)); + case SyntaxKind.TypeAliasDeclaration: + return checkTypeAliasDeclaration((node)); + case SyntaxKind.EnumDeclaration: + return checkEnumDeclaration((node)); + case SyntaxKind.ModuleDeclaration: + return checkModuleDeclaration((node)); + case SyntaxKind.ImportDeclaration: + return checkImportDeclaration((node)); + case SyntaxKind.ImportEqualsDeclaration: + return checkImportEqualsDeclaration((node)); + case SyntaxKind.ExportDeclaration: + return checkExportDeclaration((node)); + case SyntaxKind.ExportAssignment: + return checkExportAssignment((node)); + case SyntaxKind.EmptyStatement: + case SyntaxKind.DebuggerStatement: + checkGrammarStatementInAmbientContext(node); + return; + case SyntaxKind.MissingDeclaration: + return checkMissingDeclaration(node); + } + } + function checkJSDocTypeIsInJsFile(node: Node): void { + if (!isInJSFile(node)) { + grammarErrorOnNode(node, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); + } + } + function checkJSDocVariadicType(node: JSDocVariadicType): void { + checkJSDocTypeIsInJsFile(node); + checkSourceElement(node.type); + // Only legal location is in the *last* parameter tag or last parameter of a JSDoc function. + const { parent } = node; + if (isParameter(parent) && isJSDocFunctionType(parent.parent)) { + if (last(parent.parent.parameters) !== parent) { + error(node, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); } - - return resolveName(location, reference.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); + return; } - - function getReferencedValueDeclaration(referenceIn: Identifier): Declaration | undefined { - if (!isGeneratedIdentifier(referenceIn)) { - const reference = getParseTreeNode(referenceIn, isIdentifier); - if (reference) { - const symbol = getReferencedValueSymbol(reference); - if (symbol) { - return getExportSymbolOfValueSymbolIfExported(symbol).valueDeclaration; - } + if (!isJSDocTypeExpression(parent)) { + error(node, Diagnostics.JSDoc_may_only_appear_in_the_last_parameter_of_a_signature); + } + const paramTag = node.parent.parent; + if (!isJSDocParameterTag(paramTag)) { + error(node, Diagnostics.JSDoc_may_only_appear_in_the_last_parameter_of_a_signature); + return; + } + const param = getParameterSymbolFromJSDoc(paramTag); + if (!param) { + // We will error in `checkJSDocParameterTag`. + return; + } + const host = getHostSignatureFromJSDoc(paramTag); + if (!host || last(host.parameters).symbol !== param) { + error(node, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); + } + } + function getTypeFromJSDocVariadicType(node: JSDocVariadicType): ts.Type { + const type = getTypeFromTypeNode(node.type); + const { parent } = node; + const paramTag = node.parent.parent; + if (isJSDocTypeExpression(node.parent) && isJSDocParameterTag(paramTag)) { + // Else we will add a diagnostic, see `checkJSDocVariadicType`. + const host = getHostSignatureFromJSDoc(paramTag); + if (host) { + /* + Only return an array type if the corresponding parameter is marked as a rest parameter, or if there are no parameters. + So in the following situation we will not create an array type: + /** @param {...number} a * / + function f(a) {} + Because `a` will just be of type `number | undefined`. A synthetic `...args` will also be added, which *will* get an array type. + */ + const lastParamDeclaration = lastOrUndefined(host.parameters); + const symbol = getParameterSymbolFromJSDoc(paramTag); + if (!lastParamDeclaration || + symbol && lastParamDeclaration.symbol === symbol && isRestParameter(lastParamDeclaration)) { + return createArrayType(type); } } - - return undefined; } - - function isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean { - if (isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node)) { - return isFreshLiteralType(getTypeOfSymbol(getSymbolOfNode(node))); - } - return false; + if (isParameter(parent) && isJSDocFunctionType(parent.parent)) { + return createArrayType(type); } - - function literalTypeToNode(type: FreshableType, enclosing: Node, tracker: SymbolTracker): Expression { - const enumResult = type.flags & TypeFlags.EnumLiteral ? nodeBuilder.symbolToExpression(type.symbol, SymbolFlags.Value, enclosing, /*flags*/ undefined, tracker) - : type === trueType ? createTrue() : type === falseType && createFalse(); - return enumResult || createLiteral((type as LiteralType).value); + return addOptionality(type); + } + // Function and class expression bodies are checked after all statements in the enclosing body. This is + // to ensure constructs like the following are permitted: + // const foo = function () { + // const s = foo(); + // return "hello"; + // } + // Here, performing a full type check of the body of the function expression whilst in the process of + // determining the type of foo would cause foo to be given type any because of the recursive reference. + // Delaying the type check of the body ensures foo has been assigned a type. + function checkNodeDeferred(node: Node) { + const enclosingFile = getSourceFileOfNode(node); + const links = getNodeLinks(enclosingFile); + if (!(links.flags & NodeCheckFlags.TypeChecked)) { + links.deferredNodes = links.deferredNodes || createMap(); + const id = "" + getNodeId(node); + links.deferredNodes.set(id, node); } - - function createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, tracker: SymbolTracker) { - const type = getTypeOfSymbol(getSymbolOfNode(node)); - return literalTypeToNode(type, node, tracker); + } + function checkDeferredNodes(context: SourceFile) { + const links = getNodeLinks(context); + if (links.deferredNodes) { + links.deferredNodes.forEach(checkDeferredNode); } - - function createResolver(): EmitResolver { - // this variable and functions that use it are deliberately moved here from the outer scope - // to avoid scope pollution - const resolvedTypeReferenceDirectives = host.getResolvedTypeReferenceDirectives(); - let fileToDirective: Map; - if (resolvedTypeReferenceDirectives) { - // populate reverse mapping: file path -> type reference directive that was resolved to this file - fileToDirective = createMap(); - resolvedTypeReferenceDirectives.forEach((resolvedDirective, key) => { - if (!resolvedDirective || !resolvedDirective.resolvedFileName) { - return; + } + function checkDeferredNode(node: Node) { + const saveCurrentNode = currentNode; + currentNode = node; + instantiationCount = 0; + switch (node.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + checkFunctionExpressionOrObjectLiteralMethodDeferred((node)); + break; + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + checkAccessorDeclaration((node)); + break; + case SyntaxKind.ClassExpression: + checkClassExpressionDeferred((node)); + break; + case SyntaxKind.JsxSelfClosingElement: + checkJsxSelfClosingElementDeferred((node)); + break; + case SyntaxKind.JsxElement: + checkJsxElementDeferred((node)); + break; + } + currentNode = saveCurrentNode; + } + function checkSourceFile(node: SourceFile) { + mark("beforeCheck"); + checkSourceFileWorker(node); + mark("afterCheck"); + measure("Check", "beforeCheck", "afterCheck"); + } + function unusedIsError(kind: UnusedKind, isAmbient: boolean): boolean { + if (isAmbient) { + return false; + } + switch (kind) { + case UnusedKind.Local: + return !!compilerOptions.noUnusedLocals; + case UnusedKind.Parameter: + return !!compilerOptions.noUnusedParameters; + default: + return Debug.assertNever(kind); + } + } + function getPotentiallyUnusedIdentifiers(sourceFile: SourceFile): readonly PotentiallyUnusedIdentifier[] { + return allPotentiallyUnusedIdentifiers.get(sourceFile.path) || emptyArray; + } + // Fully type check a source file and collect the relevant diagnostics. + function checkSourceFileWorker(node: SourceFile) { + const links = getNodeLinks(node); + if (!(links.flags & NodeCheckFlags.TypeChecked)) { + if (skipTypeChecking(node, compilerOptions, host)) { + return; + } + // Grammar checking + checkGrammarSourceFile(node); + clear(potentialThisCollisions); + clear(potentialNewTargetCollisions); + forEach(node.statements, checkSourceElement); + checkSourceElement(node.endOfFileToken); + checkDeferredNodes(node); + if (isExternalOrCommonJsModule(node)) { + registerForUnusedIdentifiersCheck(node); + } + if (!node.isDeclarationFile && (compilerOptions.noUnusedLocals || compilerOptions.noUnusedParameters)) { + checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(node), (containingNode, kind, diag) => { + if (!containsParseError(containingNode) && unusedIsError(kind, !!(containingNode.flags & NodeFlags.Ambient))) { + diagnostics.add(diag); } - const file = host.getSourceFile(resolvedDirective.resolvedFileName)!; - // Add the transitive closure of path references loaded by this file (as long as they are not) - // part of an existing type reference. - addReferencedFilesToTypeDirective(file, key); }); } - - return { - getReferencedExportContainer, - getReferencedImportDeclaration, - getReferencedDeclarationWithCollidingName, - isDeclarationWithCollidingName, - isValueAliasDeclaration: node => { - node = getParseTreeNode(node); - // Synthesized nodes are always treated like values. - return node ? isValueAliasDeclaration(node) : true; - }, - hasGlobalName, - isReferencedAliasDeclaration: (node, checkChildren?) => { - node = getParseTreeNode(node); - // Synthesized nodes are always treated as referenced. - return node ? isReferencedAliasDeclaration(node, checkChildren) : true; - }, - getNodeCheckFlags: node => { - node = getParseTreeNode(node); - return node ? getNodeCheckFlags(node) : 0; - }, - isTopLevelValueImportEqualsWithEntityName, - isDeclarationVisible, - isImplementationOfOverload, - isRequiredInitializedParameter, - isOptionalUninitializedParameterProperty, - isExpandoFunctionDeclaration, - getPropertiesOfContainerFunction, - createTypeOfDeclaration, - createReturnTypeOfSignatureDeclaration, - createTypeOfExpression, - createLiteralConstValue, - isSymbolAccessible, - isEntityNameVisible, - getConstantValue: nodeIn => { - const node = getParseTreeNode(nodeIn, canHaveConstantValue); - return node ? getConstantValue(node) : undefined; - }, - collectLinkedAliases, - getReferencedValueDeclaration, - getTypeReferenceSerializationKind, - isOptionalParameter, - moduleExportsSomeValue, - isArgumentsLocalBinding, - getExternalModuleFileFromDeclaration, - getTypeReferenceDirectivesForEntityName, - getTypeReferenceDirectivesForSymbol, - isLiteralConstDeclaration, - isLateBound: (nodeIn: Declaration): nodeIn is LateBoundDeclaration => { - const node = getParseTreeNode(nodeIn, isDeclaration); - const symbol = node && getSymbolOfNode(node); - return !!(symbol && getCheckFlags(symbol) & CheckFlags.Late); - }, - getJsxFactoryEntity: location => location ? (getJsxNamespace(location), (getSourceFileOfNode(location).localJsxFactory || _jsxFactoryEntity)) : _jsxFactoryEntity, - getAllAccessorDeclarations(accessor: AccessorDeclaration): AllAccessorDeclarations { - accessor = getParseTreeNode(accessor, isGetOrSetAccessorDeclaration)!; // TODO: GH#18217 - const otherKind = accessor.kind === SyntaxKind.SetAccessor ? SyntaxKind.GetAccessor : SyntaxKind.SetAccessor; - const otherAccessor = getDeclarationOfKind(getSymbolOfNode(accessor), otherKind); - const firstAccessor = otherAccessor && (otherAccessor.pos < accessor.pos) ? otherAccessor : accessor; - const secondAccessor = otherAccessor && (otherAccessor.pos < accessor.pos) ? accessor : otherAccessor; - const setAccessor = accessor.kind === SyntaxKind.SetAccessor ? accessor : otherAccessor as SetAccessorDeclaration; - const getAccessor = accessor.kind === SyntaxKind.GetAccessor ? accessor : otherAccessor as GetAccessorDeclaration; - return { - firstAccessor, - secondAccessor, - setAccessor, - getAccessor - }; - }, - getSymbolOfExternalModuleSpecifier: moduleName => resolveExternalModuleNameWorker(moduleName, moduleName, /*moduleNotFoundError*/ undefined), - isBindingCapturedByNode: (node, decl) => { - const parseNode = getParseTreeNode(node); - const parseDecl = getParseTreeNode(decl); - return !!parseNode && !!parseDecl && (isVariableDeclaration(parseDecl) || isBindingElement(parseDecl)) && isBindingCapturedByNode(parseNode, parseDecl); - }, - getDeclarationStatementsForSourceFile: (node, flags, tracker, bundled) => { - const n = getParseTreeNode(node) as SourceFile; - Debug.assert(n && n.kind === SyntaxKind.SourceFile, "Non-sourcefile node passed into getDeclarationsForSourceFile"); - const sym = getSymbolOfNode(node); - if (!sym) { - return !node.locals ? [] : nodeBuilder.symbolTableToDeclarationStatements(node.locals, node, flags, tracker, bundled); - } - return !sym.exports ? [] : nodeBuilder.symbolTableToDeclarationStatements(sym.exports, node, flags, tracker, bundled); - } - }; - - function isInHeritageClause(node: PropertyAccessEntityNameExpression) { - return node.parent && node.parent.kind === SyntaxKind.ExpressionWithTypeArguments && node.parent.parent && node.parent.parent.kind === SyntaxKind.HeritageClause; + if (isExternalOrCommonJsModule(node)) { + checkExternalModuleExports(node); } - - // defined here to avoid outer scope pollution - function getTypeReferenceDirectivesForEntityName(node: EntityNameOrEntityNameExpression): string[] | undefined { - // program does not have any files with type reference directives - bail out - if (!fileToDirective) { - return undefined; - } - // property access can only be used as values, or types when within an expression with type arguments inside a heritage clause - // qualified names can only be used as types\namespaces - // identifiers are treated as values only if they appear in type queries - let meaning = SymbolFlags.Type | SymbolFlags.Namespace; - if ((node.kind === SyntaxKind.Identifier && isInTypeQuery(node)) || (node.kind === SyntaxKind.PropertyAccessExpression && !isInHeritageClause(node))) { - meaning = SymbolFlags.Value | SymbolFlags.ExportValue; - } - - const symbol = resolveEntityName(node, meaning, /*ignoreErrors*/ true); - return symbol && symbol !== unknownSymbol ? getTypeReferenceDirectivesForSymbol(symbol, meaning) : undefined; + if (potentialThisCollisions.length) { + forEach(potentialThisCollisions, checkIfThisIsCapturedInEnclosingScope); + clear(potentialThisCollisions); } - - // defined here to avoid outer scope pollution - function getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): string[] | undefined { - // program does not have any files with type reference directives - bail out - if (!fileToDirective) { - return undefined; - } - if (!isSymbolFromTypeDeclarationFile(symbol)) { - return undefined; + if (potentialNewTargetCollisions.length) { + forEach(potentialNewTargetCollisions, checkIfNewTargetIsCapturedInEnclosingScope); + clear(potentialNewTargetCollisions); + } + links.flags |= NodeCheckFlags.TypeChecked; + } + } + function getDiagnostics(sourceFile: SourceFile, ct: CancellationToken): Diagnostic[] { + try { + // Record the cancellation token so it can be checked later on during checkSourceElement. + // Do this in a finally block so we can ensure that it gets reset back to nothing after + // this call is done. + cancellationToken = ct; + return getDiagnosticsWorker(sourceFile); + } + finally { + cancellationToken = undefined; + } + } + function getDiagnosticsWorker(sourceFile: SourceFile): Diagnostic[] { + throwIfNonDiagnosticsProducing(); + if (sourceFile) { + // Some global diagnostics are deferred until they are needed and + // may not be reported in the first call to getGlobalDiagnostics. + // We should catch these changes and report them. + const previousGlobalDiagnostics = diagnostics.getGlobalDiagnostics(); + const previousGlobalDiagnosticsSize = previousGlobalDiagnostics.length; + checkSourceFile(sourceFile); + const semanticDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName); + const currentGlobalDiagnostics = diagnostics.getGlobalDiagnostics(); + if (currentGlobalDiagnostics !== previousGlobalDiagnostics) { + // If the arrays are not the same reference, new diagnostics were added. + const deferredGlobalDiagnostics = relativeComplement(previousGlobalDiagnostics, currentGlobalDiagnostics, compareDiagnostics); + return concatenate(deferredGlobalDiagnostics, semanticDiagnostics); + } + else if (previousGlobalDiagnosticsSize === 0 && currentGlobalDiagnostics.length > 0) { + // If the arrays are the same reference, but the length has changed, a single + // new diagnostic was added as DiagnosticCollection attempts to reuse the + // same array. + return concatenate(currentGlobalDiagnostics, semanticDiagnostics); + } + return semanticDiagnostics; + } + // Global diagnostics are always added when a file is not provided to + // getDiagnostics + forEach(host.getSourceFiles(), checkSourceFile); + return diagnostics.getDiagnostics(); + } + function getGlobalDiagnostics(): Diagnostic[] { + throwIfNonDiagnosticsProducing(); + return diagnostics.getGlobalDiagnostics(); + } + function throwIfNonDiagnosticsProducing() { + if (!produceDiagnostics) { + throw new Error("Trying to get diagnostics from a type checker that does not produce them."); + } + } + // Language service support + function getSymbolsInScope(location: Node, meaning: SymbolFlags): ts.Symbol[] { + if (location.flags & NodeFlags.InWithStatement) { + // We cannot answer semantic questions within a with block, do not proceed any further + return []; + } + const symbols = createSymbolTable(); + let isStatic = false; + populateSymbols(); + symbols.delete(InternalSymbolName.This); // Not a symbol, a keyword + return symbolsToArray(symbols); + function populateSymbols() { + while (location) { + if (location.locals && !isGlobalSourceFile(location)) { + copySymbols(location.locals, meaning); } - // check what declarations in the symbol can contribute to the target meaning - let typeReferenceDirectives: string[] | undefined; - for (const decl of symbol.declarations) { - // check meaning of the local symbol to see if declaration needs to be analyzed further - if (decl.symbol && decl.symbol.flags & meaning!) { - const file = getSourceFileOfNode(decl); - const typeReferenceDirective = fileToDirective.get(file.path); - if (typeReferenceDirective) { - (typeReferenceDirectives || (typeReferenceDirectives = [])).push(typeReferenceDirective); + switch (location.kind) { + case SyntaxKind.SourceFile: + if (!isExternalOrCommonJsModule((location))) + break; + // falls through + case SyntaxKind.ModuleDeclaration: + copySymbols((getSymbolOfNode((location as ModuleDeclaration | SourceFile)).exports!), meaning & SymbolFlags.ModuleMember); + break; + case SyntaxKind.EnumDeclaration: + copySymbols((getSymbolOfNode((location as EnumDeclaration)).exports!), meaning & SymbolFlags.EnumMember); + break; + case SyntaxKind.ClassExpression: + const className = (location as ClassExpression).name; + if (className) { + copySymbol(location.symbol, meaning); } - else { - // found at least one entry that does not originate from type reference directive - return undefined; + // this fall-through is necessary because we would like to handle + // type parameter inside class expression similar to how we handle it in classDeclaration and interface Declaration. + // falls through + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + // If we didn't come from static member of class or interface, + // add the type parameters into the symbol table + // (type parameters of classDeclaration/classExpression and interface are in member property of the symbol. + // Note: that the memberFlags come from previous iteration. + if (!isStatic) { + copySymbols(getMembersOfSymbol(getSymbolOfNode((location as ClassDeclaration | InterfaceDeclaration))), meaning & SymbolFlags.Type); + } + break; + case SyntaxKind.FunctionExpression: + const funcName = (location as FunctionExpression).name; + if (funcName) { + copySymbol(location.symbol, meaning); } - } - } - return typeReferenceDirectives; - } - - function isSymbolFromTypeDeclarationFile(symbol: Symbol): boolean { - // bail out if symbol does not have associated declarations (i.e. this is transient symbol created for property in binding pattern) - if (!symbol.declarations) { - return false; - } - - // walk the parent chain for symbols to make sure that top level parent symbol is in the global scope - // external modules cannot define or contribute to type declaration files - let current = symbol; - while (true) { - const parent = getParentOfSymbol(current); - if (parent) { - current = parent; - } - else { break; - } - } - - if (current.valueDeclaration && current.valueDeclaration.kind === SyntaxKind.SourceFile && current.flags & SymbolFlags.ValueModule) { - return false; } - - // check that at least one declaration of top level symbol originates from type declaration file - for (const decl of symbol.declarations) { - const file = getSourceFileOfNode(decl); - if (fileToDirective.has(file.path)) { - return true; - } + if (introducesArgumentsExoticObject(location)) { + copySymbol(argumentsSymbol, meaning); } - return false; + isStatic = hasModifier(location, ModifierFlags.Static); + location = location.parent; } - - function addReferencedFilesToTypeDirective(file: SourceFile, key: string) { - if (fileToDirective.has(file.path)) return; - fileToDirective.set(file.path, key); - for (const { fileName } of file.referencedFiles) { - const resolvedFile = resolveTripleslashReference(fileName, file.originalFileName); - const referencedFile = host.getSourceFile(resolvedFile); - if (referencedFile) { - addReferencedFilesToTypeDirective(referencedFile, key); - } + copySymbols(globals, meaning); + } + /** + * Copy the given symbol into symbol tables if the symbol has the given meaning + * and it doesn't already existed in the symbol table + * @param key a key for storing in symbol table; if undefined, use symbol.name + * @param symbol the symbol to be added into symbol table + * @param meaning meaning of symbol to filter by before adding to symbol table + */ + function copySymbol(symbol: ts.Symbol, meaning: SymbolFlags): void { + if (getCombinedLocalAndExportSymbolFlags(symbol) & meaning) { + const id = symbol.escapedName; + // We will copy all symbol regardless of its reserved name because + // symbolsToArray will check whether the key is a reserved name and + // it will not copy symbol with reserved name to the array + if (!symbols.has(id)) { + symbols.set(id, symbol); } } } - - function getExternalModuleFileFromDeclaration(declaration: AnyImportOrReExport | ModuleDeclaration | ImportTypeNode): SourceFile | undefined { - const specifier = declaration.kind === SyntaxKind.ModuleDeclaration ? tryCast(declaration.name, isStringLiteral) : getExternalModuleName(declaration); - const moduleSymbol = resolveExternalModuleNameWorker(specifier!, specifier!, /*moduleNotFoundError*/ undefined); // TODO: GH#18217 - if (!moduleSymbol) { - return undefined; + function copySymbols(source: SymbolTable, meaning: SymbolFlags): void { + if (meaning) { + source.forEach(symbol => { + copySymbol(symbol, meaning); + }); } - return getDeclarationOfKind(moduleSymbol, SyntaxKind.SourceFile); } - - function initializeTypeChecker() { - // Bind all source files and propagate errors - for (const file of host.getSourceFiles()) { - bindSourceFile(file, compilerOptions); + } + function isTypeDeclarationName(name: Node): boolean { + return name.kind === SyntaxKind.Identifier && + isTypeDeclaration(name.parent) && + (name.parent).name === name; + } + function isTypeDeclaration(node: Node): node is TypeParameterDeclaration | ClassDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumDeclaration { + switch (node.kind) { + case SyntaxKind.TypeParameter: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.EnumDeclaration: + return true; + default: + return false; + } + } + // True if the given identifier is part of a type reference + function isTypeReferenceIdentifier(node: EntityName): boolean { + while (node.parent.kind === SyntaxKind.QualifiedName) { + node = (node.parent as QualifiedName); + } + return node.parent.kind === SyntaxKind.TypeReference; + } + function isHeritageClauseElementIdentifier(node: Node): boolean { + while (node.parent.kind === SyntaxKind.PropertyAccessExpression) { + node = node.parent; + } + return node.parent.kind === SyntaxKind.ExpressionWithTypeArguments; + } + function forEachEnclosingClass(node: Node, callback: (node: Node) => T | undefined): T | undefined { + let result: T | undefined; + while (true) { + node = (getContainingClass(node)!); + if (!node) + break; + if (result = callback(node)) + break; + } + return result; + } + function isNodeUsedDuringClassInitialization(node: Node) { + return !!findAncestor(node, element => { + if (isConstructorDeclaration(element) && nodeIsPresent(element.body) || isPropertyDeclaration(element)) { + return true; } - - amalgamatedDuplicates = createMap(); - - // Initialize global symbol table - let augmentations: (readonly (StringLiteral | Identifier)[])[] | undefined; - for (const file of host.getSourceFiles()) { - if (file.redirectInfo) { - continue; - } - if (!isExternalOrCommonJsModule(file)) { - // It is an error for a non-external-module (i.e. script) to declare its own `globalThis`. - // We can't use `builtinGlobals` for this due to synthetic expando-namespace generation in JS files. - const fileGlobalThisSymbol = file.locals!.get("globalThis" as __String); - if (fileGlobalThisSymbol) { - for (const declaration of fileGlobalThisSymbol.declarations) { - diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0, "globalThis")); - } - } - mergeSymbolTable(globals, file.locals!); - } - if (file.jsGlobalAugmentations) { - mergeSymbolTable(globals, file.jsGlobalAugmentations); + else if (isClassLike(element) || isFunctionLikeDeclaration(element)) { + return "quit"; + } + return false; + }); + } + function isNodeWithinClass(node: Node, classDeclaration: ClassLikeDeclaration) { + return !!forEachEnclosingClass(node, n => n === classDeclaration); + } + function getLeftSideOfImportEqualsOrExportAssignment(nodeOnRightSide: EntityName): ImportEqualsDeclaration | ExportAssignment | undefined { + while (nodeOnRightSide.parent.kind === SyntaxKind.QualifiedName) { + nodeOnRightSide = (nodeOnRightSide.parent); + } + if (nodeOnRightSide.parent.kind === SyntaxKind.ImportEqualsDeclaration) { + return (nodeOnRightSide.parent).moduleReference === nodeOnRightSide ? nodeOnRightSide.parent : undefined; + } + if (nodeOnRightSide.parent.kind === SyntaxKind.ExportAssignment) { + return (nodeOnRightSide.parent).expression === (nodeOnRightSide) ? nodeOnRightSide.parent : undefined; + } + return undefined; + } + function isInRightSideOfImportOrExportAssignment(node: EntityName) { + return getLeftSideOfImportEqualsOrExportAssignment(node) !== undefined; + } + function getSpecialPropertyAssignmentSymbolFromEntityName(entityName: EntityName | PropertyAccessExpression) { + const specialPropertyAssignmentKind = getAssignmentDeclarationKind((entityName.parent.parent as BinaryExpression)); + switch (specialPropertyAssignmentKind) { + case AssignmentDeclarationKind.ExportsProperty: + case AssignmentDeclarationKind.PrototypeProperty: + return getSymbolOfNode(entityName.parent); + case AssignmentDeclarationKind.ThisProperty: + case AssignmentDeclarationKind.ModuleExports: + case AssignmentDeclarationKind.Property: + return getSymbolOfNode(entityName.parent.parent); + } + } + function isImportTypeQualifierPart(node: EntityName): ImportTypeNode | undefined { + let parent = node.parent; + while (isQualifiedName(parent)) { + node = parent; + parent = parent.parent; + } + if (parent && parent.kind === SyntaxKind.ImportType && (parent as ImportTypeNode).qualifier === node) { + return parent as ImportTypeNode; + } + return undefined; + } + function getSymbolOfEntityNameOrPropertyAccessExpression(entityName: EntityName | PropertyAccessExpression): ts.Symbol | undefined { + if (isDeclarationName(entityName)) { + return getSymbolOfNode(entityName.parent); + } + if (isInJSFile(entityName) && + entityName.parent.kind === SyntaxKind.PropertyAccessExpression && + entityName.parent === (entityName.parent.parent as BinaryExpression).left) { + // Check if this is a special property assignment + const specialPropertyAssignmentSymbol = getSpecialPropertyAssignmentSymbolFromEntityName(entityName); + if (specialPropertyAssignmentSymbol) { + return specialPropertyAssignmentSymbol; + } + } + if (entityName.parent.kind === SyntaxKind.ExportAssignment && isEntityNameExpression(entityName)) { + // Even an entity name expression that doesn't resolve as an entityname may still typecheck as a property access expression + const success = resolveEntityName(entityName, + /*all meanings*/ SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*ignoreErrors*/ true); + if (success && success !== unknownSymbol) { + return success; + } + } + else if (!isPropertyAccessExpression(entityName) && isInRightSideOfImportOrExportAssignment(entityName)) { + // Since we already checked for ExportAssignment, this really could only be an Import + const importEqualsDeclaration = getAncestor(entityName, SyntaxKind.ImportEqualsDeclaration); + Debug.assert(importEqualsDeclaration !== undefined); + return getSymbolOfPartOfRightHandSideOfImportEquals(entityName, /*dontResolveAlias*/ true); + } + if (!isPropertyAccessExpression(entityName)) { + const possibleImportNode = isImportTypeQualifierPart(entityName); + if (possibleImportNode) { + getTypeFromTypeNode(possibleImportNode); + const sym = getNodeLinks(entityName).resolvedSymbol; + return sym === unknownSymbol ? undefined : sym; + } + } + while (isRightSideOfQualifiedNameOrPropertyAccess(entityName)) { + entityName = (entityName.parent); + } + if (isHeritageClauseElementIdentifier(entityName)) { + let meaning = SymbolFlags.None; + // In an interface or class, we're definitely interested in a type. + if (entityName.parent.kind === SyntaxKind.ExpressionWithTypeArguments) { + meaning = SymbolFlags.Type; + // In a class 'extends' clause we are also looking for a value. + if (isExpressionWithTypeArgumentsInClassExtendsClause(entityName.parent)) { + meaning |= SymbolFlags.Value; } - if (file.patternAmbientModules && file.patternAmbientModules.length) { - patternAmbientModules = concatenate(patternAmbientModules, file.patternAmbientModules); + } + else { + meaning = SymbolFlags.Namespace; + } + meaning |= SymbolFlags.Alias; + const entityNameSymbol = isEntityNameExpression(entityName) ? resolveEntityName(entityName, meaning) : undefined; + if (entityNameSymbol) { + return entityNameSymbol; + } + } + if (entityName.parent.kind === SyntaxKind.JSDocParameterTag) { + return getParameterSymbolFromJSDoc((entityName.parent as JSDocParameterTag)); + } + if (entityName.parent.kind === SyntaxKind.TypeParameter && entityName.parent.parent.kind === SyntaxKind.JSDocTemplateTag) { + Debug.assert(!isInJSFile(entityName)); // Otherwise `isDeclarationName` would have been true. + const typeParameter = getTypeParameterFromJsDoc((entityName.parent as TypeParameterDeclaration & { + parent: JSDocTemplateTag; + })); + return typeParameter && typeParameter.symbol; + } + if (isExpressionNode(entityName)) { + if (nodeIsMissing(entityName)) { + // Missing entity name. + return undefined; + } + if (entityName.kind === SyntaxKind.Identifier) { + if (isJSXTagName(entityName) && isJsxIntrinsicIdentifier(entityName)) { + const symbol = getIntrinsicTagSymbol((entityName.parent)); + return symbol === unknownSymbol ? undefined : symbol; } - if (file.moduleAugmentations.length) { - (augmentations || (augmentations = [])).push(file.moduleAugmentations); + return resolveEntityName(entityName, SymbolFlags.Value, /*ignoreErrors*/ false, /*dontResolveAlias*/ true); + } + else if (entityName.kind === SyntaxKind.PropertyAccessExpression || entityName.kind === SyntaxKind.QualifiedName) { + const links = getNodeLinks(entityName); + if (links.resolvedSymbol) { + return links.resolvedSymbol; } - if (file.symbol && file.symbol.globalExports) { - // Merge in UMD exports with first-in-wins semantics (see #9771) - const source = file.symbol.globalExports; - source.forEach((sourceSymbol, id) => { - if (!globals.has(id)) { - globals.set(id, sourceSymbol); - } - }); + if (entityName.kind === SyntaxKind.PropertyAccessExpression) { + checkPropertyAccessExpression(entityName); } - } - - // We do global augmentations separately from module augmentations (and before creating global types) because they - // 1. Affect global types. We won't have the correct global types until global augmentations are merged. Also, - // 2. Module augmentation instantiation requires creating the type of a module, which, in turn, can require - // checking for an export or property on the module (if export=) which, in turn, can fall back to the - // apparent type of the module - either globalObjectType or globalFunctionType - which wouldn't exist if we - // did module augmentations prior to finalizing the global types. - if (augmentations) { - // merge _global_ module augmentations. - // this needs to be done after global symbol table is initialized to make sure that all ambient modules are indexed - for (const list of augmentations) { - for (const augmentation of list) { - if (!isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)) continue; - mergeModuleAugmentation(augmentation); - } + else { + checkQualifiedName(entityName); } + return links.resolvedSymbol; } - - // Setup global builtins - addToSymbolTable(globals, builtinGlobals, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0); - - getSymbolLinks(undefinedSymbol).type = undefinedWideningType; - getSymbolLinks(argumentsSymbol).type = getGlobalType("IArguments" as __String, /*arity*/ 0, /*reportErrors*/ true); - getSymbolLinks(unknownSymbol).type = errorType; - getSymbolLinks(globalThisSymbol).type = createObjectType(ObjectFlags.Anonymous, globalThisSymbol); - - // Initialize special types - globalArrayType = getGlobalType("Array" as __String, /*arity*/ 1, /*reportErrors*/ true); - globalObjectType = getGlobalType("Object" as __String, /*arity*/ 0, /*reportErrors*/ true); - globalFunctionType = getGlobalType("Function" as __String, /*arity*/ 0, /*reportErrors*/ true); - globalCallableFunctionType = strictBindCallApply && getGlobalType("CallableFunction" as __String, /*arity*/ 0, /*reportErrors*/ true) || globalFunctionType; - globalNewableFunctionType = strictBindCallApply && getGlobalType("NewableFunction" as __String, /*arity*/ 0, /*reportErrors*/ true) || globalFunctionType; - globalStringType = getGlobalType("String" as __String, /*arity*/ 0, /*reportErrors*/ true); - globalNumberType = getGlobalType("Number" as __String, /*arity*/ 0, /*reportErrors*/ true); - globalBooleanType = getGlobalType("Boolean" as __String, /*arity*/ 0, /*reportErrors*/ true); - globalRegExpType = getGlobalType("RegExp" as __String, /*arity*/ 0, /*reportErrors*/ true); - anyArrayType = createArrayType(anyType); - - autoArrayType = createArrayType(autoType); - if (autoArrayType === emptyObjectType) { - // autoArrayType is used as a marker, so even if global Array type is not defined, it needs to be a unique type - autoArrayType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); - } - - globalReadonlyArrayType = getGlobalTypeOrUndefined("ReadonlyArray" as __String, /*arity*/ 1) || globalArrayType; - anyReadonlyArrayType = globalReadonlyArrayType ? createTypeFromGenericGlobalType(globalReadonlyArrayType, [anyType]) : anyArrayType; - globalThisType = getGlobalTypeOrUndefined("ThisType" as __String, /*arity*/ 1); - - if (augmentations) { - // merge _nonglobal_ module augmentations. - // this needs to be done after global symbol table is initialized to make sure that all ambient modules are indexed - for (const list of augmentations) { - for (const augmentation of list) { - if (isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)) continue; - mergeModuleAugmentation(augmentation); - } + } + else if (isTypeReferenceIdentifier((entityName))) { + const meaning = entityName.parent.kind === SyntaxKind.TypeReference ? SymbolFlags.Type : SymbolFlags.Namespace; + return resolveEntityName((entityName), meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true); + } + if (entityName.parent.kind === SyntaxKind.TypePredicate) { + return resolveEntityName((entityName), /*meaning*/ SymbolFlags.FunctionScopedVariable); + } + // Do we want to return undefined here? + return undefined; + } + function getSymbolAtLocation(node: Node): ts.Symbol | undefined { + if (node.kind === SyntaxKind.SourceFile) { + return isExternalModule((node)) ? getMergedSymbol(node.symbol) : undefined; + } + const { parent } = node; + const grandParent = parent.parent; + if (node.flags & NodeFlags.InWithStatement) { + // We cannot answer semantic questions within a with block, do not proceed any further + return undefined; + } + if (isDeclarationNameOrImportPropertyName(node)) { + // This is a declaration, call getSymbolOfNode + const parentSymbol = getSymbolOfNode(parent)!; + return isImportOrExportSpecifier(node.parent) && node.parent.propertyName === node + ? getImmediateAliasedSymbol(parentSymbol) + : parentSymbol; + } + else if (isLiteralComputedPropertyDeclarationName(node)) { + return getSymbolOfNode(parent.parent); + } + if (node.kind === SyntaxKind.Identifier) { + if (isInRightSideOfImportOrExportAssignment((node))) { + return getSymbolOfEntityNameOrPropertyAccessExpression((node)); + } + else if (parent.kind === SyntaxKind.BindingElement && + grandParent.kind === SyntaxKind.ObjectBindingPattern && + node === (parent).propertyName) { + const typeOfPattern = getTypeOfNode(grandParent); + const propertyDeclaration = getPropertyOfType(typeOfPattern, (node).escapedText); + if (propertyDeclaration) { + return propertyDeclaration; } } - - amalgamatedDuplicates.forEach(({ firstFile, secondFile, conflictingSymbols }) => { - // If not many things conflict, issue individual errors - if (conflictingSymbols.size < 8) { - conflictingSymbols.forEach(({ isBlockScoped, firstFileLocations, secondFileLocations }, symbolName) => { - const message = isBlockScoped ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 : Diagnostics.Duplicate_identifier_0; - for (const node of firstFileLocations) { - addDuplicateDeclarationError(node, message, symbolName, secondFileLocations); - } - for (const node of secondFileLocations) { - addDuplicateDeclarationError(node, message, symbolName, firstFileLocations); - } - }); + } + switch (node.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.QualifiedName: + return getSymbolOfEntityNameOrPropertyAccessExpression((node)); + case SyntaxKind.ThisKeyword: + const container = getThisContainer(node, /*includeArrowFunctions*/ false); + if (isFunctionLike(container)) { + const sig = getSignatureFromDeclaration(container); + if (sig.thisParameter) { + return sig.thisParameter; + } + } + if (isInExpressionContext(node)) { + return checkExpression((node as Expression)).symbol; + } + // falls through + case SyntaxKind.ThisType: + return getTypeFromThisTypeNode((node as ThisExpression | ThisTypeNode)).symbol; + case SyntaxKind.SuperKeyword: + return checkExpression((node as Expression)).symbol; + case SyntaxKind.ConstructorKeyword: + // constructor keyword for an overload, should take us to the definition if it exist + const constructorDeclaration = node.parent; + if (constructorDeclaration && constructorDeclaration.kind === SyntaxKind.Constructor) { + return (constructorDeclaration.parent).symbol; } - else { - // Otherwise issue top-level error since the files appear very identical in terms of what they contain - const list = arrayFrom(conflictingSymbols.keys()).join(", "); - diagnostics.add(addRelatedInfo( - createDiagnosticForNode(firstFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list), - createDiagnosticForNode(secondFile, Diagnostics.Conflicts_are_in_this_file) - )); - diagnostics.add(addRelatedInfo( - createDiagnosticForNode(secondFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list), - createDiagnosticForNode(firstFile, Diagnostics.Conflicts_are_in_this_file) - )); + return undefined; + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + // 1). import x = require("./mo/*gotToDefinitionHere*/d") + // 2). External module name in an import declaration + // 3). Dynamic import call or require in javascript + // 4). type A = import("./f/*gotToDefinitionHere*/oo") + if ((isExternalModuleImportEqualsDeclaration(node.parent.parent) && getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node) || + ((node.parent.kind === SyntaxKind.ImportDeclaration || node.parent.kind === SyntaxKind.ExportDeclaration) && (node.parent).moduleSpecifier === node) || + ((isInJSFile(node) && isRequireCall(node.parent, /*checkArgumentIsStringLiteralLike*/ false)) || isImportCall(node.parent)) || + (isLiteralTypeNode(node.parent) && isLiteralImportTypeNode(node.parent.parent) && node.parent.parent.argument === node.parent)) { + return resolveExternalModuleName(node, (node)); + } + if (isCallExpression(parent) && isBindableObjectDefinePropertyCall(parent) && parent.arguments[1] === node) { + return getSymbolOfNode(parent); + } + // falls through + case SyntaxKind.NumericLiteral: + // index access + const objectType = isElementAccessExpression(parent) + ? parent.argumentExpression === node ? getTypeOfExpression(parent.expression) : undefined + : isLiteralTypeNode(parent) && isIndexedAccessTypeNode(grandParent) + ? getTypeFromTypeNode(grandParent.objectType) + : undefined; + return objectType && getPropertyOfType(objectType, escapeLeadingUnderscores((node as StringLiteral | NumericLiteral).text)); + case SyntaxKind.DefaultKeyword: + case SyntaxKind.FunctionKeyword: + case SyntaxKind.EqualsGreaterThanToken: + case SyntaxKind.ClassKeyword: + return getSymbolOfNode(node.parent); + case SyntaxKind.ImportType: + return isLiteralImportTypeNode(node) ? getSymbolAtLocation(node.argument.literal) : undefined; + case SyntaxKind.ExportKeyword: + return isExportAssignment(node.parent) ? Debug.assertDefined(node.parent.symbol) : undefined; + default: + return undefined; + } + } + function getShorthandAssignmentValueSymbol(location: Node): ts.Symbol | undefined { + if (location && location.kind === SyntaxKind.ShorthandPropertyAssignment) { + return resolveEntityName((location).name, SymbolFlags.Value | SymbolFlags.Alias); + } + return undefined; + } + /** Returns the target of an export specifier without following aliases */ + function getExportSpecifierLocalTargetSymbol(node: ExportSpecifier): ts.Symbol | undefined { + return node.parent.parent.moduleSpecifier ? + getExternalModuleMember(node.parent.parent, node) : + resolveEntityName(node.propertyName || node.name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); + } + function getTypeOfNode(node: Node): ts.Type { + if (node.flags & NodeFlags.InWithStatement) { + // We cannot answer semantic questions within a with block, do not proceed any further + return errorType; + } + const classDecl = tryGetClassImplementingOrExtendingExpressionWithTypeArguments(node); + const classType = classDecl && getDeclaredTypeOfClassOrInterface(getSymbolOfNode(classDecl.class)); + if (isPartOfTypeNode(node)) { + const typeFromTypeNode = getTypeFromTypeNode((node)); + return classType ? getTypeWithThisArgument(typeFromTypeNode, classType.thisType) : typeFromTypeNode; + } + if (isExpressionNode(node)) { + return getRegularTypeOfExpression((node)); + } + if (classType && !classDecl!.isImplements) { + // A SyntaxKind.ExpressionWithTypeArguments is considered a type node, except when it occurs in the + // extends clause of a class. We handle that case here. + const baseType = firstOrUndefined(getBaseTypes(classType)); + return baseType ? getTypeWithThisArgument(baseType, classType.thisType) : errorType; + } + if (isTypeDeclaration(node)) { + // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration + const symbol = getSymbolOfNode(node); + return getDeclaredTypeOfSymbol(symbol); + } + if (isTypeDeclarationName(node)) { + const symbol = getSymbolAtLocation(node); + return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType; + } + if (isDeclaration(node)) { + // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration + const symbol = getSymbolOfNode(node); + return getTypeOfSymbol(symbol); + } + if (isDeclarationNameOrImportPropertyName(node)) { + const symbol = getSymbolAtLocation(node); + return symbol ? getTypeOfSymbol(symbol) : errorType; + } + if (isBindingPattern(node)) { + return getTypeForVariableLikeDeclaration(node.parent, /*includeOptionality*/ true) || errorType; + } + if (isInRightSideOfImportOrExportAssignment((node))) { + const symbol = getSymbolAtLocation(node); + if (symbol) { + const declaredType = getDeclaredTypeOfSymbol(symbol); + return declaredType !== errorType ? declaredType : getTypeOfSymbol(symbol); + } + } + return errorType; + } + // Gets the type of object literal or array literal of destructuring assignment. + // { a } from + // for ( { a } of elems) { + // } + // [ a ] from + // [a] = [ some array ...] + function getTypeOfAssignmentPattern(expr: AssignmentPattern): ts.Type | undefined { + Debug.assert(expr.kind === SyntaxKind.ObjectLiteralExpression || expr.kind === SyntaxKind.ArrayLiteralExpression); + // If this is from "for of" + // for ( { a } of elems) { + // } + if (expr.parent.kind === SyntaxKind.ForOfStatement) { + const iteratedType = checkRightHandSideOfForOf((expr.parent).expression, (expr.parent).awaitModifier); + return checkDestructuringAssignment(expr, iteratedType || errorType); + } + // If this is from "for" initializer + // for ({a } = elems[0];.....) { } + if (expr.parent.kind === SyntaxKind.BinaryExpression) { + const iteratedType = getTypeOfExpression((expr.parent).right); + return checkDestructuringAssignment(expr, iteratedType || errorType); + } + // If this is from nested object binding pattern + // for ({ skills: { primary, secondary } } = multiRobot, i = 0; i < 1; i++) { + if (expr.parent.kind === SyntaxKind.PropertyAssignment) { + const node = cast(expr.parent.parent, isObjectLiteralExpression); + const typeOfParentObjectLiteral = getTypeOfAssignmentPattern(node) || errorType; + const propertyIndex = indexOfNode(node.properties, expr.parent); + return checkObjectLiteralDestructuringPropertyAssignment(node, typeOfParentObjectLiteral, propertyIndex); + } + // Array literal assignment - array destructuring pattern + const node = cast(expr.parent, isArrayLiteralExpression); + // [{ property1: p1, property2 }] = elems; + const typeOfArrayLiteral = getTypeOfAssignmentPattern(node) || errorType; + const elementType = checkIteratedTypeOrElementType(IterationUse.Destructuring, typeOfArrayLiteral, undefinedType, expr.parent) || errorType; + return checkArrayLiteralDestructuringElementAssignment(node, typeOfArrayLiteral, node.elements.indexOf(expr), elementType); + } + // Gets the property symbol corresponding to the property in destructuring assignment + // 'property1' from + // for ( { property1: a } of elems) { + // } + // 'property1' at location 'a' from: + // [a] = [ property1, property2 ] + function getPropertySymbolOfDestructuringAssignment(location: Identifier) { + // Get the type of the object or array literal and then look for property of given name in the type + const typeOfObjectLiteral = getTypeOfAssignmentPattern(cast(location.parent.parent, isAssignmentPattern)); + return typeOfObjectLiteral && getPropertyOfType(typeOfObjectLiteral, location.escapedText); + } + function getRegularTypeOfExpression(expr: Expression): ts.Type { + if (isRightSideOfQualifiedNameOrPropertyAccess(expr)) { + expr = (expr.parent); + } + return getRegularTypeOfLiteralType(getTypeOfExpression(expr)); + } + /** + * Gets either the static or instance type of a class element, based on + * whether the element is declared as "static". + */ + function getParentTypeOfClassElement(node: ClassElement) { + const classSymbol = getSymbolOfNode(node.parent)!; + return hasModifier(node, ModifierFlags.Static) + ? getTypeOfSymbol(classSymbol) + : getDeclaredTypeOfSymbol(classSymbol); + } + function getClassElementPropertyKeyType(element: ClassElement) { + const name = element.name!; + switch (name.kind) { + case SyntaxKind.Identifier: + return getLiteralType(idText(name)); + case SyntaxKind.NumericLiteral: + case SyntaxKind.StringLiteral: + return getLiteralType(name.text); + case SyntaxKind.ComputedPropertyName: + const nameType = checkComputedPropertyName(name); + return isTypeAssignableToKind(nameType, TypeFlags.ESSymbolLike) ? nameType : stringType; + default: + return Debug.fail("Unsupported property name."); + } + } + // Return the list of properties of the given type, augmented with properties from Function + // if the type has call or construct signatures + function getAugmentedPropertiesOfType(type: ts.Type): ts.Symbol[] { + type = getApparentType(type); + const propsByName = createSymbolTable(getPropertiesOfType(type)); + const functionType = getSignaturesOfType(type, SignatureKind.Call).length ? globalCallableFunctionType : + getSignaturesOfType(type, SignatureKind.Construct).length ? globalNewableFunctionType : + undefined; + if (functionType) { + forEach(getPropertiesOfType(functionType), p => { + if (!propsByName.has(p.escapedName)) { + propsByName.set(p.escapedName, p); } }); - amalgamatedDuplicates = undefined; } - - function checkExternalEmitHelpers(location: Node, helpers: ExternalEmitHelpers) { - if ((requestedExternalEmitHelpers & helpers) !== helpers && compilerOptions.importHelpers) { - const sourceFile = getSourceFileOfNode(location); - if (isEffectiveExternalModule(sourceFile, compilerOptions) && !(location.flags & NodeFlags.Ambient)) { - const helpersModule = resolveHelpersModule(sourceFile, location); - if (helpersModule !== unknownSymbol) { - const uncheckedHelpers = helpers & ~requestedExternalEmitHelpers; - for (let helper = ExternalEmitHelpers.FirstEmitHelper; helper <= ExternalEmitHelpers.LastEmitHelper; helper <<= 1) { - if (uncheckedHelpers & helper) { - const name = getHelperName(helper); - const symbol = getSymbol(helpersModule.exports!, escapeLeadingUnderscores(name), SymbolFlags.Value); - if (!symbol) { - error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_which_does_not_exist_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name); - } - } - } - } - requestedExternalEmitHelpers |= helpers; - } - } + return getNamedMembers(propsByName); + } + function typeHasCallOrConstructSignatures(type: ts.Type): boolean { + return ts.typeHasCallOrConstructSignatures(type, checker); + } + function getRootSymbols(symbol: ts.Symbol): readonly ts.Symbol[] { + const roots = getImmediateRootSymbols(symbol); + return roots ? flatMap(roots, getRootSymbols) : [symbol]; + } + function getImmediateRootSymbols(symbol: ts.Symbol): readonly ts.Symbol[] | undefined { + if (getCheckFlags(symbol) & CheckFlags.Synthetic) { + return mapDefined(getSymbolLinks(symbol).containingType!.types, type => getPropertyOfType(type, symbol.escapedName)); + } + else if (symbol.flags & SymbolFlags.Transient) { + const { leftSpread, rightSpread, syntheticOrigin } = (symbol as TransientSymbol); + return leftSpread ? [leftSpread, rightSpread!] + : syntheticOrigin ? [syntheticOrigin] + : singleElementArray(tryGetAliasTarget(symbol)); } - - function getHelperName(helper: ExternalEmitHelpers) { - switch (helper) { - case ExternalEmitHelpers.Extends: return "__extends"; - case ExternalEmitHelpers.Assign: return "__assign"; - case ExternalEmitHelpers.Rest: return "__rest"; - case ExternalEmitHelpers.Decorate: return "__decorate"; - case ExternalEmitHelpers.Metadata: return "__metadata"; - case ExternalEmitHelpers.Param: return "__param"; - case ExternalEmitHelpers.Awaiter: return "__awaiter"; - case ExternalEmitHelpers.Generator: return "__generator"; - case ExternalEmitHelpers.Values: return "__values"; - case ExternalEmitHelpers.Read: return "__read"; - case ExternalEmitHelpers.Spread: return "__spread"; - case ExternalEmitHelpers.SpreadArrays: return "__spreadArrays"; - case ExternalEmitHelpers.Await: return "__await"; - case ExternalEmitHelpers.AsyncGenerator: return "__asyncGenerator"; - case ExternalEmitHelpers.AsyncDelegator: return "__asyncDelegator"; - case ExternalEmitHelpers.AsyncValues: return "__asyncValues"; - case ExternalEmitHelpers.ExportStar: return "__exportStar"; - case ExternalEmitHelpers.MakeTemplateObject: return "__makeTemplateObject"; - default: return Debug.fail("Unrecognized helper"); - } + return undefined; + } + function tryGetAliasTarget(symbol: ts.Symbol): ts.Symbol | undefined { + let target: ts.Symbol | undefined; + let next: ts.Symbol | undefined = symbol; + while (next = getSymbolLinks(next).target) { + target = next; } - - function resolveHelpersModule(node: SourceFile, errorNode: Node) { - if (!externalHelpersModule) { - externalHelpersModule = resolveExternalModule(node, externalHelpersModuleNameText, Diagnostics.This_syntax_requires_an_imported_helper_but_module_0_cannot_be_found, errorNode) || unknownSymbol; + return target; + } + // Emitter support + function isArgumentsLocalBinding(nodeIn: Identifier): boolean { + if (!isGeneratedIdentifier(nodeIn)) { + const node = getParseTreeNode(nodeIn, isIdentifier); + if (node) { + const isPropertyName = node.parent.kind === SyntaxKind.PropertyAccessExpression && (node.parent).name === node; + return !isPropertyName && getReferencedValueSymbol(node) === argumentsSymbol; } - return externalHelpersModule; } - - // GRAMMAR CHECKING - function checkGrammarDecoratorsAndModifiers(node: Node): boolean { - return checkGrammarDecorators(node) || checkGrammarModifiers(node); + return false; + } + function moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean { + let moduleSymbol = resolveExternalModuleName(moduleReferenceExpression.parent, moduleReferenceExpression); + if (!moduleSymbol || isShorthandAmbientModuleSymbol(moduleSymbol)) { + // If the module is not found or is shorthand, assume that it may export a value. + return true; } - - function checkGrammarDecorators(node: Node): boolean { - if (!node.decorators) { - return false; - } - if (!nodeCanBeDecorated(node, node.parent, node.parent.parent)) { - if (node.kind === SyntaxKind.MethodDeclaration && !nodeIsPresent((node).body)) { - return grammarErrorOnFirstToken(node, Diagnostics.A_decorator_can_only_decorate_a_method_implementation_not_an_overload); - } - else { - return grammarErrorOnFirstToken(node, Diagnostics.Decorators_are_not_valid_here); + const hasExportAssignment = hasExportAssignmentSymbol(moduleSymbol); + // if module has export assignment then 'resolveExternalModuleSymbol' will return resolved symbol for export assignment + // otherwise it will return moduleSymbol itself + moduleSymbol = resolveExternalModuleSymbol(moduleSymbol); + const symbolLinks = getSymbolLinks(moduleSymbol); + if (symbolLinks.exportsSomeValue === undefined) { + // for export assignments - check if resolved symbol for RHS is itself a value + // otherwise - check if at least one export is value + symbolLinks.exportsSomeValue = hasExportAssignment + ? !!(moduleSymbol.flags & SymbolFlags.Value) + : forEachEntry(getExportsOfModule(moduleSymbol), isValue); + } + return symbolLinks.exportsSomeValue!; + function isValue(s: ts.Symbol): boolean { + s = resolveSymbol(s); + return s && !!(s.flags & SymbolFlags.Value); + } + } + function isNameOfModuleOrEnumDeclaration(node: Identifier) { + return isModuleOrEnumDeclaration(node.parent) && node === node.parent.name; + } + // When resolved as an expression identifier, if the given node references an exported entity, return the declaration + // node of the exported entity's container. Otherwise, return undefined. + function getReferencedExportContainer(nodeIn: Identifier, prefixLocals?: boolean): SourceFile | ModuleDeclaration | EnumDeclaration | undefined { + const node = getParseTreeNode(nodeIn, isIdentifier); + if (node) { + // When resolving the export container for the name of a module or enum + // declaration, we need to start resolution at the declaration's container. + // Otherwise, we could incorrectly resolve the export container as the + // declaration if it contains an exported member with the same name. + let symbol = getReferencedValueSymbol(node, /*startInDeclarationContainer*/ isNameOfModuleOrEnumDeclaration(node)); + if (symbol) { + if (symbol.flags & SymbolFlags.ExportValue) { + // If we reference an exported entity within the same module declaration, then whether + // we prefix depends on the kind of entity. SymbolFlags.ExportHasLocal encompasses all the + // kinds that we do NOT prefix. + const exportSymbol = getMergedSymbol(symbol.exportSymbol!); + if (!prefixLocals && exportSymbol.flags & SymbolFlags.ExportHasLocal && !(exportSymbol.flags & SymbolFlags.Variable)) { + return undefined; + } + symbol = exportSymbol; } - } - else if (node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor) { - const accessors = getAllAccessorDeclarations((node.parent).members, node); - if (accessors.firstAccessor.decorators && node === accessors.secondAccessor) { - return grammarErrorOnFirstToken(node, Diagnostics.Decorators_cannot_be_applied_to_multiple_get_Slashset_accessors_of_the_same_name); + const parentSymbol = getParentOfSymbol(symbol); + if (parentSymbol) { + if (parentSymbol.flags & SymbolFlags.ValueModule && parentSymbol.valueDeclaration.kind === SyntaxKind.SourceFile) { + const symbolFile = (parentSymbol.valueDeclaration); + const referenceFile = getSourceFileOfNode(node); + // If `node` accesses an export and that export isn't in the same file, then symbol is a namespace export, so return undefined. + const symbolIsUmdExport = symbolFile !== referenceFile; + return symbolIsUmdExport ? undefined : symbolFile; + } + return findAncestor(node.parent, (n): n is ModuleDeclaration | EnumDeclaration => isModuleOrEnumDeclaration(n) && getSymbolOfNode(n) === parentSymbol); } } - return false; } - - function checkGrammarModifiers(node: Node): boolean { - const quickResult = reportObviousModifierErrors(node); - if (quickResult !== undefined) { - return quickResult; - } - - let lastStatic: Node | undefined, lastDeclare: Node | undefined, lastAsync: Node | undefined, lastReadonly: Node | undefined; - let flags = ModifierFlags.None; - for (const modifier of node.modifiers!) { - if (modifier.kind !== SyntaxKind.ReadonlyKeyword) { - if (node.kind === SyntaxKind.PropertySignature || node.kind === SyntaxKind.MethodSignature) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_type_member, tokenToString(modifier.kind)); + } + // When resolved as an expression identifier, if the given node references an import, return the declaration of + // that import. Otherwise, return undefined. + function getReferencedImportDeclaration(nodeIn: Identifier): Declaration | undefined { + const node = getParseTreeNode(nodeIn, isIdentifier); + if (node) { + const symbol = getReferencedValueSymbol(node); + // We should only get the declaration of an alias if there isn't a local value + // declaration for the symbol + if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value)) { + return getDeclarationOfAliasSymbol(symbol); + } + } + return undefined; + } + function isSymbolOfDestructuredElementOfCatchBinding(symbol: ts.Symbol) { + return isBindingElement(symbol.valueDeclaration) + && walkUpBindingElementsAndPatterns(symbol.valueDeclaration).parent.kind === SyntaxKind.CatchClause; + } + function isSymbolOfDeclarationWithCollidingName(symbol: ts.Symbol): boolean { + if (symbol.flags & SymbolFlags.BlockScoped && !isSourceFile(symbol.valueDeclaration)) { + const links = getSymbolLinks(symbol); + if (links.isDeclarationWithCollidingName === undefined) { + const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration); + if (isStatementWithLocals(container) || isSymbolOfDestructuredElementOfCatchBinding(symbol)) { + const nodeLinks = getNodeLinks(symbol.valueDeclaration); + if (resolveName(container.parent, symbol.escapedName, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false)) { + // redeclaration - always should be renamed + links.isDeclarationWithCollidingName = true; + } + else if (nodeLinks.flags & NodeCheckFlags.CapturedBlockScopedBinding) { + // binding is captured in the function + // should be renamed if: + // - binding is not top level - top level bindings never collide with anything + // AND + // - binding is not declared in loop, should be renamed to avoid name reuse across siblings + // let a, b + // { let x = 1; a = () => x; } + // { let x = 100; b = () => x; } + // console.log(a()); // should print '1' + // console.log(b()); // should print '100' + // OR + // - binding is declared inside loop but not in inside initializer of iteration statement or directly inside loop body + // * variables from initializer are passed to rewritten loop body as parameters so they are not captured directly + // * variables that are declared immediately in loop body will become top level variable after loop is rewritten and thus + // they will not collide with anything + const isDeclaredInLoop = nodeLinks.flags & NodeCheckFlags.BlockScopedBindingInLoop; + const inLoopInitializer = isIterationStatement(container, /*lookInLabeledStatements*/ false); + const inLoopBodyBlock = container.kind === SyntaxKind.Block && isIterationStatement(container.parent, /*lookInLabeledStatements*/ false); + links.isDeclarationWithCollidingName = !isBlockScopedContainerTopLevel(container) && (!isDeclaredInLoop || (!inLoopInitializer && !inLoopBodyBlock)); } - if (node.kind === SyntaxKind.IndexSignature) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_an_index_signature, tokenToString(modifier.kind)); + else { + links.isDeclarationWithCollidingName = false; } } - switch (modifier.kind) { - case SyntaxKind.ConstKeyword: - if (node.kind !== SyntaxKind.EnumDeclaration) { - return grammarErrorOnNode(node, Diagnostics.A_class_member_cannot_have_the_0_keyword, tokenToString(SyntaxKind.ConstKeyword)); - } - break; - case SyntaxKind.PublicKeyword: - case SyntaxKind.ProtectedKeyword: - case SyntaxKind.PrivateKeyword: - const text = visibilityToString(modifierToFlag(modifier.kind)); - - if (flags & ModifierFlags.AccessibilityModifier) { - return grammarErrorOnNode(modifier, Diagnostics.Accessibility_modifier_already_seen); - } - else if (flags & ModifierFlags.Static) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "static"); - } - else if (flags & ModifierFlags.Readonly) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "readonly"); - } - else if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "async"); - } - else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_module_or_namespace_element, text); - } - else if (flags & ModifierFlags.Abstract) { - if (modifier.kind === SyntaxKind.PrivateKeyword) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, text, "abstract"); - } - else { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "abstract"); - } - } - flags |= modifierToFlag(modifier.kind); - break; - - case SyntaxKind.StaticKeyword: - if (flags & ModifierFlags.Static) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "static"); - } - else if (flags & ModifierFlags.Readonly) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "readonly"); - } - else if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "async"); - } - else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_module_or_namespace_element, "static"); - } - else if (node.kind === SyntaxKind.Parameter) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "static"); - } - else if (flags & ModifierFlags.Abstract) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "abstract"); - } - flags |= ModifierFlags.Static; - lastStatic = modifier; - break; - - case SyntaxKind.ReadonlyKeyword: - if (flags & ModifierFlags.Readonly) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "readonly"); - } - else if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature && node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.Parameter) { - // If node.kind === SyntaxKind.Parameter, checkParameter report an error if it's not a parameter property. - return grammarErrorOnNode(modifier, Diagnostics.readonly_modifier_can_only_appear_on_a_property_declaration_or_index_signature); - } - flags |= ModifierFlags.Readonly; - lastReadonly = modifier; - break; - - case SyntaxKind.ExportKeyword: - if (flags & ModifierFlags.Export) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "export"); - } - else if (flags & ModifierFlags.Ambient) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "declare"); - } - else if (flags & ModifierFlags.Abstract) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "abstract"); - } - else if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "async"); - } - else if (isClassLike(node.parent)) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_class_element, "export"); - } - else if (node.kind === SyntaxKind.Parameter) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "export"); - } - flags |= ModifierFlags.Export; - break; - case SyntaxKind.DefaultKeyword: - const container = node.parent.kind === SyntaxKind.SourceFile ? node.parent : node.parent.parent; - if (container.kind === SyntaxKind.ModuleDeclaration && !isAmbientModule(container)) { - return grammarErrorOnNode(modifier, Diagnostics.A_default_export_can_only_be_used_in_an_ECMAScript_style_module); - } - - flags |= ModifierFlags.Default; - break; - case SyntaxKind.DeclareKeyword: - if (flags & ModifierFlags.Ambient) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "declare"); - } - else if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async"); - } - else if (isClassLike(node.parent) && !isPropertyDeclaration(node)) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_class_element, "declare"); - } - else if (node.kind === SyntaxKind.Parameter) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "declare"); - } - else if ((node.parent.flags & NodeFlags.Ambient) && node.parent.kind === SyntaxKind.ModuleBlock) { - return grammarErrorOnNode(modifier, Diagnostics.A_declare_modifier_cannot_be_used_in_an_already_ambient_context); - } - flags |= ModifierFlags.Ambient; - lastDeclare = modifier; - break; - - case SyntaxKind.AbstractKeyword: - if (flags & ModifierFlags.Abstract) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "abstract"); - } - if (node.kind !== SyntaxKind.ClassDeclaration) { - if (node.kind !== SyntaxKind.MethodDeclaration && - node.kind !== SyntaxKind.PropertyDeclaration && - node.kind !== SyntaxKind.GetAccessor && - node.kind !== SyntaxKind.SetAccessor) { - return grammarErrorOnNode(modifier, Diagnostics.abstract_modifier_can_only_appear_on_a_class_method_or_property_declaration); - } - if (!(node.parent.kind === SyntaxKind.ClassDeclaration && hasModifier(node.parent, ModifierFlags.Abstract))) { - return grammarErrorOnNode(modifier, Diagnostics.Abstract_methods_can_only_appear_within_an_abstract_class); - } - if (flags & ModifierFlags.Static) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "abstract"); - } - if (flags & ModifierFlags.Private) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "private", "abstract"); - } - } - - flags |= ModifierFlags.Abstract; - break; - - case SyntaxKind.AsyncKeyword: - if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "async"); - } - else if (flags & ModifierFlags.Ambient || node.parent.flags & NodeFlags.Ambient) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async"); - } - else if (node.kind === SyntaxKind.Parameter) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "async"); - } - flags |= ModifierFlags.Async; - lastAsync = modifier; - break; - } } - - if (node.kind === SyntaxKind.Constructor) { - if (flags & ModifierFlags.Static) { - return grammarErrorOnNode(lastStatic!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "static"); - } - if (flags & ModifierFlags.Abstract) { - return grammarErrorOnNode(lastStatic!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "abstract"); // TODO: GH#18217 - } - else if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(lastAsync!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "async"); - } - else if (flags & ModifierFlags.Readonly) { - return grammarErrorOnNode(lastReadonly!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "readonly"); + return links.isDeclarationWithCollidingName!; + } + return false; + } + // When resolved as an expression identifier, if the given node references a nested block scoped entity with + // a name that either hides an existing name or might hide it when compiled downlevel, + // return the declaration of that entity. Otherwise, return undefined. + function getReferencedDeclarationWithCollidingName(nodeIn: Identifier): Declaration | undefined { + if (!isGeneratedIdentifier(nodeIn)) { + const node = getParseTreeNode(nodeIn, isIdentifier); + if (node) { + const symbol = getReferencedValueSymbol(node); + if (symbol && isSymbolOfDeclarationWithCollidingName(symbol)) { + return symbol.valueDeclaration; } - return false; } - else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && flags & ModifierFlags.Ambient) { - return grammarErrorOnNode(lastDeclare!, Diagnostics.A_0_modifier_cannot_be_used_with_an_import_declaration, "declare"); - } - else if (node.kind === SyntaxKind.Parameter && (flags & ModifierFlags.ParameterPropertyModifier) && isBindingPattern((node).name)) { - return grammarErrorOnNode(node, Diagnostics.A_parameter_property_may_not_be_declared_using_a_binding_pattern); + } + return undefined; + } + // Return true if the given node is a declaration of a nested block scoped entity with a name that either hides an + // existing name or might hide a name when compiled downlevel + function isDeclarationWithCollidingName(nodeIn: Declaration): boolean { + const node = getParseTreeNode(nodeIn, isDeclaration); + if (node) { + const symbol = getSymbolOfNode(node); + if (symbol) { + return isSymbolOfDeclarationWithCollidingName(symbol); } - else if (node.kind === SyntaxKind.Parameter && (flags & ModifierFlags.ParameterPropertyModifier) && (node).dotDotDotToken) { - return grammarErrorOnNode(node, Diagnostics.A_parameter_property_cannot_be_declared_using_a_rest_parameter); + } + return false; + } + function isValueAliasDeclaration(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ImportClause: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: + return isAliasResolvedToValue(getSymbolOfNode(node) || unknownSymbol); + case SyntaxKind.ExportDeclaration: + const exportClause = (node).exportClause; + return !!exportClause && some(exportClause.elements, isValueAliasDeclaration); + case SyntaxKind.ExportAssignment: + return (node).expression && (node).expression.kind === SyntaxKind.Identifier ? + isAliasResolvedToValue(getSymbolOfNode(node) || unknownSymbol) : + true; + } + return false; + } + function isTopLevelValueImportEqualsWithEntityName(nodeIn: ImportEqualsDeclaration): boolean { + const node = getParseTreeNode(nodeIn, isImportEqualsDeclaration); + if (node === undefined || node.parent.kind !== SyntaxKind.SourceFile || !isInternalModuleImportEqualsDeclaration(node)) { + // parent is not source file or it is not reference to internal module + return false; + } + const isValue = isAliasResolvedToValue(getSymbolOfNode(node)); + return isValue && node.moduleReference && !nodeIsMissing(node.moduleReference); + } + function isAliasResolvedToValue(symbol: ts.Symbol): boolean { + const target = resolveAlias(symbol); + if (target === unknownSymbol) { + return true; + } + // const enums and modules that contain only const enums are not considered values from the emit perspective + // unless 'preserveConstEnums' option is set to true + return !!(target.flags & SymbolFlags.Value) && + (compilerOptions.preserveConstEnums || !isConstEnumOrConstEnumOnlyModule(target)); + } + function isConstEnumOrConstEnumOnlyModule(s: ts.Symbol): boolean { + return isConstEnumSymbol(s) || !!s.constEnumOnlyModule; + } + function isReferencedAliasDeclaration(node: Node, checkChildren?: boolean): boolean { + if (isAliasSymbolDeclaration(node)) { + const symbol = getSymbolOfNode(node); + if (symbol && getSymbolLinks(symbol).referenced) { + return true; } - if (flags & ModifierFlags.Async) { - return checkGrammarAsyncModifier(node, lastAsync!); + const target = getSymbolLinks(symbol!).target; // TODO: GH#18217 + if (target && getModifierFlags(node) & ModifierFlags.Export && + target.flags & SymbolFlags.Value && (compilerOptions.preserveConstEnums || !isConstEnumOrConstEnumOnlyModule(target))) { + // An `export import ... =` of a value symbol is always considered referenced + return true; } + } + if (checkChildren) { + return !!forEachChild(node, node => isReferencedAliasDeclaration(node, checkChildren)); + } + return false; + } + function isImplementationOfOverload(node: SignatureDeclaration) { + if (nodeIsPresent((node as FunctionLikeDeclaration).body)) { + if (isGetAccessor(node) || isSetAccessor(node)) + return false; // Get or set accessors can never be overload implementations, but can have up to 2 signatures + const symbol = getSymbolOfNode(node); + const signaturesOfSymbol = getSignaturesOfSymbol(symbol); + // If this function body corresponds to function with multiple signature, it is implementation of overload + // e.g.: function foo(a: string): string; + // function foo(a: number): number; + // function foo(a: any) { // This is implementation of the overloads + // return a; + // } + return signaturesOfSymbol.length > 1 || + // If there is single signature for the symbol, it is overload if that signature isn't coming from the node + // e.g.: function foo(a: string): string; + // function foo(a: any) { // This is implementation of the overloads + // return a; + // } + (signaturesOfSymbol.length === 1 && signaturesOfSymbol[0].declaration !== node); + } + return false; + } + function isRequiredInitializedParameter(parameter: ParameterDeclaration | JSDocParameterTag): boolean { + return !!strictNullChecks && + !isOptionalParameter(parameter) && + !isJSDocParameterTag(parameter) && + !!parameter.initializer && + !hasModifier(parameter, ModifierFlags.ParameterPropertyModifier); + } + function isOptionalUninitializedParameterProperty(parameter: ParameterDeclaration) { + return strictNullChecks && + isOptionalParameter(parameter) && + !parameter.initializer && + hasModifier(parameter, ModifierFlags.ParameterPropertyModifier); + } + function isExpandoFunctionDeclaration(node: Declaration): boolean { + const declaration = getParseTreeNode(node, isFunctionDeclaration); + if (!declaration) { return false; } - - /** - * true | false: Early return this value from checkGrammarModifiers. - * undefined: Need to do full checking on the modifiers. - */ - function reportObviousModifierErrors(node: Node): boolean | undefined { - return !node.modifiers - ? false - : shouldReportBadModifier(node) - ? grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here) - : undefined; - } - function shouldReportBadModifier(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.Constructor: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ExportDeclaration: - case SyntaxKind.ExportAssignment: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.Parameter: - return false; - default: - if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { - return false; - } - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - return nodeHasAnyModifiersExcept(node, SyntaxKind.AsyncKeyword); - case SyntaxKind.ClassDeclaration: - return nodeHasAnyModifiersExcept(node, SyntaxKind.AbstractKeyword); - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.VariableStatement: - case SyntaxKind.TypeAliasDeclaration: - return true; - case SyntaxKind.EnumDeclaration: - return nodeHasAnyModifiersExcept(node, SyntaxKind.ConstKeyword); - default: - Debug.fail(); - return false; - } + const symbol = getSymbolOfNode(declaration); + if (!symbol || !(symbol.flags & SymbolFlags.Function)) { + return false; + } + return !!forEachEntry(getExportsOfSymbol(symbol), p => p.flags & SymbolFlags.Value && p.valueDeclaration && isPropertyAccessExpression(p.valueDeclaration)); + } + function getPropertiesOfContainerFunction(node: Declaration): ts.Symbol[] { + const declaration = getParseTreeNode(node, isFunctionDeclaration); + if (!declaration) { + return emptyArray; + } + const symbol = getSymbolOfNode(declaration); + return symbol && getPropertiesOfType(getTypeOfSymbol(symbol)) || emptyArray; + } + function getNodeCheckFlags(node: Node): NodeCheckFlags { + return getNodeLinks(node).flags || 0; + } + function getEnumMemberValue(node: EnumMember): string | number | undefined { + computeEnumMemberValues(node.parent); + return getNodeLinks(node).enumMemberValue; + } + function canHaveConstantValue(node: Node): node is EnumMember | AccessExpression { + switch (node.kind) { + case SyntaxKind.EnumMember: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return true; + } + return false; + } + function getConstantValue(node: EnumMember | AccessExpression): string | number | undefined { + if (node.kind === SyntaxKind.EnumMember) { + return getEnumMemberValue(node); + } + const symbol = getNodeLinks(node).resolvedSymbol; + if (symbol && (symbol.flags & SymbolFlags.EnumMember)) { + // inline property\index accesses only for const enums + const member = (symbol.valueDeclaration as EnumMember); + if (isEnumConst(member.parent)) { + return getEnumMemberValue(member); } } - function nodeHasAnyModifiersExcept(node: Node, allowedModifier: SyntaxKind): boolean { - return node.modifiers!.length > 1 || node.modifiers![0].kind !== allowedModifier; + return undefined; + } + function isFunctionType(type: ts.Type): boolean { + return !!(type.flags & TypeFlags.Object) && getSignaturesOfType(type, SignatureKind.Call).length > 0; + } + function getTypeReferenceSerializationKind(typeNameIn: EntityName, location?: Node): TypeReferenceSerializationKind { + // ensure both `typeName` and `location` are parse tree nodes. + const typeName = getParseTreeNode(typeNameIn, isEntityName); + if (!typeName) + return TypeReferenceSerializationKind.Unknown; + if (location) { + location = getParseTreeNode(location); + if (!location) + return TypeReferenceSerializationKind.Unknown; } - - function checkGrammarAsyncModifier(node: Node, asyncModifier: Node): boolean { - switch (node.kind) { - case SyntaxKind.MethodDeclaration: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return false; + // Resolve the symbol as a value to ensure the type can be reached at runtime during emit. + const valueSymbol = resolveEntityName(typeName, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, location); + // Resolve the symbol as a type so that we can provide a more useful hint for the type serializer. + const typeSymbol = resolveEntityName(typeName, SymbolFlags.Type, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, location); + if (valueSymbol && valueSymbol === typeSymbol) { + const globalPromiseSymbol = getGlobalPromiseConstructorSymbol(/*reportErrors*/ false); + if (globalPromiseSymbol && valueSymbol === globalPromiseSymbol) { + return TypeReferenceSerializationKind.Promise; } - - return grammarErrorOnNode(asyncModifier, Diagnostics._0_modifier_cannot_be_used_here, "async"); + const constructorType = getTypeOfSymbol(valueSymbol); + if (constructorType && isConstructorType(constructorType)) { + return TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue; + } + } + // We might not be able to resolve type symbol so use unknown type in that case (eg error case) + if (!typeSymbol) { + return TypeReferenceSerializationKind.Unknown; + } + const type = getDeclaredTypeOfSymbol(typeSymbol); + if (type === errorType) { + return TypeReferenceSerializationKind.Unknown; + } + else if (type.flags & TypeFlags.AnyOrUnknown) { + return TypeReferenceSerializationKind.ObjectType; + } + else if (isTypeAssignableToKind(type, TypeFlags.Void | TypeFlags.Nullable | TypeFlags.Never)) { + return TypeReferenceSerializationKind.VoidNullableOrNeverType; + } + else if (isTypeAssignableToKind(type, TypeFlags.BooleanLike)) { + return TypeReferenceSerializationKind.BooleanType; + } + else if (isTypeAssignableToKind(type, TypeFlags.NumberLike)) { + return TypeReferenceSerializationKind.NumberLikeType; + } + else if (isTypeAssignableToKind(type, TypeFlags.BigIntLike)) { + return TypeReferenceSerializationKind.BigIntLikeType; + } + else if (isTypeAssignableToKind(type, TypeFlags.StringLike)) { + return TypeReferenceSerializationKind.StringLikeType; + } + else if (isTupleType(type)) { + return TypeReferenceSerializationKind.ArrayLikeType; + } + else if (isTypeAssignableToKind(type, TypeFlags.ESSymbolLike)) { + return TypeReferenceSerializationKind.ESSymbolType; + } + else if (isFunctionType(type)) { + return TypeReferenceSerializationKind.TypeWithCallSignature; + } + else if (isArrayType(type)) { + return TypeReferenceSerializationKind.ArrayLikeType; + } + else { + return TypeReferenceSerializationKind.ObjectType; + } + } + function createTypeOfDeclaration(declarationIn: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean) { + const declaration = getParseTreeNode(declarationIn, isVariableLikeOrAccessor); + if (!declaration) { + return createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; + } + // Get type of the symbol if this is the valid symbol otherwise get type at location + const symbol = getSymbolOfNode(declaration); + let type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature)) + ? getWidenedLiteralType(getTypeOfSymbol(symbol)) + : errorType; + if (type.flags & TypeFlags.UniqueESSymbol && + type.symbol === symbol) { + flags |= NodeBuilderFlags.AllowUniqueESSymbolType; + } + if (addUndefined) { + type = getOptionalType(type); + } + return nodeBuilder.typeToTypeNode(type, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); + } + function createReturnTypeOfSignatureDeclaration(signatureDeclarationIn: SignatureDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker) { + const signatureDeclaration = getParseTreeNode(signatureDeclarationIn, isFunctionLike); + if (!signatureDeclaration) { + return createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; } - - function checkGrammarForDisallowedTrailingComma(list: NodeArray | undefined, diag = Diagnostics.Trailing_comma_not_allowed): boolean { - if (list && list.hasTrailingComma) { - return grammarErrorAtPos(list[0], list.end - ",".length, ",".length, diag); - } - return false; + const signature = getSignatureFromDeclaration(signatureDeclaration); + return nodeBuilder.typeToTypeNode(getReturnTypeOfSignature(signature), enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); + } + function createTypeOfExpression(exprIn: Expression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker) { + const expr = getParseTreeNode(exprIn, isExpression); + if (!expr) { + return createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; } - - function checkGrammarTypeParameterList(typeParameters: NodeArray | undefined, file: SourceFile): boolean { - if (typeParameters && typeParameters.length === 0) { - const start = typeParameters.pos - "<".length; - const end = skipTrivia(file.text, typeParameters.end) + ">".length; - return grammarErrorAtPos(file, start, end - start, Diagnostics.Type_parameter_list_cannot_be_empty); + const type = getWidenedType(getRegularTypeOfExpression(expr)); + return nodeBuilder.typeToTypeNode(type, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); + } + function hasGlobalName(name: string): boolean { + return globals.has(escapeLeadingUnderscores(name)); + } + function getReferencedValueSymbol(reference: Identifier, startInDeclarationContainer?: boolean): ts.Symbol | undefined { + const resolvedSymbol = getNodeLinks(reference).resolvedSymbol; + if (resolvedSymbol) { + return resolvedSymbol; + } + let location: Node = reference; + if (startInDeclarationContainer) { + // When resolving the name of a declaration as a value, we need to start resolution + // at a point outside of the declaration. + const parent = reference.parent; + if (isDeclaration(parent) && reference === parent.name) { + location = getDeclarationContainer(parent); } - return false; } - - function checkGrammarParameterList(parameters: NodeArray) { - let seenOptionalParameter = false; - const parameterCount = parameters.length; - - for (let i = 0; i < parameterCount; i++) { - const parameter = parameters[i]; - if (parameter.dotDotDotToken) { - if (i !== (parameterCount - 1)) { - return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); - } - if (!(parameter.flags & NodeFlags.Ambient)) { // Allow `...foo,` in ambient declarations; see GH#23070 - checkGrammarForDisallowedTrailingComma(parameters, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); - } - - if (parameter.questionToken) { - return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_rest_parameter_cannot_be_optional); - } - - if (parameter.initializer) { - return grammarErrorOnNode(parameter.name, Diagnostics.A_rest_parameter_cannot_have_an_initializer); - } + return resolveName(location, reference.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); + } + function getReferencedValueDeclaration(referenceIn: Identifier): Declaration | undefined { + if (!isGeneratedIdentifier(referenceIn)) { + const reference = getParseTreeNode(referenceIn, isIdentifier); + if (reference) { + const symbol = getReferencedValueSymbol(reference); + if (symbol) { + return getExportSymbolOfValueSymbolIfExported(symbol).valueDeclaration; } - else if (parameter.questionToken) { - seenOptionalParameter = true; - - if (parameter.initializer) { - return grammarErrorOnNode(parameter.name, Diagnostics.Parameter_cannot_have_question_mark_and_initializer); - } + } + } + return undefined; + } + function isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean { + if (isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node)) { + return isFreshLiteralType(getTypeOfSymbol(getSymbolOfNode(node))); + } + return false; + } + function literalTypeToNode(type: FreshableType, enclosing: Node, tracker: SymbolTracker): Expression { + const enumResult = type.flags & TypeFlags.EnumLiteral ? nodeBuilder.symbolToExpression(type.symbol, SymbolFlags.Value, enclosing, /*flags*/ undefined, tracker) + : type === trueType ? createTrue() : type === falseType && createFalse(); + return enumResult || createLiteral((type as LiteralType).value); + } + function createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, tracker: SymbolTracker) { + const type = getTypeOfSymbol(getSymbolOfNode(node)); + return literalTypeToNode((type), node, tracker); + } + function createResolver(): EmitResolver { + // this variable and functions that use it are deliberately moved here from the outer scope + // to avoid scope pollution + const resolvedTypeReferenceDirectives = host.getResolvedTypeReferenceDirectives(); + let fileToDirective: ts.Map; + if (resolvedTypeReferenceDirectives) { + // populate reverse mapping: file path -> type reference directive that was resolved to this file + fileToDirective = createMap(); + resolvedTypeReferenceDirectives.forEach((resolvedDirective, key) => { + if (!resolvedDirective || !resolvedDirective.resolvedFileName) { + return; } - else if (seenOptionalParameter && !parameter.initializer) { - return grammarErrorOnNode(parameter.name, Diagnostics.A_required_parameter_cannot_follow_an_optional_parameter); + const file = host.getSourceFile(resolvedDirective.resolvedFileName)!; + // Add the transitive closure of path references loaded by this file (as long as they are not) + // part of an existing type reference. + addReferencedFilesToTypeDirective(file, key); + }); + } + return { + getReferencedExportContainer, + getReferencedImportDeclaration, + getReferencedDeclarationWithCollidingName, + isDeclarationWithCollidingName, + isValueAliasDeclaration: node => { + node = getParseTreeNode(node); + // Synthesized nodes are always treated like values. + return node ? isValueAliasDeclaration(node) : true; + }, + hasGlobalName, + isReferencedAliasDeclaration: (node, checkChildren?) => { + node = getParseTreeNode(node); + // Synthesized nodes are always treated as referenced. + return node ? isReferencedAliasDeclaration(node, checkChildren) : true; + }, + getNodeCheckFlags: node => { + node = getParseTreeNode(node); + return node ? getNodeCheckFlags(node) : 0; + }, + isTopLevelValueImportEqualsWithEntityName, + isDeclarationVisible, + isImplementationOfOverload, + isRequiredInitializedParameter, + isOptionalUninitializedParameterProperty, + isExpandoFunctionDeclaration, + getPropertiesOfContainerFunction, + createTypeOfDeclaration, + createReturnTypeOfSignatureDeclaration, + createTypeOfExpression, + createLiteralConstValue, + isSymbolAccessible, + isEntityNameVisible, + getConstantValue: nodeIn => { + const node = getParseTreeNode(nodeIn, canHaveConstantValue); + return node ? getConstantValue(node) : undefined; + }, + collectLinkedAliases, + getReferencedValueDeclaration, + getTypeReferenceSerializationKind, + isOptionalParameter, + moduleExportsSomeValue, + isArgumentsLocalBinding, + getExternalModuleFileFromDeclaration, + getTypeReferenceDirectivesForEntityName, + getTypeReferenceDirectivesForSymbol, + isLiteralConstDeclaration, + isLateBound: (nodeIn: Declaration): nodeIn is LateBoundDeclaration => { + const node = getParseTreeNode(nodeIn, isDeclaration); + const symbol = node && getSymbolOfNode(node); + return !!(symbol && getCheckFlags(symbol) & CheckFlags.Late); + }, + getJsxFactoryEntity: location => location ? (getJsxNamespace(location), (getSourceFileOfNode(location).localJsxFactory || _jsxFactoryEntity)) : _jsxFactoryEntity, + getAllAccessorDeclarations(accessor: AccessorDeclaration): AllAccessorDeclarations { + accessor = (getParseTreeNode(accessor, isGetOrSetAccessorDeclaration)!); // TODO: GH#18217 + const otherKind = accessor.kind === SyntaxKind.SetAccessor ? SyntaxKind.GetAccessor : SyntaxKind.SetAccessor; + const otherAccessor = getDeclarationOfKind(getSymbolOfNode(accessor), otherKind); + const firstAccessor = otherAccessor && (otherAccessor.pos < accessor.pos) ? otherAccessor : accessor; + const secondAccessor = otherAccessor && (otherAccessor.pos < accessor.pos) ? accessor : otherAccessor; + const setAccessor = accessor.kind === SyntaxKind.SetAccessor ? accessor : otherAccessor as SetAccessorDeclaration; + const getAccessor = accessor.kind === SyntaxKind.GetAccessor ? accessor : otherAccessor as GetAccessorDeclaration; + return { + firstAccessor, + secondAccessor, + setAccessor, + getAccessor + }; + }, + getSymbolOfExternalModuleSpecifier: moduleName => resolveExternalModuleNameWorker(moduleName, moduleName, /*moduleNotFoundError*/ undefined), + isBindingCapturedByNode: (node, decl) => { + const parseNode = getParseTreeNode(node); + const parseDecl = getParseTreeNode(decl); + return !!parseNode && !!parseDecl && (isVariableDeclaration(parseDecl) || isBindingElement(parseDecl)) && isBindingCapturedByNode(parseNode, parseDecl); + }, + getDeclarationStatementsForSourceFile: (node, flags, tracker, bundled) => { + const n = (getParseTreeNode(node) as SourceFile); + Debug.assert(n && n.kind === SyntaxKind.SourceFile, "Non-sourcefile node passed into getDeclarationsForSourceFile"); + const sym = getSymbolOfNode(node); + if (!sym) { + return !node.locals ? [] : nodeBuilder.symbolTableToDeclarationStatements(node.locals, node, flags, tracker, bundled); } + return !sym.exports ? [] : nodeBuilder.symbolTableToDeclarationStatements(sym.exports, node, flags, tracker, bundled); } + }; + function isInHeritageClause(node: PropertyAccessEntityNameExpression) { + return node.parent && node.parent.kind === SyntaxKind.ExpressionWithTypeArguments && node.parent.parent && node.parent.parent.kind === SyntaxKind.HeritageClause; } - - function getNonSimpleParameters(parameters: readonly ParameterDeclaration[]): readonly ParameterDeclaration[] { - return filter(parameters, parameter => !!parameter.initializer || isBindingPattern(parameter.name) || isRestParameter(parameter)); + // defined here to avoid outer scope pollution + function getTypeReferenceDirectivesForEntityName(node: EntityNameOrEntityNameExpression): string[] | undefined { + // program does not have any files with type reference directives - bail out + if (!fileToDirective) { + return undefined; + } + // property access can only be used as values, or types when within an expression with type arguments inside a heritage clause + // qualified names can only be used as types\namespaces + // identifiers are treated as values only if they appear in type queries + let meaning = SymbolFlags.Type | SymbolFlags.Namespace; + if ((node.kind === SyntaxKind.Identifier && isInTypeQuery(node)) || (node.kind === SyntaxKind.PropertyAccessExpression && !isInHeritageClause(node))) { + meaning = SymbolFlags.Value | SymbolFlags.ExportValue; + } + const symbol = resolveEntityName(node, meaning, /*ignoreErrors*/ true); + return symbol && symbol !== unknownSymbol ? getTypeReferenceDirectivesForSymbol(symbol, meaning) : undefined; } - - function checkGrammarForUseStrictSimpleParameterList(node: FunctionLikeDeclaration): boolean { - if (languageVersion >= ScriptTarget.ES2016) { - const useStrictDirective = node.body && isBlock(node.body) && findUseStrictPrologue(node.body.statements); - if (useStrictDirective) { - const nonSimpleParameters = getNonSimpleParameters(node.parameters); - if (length(nonSimpleParameters)) { - forEach(nonSimpleParameters, parameter => { - addRelatedInfo( - error(parameter, Diagnostics.This_parameter_is_not_allowed_with_use_strict_directive), - createDiagnosticForNode(useStrictDirective, Diagnostics.use_strict_directive_used_here) - ); - }); - - const diagnostics = nonSimpleParameters.map((parameter, index) => ( - index === 0 ? createDiagnosticForNode(parameter, Diagnostics.Non_simple_parameter_declared_here) : createDiagnosticForNode(parameter, Diagnostics.and_here) - )) as [DiagnosticWithLocation, ...DiagnosticWithLocation[]]; - addRelatedInfo(error(useStrictDirective, Diagnostics.use_strict_directive_cannot_be_used_with_non_simple_parameter_list), ...diagnostics); - return true; + // defined here to avoid outer scope pollution + function getTypeReferenceDirectivesForSymbol(symbol: ts.Symbol, meaning?: SymbolFlags): string[] | undefined { + // program does not have any files with type reference directives - bail out + if (!fileToDirective) { + return undefined; + } + if (!isSymbolFromTypeDeclarationFile(symbol)) { + return undefined; + } + // check what declarations in the symbol can contribute to the target meaning + let typeReferenceDirectives: string[] | undefined; + for (const decl of symbol.declarations) { + // check meaning of the local symbol to see if declaration needs to be analyzed further + if (decl.symbol && decl.symbol.flags & meaning!) { + const file = getSourceFileOfNode(decl); + const typeReferenceDirective = fileToDirective.get(file.path); + if (typeReferenceDirective) { + (typeReferenceDirectives || (typeReferenceDirectives = [])).push(typeReferenceDirective); + } + else { + // found at least one entry that does not originate from type reference directive + return undefined; } } } - return false; - } - - function checkGrammarFunctionLikeDeclaration(node: FunctionLikeDeclaration | MethodSignature): boolean { - // Prevent cascading error by short-circuit - const file = getSourceFileOfNode(node); - return checkGrammarDecoratorsAndModifiers(node) || checkGrammarTypeParameterList(node.typeParameters, file) || - checkGrammarParameterList(node.parameters) || checkGrammarArrowFunction(node, file) || - (isFunctionLikeDeclaration(node) && checkGrammarForUseStrictSimpleParameterList(node)); - } - - function checkGrammarClassLikeDeclaration(node: ClassLikeDeclaration): boolean { - const file = getSourceFileOfNode(node); - return checkGrammarClassDeclarationHeritageClauses(node) || checkGrammarTypeParameterList(node.typeParameters, file); + return typeReferenceDirectives; } - - function checkGrammarArrowFunction(node: Node, file: SourceFile): boolean { - if (!isArrowFunction(node)) { + function isSymbolFromTypeDeclarationFile(symbol: ts.Symbol): boolean { + // bail out if symbol does not have associated declarations (i.e. this is transient symbol created for property in binding pattern) + if (!symbol.declarations) { return false; } - - const { equalsGreaterThanToken } = node; - const startLine = getLineAndCharacterOfPosition(file, equalsGreaterThanToken.pos).line; - const endLine = getLineAndCharacterOfPosition(file, equalsGreaterThanToken.end).line; - return startLine !== endLine && grammarErrorOnNode(equalsGreaterThanToken, Diagnostics.Line_terminator_not_permitted_before_arrow); - } - - function checkGrammarIndexSignatureParameters(node: SignatureDeclaration): boolean { - const parameter = node.parameters[0]; - if (node.parameters.length !== 1) { - if (parameter) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_must_have_exactly_one_parameter); + // walk the parent chain for symbols to make sure that top level parent symbol is in the global scope + // external modules cannot define or contribute to type declaration files + let current = symbol; + while (true) { + const parent = getParentOfSymbol(current); + if (parent) { + current = parent; } else { - return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_exactly_one_parameter); + break; } } - if (parameter.dotDotDotToken) { - return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.An_index_signature_cannot_have_a_rest_parameter); - } - if (hasModifiers(parameter)) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_cannot_have_an_accessibility_modifier); - } - if (parameter.questionToken) { - return grammarErrorOnNode(parameter.questionToken, Diagnostics.An_index_signature_parameter_cannot_have_a_question_mark); - } - if (parameter.initializer) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_cannot_have_an_initializer); - } - if (!parameter.type) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_must_have_a_type_annotation); + if (current.valueDeclaration && current.valueDeclaration.kind === SyntaxKind.SourceFile && current.flags & SymbolFlags.ValueModule) { + return false; } - if (parameter.type.kind !== SyntaxKind.StringKeyword && parameter.type.kind !== SyntaxKind.NumberKeyword) { - const type = getTypeFromTypeNode(parameter.type); - - if (type.flags & TypeFlags.String || type.flags & TypeFlags.Number) { - return grammarErrorOnNode(parameter.name, - Diagnostics.An_index_signature_parameter_type_cannot_be_a_type_alias_Consider_writing_0_Colon_1_Colon_2_instead, - getTextOfNode(parameter.name), - typeToString(type), - typeToString(node.type ? getTypeFromTypeNode(node.type) : anyType)); - } - - if (type.flags & TypeFlags.Union && allTypesAssignableToKind(type, TypeFlags.StringOrNumberLiteral, /*strict*/ true)) { - return grammarErrorOnNode(parameter.name, - Diagnostics.An_index_signature_parameter_type_cannot_be_a_union_type_Consider_using_a_mapped_object_type_instead); + // check that at least one declaration of top level symbol originates from type declaration file + for (const decl of symbol.declarations) { + const file = getSourceFileOfNode(decl); + if (fileToDirective.has(file.path)) { + return true; } - - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_must_be_either_string_or_number); - } - if (!node.type) { - return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_a_type_annotation); } return false; } - - function checkGrammarIndexSignature(node: SignatureDeclaration) { - // Prevent cascading error by short-circuit - return checkGrammarDecoratorsAndModifiers(node) || checkGrammarIndexSignatureParameters(node); - } - - function checkGrammarForAtLeastOneTypeArgument(node: Node, typeArguments: NodeArray | undefined): boolean { - if (typeArguments && typeArguments.length === 0) { - const sourceFile = getSourceFileOfNode(node); - const start = typeArguments.pos - "<".length; - const end = skipTrivia(sourceFile.text, typeArguments.end) + ">".length; - return grammarErrorAtPos(sourceFile, start, end - start, Diagnostics.Type_argument_list_cannot_be_empty); + function addReferencedFilesToTypeDirective(file: SourceFile, key: string) { + if (fileToDirective.has(file.path)) + return; + fileToDirective.set(file.path, key); + for (const { fileName } of file.referencedFiles) { + const resolvedFile = resolveTripleslashReference(fileName, file.originalFileName); + const referencedFile = host.getSourceFile(resolvedFile); + if (referencedFile) { + addReferencedFilesToTypeDirective(referencedFile, key); + } } - return false; } - - function checkGrammarTypeArguments(node: Node, typeArguments: NodeArray | undefined): boolean { - return checkGrammarForDisallowedTrailingComma(typeArguments) || - checkGrammarForAtLeastOneTypeArgument(node, typeArguments); + } + function getExternalModuleFileFromDeclaration(declaration: AnyImportOrReExport | ModuleDeclaration | ImportTypeNode): SourceFile | undefined { + const specifier = declaration.kind === SyntaxKind.ModuleDeclaration ? tryCast(declaration.name, isStringLiteral) : getExternalModuleName(declaration); + const moduleSymbol = resolveExternalModuleNameWorker(specifier!, specifier!, /*moduleNotFoundError*/ undefined); // TODO: GH#18217 + if (!moduleSymbol) { + return undefined; } - - function checkGrammarTaggedTemplateChain(node: TaggedTemplateExpression): boolean { - if (node.questionDotToken || node.flags & NodeFlags.OptionalChain) { - return grammarErrorOnNode(node.template, Diagnostics.Tagged_template_expressions_are_not_permitted_in_an_optional_chain); - } - return false; + return getDeclarationOfKind(moduleSymbol, SyntaxKind.SourceFile); + } + function initializeTypeChecker() { + // Bind all source files and propagate errors + for (const file of host.getSourceFiles()) { + bindSourceFile(file, compilerOptions); } - - function checkGrammarForOmittedArgument(args: NodeArray | undefined): boolean { - if (args) { - for (const arg of args) { - if (arg.kind === SyntaxKind.OmittedExpression) { - return grammarErrorAtPos(arg, arg.pos, 0, Diagnostics.Argument_expression_expected); + amalgamatedDuplicates = createMap(); + // Initialize global symbol table + let augmentations: (readonly (StringLiteral | Identifier)[])[] | undefined; + for (const file of host.getSourceFiles()) { + if (file.redirectInfo) { + continue; + } + if (!isExternalOrCommonJsModule(file)) { + // It is an error for a non-external-module (i.e. script) to declare its own `globalThis`. + // We can't use `builtinGlobals` for this due to synthetic expando-namespace generation in JS files. + const fileGlobalThisSymbol = file.locals!.get(("globalThis" as __String)); + if (fileGlobalThisSymbol) { + for (const declaration of fileGlobalThisSymbol.declarations) { + diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0, "globalThis")); } } + mergeSymbolTable(globals, file.locals!); } - return false; - } - - function checkGrammarArguments(args: NodeArray | undefined): boolean { - return checkGrammarForOmittedArgument(args); - } - - function checkGrammarHeritageClause(node: HeritageClause): boolean { - const types = node.types; - if (checkGrammarForDisallowedTrailingComma(types)) { - return true; + if (file.jsGlobalAugmentations) { + mergeSymbolTable(globals, file.jsGlobalAugmentations); + } + if (file.patternAmbientModules && file.patternAmbientModules.length) { + patternAmbientModules = concatenate(patternAmbientModules, file.patternAmbientModules); } - if (types && types.length === 0) { - const listType = tokenToString(node.token); - return grammarErrorAtPos(node, types.pos, 0, Diagnostics._0_list_cannot_be_empty, listType); + if (file.moduleAugmentations.length) { + (augmentations || (augmentations = [])).push(file.moduleAugmentations); + } + if (file.symbol && file.symbol.globalExports) { + // Merge in UMD exports with first-in-wins semantics (see #9771) + const source = file.symbol.globalExports; + source.forEach((sourceSymbol, id) => { + if (!globals.has(id)) { + globals.set(id, sourceSymbol); + } + }); } - return some(types, checkGrammarExpressionWithTypeArguments); } - - function checkGrammarExpressionWithTypeArguments(node: ExpressionWithTypeArguments) { - return checkGrammarTypeArguments(node, node.typeArguments); + // We do global augmentations separately from module augmentations (and before creating global types) because they + // 1. Affect global types. We won't have the correct global types until global augmentations are merged. Also, + // 2. Module augmentation instantiation requires creating the type of a module, which, in turn, can require + // checking for an export or property on the module (if export=) which, in turn, can fall back to the + // apparent type of the module - either globalObjectType or globalFunctionType - which wouldn't exist if we + // did module augmentations prior to finalizing the global types. + if (augmentations) { + // merge _global_ module augmentations. + // this needs to be done after global symbol table is initialized to make sure that all ambient modules are indexed + for (const list of augmentations) { + for (const augmentation of list) { + if (!isGlobalScopeAugmentation((augmentation.parent as ModuleDeclaration))) + continue; + mergeModuleAugmentation(augmentation); + } + } + } + // Setup global builtins + addToSymbolTable(globals, builtinGlobals, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0); + getSymbolLinks(undefinedSymbol).type = undefinedWideningType; + getSymbolLinks(argumentsSymbol).type = getGlobalType(("IArguments" as __String), /*arity*/ 0, /*reportErrors*/ true); + getSymbolLinks(unknownSymbol).type = errorType; + getSymbolLinks(globalThisSymbol).type = createObjectType(ObjectFlags.Anonymous, globalThisSymbol); + // Initialize special types + globalArrayType = getGlobalType(("Array" as __String), /*arity*/ 1, /*reportErrors*/ true); + globalObjectType = getGlobalType(("Object" as __String), /*arity*/ 0, /*reportErrors*/ true); + globalFunctionType = getGlobalType(("Function" as __String), /*arity*/ 0, /*reportErrors*/ true); + globalCallableFunctionType = strictBindCallApply && getGlobalType(("CallableFunction" as __String), /*arity*/ 0, /*reportErrors*/ true) || globalFunctionType; + globalNewableFunctionType = strictBindCallApply && getGlobalType(("NewableFunction" as __String), /*arity*/ 0, /*reportErrors*/ true) || globalFunctionType; + globalStringType = getGlobalType(("String" as __String), /*arity*/ 0, /*reportErrors*/ true); + globalNumberType = getGlobalType(("Number" as __String), /*arity*/ 0, /*reportErrors*/ true); + globalBooleanType = getGlobalType(("Boolean" as __String), /*arity*/ 0, /*reportErrors*/ true); + globalRegExpType = getGlobalType(("RegExp" as __String), /*arity*/ 0, /*reportErrors*/ true); + anyArrayType = createArrayType(anyType); + autoArrayType = createArrayType(autoType); + if (autoArrayType === emptyObjectType) { + // autoArrayType is used as a marker, so even if global Array type is not defined, it needs to be a unique type + autoArrayType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); + } + globalReadonlyArrayType = (getGlobalTypeOrUndefined(("ReadonlyArray" as __String), /*arity*/ 1)) || globalArrayType; + anyReadonlyArrayType = globalReadonlyArrayType ? createTypeFromGenericGlobalType(globalReadonlyArrayType, [anyType]) : anyArrayType; + globalThisType = (getGlobalTypeOrUndefined(("ThisType" as __String), /*arity*/ 1)); + if (augmentations) { + // merge _nonglobal_ module augmentations. + // this needs to be done after global symbol table is initialized to make sure that all ambient modules are indexed + for (const list of augmentations) { + for (const augmentation of list) { + if (isGlobalScopeAugmentation((augmentation.parent as ModuleDeclaration))) + continue; + mergeModuleAugmentation(augmentation); + } + } } - - function checkGrammarClassDeclarationHeritageClauses(node: ClassLikeDeclaration) { - let seenExtendsClause = false; - let seenImplementsClause = false; - - if (!checkGrammarDecoratorsAndModifiers(node) && node.heritageClauses) { - for (const heritageClause of node.heritageClauses) { - if (heritageClause.token === SyntaxKind.ExtendsKeyword) { - if (seenExtendsClause) { - return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_already_seen); - } - - if (seenImplementsClause) { - return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_must_precede_implements_clause); - } - - if (heritageClause.types.length > 1) { - return grammarErrorOnFirstToken(heritageClause.types[1], Diagnostics.Classes_can_only_extend_a_single_class); - } - - seenExtendsClause = true; + amalgamatedDuplicates.forEach(({ firstFile, secondFile, conflictingSymbols }) => { + // If not many things conflict, issue individual errors + if (conflictingSymbols.size < 8) { + conflictingSymbols.forEach(({ isBlockScoped, firstFileLocations, secondFileLocations }, symbolName) => { + const message = isBlockScoped ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 : Diagnostics.Duplicate_identifier_0; + for (const node of firstFileLocations) { + addDuplicateDeclarationError(node, message, symbolName, secondFileLocations); } - else { - Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); - if (seenImplementsClause) { - return grammarErrorOnFirstToken(heritageClause, Diagnostics.implements_clause_already_seen); - } - - seenImplementsClause = true; + for (const node of secondFileLocations) { + addDuplicateDeclarationError(node, message, symbolName, firstFileLocations); } - - // Grammar checking heritageClause inside class declaration - checkGrammarHeritageClause(heritageClause); - } + }); } - } - - function checkGrammarInterfaceDeclaration(node: InterfaceDeclaration) { - let seenExtendsClause = false; - - if (node.heritageClauses) { - for (const heritageClause of node.heritageClauses) { - if (heritageClause.token === SyntaxKind.ExtendsKeyword) { - if (seenExtendsClause) { - return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_already_seen); + else { + // Otherwise issue top-level error since the files appear very identical in terms of what they contain + const list = arrayFrom(conflictingSymbols.keys()).join(", "); + diagnostics.add(addRelatedInfo(createDiagnosticForNode(firstFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list), createDiagnosticForNode(secondFile, Diagnostics.Conflicts_are_in_this_file))); + diagnostics.add(addRelatedInfo(createDiagnosticForNode(secondFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list), createDiagnosticForNode(firstFile, Diagnostics.Conflicts_are_in_this_file))); + } + }); + amalgamatedDuplicates = undefined; + } + function checkExternalEmitHelpers(location: Node, helpers: ExternalEmitHelpers) { + if ((requestedExternalEmitHelpers & helpers) !== helpers && compilerOptions.importHelpers) { + const sourceFile = getSourceFileOfNode(location); + if (isEffectiveExternalModule(sourceFile, compilerOptions) && !(location.flags & NodeFlags.Ambient)) { + const helpersModule = resolveHelpersModule(sourceFile, location); + if (helpersModule !== unknownSymbol) { + const uncheckedHelpers = helpers & ~requestedExternalEmitHelpers; + for (let helper = ExternalEmitHelpers.FirstEmitHelper; helper <= ExternalEmitHelpers.LastEmitHelper; helper <<= 1) { + if (uncheckedHelpers & helper) { + const name = getHelperName(helper); + const symbol = getSymbol((helpersModule.exports!), escapeLeadingUnderscores(name), SymbolFlags.Value); + if (!symbol) { + error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_which_does_not_exist_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name); + } } - - seenExtendsClause = true; - } - else { - Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); - return grammarErrorOnFirstToken(heritageClause, Diagnostics.Interface_declaration_cannot_have_implements_clause); } - - // Grammar checking heritageClause inside class declaration - checkGrammarHeritageClause(heritageClause); } + requestedExternalEmitHelpers |= helpers; } + } + } + function getHelperName(helper: ExternalEmitHelpers) { + switch (helper) { + case ExternalEmitHelpers.Extends: return "__extends"; + case ExternalEmitHelpers.Assign: return "__assign"; + case ExternalEmitHelpers.Rest: return "__rest"; + case ExternalEmitHelpers.Decorate: return "__decorate"; + case ExternalEmitHelpers.Metadata: return "__metadata"; + case ExternalEmitHelpers.Param: return "__param"; + case ExternalEmitHelpers.Awaiter: return "__awaiter"; + case ExternalEmitHelpers.Generator: return "__generator"; + case ExternalEmitHelpers.Values: return "__values"; + case ExternalEmitHelpers.Read: return "__read"; + case ExternalEmitHelpers.Spread: return "__spread"; + case ExternalEmitHelpers.SpreadArrays: return "__spreadArrays"; + case ExternalEmitHelpers.Await: return "__await"; + case ExternalEmitHelpers.AsyncGenerator: return "__asyncGenerator"; + case ExternalEmitHelpers.AsyncDelegator: return "__asyncDelegator"; + case ExternalEmitHelpers.AsyncValues: return "__asyncValues"; + case ExternalEmitHelpers.ExportStar: return "__exportStar"; + case ExternalEmitHelpers.MakeTemplateObject: return "__makeTemplateObject"; + default: return Debug.fail("Unrecognized helper"); + } + } + function resolveHelpersModule(node: SourceFile, errorNode: Node) { + if (!externalHelpersModule) { + externalHelpersModule = resolveExternalModule(node, externalHelpersModuleNameText, Diagnostics.This_syntax_requires_an_imported_helper_but_module_0_cannot_be_found, errorNode) || unknownSymbol; + } + return externalHelpersModule; + } + // GRAMMAR CHECKING + function checkGrammarDecoratorsAndModifiers(node: Node): boolean { + return checkGrammarDecorators(node) || checkGrammarModifiers(node); + } + function checkGrammarDecorators(node: Node): boolean { + if (!node.decorators) { return false; } - - function checkGrammarComputedPropertyName(node: Node): boolean { - // If node is not a computedPropertyName, just skip the grammar checking - if (node.kind !== SyntaxKind.ComputedPropertyName) { - return false; + if (!nodeCanBeDecorated(node, node.parent, node.parent.parent)) { + if (node.kind === SyntaxKind.MethodDeclaration && !nodeIsPresent((node).body)) { + return grammarErrorOnFirstToken(node, Diagnostics.A_decorator_can_only_decorate_a_method_implementation_not_an_overload); } - - const computedPropertyName = node; - if (computedPropertyName.expression.kind === SyntaxKind.BinaryExpression && (computedPropertyName.expression).operatorToken.kind === SyntaxKind.CommaToken) { - return grammarErrorOnNode(computedPropertyName.expression, Diagnostics.A_comma_expression_is_not_allowed_in_a_computed_property_name); + else { + return grammarErrorOnFirstToken(node, Diagnostics.Decorators_are_not_valid_here); } - return false; } - - function checkGrammarForGenerator(node: FunctionLikeDeclaration) { - if (node.asteriskToken) { - Debug.assert( - node.kind === SyntaxKind.FunctionDeclaration || - node.kind === SyntaxKind.FunctionExpression || - node.kind === SyntaxKind.MethodDeclaration); - if (node.flags & NodeFlags.Ambient) { - return grammarErrorOnNode(node.asteriskToken!, Diagnostics.Generators_are_not_allowed_in_an_ambient_context); - } - if (!node.body) { - return grammarErrorOnNode(node.asteriskToken!, Diagnostics.An_overload_signature_cannot_be_declared_as_a_generator); - } + else if (node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor) { + const accessors = getAllAccessorDeclarations((node.parent).members, (node)); + if (accessors.firstAccessor.decorators && node === accessors.secondAccessor) { + return grammarErrorOnFirstToken(node, Diagnostics.Decorators_cannot_be_applied_to_multiple_get_Slashset_accessors_of_the_same_name); } } - - function checkGrammarForInvalidQuestionMark(questionToken: QuestionToken | undefined, message: DiagnosticMessage): boolean { - return !!questionToken && grammarErrorOnNode(questionToken, message); - } - - function checkGrammarForInvalidExclamationToken(exclamationToken: ExclamationToken | undefined, message: DiagnosticMessage): boolean { - return !!exclamationToken && grammarErrorOnNode(exclamationToken, message); + return false; + } + function checkGrammarModifiers(node: Node): boolean { + const quickResult = reportObviousModifierErrors(node); + if (quickResult !== undefined) { + return quickResult; } - - function checkGrammarObjectLiteralExpression(node: ObjectLiteralExpression, inDestructuring: boolean) { - const seen = createUnderscoreEscapedMap(); - - for (const prop of node.properties) { - if (prop.kind === SyntaxKind.SpreadAssignment) { - if (inDestructuring) { - // a rest property cannot be destructured any further - const expression = skipParentheses(prop.expression); - if (isArrayLiteralExpression(expression) || isObjectLiteralExpression(expression)) { - return grammarErrorOnNode(prop.expression, Diagnostics.A_rest_element_cannot_contain_a_binding_pattern); - } - } - continue; + let lastStatic: Node | undefined, lastDeclare: Node | undefined, lastAsync: Node | undefined, lastReadonly: Node | undefined; + let flags = ModifierFlags.None; + for (const modifier of node.modifiers!) { + if (modifier.kind !== SyntaxKind.ReadonlyKeyword) { + if (node.kind === SyntaxKind.PropertySignature || node.kind === SyntaxKind.MethodSignature) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_type_member, tokenToString(modifier.kind)); } - const name = prop.name; - if (name.kind === SyntaxKind.ComputedPropertyName) { - // If the name is not a ComputedPropertyName, the grammar checking will skip it - checkGrammarComputedPropertyName(name); + if (node.kind === SyntaxKind.IndexSignature) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_an_index_signature, tokenToString(modifier.kind)); } - - if (prop.kind === SyntaxKind.ShorthandPropertyAssignment && !inDestructuring && prop.objectAssignmentInitializer) { - // having objectAssignmentInitializer is only valid in ObjectAssignmentPattern - // outside of destructuring it is a syntax error - return grammarErrorOnNode(prop.equalsToken!, Diagnostics.can_only_be_used_in_an_object_literal_property_inside_a_destructuring_assignment); - } - - // Modifiers are never allowed on properties except for 'async' on a method declaration - if (prop.modifiers) { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - for (const mod of prop.modifiers!) { // TODO: GH#19955 - if (mod.kind !== SyntaxKind.AsyncKeyword || prop.kind !== SyntaxKind.MethodDeclaration) { - grammarErrorOnNode(mod, Diagnostics._0_modifier_cannot_be_used_here, getTextOfNode(mod)); - } + } + switch (modifier.kind) { + case SyntaxKind.ConstKeyword: + if (node.kind !== SyntaxKind.EnumDeclaration) { + return grammarErrorOnNode(node, Diagnostics.A_class_member_cannot_have_the_0_keyword, tokenToString(SyntaxKind.ConstKeyword)); } - } - - // ECMA-262 11.1.5 Object Initializer - // If previous is not undefined then throw a SyntaxError exception if any of the following conditions are true - // a.This production is contained in strict code and IsDataDescriptor(previous) is true and - // IsDataDescriptor(propId.descriptor) is true. - // b.IsDataDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true. - // c.IsAccessorDescriptor(previous) is true and IsDataDescriptor(propId.descriptor) is true. - // d.IsAccessorDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true - // and either both previous and propId.descriptor have[[Get]] fields or both previous and propId.descriptor have[[Set]] fields - let currentKind: DeclarationMeaning; - switch (prop.kind) { - case SyntaxKind.ShorthandPropertyAssignment: - checkGrammarForInvalidExclamationToken(prop.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context); - // falls through - case SyntaxKind.PropertyAssignment: - // Grammar checking for computedPropertyName and shorthandPropertyAssignment - checkGrammarForInvalidQuestionMark(prop.questionToken, Diagnostics.An_object_member_cannot_be_declared_optional); - if (name.kind === SyntaxKind.NumericLiteral) { - checkGrammarNumericLiteral(name); - } - currentKind = DeclarationMeaning.PropertyAssignment; - break; - case SyntaxKind.MethodDeclaration: - currentKind = DeclarationMeaning.Method; - break; - case SyntaxKind.GetAccessor: - currentKind = DeclarationMeaning.GetAccessor; - break; - case SyntaxKind.SetAccessor: - currentKind = DeclarationMeaning.SetAccessor; - break; - default: - throw Debug.assertNever(prop, "Unexpected syntax kind:" + (prop).kind); - } - - const effectiveName = getPropertyNameForPropertyNameNode(name); - if (effectiveName === undefined) { - continue; - } - - const existingKind = seen.get(effectiveName); - if (!existingKind) { - seen.set(effectiveName, currentKind); - } - else { - if ((currentKind & DeclarationMeaning.PropertyAssignmentOrMethod) && (existingKind & DeclarationMeaning.PropertyAssignmentOrMethod)) { - grammarErrorOnNode(name, Diagnostics.Duplicate_identifier_0, getTextOfNode(name)); + break; + case SyntaxKind.PublicKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.PrivateKeyword: + const text = visibilityToString(modifierToFlag(modifier.kind)); + if (flags & ModifierFlags.AccessibilityModifier) { + return grammarErrorOnNode(modifier, Diagnostics.Accessibility_modifier_already_seen); } - else if ((currentKind & DeclarationMeaning.GetOrSetAccessor) && (existingKind & DeclarationMeaning.GetOrSetAccessor)) { - if (existingKind !== DeclarationMeaning.GetOrSetAccessor && currentKind !== existingKind) { - seen.set(effectiveName, currentKind | existingKind); + else if (flags & ModifierFlags.Static) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "static"); + } + else if (flags & ModifierFlags.Readonly) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "readonly"); + } + else if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "async"); + } + else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_module_or_namespace_element, text); + } + else if (flags & ModifierFlags.Abstract) { + if (modifier.kind === SyntaxKind.PrivateKeyword) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, text, "abstract"); } else { - return grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_multiple_get_Slashset_accessors_with_the_same_name); + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "abstract"); } } - else { - return grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_property_and_accessor_with_the_same_name); + flags |= modifierToFlag(modifier.kind); + break; + case SyntaxKind.StaticKeyword: + if (flags & ModifierFlags.Static) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "static"); } - } - } - } - - function checkGrammarJsxElement(node: JsxOpeningLikeElement) { - checkGrammarTypeArguments(node, node.typeArguments); - const seen = createUnderscoreEscapedMap(); - - for (const attr of node.attributes.properties) { - if (attr.kind === SyntaxKind.JsxSpreadAttribute) { - continue; - } - - const { name, initializer } = attr; - if (!seen.get(name.escapedText)) { - seen.set(name.escapedText, true); - } - else { - return grammarErrorOnNode(name, Diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name); - } - - if (initializer && initializer.kind === SyntaxKind.JsxExpression && !initializer.expression) { - return grammarErrorOnNode(initializer, Diagnostics.JSX_attributes_must_only_be_assigned_a_non_empty_expression); - } - } - } - - function checkGrammarJsxExpression(node: JsxExpression) { - if (node.expression && isCommaSequence(node.expression)) { - return grammarErrorOnNode(node.expression, Diagnostics.JSX_expressions_may_not_use_the_comma_operator_Did_you_mean_to_write_an_array); - } - } - - function checkGrammarForInOrForOfStatement(forInOrOfStatement: ForInOrOfStatement): boolean { - if (checkGrammarStatementInAmbientContext(forInOrOfStatement)) { - return true; - } - - if (forInOrOfStatement.kind === SyntaxKind.ForOfStatement && forInOrOfStatement.awaitModifier) { - if ((forInOrOfStatement.flags & NodeFlags.AwaitContext) === NodeFlags.None) { - // use of 'for-await-of' in non-async function - const sourceFile = getSourceFileOfNode(forInOrOfStatement); - if (!hasParseDiagnostics(sourceFile)) { - const diagnostic = createDiagnosticForNode(forInOrOfStatement.awaitModifier, Diagnostics.A_for_await_of_statement_is_only_allowed_within_an_async_function_or_async_generator); - const func = getContainingFunction(forInOrOfStatement); - if (func && func.kind !== SyntaxKind.Constructor) { - Debug.assert((getFunctionFlags(func) & FunctionFlags.Async) === 0, "Enclosing function should never be an async function."); - const relatedInfo = createDiagnosticForNode(func, Diagnostics.Did_you_mean_to_mark_this_function_as_async); - addRelatedInfo(diagnostic, relatedInfo); - } - diagnostics.add(diagnostic); - return true; + else if (flags & ModifierFlags.Readonly) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "readonly"); + } + else if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "async"); + } + else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_module_or_namespace_element, "static"); + } + else if (node.kind === SyntaxKind.Parameter) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "static"); + } + else if (flags & ModifierFlags.Abstract) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "abstract"); + } + flags |= ModifierFlags.Static; + lastStatic = modifier; + break; + case SyntaxKind.ReadonlyKeyword: + if (flags & ModifierFlags.Readonly) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "readonly"); + } + else if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature && node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.Parameter) { + // If node.kind === SyntaxKind.Parameter, checkParameter report an error if it's not a parameter property. + return grammarErrorOnNode(modifier, Diagnostics.readonly_modifier_can_only_appear_on_a_property_declaration_or_index_signature); + } + flags |= ModifierFlags.Readonly; + lastReadonly = modifier; + break; + case SyntaxKind.ExportKeyword: + if (flags & ModifierFlags.Export) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "export"); + } + else if (flags & ModifierFlags.Ambient) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "declare"); + } + else if (flags & ModifierFlags.Abstract) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "abstract"); + } + else if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "async"); + } + else if (isClassLike(node.parent)) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_class_element, "export"); + } + else if (node.kind === SyntaxKind.Parameter) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "export"); + } + flags |= ModifierFlags.Export; + break; + case SyntaxKind.DefaultKeyword: + const container = node.parent.kind === SyntaxKind.SourceFile ? node.parent : node.parent.parent; + if (container.kind === SyntaxKind.ModuleDeclaration && !isAmbientModule(container)) { + return grammarErrorOnNode(modifier, Diagnostics.A_default_export_can_only_be_used_in_an_ECMAScript_style_module); + } + flags |= ModifierFlags.Default; + break; + case SyntaxKind.DeclareKeyword: + if (flags & ModifierFlags.Ambient) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "declare"); + } + else if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async"); + } + else if (isClassLike(node.parent) && !isPropertyDeclaration(node)) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_class_element, "declare"); + } + else if (node.kind === SyntaxKind.Parameter) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "declare"); + } + else if ((node.parent.flags & NodeFlags.Ambient) && node.parent.kind === SyntaxKind.ModuleBlock) { + return grammarErrorOnNode(modifier, Diagnostics.A_declare_modifier_cannot_be_used_in_an_already_ambient_context); } - return false; - } - } - - if (forInOrOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList) { - const variableList = forInOrOfStatement.initializer; - if (!checkGrammarVariableDeclarationList(variableList)) { - const declarations = variableList.declarations; - - // declarations.length can be zero if there is an error in variable declaration in for-of or for-in - // See http://www.ecma-international.org/ecma-262/6.0/#sec-for-in-and-for-of-statements for details - // For example: - // var let = 10; - // for (let of [1,2,3]) {} // this is invalid ES6 syntax - // for (let in [1,2,3]) {} // this is invalid ES6 syntax - // We will then want to skip on grammar checking on variableList declaration - if (!declarations.length) { - return false; + flags |= ModifierFlags.Ambient; + lastDeclare = modifier; + break; + case SyntaxKind.AbstractKeyword: + if (flags & ModifierFlags.Abstract) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "abstract"); } - - if (declarations.length > 1) { - const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement - ? Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_in_statement - : Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_of_statement; - return grammarErrorOnFirstToken(variableList.declarations[1], diagnostic); + if (node.kind !== SyntaxKind.ClassDeclaration) { + if (node.kind !== SyntaxKind.MethodDeclaration && + node.kind !== SyntaxKind.PropertyDeclaration && + node.kind !== SyntaxKind.GetAccessor && + node.kind !== SyntaxKind.SetAccessor) { + return grammarErrorOnNode(modifier, Diagnostics.abstract_modifier_can_only_appear_on_a_class_method_or_property_declaration); + } + if (!(node.parent.kind === SyntaxKind.ClassDeclaration && hasModifier(node.parent, ModifierFlags.Abstract))) { + return grammarErrorOnNode(modifier, Diagnostics.Abstract_methods_can_only_appear_within_an_abstract_class); + } + if (flags & ModifierFlags.Static) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "abstract"); + } + if (flags & ModifierFlags.Private) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "private", "abstract"); + } } - const firstDeclaration = declarations[0]; - - if (firstDeclaration.initializer) { - const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement - ? Diagnostics.The_variable_declaration_of_a_for_in_statement_cannot_have_an_initializer - : Diagnostics.The_variable_declaration_of_a_for_of_statement_cannot_have_an_initializer; - return grammarErrorOnNode(firstDeclaration.name, diagnostic); + flags |= ModifierFlags.Abstract; + break; + case SyntaxKind.AsyncKeyword: + if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "async"); } - if (firstDeclaration.type) { - const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement - ? Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_use_a_type_annotation - : Diagnostics.The_left_hand_side_of_a_for_of_statement_cannot_use_a_type_annotation; - return grammarErrorOnNode(firstDeclaration, diagnostic); + else if (flags & ModifierFlags.Ambient || node.parent.flags & NodeFlags.Ambient) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async"); } - } + else if (node.kind === SyntaxKind.Parameter) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "async"); + } + flags |= ModifierFlags.Async; + lastAsync = modifier; + break; } - - return false; } - - function checkGrammarAccessor(accessor: AccessorDeclaration): boolean { - if (!(accessor.flags & NodeFlags.Ambient)) { - if (languageVersion < ScriptTarget.ES5) { - return grammarErrorOnNode(accessor.name, Diagnostics.Accessors_are_only_available_when_targeting_ECMAScript_5_and_higher); - } - if (accessor.body === undefined && !hasModifier(accessor, ModifierFlags.Abstract)) { - return grammarErrorAtPos(accessor, accessor.end - 1, ";".length, Diagnostics._0_expected, "{"); - } + if (node.kind === SyntaxKind.Constructor) { + if (flags & ModifierFlags.Static) { + return grammarErrorOnNode((lastStatic!), Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "static"); } - if (accessor.body && hasModifier(accessor, ModifierFlags.Abstract)) { - return grammarErrorOnNode(accessor, Diagnostics.An_abstract_accessor_cannot_have_an_implementation); + if (flags & ModifierFlags.Abstract) { + return grammarErrorOnNode((lastStatic!), Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "abstract"); // TODO: GH#18217 } - if (accessor.typeParameters) { - return grammarErrorOnNode(accessor.name, Diagnostics.An_accessor_cannot_have_type_parameters); + else if (flags & ModifierFlags.Async) { + return grammarErrorOnNode((lastAsync!), Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "async"); } - if (!doesAccessorHaveCorrectParameterCount(accessor)) { - return grammarErrorOnNode(accessor.name, - accessor.kind === SyntaxKind.GetAccessor ? - Diagnostics.A_get_accessor_cannot_have_parameters : - Diagnostics.A_set_accessor_must_have_exactly_one_parameter); + else if (flags & ModifierFlags.Readonly) { + return grammarErrorOnNode((lastReadonly!), Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "readonly"); } - if (accessor.kind === SyntaxKind.SetAccessor) { - if (accessor.type) { - return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_cannot_have_a_return_type_annotation); + return false; + } + else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && flags & ModifierFlags.Ambient) { + return grammarErrorOnNode((lastDeclare!), Diagnostics.A_0_modifier_cannot_be_used_with_an_import_declaration, "declare"); + } + else if (node.kind === SyntaxKind.Parameter && (flags & ModifierFlags.ParameterPropertyModifier) && isBindingPattern((node).name)) { + return grammarErrorOnNode(node, Diagnostics.A_parameter_property_may_not_be_declared_using_a_binding_pattern); + } + else if (node.kind === SyntaxKind.Parameter && (flags & ModifierFlags.ParameterPropertyModifier) && (node).dotDotDotToken) { + return grammarErrorOnNode(node, Diagnostics.A_parameter_property_cannot_be_declared_using_a_rest_parameter); + } + if (flags & ModifierFlags.Async) { + return checkGrammarAsyncModifier(node, lastAsync!); + } + return false; + } + /** + * true | false: Early return this value from checkGrammarModifiers. + * undefined: Need to do full checking on the modifiers. + */ + function reportObviousModifierErrors(node: Node): boolean | undefined { + return !node.modifiers + ? false + : shouldReportBadModifier(node) + ? grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here) + : undefined; + } + function shouldReportBadModifier(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.Constructor: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ExportDeclaration: + case SyntaxKind.ExportAssignment: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.Parameter: + return false; + default: + if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { + return false; + } + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + return nodeHasAnyModifiersExcept(node, SyntaxKind.AsyncKeyword); + case SyntaxKind.ClassDeclaration: + return nodeHasAnyModifiersExcept(node, SyntaxKind.AbstractKeyword); + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.VariableStatement: + case SyntaxKind.TypeAliasDeclaration: + return true; + case SyntaxKind.EnumDeclaration: + return nodeHasAnyModifiersExcept(node, SyntaxKind.ConstKeyword); + default: + Debug.fail(); + return false; + } + } + } + function nodeHasAnyModifiersExcept(node: Node, allowedModifier: SyntaxKind): boolean { + return node.modifiers!.length > 1 || node.modifiers![0].kind !== allowedModifier; + } + function checkGrammarAsyncModifier(node: Node, asyncModifier: Node): boolean { + switch (node.kind) { + case SyntaxKind.MethodDeclaration: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return false; + } + return grammarErrorOnNode(asyncModifier, Diagnostics._0_modifier_cannot_be_used_here, "async"); + } + function checkGrammarForDisallowedTrailingComma(list: NodeArray | undefined, diag = Diagnostics.Trailing_comma_not_allowed): boolean { + if (list && list.hasTrailingComma) { + return grammarErrorAtPos(list[0], list.end - ",".length, ",".length, diag); + } + return false; + } + function checkGrammarTypeParameterList(typeParameters: NodeArray | undefined, file: SourceFile): boolean { + if (typeParameters && typeParameters.length === 0) { + const start = typeParameters.pos - "<".length; + const end = skipTrivia(file.text, typeParameters.end) + ">".length; + return grammarErrorAtPos(file, start, end - start, Diagnostics.Type_parameter_list_cannot_be_empty); + } + return false; + } + function checkGrammarParameterList(parameters: NodeArray) { + let seenOptionalParameter = false; + const parameterCount = parameters.length; + for (let i = 0; i < parameterCount; i++) { + const parameter = parameters[i]; + if (parameter.dotDotDotToken) { + if (i !== (parameterCount - 1)) { + return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); } - const parameter = Debug.assertDefined(getSetAccessorValueParameter(accessor), "Return value does not match parameter count assertion."); - if (parameter.dotDotDotToken) { - return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_set_accessor_cannot_have_rest_parameter); + if (!(parameter.flags & NodeFlags.Ambient)) { // Allow `...foo,` in ambient declarations; see GH#23070 + checkGrammarForDisallowedTrailingComma(parameters, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); } if (parameter.questionToken) { - return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_set_accessor_cannot_have_an_optional_parameter); + return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_rest_parameter_cannot_be_optional); } if (parameter.initializer) { - return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_parameter_cannot_have_an_initializer); + return grammarErrorOnNode(parameter.name, Diagnostics.A_rest_parameter_cannot_have_an_initializer); } } - return false; - } - - /** Does the accessor have the right number of parameters? - * A get accessor has no parameters or a single `this` parameter. - * A set accessor has one parameter or a `this` parameter and one more parameter. - */ - function doesAccessorHaveCorrectParameterCount(accessor: AccessorDeclaration) { - return getAccessorThisParameter(accessor) || accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 0 : 1); - } - - function getAccessorThisParameter(accessor: AccessorDeclaration): ParameterDeclaration | undefined { - if (accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 1 : 2)) { - return getThisParameter(accessor); + else if (parameter.questionToken) { + seenOptionalParameter = true; + if (parameter.initializer) { + return grammarErrorOnNode(parameter.name, Diagnostics.Parameter_cannot_have_question_mark_and_initializer); + } + } + else if (seenOptionalParameter && !parameter.initializer) { + return grammarErrorOnNode(parameter.name, Diagnostics.A_required_parameter_cannot_follow_an_optional_parameter); } } - - function checkGrammarTypeOperatorNode(node: TypeOperatorNode) { - if (node.operator === SyntaxKind.UniqueKeyword) { - if (node.type.kind !== SyntaxKind.SymbolKeyword) { - return grammarErrorOnNode(node.type, Diagnostics._0_expected, tokenToString(SyntaxKind.SymbolKeyword)); - } - - const parent = walkUpParenthesizedTypes(node.parent); - switch (parent.kind) { - case SyntaxKind.VariableDeclaration: - const decl = parent as VariableDeclaration; - if (decl.name.kind !== SyntaxKind.Identifier) { - return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_may_not_be_used_on_a_variable_declaration_with_a_binding_name); - } - if (!isVariableDeclarationInVariableStatement(decl)) { - return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_only_allowed_on_variables_in_a_variable_statement); - } - if (!(decl.parent.flags & NodeFlags.Const)) { - return grammarErrorOnNode((parent).name, Diagnostics.A_variable_whose_type_is_a_unique_symbol_type_must_be_const); - } - break; - - case SyntaxKind.PropertyDeclaration: - if (!hasModifier(parent, ModifierFlags.Static) || - !hasModifier(parent, ModifierFlags.Readonly)) { - return grammarErrorOnNode((parent).name, Diagnostics.A_property_of_a_class_whose_type_is_a_unique_symbol_type_must_be_both_static_and_readonly); - } - break; - - case SyntaxKind.PropertySignature: - if (!hasModifier(parent, ModifierFlags.Readonly)) { - return grammarErrorOnNode((parent).name, Diagnostics.A_property_of_an_interface_or_type_literal_whose_type_is_a_unique_symbol_type_must_be_readonly); - } - break; - - default: - return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_not_allowed_here); + } + function getNonSimpleParameters(parameters: readonly ParameterDeclaration[]): readonly ParameterDeclaration[] { + return filter(parameters, parameter => !!parameter.initializer || isBindingPattern(parameter.name) || isRestParameter(parameter)); + } + function checkGrammarForUseStrictSimpleParameterList(node: FunctionLikeDeclaration): boolean { + if (languageVersion >= ScriptTarget.ES2016) { + const useStrictDirective = node.body && isBlock(node.body) && findUseStrictPrologue(node.body.statements); + if (useStrictDirective) { + const nonSimpleParameters = getNonSimpleParameters(node.parameters); + if (length(nonSimpleParameters)) { + forEach(nonSimpleParameters, parameter => { + addRelatedInfo(error(parameter, Diagnostics.This_parameter_is_not_allowed_with_use_strict_directive), createDiagnosticForNode(useStrictDirective, Diagnostics.use_strict_directive_used_here)); + }); + const diagnostics = (nonSimpleParameters.map((parameter, index) => (index === 0 ? createDiagnosticForNode(parameter, Diagnostics.Non_simple_parameter_declared_here) : createDiagnosticForNode(parameter, Diagnostics.and_here))) as [DiagnosticWithLocation, ...DiagnosticWithLocation[]]); + addRelatedInfo(error(useStrictDirective, Diagnostics.use_strict_directive_cannot_be_used_with_non_simple_parameter_list), ...diagnostics); + return true; } } - else if (node.operator === SyntaxKind.ReadonlyKeyword) { - if (node.type.kind !== SyntaxKind.ArrayType && node.type.kind !== SyntaxKind.TupleType) { - return grammarErrorOnFirstToken(node, Diagnostics.readonly_type_modifier_is_only_permitted_on_array_and_tuple_literal_types, tokenToString(SyntaxKind.SymbolKeyword)); - } + } + return false; + } + function checkGrammarFunctionLikeDeclaration(node: FunctionLikeDeclaration | MethodSignature): boolean { + // Prevent cascading error by short-circuit + const file = getSourceFileOfNode(node); + return checkGrammarDecoratorsAndModifiers(node) || checkGrammarTypeParameterList(node.typeParameters, file) || + checkGrammarParameterList(node.parameters) || checkGrammarArrowFunction(node, file) || + (isFunctionLikeDeclaration(node) && checkGrammarForUseStrictSimpleParameterList(node)); + } + function checkGrammarClassLikeDeclaration(node: ClassLikeDeclaration): boolean { + const file = getSourceFileOfNode(node); + return checkGrammarClassDeclarationHeritageClauses(node) || checkGrammarTypeParameterList(node.typeParameters, file); + } + function checkGrammarArrowFunction(node: Node, file: SourceFile): boolean { + if (!isArrowFunction(node)) { + return false; + } + const { equalsGreaterThanToken } = node; + const startLine = getLineAndCharacterOfPosition(file, equalsGreaterThanToken.pos).line; + const endLine = getLineAndCharacterOfPosition(file, equalsGreaterThanToken.end).line; + return startLine !== endLine && grammarErrorOnNode(equalsGreaterThanToken, Diagnostics.Line_terminator_not_permitted_before_arrow); + } + function checkGrammarIndexSignatureParameters(node: SignatureDeclaration): boolean { + const parameter = node.parameters[0]; + if (node.parameters.length !== 1) { + if (parameter) { + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_must_have_exactly_one_parameter); + } + else { + return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_exactly_one_parameter); } } - - function checkGrammarForInvalidDynamicName(node: DeclarationName, message: DiagnosticMessage) { - if (isNonBindableDynamicName(node)) { - return grammarErrorOnNode(node, message); + if (parameter.dotDotDotToken) { + return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.An_index_signature_cannot_have_a_rest_parameter); + } + if (hasModifiers(parameter)) { + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_cannot_have_an_accessibility_modifier); + } + if (parameter.questionToken) { + return grammarErrorOnNode(parameter.questionToken, Diagnostics.An_index_signature_parameter_cannot_have_a_question_mark); + } + if (parameter.initializer) { + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_cannot_have_an_initializer); + } + if (!parameter.type) { + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_must_have_a_type_annotation); + } + if (parameter.type.kind !== SyntaxKind.StringKeyword && parameter.type.kind !== SyntaxKind.NumberKeyword) { + const type = getTypeFromTypeNode(parameter.type); + if (type.flags & TypeFlags.String || type.flags & TypeFlags.Number) { + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_cannot_be_a_type_alias_Consider_writing_0_Colon_1_Colon_2_instead, getTextOfNode(parameter.name), typeToString(type), typeToString(node.type ? getTypeFromTypeNode(node.type) : anyType)); + } + if (type.flags & TypeFlags.Union && allTypesAssignableToKind(type, TypeFlags.StringOrNumberLiteral, /*strict*/ true)) { + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_cannot_be_a_union_type_Consider_using_a_mapped_object_type_instead); } + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_must_be_either_string_or_number); } - - function checkGrammarMethod(node: MethodDeclaration | MethodSignature) { - if (checkGrammarFunctionLikeDeclaration(node)) { - return true; + if (!node.type) { + return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_a_type_annotation); + } + return false; + } + function checkGrammarIndexSignature(node: SignatureDeclaration) { + // Prevent cascading error by short-circuit + return checkGrammarDecoratorsAndModifiers(node) || checkGrammarIndexSignatureParameters(node); + } + function checkGrammarForAtLeastOneTypeArgument(node: Node, typeArguments: NodeArray | undefined): boolean { + if (typeArguments && typeArguments.length === 0) { + const sourceFile = getSourceFileOfNode(node); + const start = typeArguments.pos - "<".length; + const end = skipTrivia(sourceFile.text, typeArguments.end) + ">".length; + return grammarErrorAtPos(sourceFile, start, end - start, Diagnostics.Type_argument_list_cannot_be_empty); + } + return false; + } + function checkGrammarTypeArguments(node: Node, typeArguments: NodeArray | undefined): boolean { + return checkGrammarForDisallowedTrailingComma(typeArguments) || + checkGrammarForAtLeastOneTypeArgument(node, typeArguments); + } + function checkGrammarTaggedTemplateChain(node: TaggedTemplateExpression): boolean { + if (node.questionDotToken || node.flags & NodeFlags.OptionalChain) { + return grammarErrorOnNode(node.template, Diagnostics.Tagged_template_expressions_are_not_permitted_in_an_optional_chain); + } + return false; + } + function checkGrammarForOmittedArgument(args: NodeArray | undefined): boolean { + if (args) { + for (const arg of args) { + if (arg.kind === SyntaxKind.OmittedExpression) { + return grammarErrorAtPos(arg, arg.pos, 0, Diagnostics.Argument_expression_expected); + } } - - if (node.kind === SyntaxKind.MethodDeclaration) { - if (node.parent.kind === SyntaxKind.ObjectLiteralExpression) { - // We only disallow modifier on a method declaration if it is a property of object-literal-expression - if (node.modifiers && !(node.modifiers.length === 1 && first(node.modifiers).kind === SyntaxKind.AsyncKeyword)) { - return grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here); - } - else if (checkGrammarForInvalidQuestionMark(node.questionToken, Diagnostics.An_object_member_cannot_be_declared_optional)) { - return true; + } + return false; + } + function checkGrammarArguments(args: NodeArray | undefined): boolean { + return checkGrammarForOmittedArgument(args); + } + function checkGrammarHeritageClause(node: HeritageClause): boolean { + const types = node.types; + if (checkGrammarForDisallowedTrailingComma(types)) { + return true; + } + if (types && types.length === 0) { + const listType = tokenToString(node.token); + return grammarErrorAtPos(node, types.pos, 0, Diagnostics._0_list_cannot_be_empty, listType); + } + return some(types, checkGrammarExpressionWithTypeArguments); + } + function checkGrammarExpressionWithTypeArguments(node: ExpressionWithTypeArguments) { + return checkGrammarTypeArguments(node, node.typeArguments); + } + function checkGrammarClassDeclarationHeritageClauses(node: ClassLikeDeclaration) { + let seenExtendsClause = false; + let seenImplementsClause = false; + if (!checkGrammarDecoratorsAndModifiers(node) && node.heritageClauses) { + for (const heritageClause of node.heritageClauses) { + if (heritageClause.token === SyntaxKind.ExtendsKeyword) { + if (seenExtendsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_already_seen); } - else if (checkGrammarForInvalidExclamationToken(node.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context)) { - return true; + if (seenImplementsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_must_precede_implements_clause); } - else if (node.body === undefined) { - return grammarErrorAtPos(node, node.end - 1, ";".length, Diagnostics._0_expected, "{"); + if (heritageClause.types.length > 1) { + return grammarErrorOnFirstToken(heritageClause.types[1], Diagnostics.Classes_can_only_extend_a_single_class); } + seenExtendsClause = true; } - if (checkGrammarForGenerator(node)) { - return true; + else { + Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); + if (seenImplementsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics.implements_clause_already_seen); + } + seenImplementsClause = true; } + // Grammar checking heritageClause inside class declaration + checkGrammarHeritageClause(heritageClause); } - - if (isClassLike(node.parent)) { - // Technically, computed properties in ambient contexts is disallowed - // for property declarations and accessors too, not just methods. - // However, property declarations disallow computed names in general, - // and accessors are not allowed in ambient contexts in general, - // so this error only really matters for methods. - if (node.flags & NodeFlags.Ambient) { - return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_ambient_context_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + } + } + function checkGrammarInterfaceDeclaration(node: InterfaceDeclaration) { + let seenExtendsClause = false; + if (node.heritageClauses) { + for (const heritageClause of node.heritageClauses) { + if (heritageClause.token === SyntaxKind.ExtendsKeyword) { + if (seenExtendsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_already_seen); + } + seenExtendsClause = true; } - else if (node.kind === SyntaxKind.MethodDeclaration && !node.body) { - return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_method_overload_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + else { + Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); + return grammarErrorOnFirstToken(heritageClause, Diagnostics.Interface_declaration_cannot_have_implements_clause); } + // Grammar checking heritageClause inside class declaration + checkGrammarHeritageClause(heritageClause); } - else if (node.parent.kind === SyntaxKind.InterfaceDeclaration) { - return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_interface_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + } + return false; + } + function checkGrammarComputedPropertyName(node: Node): boolean { + // If node is not a computedPropertyName, just skip the grammar checking + if (node.kind !== SyntaxKind.ComputedPropertyName) { + return false; + } + const computedPropertyName = (node); + if (computedPropertyName.expression.kind === SyntaxKind.BinaryExpression && (computedPropertyName.expression).operatorToken.kind === SyntaxKind.CommaToken) { + return grammarErrorOnNode(computedPropertyName.expression, Diagnostics.A_comma_expression_is_not_allowed_in_a_computed_property_name); + } + return false; + } + function checkGrammarForGenerator(node: FunctionLikeDeclaration) { + if (node.asteriskToken) { + Debug.assert(node.kind === SyntaxKind.FunctionDeclaration || + node.kind === SyntaxKind.FunctionExpression || + node.kind === SyntaxKind.MethodDeclaration); + if (node.flags & NodeFlags.Ambient) { + return grammarErrorOnNode((node.asteriskToken!), Diagnostics.Generators_are_not_allowed_in_an_ambient_context); } - else if (node.parent.kind === SyntaxKind.TypeLiteral) { - return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_type_literal_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + if (!node.body) { + return grammarErrorOnNode((node.asteriskToken!), Diagnostics.An_overload_signature_cannot_be_declared_as_a_generator); } } - - function checkGrammarBreakOrContinueStatement(node: BreakOrContinueStatement): boolean { - let current: Node = node; - while (current) { - if (isFunctionLike(current)) { - return grammarErrorOnNode(node, Diagnostics.Jump_target_cannot_cross_function_boundary); - } - - switch (current.kind) { - case SyntaxKind.LabeledStatement: - if (node.label && (current).label.escapedText === node.label.escapedText) { - // found matching label - verify that label usage is correct - // continue can only target labels that are on iteration statements - const isMisplacedContinueLabel = node.kind === SyntaxKind.ContinueStatement - && !isIterationStatement((current).statement, /*lookInLabeledStatement*/ true); - - if (isMisplacedContinueLabel) { - return grammarErrorOnNode(node, Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement); - } - - return false; - } - break; - case SyntaxKind.SwitchStatement: - if (node.kind === SyntaxKind.BreakStatement && !node.label) { - // unlabeled break within switch statement - ok - return false; - } - break; - default: - if (isIterationStatement(current, /*lookInLabeledStatement*/ false) && !node.label) { - // unlabeled break or continue within iteration statement - ok - return false; - } - break; - } - - current = current.parent; + } + function checkGrammarForInvalidQuestionMark(questionToken: QuestionToken | undefined, message: DiagnosticMessage): boolean { + return !!questionToken && grammarErrorOnNode(questionToken, message); + } + function checkGrammarForInvalidExclamationToken(exclamationToken: ExclamationToken | undefined, message: DiagnosticMessage): boolean { + return !!exclamationToken && grammarErrorOnNode(exclamationToken, message); + } + function checkGrammarObjectLiteralExpression(node: ObjectLiteralExpression, inDestructuring: boolean) { + const seen = createUnderscoreEscapedMap(); + for (const prop of node.properties) { + if (prop.kind === SyntaxKind.SpreadAssignment) { + if (inDestructuring) { + // a rest property cannot be destructured any further + const expression = skipParentheses(prop.expression); + if (isArrayLiteralExpression(expression) || isObjectLiteralExpression(expression)) { + return grammarErrorOnNode(prop.expression, Diagnostics.A_rest_element_cannot_contain_a_binding_pattern); + } + } + continue; + } + const name = prop.name; + if (name.kind === SyntaxKind.ComputedPropertyName) { + // If the name is not a ComputedPropertyName, the grammar checking will skip it + checkGrammarComputedPropertyName(name); + } + if (prop.kind === SyntaxKind.ShorthandPropertyAssignment && !inDestructuring && prop.objectAssignmentInitializer) { + // having objectAssignmentInitializer is only valid in ObjectAssignmentPattern + // outside of destructuring it is a syntax error + return grammarErrorOnNode((prop.equalsToken!), Diagnostics.can_only_be_used_in_an_object_literal_property_inside_a_destructuring_assignment); + } + // Modifiers are never allowed on properties except for 'async' on a method declaration + if (prop.modifiers) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + for (const mod of prop.modifiers!) { // TODO: GH#19955 + if (mod.kind !== SyntaxKind.AsyncKeyword || prop.kind !== SyntaxKind.MethodDeclaration) { + grammarErrorOnNode(mod, Diagnostics._0_modifier_cannot_be_used_here, getTextOfNode(mod)); + } + } + } + // ECMA-262 11.1.5 Object Initializer + // If previous is not undefined then throw a SyntaxError exception if any of the following conditions are true + // a.This production is contained in strict code and IsDataDescriptor(previous) is true and + // IsDataDescriptor(propId.descriptor) is true. + // b.IsDataDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true. + // c.IsAccessorDescriptor(previous) is true and IsDataDescriptor(propId.descriptor) is true. + // d.IsAccessorDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true + // and either both previous and propId.descriptor have[[Get]] fields or both previous and propId.descriptor have[[Set]] fields + let currentKind: DeclarationMeaning; + switch (prop.kind) { + case SyntaxKind.ShorthandPropertyAssignment: + checkGrammarForInvalidExclamationToken(prop.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context); + // falls through + case SyntaxKind.PropertyAssignment: + // Grammar checking for computedPropertyName and shorthandPropertyAssignment + checkGrammarForInvalidQuestionMark(prop.questionToken, Diagnostics.An_object_member_cannot_be_declared_optional); + if (name.kind === SyntaxKind.NumericLiteral) { + checkGrammarNumericLiteral(name); + } + currentKind = DeclarationMeaning.PropertyAssignment; + break; + case SyntaxKind.MethodDeclaration: + currentKind = DeclarationMeaning.Method; + break; + case SyntaxKind.GetAccessor: + currentKind = DeclarationMeaning.GetAccessor; + break; + case SyntaxKind.SetAccessor: + currentKind = DeclarationMeaning.SetAccessor; + break; + default: + throw Debug.assertNever(prop, "Unexpected syntax kind:" + (prop).kind); } - - if (node.label) { - const message = node.kind === SyntaxKind.BreakStatement - ? Diagnostics.A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement - : Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement; - - return grammarErrorOnNode(node, message); + const effectiveName = getPropertyNameForPropertyNameNode(name); + if (effectiveName === undefined) { + continue; } - else { - const message = node.kind === SyntaxKind.BreakStatement - ? Diagnostics.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement - : Diagnostics.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement; - return grammarErrorOnNode(node, message); + const existingKind = seen.get(effectiveName); + if (!existingKind) { + seen.set(effectiveName, currentKind); } - } - - function checkGrammarBindingElement(node: BindingElement) { - if (node.dotDotDotToken) { - const elements = node.parent.elements; - if (node !== last(elements)) { - return grammarErrorOnNode(node, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); + else { + if ((currentKind & DeclarationMeaning.PropertyAssignmentOrMethod) && (existingKind & DeclarationMeaning.PropertyAssignmentOrMethod)) { + grammarErrorOnNode(name, Diagnostics.Duplicate_identifier_0, getTextOfNode(name)); } - checkGrammarForDisallowedTrailingComma(elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); - - if (node.propertyName) { - return grammarErrorOnNode(node.name, Diagnostics.A_rest_element_cannot_have_a_property_name); + else if ((currentKind & DeclarationMeaning.GetOrSetAccessor) && (existingKind & DeclarationMeaning.GetOrSetAccessor)) { + if (existingKind !== DeclarationMeaning.GetOrSetAccessor && currentKind !== existingKind) { + seen.set(effectiveName, currentKind | existingKind); + } + else { + return grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_multiple_get_Slashset_accessors_with_the_same_name); + } } - - if (node.initializer) { - // Error on equals token which immediately precedes the initializer - return grammarErrorAtPos(node, node.initializer.pos - 1, 1, Diagnostics.A_rest_element_cannot_have_an_initializer); + else { + return grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_property_and_accessor_with_the_same_name); } } } - - function isStringOrNumberLiteralExpression(expr: Expression) { - return isStringOrNumericLiteralLike(expr) || - expr.kind === SyntaxKind.PrefixUnaryExpression && (expr).operator === SyntaxKind.MinusToken && - (expr).operand.kind === SyntaxKind.NumericLiteral; + } + function checkGrammarJsxElement(node: JsxOpeningLikeElement) { + checkGrammarTypeArguments(node, node.typeArguments); + const seen = createUnderscoreEscapedMap(); + for (const attr of node.attributes.properties) { + if (attr.kind === SyntaxKind.JsxSpreadAttribute) { + continue; + } + const { name, initializer } = attr; + if (!seen.get(name.escapedText)) { + seen.set(name.escapedText, true); + } + else { + return grammarErrorOnNode(name, Diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name); + } + if (initializer && initializer.kind === SyntaxKind.JsxExpression && !initializer.expression) { + return grammarErrorOnNode(initializer, Diagnostics.JSX_attributes_must_only_be_assigned_a_non_empty_expression); + } } - - function isBigIntLiteralExpression(expr: Expression) { - return expr.kind === SyntaxKind.BigIntLiteral || - expr.kind === SyntaxKind.PrefixUnaryExpression && (expr).operator === SyntaxKind.MinusToken && - (expr).operand.kind === SyntaxKind.BigIntLiteral; + } + function checkGrammarJsxExpression(node: JsxExpression) { + if (node.expression && isCommaSequence(node.expression)) { + return grammarErrorOnNode(node.expression, Diagnostics.JSX_expressions_may_not_use_the_comma_operator_Did_you_mean_to_write_an_array); } - - function isSimpleLiteralEnumReference(expr: Expression) { - if ( - (isPropertyAccessExpression(expr) || (isElementAccessExpression(expr) && isStringOrNumberLiteralExpression(expr.argumentExpression))) && - isEntityNameExpression(expr.expression) - ) return !!(checkExpressionCached(expr).flags & TypeFlags.EnumLiteral); + } + function checkGrammarForInOrForOfStatement(forInOrOfStatement: ForInOrOfStatement): boolean { + if (checkGrammarStatementInAmbientContext(forInOrOfStatement)) { + return true; } - - function checkAmbientInitializer(node: VariableDeclaration | PropertyDeclaration | PropertySignature) { - const {initializer} = node; - if (initializer) { - const isInvalidInitializer = !( - isStringOrNumberLiteralExpression(initializer) || - isSimpleLiteralEnumReference(initializer) || - initializer.kind === SyntaxKind.TrueKeyword || initializer.kind === SyntaxKind.FalseKeyword || - isBigIntLiteralExpression(initializer) - ); - const isConstOrReadonly = isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node); - if (isConstOrReadonly && !node.type) { - if (isInvalidInitializer) { - return grammarErrorOnNode(initializer, Diagnostics.A_const_initializer_in_an_ambient_context_must_be_a_string_or_numeric_literal_or_literal_enum_reference); - } - } - else { - return grammarErrorOnNode(initializer, Diagnostics.Initializers_are_not_allowed_in_ambient_contexts); - } - if (!isConstOrReadonly || isInvalidInitializer) { - return grammarErrorOnNode(initializer, Diagnostics.Initializers_are_not_allowed_in_ambient_contexts); + if (forInOrOfStatement.kind === SyntaxKind.ForOfStatement && forInOrOfStatement.awaitModifier) { + if ((forInOrOfStatement.flags & NodeFlags.AwaitContext) === NodeFlags.None) { + // use of 'for-await-of' in non-async function + const sourceFile = getSourceFileOfNode(forInOrOfStatement); + if (!hasParseDiagnostics(sourceFile)) { + const diagnostic = createDiagnosticForNode(forInOrOfStatement.awaitModifier, Diagnostics.A_for_await_of_statement_is_only_allowed_within_an_async_function_or_async_generator); + const func = getContainingFunction(forInOrOfStatement); + if (func && func.kind !== SyntaxKind.Constructor) { + Debug.assert((getFunctionFlags(func) & FunctionFlags.Async) === 0, "Enclosing function should never be an async function."); + const relatedInfo = createDiagnosticForNode(func, Diagnostics.Did_you_mean_to_mark_this_function_as_async); + addRelatedInfo(diagnostic, relatedInfo); + } + diagnostics.add(diagnostic); + return true; } + return false; } } - - function checkGrammarVariableDeclaration(node: VariableDeclaration) { - if (node.parent.parent.kind !== SyntaxKind.ForInStatement && node.parent.parent.kind !== SyntaxKind.ForOfStatement) { - if (node.flags & NodeFlags.Ambient) { - checkAmbientInitializer(node); + if (forInOrOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList) { + const variableList = (forInOrOfStatement.initializer); + if (!checkGrammarVariableDeclarationList(variableList)) { + const declarations = variableList.declarations; + // declarations.length can be zero if there is an error in variable declaration in for-of or for-in + // See http://www.ecma-international.org/ecma-262/6.0/#sec-for-in-and-for-of-statements for details + // For example: + // var let = 10; + // for (let of [1,2,3]) {} // this is invalid ES6 syntax + // for (let in [1,2,3]) {} // this is invalid ES6 syntax + // We will then want to skip on grammar checking on variableList declaration + if (!declarations.length) { + return false; } - else if (!node.initializer) { - if (isBindingPattern(node.name) && !isBindingPattern(node.parent)) { - return grammarErrorOnNode(node, Diagnostics.A_destructuring_declaration_must_have_an_initializer); - } - if (isVarConst(node)) { - return grammarErrorOnNode(node, Diagnostics.const_declarations_must_be_initialized); - } + if (declarations.length > 1) { + const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement + ? Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_in_statement + : Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_of_statement; + return grammarErrorOnFirstToken(variableList.declarations[1], diagnostic); + } + const firstDeclaration = declarations[0]; + if (firstDeclaration.initializer) { + const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement + ? Diagnostics.The_variable_declaration_of_a_for_in_statement_cannot_have_an_initializer + : Diagnostics.The_variable_declaration_of_a_for_of_statement_cannot_have_an_initializer; + return grammarErrorOnNode(firstDeclaration.name, diagnostic); + } + if (firstDeclaration.type) { + const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement + ? Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_use_a_type_annotation + : Diagnostics.The_left_hand_side_of_a_for_of_statement_cannot_use_a_type_annotation; + return grammarErrorOnNode(firstDeclaration, diagnostic); } } - - if (node.exclamationToken && (node.parent.parent.kind !== SyntaxKind.VariableStatement || !node.type || node.initializer || node.flags & NodeFlags.Ambient)) { - return grammarErrorOnNode(node.exclamationToken, Diagnostics.Definite_assignment_assertions_can_only_be_used_along_with_a_type_annotation); + } + return false; + } + function checkGrammarAccessor(accessor: AccessorDeclaration): boolean { + if (!(accessor.flags & NodeFlags.Ambient)) { + if (languageVersion < ScriptTarget.ES5) { + return grammarErrorOnNode(accessor.name, Diagnostics.Accessors_are_only_available_when_targeting_ECMAScript_5_and_higher); } - - if (compilerOptions.module !== ModuleKind.ES2015 && compilerOptions.module !== ModuleKind.ESNext && compilerOptions.module !== ModuleKind.System && !compilerOptions.noEmit && - !(node.parent.parent.flags & NodeFlags.Ambient) && hasModifier(node.parent.parent, ModifierFlags.Export)) { - checkESModuleMarker(node.name); + if (accessor.body === undefined && !hasModifier(accessor, ModifierFlags.Abstract)) { + return grammarErrorAtPos(accessor, accessor.end - 1, ";".length, Diagnostics._0_expected, "{"); } - - const checkLetConstNames = (isLet(node) || isVarConst(node)); - - // 1. LexicalDeclaration : LetOrConst BindingList ; - // It is a Syntax Error if the BoundNames of BindingList contains "let". - // 2. ForDeclaration: ForDeclaration : LetOrConst ForBinding - // It is a Syntax Error if the BoundNames of ForDeclaration contains "let". - - // It is a SyntaxError if a VariableDeclaration or VariableDeclarationNoIn occurs within strict code - // and its Identifier is eval or arguments - return checkLetConstNames && checkGrammarNameInLetOrConstDeclarations(node.name); } - - function checkESModuleMarker(name: Identifier | BindingPattern): boolean { - if (name.kind === SyntaxKind.Identifier) { - if (idText(name) === "__esModule") { - return grammarErrorOnNode(name, Diagnostics.Identifier_expected_esModule_is_reserved_as_an_exported_marker_when_transforming_ECMAScript_modules); - } + if (accessor.body && hasModifier(accessor, ModifierFlags.Abstract)) { + return grammarErrorOnNode(accessor, Diagnostics.An_abstract_accessor_cannot_have_an_implementation); + } + if (accessor.typeParameters) { + return grammarErrorOnNode(accessor.name, Diagnostics.An_accessor_cannot_have_type_parameters); + } + if (!doesAccessorHaveCorrectParameterCount(accessor)) { + return grammarErrorOnNode(accessor.name, accessor.kind === SyntaxKind.GetAccessor ? + Diagnostics.A_get_accessor_cannot_have_parameters : + Diagnostics.A_set_accessor_must_have_exactly_one_parameter); + } + if (accessor.kind === SyntaxKind.SetAccessor) { + if (accessor.type) { + return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_cannot_have_a_return_type_annotation); } - else { - const elements = name.elements; - for (const element of elements) { - if (!isOmittedExpression(element)) { - return checkESModuleMarker(element.name); + const parameter = Debug.assertDefined(getSetAccessorValueParameter(accessor), "Return value does not match parameter count assertion."); + if (parameter.dotDotDotToken) { + return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_set_accessor_cannot_have_rest_parameter); + } + if (parameter.questionToken) { + return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_set_accessor_cannot_have_an_optional_parameter); + } + if (parameter.initializer) { + return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_parameter_cannot_have_an_initializer); + } + } + return false; + } + /** Does the accessor have the right number of parameters? + * A get accessor has no parameters or a single `this` parameter. + * A set accessor has one parameter or a `this` parameter and one more parameter. + */ + function doesAccessorHaveCorrectParameterCount(accessor: AccessorDeclaration) { + return getAccessorThisParameter(accessor) || accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 0 : 1); + } + function getAccessorThisParameter(accessor: AccessorDeclaration): ParameterDeclaration | undefined { + if (accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 1 : 2)) { + return getThisParameter(accessor); + } + } + function checkGrammarTypeOperatorNode(node: TypeOperatorNode) { + if (node.operator === SyntaxKind.UniqueKeyword) { + if (node.type.kind !== SyntaxKind.SymbolKeyword) { + return grammarErrorOnNode(node.type, Diagnostics._0_expected, tokenToString(SyntaxKind.SymbolKeyword)); + } + const parent = walkUpParenthesizedTypes(node.parent); + switch (parent.kind) { + case SyntaxKind.VariableDeclaration: + const decl = (parent as VariableDeclaration); + if (decl.name.kind !== SyntaxKind.Identifier) { + return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_may_not_be_used_on_a_variable_declaration_with_a_binding_name); + } + if (!isVariableDeclarationInVariableStatement(decl)) { + return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_only_allowed_on_variables_in_a_variable_statement); + } + if (!(decl.parent.flags & NodeFlags.Const)) { + return grammarErrorOnNode((parent).name, Diagnostics.A_variable_whose_type_is_a_unique_symbol_type_must_be_const); + } + break; + case SyntaxKind.PropertyDeclaration: + if (!hasModifier(parent, ModifierFlags.Static) || + !hasModifier(parent, ModifierFlags.Readonly)) { + return grammarErrorOnNode((parent).name, Diagnostics.A_property_of_a_class_whose_type_is_a_unique_symbol_type_must_be_both_static_and_readonly); + } + break; + case SyntaxKind.PropertySignature: + if (!hasModifier(parent, ModifierFlags.Readonly)) { + return grammarErrorOnNode((parent).name, Diagnostics.A_property_of_an_interface_or_type_literal_whose_type_is_a_unique_symbol_type_must_be_readonly); } - } + break; + default: + return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_not_allowed_here); } - return false; } - - function checkGrammarNameInLetOrConstDeclarations(name: Identifier | BindingPattern): boolean { - if (name.kind === SyntaxKind.Identifier) { - if (name.originalKeywordKind === SyntaxKind.LetKeyword) { - return grammarErrorOnNode(name, Diagnostics.let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations); - } - } - else { - const elements = name.elements; - for (const element of elements) { - if (!isOmittedExpression(element)) { - checkGrammarNameInLetOrConstDeclarations(element.name); - } - } + else if (node.operator === SyntaxKind.ReadonlyKeyword) { + if (node.type.kind !== SyntaxKind.ArrayType && node.type.kind !== SyntaxKind.TupleType) { + return grammarErrorOnFirstToken(node, Diagnostics.readonly_type_modifier_is_only_permitted_on_array_and_tuple_literal_types, tokenToString(SyntaxKind.SymbolKeyword)); } - return false; } - - function checkGrammarVariableDeclarationList(declarationList: VariableDeclarationList): boolean { - const declarations = declarationList.declarations; - if (checkGrammarForDisallowedTrailingComma(declarationList.declarations)) { - return true; - } - - if (!declarationList.declarations.length) { - return grammarErrorAtPos(declarationList, declarations.pos, declarations.end - declarations.pos, Diagnostics.Variable_declaration_list_cannot_be_empty); - } - return false; + } + function checkGrammarForInvalidDynamicName(node: DeclarationName, message: DiagnosticMessage) { + if (isNonBindableDynamicName(node)) { + return grammarErrorOnNode(node, message); } - - function allowLetAndConstDeclarations(parent: Node): boolean { - switch (parent.kind) { - case SyntaxKind.IfStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.WithStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - return false; - case SyntaxKind.LabeledStatement: - return allowLetAndConstDeclarations(parent.parent); - } - + } + function checkGrammarMethod(node: MethodDeclaration | MethodSignature) { + if (checkGrammarFunctionLikeDeclaration(node)) { return true; } - - function checkGrammarForDisallowedLetOrConstStatement(node: VariableStatement) { - if (!allowLetAndConstDeclarations(node.parent)) { - if (isLet(node.declarationList)) { - return grammarErrorOnNode(node, Diagnostics.let_declarations_can_only_be_declared_inside_a_block); + if (node.kind === SyntaxKind.MethodDeclaration) { + if (node.parent.kind === SyntaxKind.ObjectLiteralExpression) { + // We only disallow modifier on a method declaration if it is a property of object-literal-expression + if (node.modifiers && !(node.modifiers.length === 1 && first(node.modifiers).kind === SyntaxKind.AsyncKeyword)) { + return grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here); + } + else if (checkGrammarForInvalidQuestionMark(node.questionToken, Diagnostics.An_object_member_cannot_be_declared_optional)) { + return true; } - else if (isVarConst(node.declarationList)) { - return grammarErrorOnNode(node, Diagnostics.const_declarations_can_only_be_declared_inside_a_block); + else if (checkGrammarForInvalidExclamationToken(node.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context)) { + return true; + } + else if (node.body === undefined) { + return grammarErrorAtPos(node, node.end - 1, ";".length, Diagnostics._0_expected, "{"); } } + if (checkGrammarForGenerator(node)) { + return true; + } } - - function checkGrammarMetaProperty(node: MetaProperty) { - const escapedText = node.name.escapedText; - switch (node.keywordToken) { - case SyntaxKind.NewKeyword: - if (escapedText !== "target") { - return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, node.name.escapedText, tokenToString(node.keywordToken), "target"); + if (isClassLike(node.parent)) { + // Technically, computed properties in ambient contexts is disallowed + // for property declarations and accessors too, not just methods. + // However, property declarations disallow computed names in general, + // and accessors are not allowed in ambient contexts in general, + // so this error only really matters for methods. + if (node.flags & NodeFlags.Ambient) { + return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_ambient_context_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + } + else if (node.kind === SyntaxKind.MethodDeclaration && !node.body) { + return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_method_overload_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + } + } + else if (node.parent.kind === SyntaxKind.InterfaceDeclaration) { + return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_interface_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + } + else if (node.parent.kind === SyntaxKind.TypeLiteral) { + return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_type_literal_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + } + } + function checkGrammarBreakOrContinueStatement(node: BreakOrContinueStatement): boolean { + let current: Node = node; + while (current) { + if (isFunctionLike(current)) { + return grammarErrorOnNode(node, Diagnostics.Jump_target_cannot_cross_function_boundary); + } + switch (current.kind) { + case SyntaxKind.LabeledStatement: + if (node.label && (current).label.escapedText === node.label.escapedText) { + // found matching label - verify that label usage is correct + // continue can only target labels that are on iteration statements + const isMisplacedContinueLabel = node.kind === SyntaxKind.ContinueStatement + && !isIterationStatement((current).statement, /*lookInLabeledStatement*/ true); + if (isMisplacedContinueLabel) { + return grammarErrorOnNode(node, Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement); + } + return false; } break; - case SyntaxKind.ImportKeyword: - if (escapedText !== "meta") { - return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, node.name.escapedText, tokenToString(node.keywordToken), "meta"); + case SyntaxKind.SwitchStatement: + if (node.kind === SyntaxKind.BreakStatement && !node.label) { + // unlabeled break within switch statement - ok + return false; + } + break; + default: + if (isIterationStatement(current, /*lookInLabeledStatement*/ false) && !node.label) { + // unlabeled break or continue within iteration statement - ok + return false; } break; } + current = current.parent; } - - function hasParseDiagnostics(sourceFile: SourceFile): boolean { - return sourceFile.parseDiagnostics.length > 0; - } - - function grammarErrorOnFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - const span = getSpanOfTokenAtPosition(sourceFile, node.pos); - diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, message, arg0, arg1, arg2)); - return true; - } - return false; + if (node.label) { + const message = node.kind === SyntaxKind.BreakStatement + ? Diagnostics.A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement + : Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement; + return grammarErrorOnNode(node, message); } - - function grammarErrorAtPos(nodeForSourceFile: Node, start: number, length: number, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { - const sourceFile = getSourceFileOfNode(nodeForSourceFile); - if (!hasParseDiagnostics(sourceFile)) { - diagnostics.add(createFileDiagnostic(sourceFile, start, length, message, arg0, arg1, arg2)); - return true; - } - return false; + else { + const message = node.kind === SyntaxKind.BreakStatement + ? Diagnostics.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement + : Diagnostics.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement; + return grammarErrorOnNode(node, message); } - - function grammarErrorOnNode(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - diagnostics.add(createDiagnosticForNode(node, message, arg0, arg1, arg2)); - return true; + } + function checkGrammarBindingElement(node: BindingElement) { + if (node.dotDotDotToken) { + const elements = node.parent.elements; + if (node !== last(elements)) { + return grammarErrorOnNode(node, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); } - return false; - } - - function checkGrammarConstructorTypeParameters(node: ConstructorDeclaration) { - const jsdocTypeParameters = isInJSFile(node) ? getJSDocTypeParameterDeclarations(node) : undefined; - const range = node.typeParameters || jsdocTypeParameters && firstOrUndefined(jsdocTypeParameters); - if (range) { - const pos = range.pos === range.end ? range.pos : skipTrivia(getSourceFileOfNode(node).text, range.pos); - return grammarErrorAtPos(node, pos, range.end - pos, Diagnostics.Type_parameters_cannot_appear_on_a_constructor_declaration); + checkGrammarForDisallowedTrailingComma(elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); + if (node.propertyName) { + return grammarErrorOnNode(node.name, Diagnostics.A_rest_element_cannot_have_a_property_name); } - } - - function checkGrammarConstructorTypeAnnotation(node: ConstructorDeclaration) { - const type = getEffectiveReturnTypeNode(node); - if (type) { - return grammarErrorOnNode(type, Diagnostics.Type_annotation_cannot_appear_on_a_constructor_declaration); + if (node.initializer) { + // Error on equals token which immediately precedes the initializer + return grammarErrorAtPos(node, node.initializer.pos - 1, 1, Diagnostics.A_rest_element_cannot_have_an_initializer); } } - - function checkGrammarProperty(node: PropertyDeclaration | PropertySignature) { - if (isClassLike(node.parent)) { - if (isStringLiteral(node.name) && node.name.text === "constructor") { - return grammarErrorOnNode(node.name, Diagnostics.Classes_may_not_have_a_field_named_constructor); - } - if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_class_property_declaration_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) { - return true; + } + function isStringOrNumberLiteralExpression(expr: Expression) { + return isStringOrNumericLiteralLike(expr) || + expr.kind === SyntaxKind.PrefixUnaryExpression && (expr).operator === SyntaxKind.MinusToken && + (expr).operand.kind === SyntaxKind.NumericLiteral; + } + function isBigIntLiteralExpression(expr: Expression) { + return expr.kind === SyntaxKind.BigIntLiteral || + expr.kind === SyntaxKind.PrefixUnaryExpression && (expr).operator === SyntaxKind.MinusToken && + (expr).operand.kind === SyntaxKind.BigIntLiteral; + } + function isSimpleLiteralEnumReference(expr: Expression) { + if ((isPropertyAccessExpression(expr) || (isElementAccessExpression(expr) && isStringOrNumberLiteralExpression(expr.argumentExpression))) && + isEntityNameExpression(expr.expression)) + return !!(checkExpressionCached(expr).flags & TypeFlags.EnumLiteral); + } + function checkAmbientInitializer(node: VariableDeclaration | PropertyDeclaration | PropertySignature) { + const { initializer } = node; + if (initializer) { + const isInvalidInitializer = !(isStringOrNumberLiteralExpression(initializer) || + isSimpleLiteralEnumReference(initializer) || + initializer.kind === SyntaxKind.TrueKeyword || initializer.kind === SyntaxKind.FalseKeyword || + isBigIntLiteralExpression(initializer)); + const isConstOrReadonly = isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node); + if (isConstOrReadonly && !node.type) { + if (isInvalidInitializer) { + return grammarErrorOnNode(initializer, Diagnostics.A_const_initializer_in_an_ambient_context_must_be_a_string_or_numeric_literal_or_literal_enum_reference); } } - else if (node.parent.kind === SyntaxKind.InterfaceDeclaration) { - if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_interface_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) { - return true; - } - if (node.initializer) { - return grammarErrorOnNode(node.initializer, Diagnostics.An_interface_property_cannot_have_an_initializer); - } + else { + return grammarErrorOnNode(initializer, Diagnostics.Initializers_are_not_allowed_in_ambient_contexts); } - else if (node.parent.kind === SyntaxKind.TypeLiteral) { - if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_type_literal_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) { - return true; - } - if (node.initializer) { - return grammarErrorOnNode(node.initializer, Diagnostics.A_type_literal_property_cannot_have_an_initializer); - } + if (!isConstOrReadonly || isInvalidInitializer) { + return grammarErrorOnNode(initializer, Diagnostics.Initializers_are_not_allowed_in_ambient_contexts); } - + } + } + function checkGrammarVariableDeclaration(node: VariableDeclaration) { + if (node.parent.parent.kind !== SyntaxKind.ForInStatement && node.parent.parent.kind !== SyntaxKind.ForOfStatement) { if (node.flags & NodeFlags.Ambient) { checkAmbientInitializer(node); } - - if (isPropertyDeclaration(node) && node.exclamationToken && (!isClassLike(node.parent) || !node.type || node.initializer || - node.flags & NodeFlags.Ambient || hasModifier(node, ModifierFlags.Static | ModifierFlags.Abstract))) { - return grammarErrorOnNode(node.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context); + else if (!node.initializer) { + if (isBindingPattern(node.name) && !isBindingPattern(node.parent)) { + return grammarErrorOnNode(node, Diagnostics.A_destructuring_declaration_must_have_an_initializer); + } + if (isVarConst(node)) { + return grammarErrorOnNode(node, Diagnostics.const_declarations_must_be_initialized); + } } } - - function checkGrammarTopLevelElementForRequiredDeclareModifier(node: Node): boolean { - // A declare modifier is required for any top level .d.ts declaration except export=, export default, export as namespace - // interfaces and imports categories: - // - // DeclarationElement: - // ExportAssignment - // export_opt InterfaceDeclaration - // export_opt TypeAliasDeclaration - // export_opt ImportDeclaration - // export_opt ExternalImportDeclaration - // export_opt AmbientDeclaration - // - // TODO: The spec needs to be amended to reflect this grammar. - if (node.kind === SyntaxKind.InterfaceDeclaration || - node.kind === SyntaxKind.TypeAliasDeclaration || - node.kind === SyntaxKind.ImportDeclaration || - node.kind === SyntaxKind.ImportEqualsDeclaration || - node.kind === SyntaxKind.ExportDeclaration || - node.kind === SyntaxKind.ExportAssignment || - node.kind === SyntaxKind.NamespaceExportDeclaration || - hasModifier(node, ModifierFlags.Ambient | ModifierFlags.Export | ModifierFlags.Default)) { - return false; + if (node.exclamationToken && (node.parent.parent.kind !== SyntaxKind.VariableStatement || !node.type || node.initializer || node.flags & NodeFlags.Ambient)) { + return grammarErrorOnNode(node.exclamationToken, Diagnostics.Definite_assignment_assertions_can_only_be_used_along_with_a_type_annotation); + } + if (compilerOptions.module !== ModuleKind.ES2015 && compilerOptions.module !== ModuleKind.ESNext && compilerOptions.module !== ModuleKind.System && !compilerOptions.noEmit && + !(node.parent.parent.flags & NodeFlags.Ambient) && hasModifier(node.parent.parent, ModifierFlags.Export)) { + checkESModuleMarker(node.name); + } + const checkLetConstNames = (isLet(node) || isVarConst(node)); + // 1. LexicalDeclaration : LetOrConst BindingList ; + // It is a Syntax Error if the BoundNames of BindingList contains "let". + // 2. ForDeclaration: ForDeclaration : LetOrConst ForBinding + // It is a Syntax Error if the BoundNames of ForDeclaration contains "let". + // It is a SyntaxError if a VariableDeclaration or VariableDeclarationNoIn occurs within strict code + // and its Identifier is eval or arguments + return checkLetConstNames && checkGrammarNameInLetOrConstDeclarations(node.name); + } + function checkESModuleMarker(name: Identifier | BindingPattern): boolean { + if (name.kind === SyntaxKind.Identifier) { + if (idText(name) === "__esModule") { + return grammarErrorOnNode(name, Diagnostics.Identifier_expected_esModule_is_reserved_as_an_exported_marker_when_transforming_ECMAScript_modules); } - - return grammarErrorOnFirstToken(node, Diagnostics.Top_level_declarations_in_d_ts_files_must_start_with_either_a_declare_or_export_modifier); } - - function checkGrammarTopLevelElementsForRequiredDeclareModifier(file: SourceFile): boolean { - for (const decl of file.statements) { - if (isDeclaration(decl) || decl.kind === SyntaxKind.VariableStatement) { - if (checkGrammarTopLevelElementForRequiredDeclareModifier(decl)) { - return true; - } + else { + const elements = name.elements; + for (const element of elements) { + if (!isOmittedExpression(element)) { + return checkESModuleMarker(element.name); } } - return false; } - - function checkGrammarSourceFile(node: SourceFile): boolean { - return !!(node.flags & NodeFlags.Ambient) && checkGrammarTopLevelElementsForRequiredDeclareModifier(node); + return false; + } + function checkGrammarNameInLetOrConstDeclarations(name: Identifier | BindingPattern): boolean { + if (name.kind === SyntaxKind.Identifier) { + if (name.originalKeywordKind === SyntaxKind.LetKeyword) { + return grammarErrorOnNode(name, Diagnostics.let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations); + } } - - function checkGrammarStatementInAmbientContext(node: Node): boolean { - if (node.flags & NodeFlags.Ambient) { - // Find containing block which is either Block, ModuleBlock, SourceFile - const links = getNodeLinks(node); - if (!links.hasReportedStatementInAmbientContext && (isFunctionLike(node.parent) || isAccessor(node.parent))) { - return getNodeLinks(node).hasReportedStatementInAmbientContext = grammarErrorOnFirstToken(node, Diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts); - } - - // We are either parented by another statement, or some sort of block. - // If we're in a block, we only want to really report an error once - // to prevent noisiness. So use a bit on the block to indicate if - // this has already been reported, and don't report if it has. - // - if (node.parent.kind === SyntaxKind.Block || node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { - const links = getNodeLinks(node.parent); - // Check if the containing block ever report this error - if (!links.hasReportedStatementInAmbientContext) { - return links.hasReportedStatementInAmbientContext = grammarErrorOnFirstToken(node, Diagnostics.Statements_are_not_allowed_in_ambient_contexts); - } - } - else { - // We must be parented by a statement. If so, there's no need - // to report the error as our parent will have already done it. - // Debug.assert(isStatement(node.parent)); + else { + const elements = name.elements; + for (const element of elements) { + if (!isOmittedExpression(element)) { + checkGrammarNameInLetOrConstDeclarations(element.name); } } - return false; } - - function checkGrammarNumericLiteral(node: NumericLiteral): boolean { - // Grammar checking - if (node.numericLiteralFlags & TokenFlags.Octal) { - let diagnosticMessage: DiagnosticMessage | undefined; - if (languageVersion >= ScriptTarget.ES5) { - diagnosticMessage = Diagnostics.Octal_literals_are_not_available_when_targeting_ECMAScript_5_and_higher_Use_the_syntax_0; - } - else if (isChildOfNodeWithKind(node, SyntaxKind.LiteralType)) { - diagnosticMessage = Diagnostics.Octal_literal_types_must_use_ES2015_syntax_Use_the_syntax_0; - } - else if (isChildOfNodeWithKind(node, SyntaxKind.EnumMember)) { - diagnosticMessage = Diagnostics.Octal_literals_are_not_allowed_in_enums_members_initializer_Use_the_syntax_0; + return false; + } + function checkGrammarVariableDeclarationList(declarationList: VariableDeclarationList): boolean { + const declarations = declarationList.declarations; + if (checkGrammarForDisallowedTrailingComma(declarationList.declarations)) { + return true; + } + if (!declarationList.declarations.length) { + return grammarErrorAtPos(declarationList, declarations.pos, declarations.end - declarations.pos, Diagnostics.Variable_declaration_list_cannot_be_empty); + } + return false; + } + function allowLetAndConstDeclarations(parent: Node): boolean { + switch (parent.kind) { + case SyntaxKind.IfStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + return false; + case SyntaxKind.LabeledStatement: + return allowLetAndConstDeclarations(parent.parent); + } + return true; + } + function checkGrammarForDisallowedLetOrConstStatement(node: VariableStatement) { + if (!allowLetAndConstDeclarations(node.parent)) { + if (isLet(node.declarationList)) { + return grammarErrorOnNode(node, Diagnostics.let_declarations_can_only_be_declared_inside_a_block); + } + else if (isVarConst(node.declarationList)) { + return grammarErrorOnNode(node, Diagnostics.const_declarations_can_only_be_declared_inside_a_block); + } + } + } + function checkGrammarMetaProperty(node: MetaProperty) { + const escapedText = node.name.escapedText; + switch (node.keywordToken) { + case SyntaxKind.NewKeyword: + if (escapedText !== "target") { + return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, node.name.escapedText, tokenToString(node.keywordToken), "target"); } - if (diagnosticMessage) { - const withMinus = isPrefixUnaryExpression(node.parent) && node.parent.operator === SyntaxKind.MinusToken; - const literal = (withMinus ? "-" : "") + "0o" + node.text; - return grammarErrorOnNode(withMinus ? node.parent : node, diagnosticMessage, literal); + break; + case SyntaxKind.ImportKeyword: + if (escapedText !== "meta") { + return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, node.name.escapedText, tokenToString(node.keywordToken), "meta"); } - } - - // Realism (size) checking - checkNumericLiteralValueSize(node); - - return false; + break; } - - function checkNumericLiteralValueSize(node: NumericLiteral) { - // Scientific notation (e.g. 2e54 and 1e00000000010) can't be converted to bigint - // Literals with 15 or fewer characters aren't long enough to reach past 2^53 - 1 - // Fractional numbers (e.g. 9000000000000000.001) are inherently imprecise anyway - if (node.numericLiteralFlags & TokenFlags.Scientific || node.text.length <= 15 || node.text.indexOf(".") !== -1) { - return; + } + function hasParseDiagnostics(sourceFile: SourceFile): boolean { + return sourceFile.parseDiagnostics.length > 0; + } + function grammarErrorOnFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const span = getSpanOfTokenAtPosition(sourceFile, node.pos); + diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, message, arg0, arg1, arg2)); + return true; + } + return false; + } + function grammarErrorAtPos(nodeForSourceFile: Node, start: number, length: number, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { + const sourceFile = getSourceFileOfNode(nodeForSourceFile); + if (!hasParseDiagnostics(sourceFile)) { + diagnostics.add(createFileDiagnostic(sourceFile, start, length, message, arg0, arg1, arg2)); + return true; + } + return false; + } + function grammarErrorOnNode(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + diagnostics.add(createDiagnosticForNode(node, message, arg0, arg1, arg2)); + return true; + } + return false; + } + function checkGrammarConstructorTypeParameters(node: ConstructorDeclaration) { + const jsdocTypeParameters = isInJSFile(node) ? getJSDocTypeParameterDeclarations(node) : undefined; + const range = node.typeParameters || jsdocTypeParameters && firstOrUndefined(jsdocTypeParameters); + if (range) { + const pos = range.pos === range.end ? range.pos : skipTrivia(getSourceFileOfNode(node).text, range.pos); + return grammarErrorAtPos(node, pos, range.end - pos, Diagnostics.Type_parameters_cannot_appear_on_a_constructor_declaration); + } + } + function checkGrammarConstructorTypeAnnotation(node: ConstructorDeclaration) { + const type = getEffectiveReturnTypeNode(node); + if (type) { + return grammarErrorOnNode(type, Diagnostics.Type_annotation_cannot_appear_on_a_constructor_declaration); + } + } + function checkGrammarProperty(node: PropertyDeclaration | PropertySignature) { + if (isClassLike(node.parent)) { + if (isStringLiteral(node.name) && node.name.text === "constructor") { + return grammarErrorOnNode(node.name, Diagnostics.Classes_may_not_have_a_field_named_constructor); } - - // We can't rely on the runtime to accurately store and compare extremely large numeric values - // Even for internal use, we use getTextOfNode: https://github.com/microsoft/TypeScript/issues/33298 - // Thus, if the runtime claims a too-large number is lower than Number.MAX_SAFE_INTEGER, - // it's likely addition operations on it will fail too - const apparentValue = +getTextOfNode(node); - if (apparentValue <= 2 ** 53 - 1 && apparentValue + 1 > apparentValue) { - return; + if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_class_property_declaration_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) { + return true; } - - addErrorOrSuggestion(/*isError*/ false, createDiagnosticForNode(node, Diagnostics.Numeric_literals_with_absolute_values_equal_to_2_53_or_greater_are_too_large_to_be_represented_accurately_as_integers)); } - - function checkGrammarBigIntLiteral(node: BigIntLiteral): boolean { - const literalType = isLiteralTypeNode(node.parent) || - isPrefixUnaryExpression(node.parent) && isLiteralTypeNode(node.parent.parent); - if (!literalType) { - if (languageVersion < ScriptTarget.ESNext) { - if (grammarErrorOnNode(node, Diagnostics.BigInt_literals_are_not_available_when_targeting_lower_than_ESNext)) { - return true; - } - } + else if (node.parent.kind === SyntaxKind.InterfaceDeclaration) { + if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_interface_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) { + return true; + } + if (node.initializer) { + return grammarErrorOnNode(node.initializer, Diagnostics.An_interface_property_cannot_have_an_initializer); } - return false; } - - function grammarErrorAfterFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - const span = getSpanOfTokenAtPosition(sourceFile, node.pos); - diagnostics.add(createFileDiagnostic(sourceFile, textSpanEnd(span), /*length*/ 0, message, arg0, arg1, arg2)); + else if (node.parent.kind === SyntaxKind.TypeLiteral) { + if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_type_literal_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) { return true; } + if (node.initializer) { + return grammarErrorOnNode(node.initializer, Diagnostics.A_type_literal_property_cannot_have_an_initializer); + } + } + if (node.flags & NodeFlags.Ambient) { + checkAmbientInitializer(node); + } + if (isPropertyDeclaration(node) && node.exclamationToken && (!isClassLike(node.parent) || !node.type || node.initializer || + node.flags & NodeFlags.Ambient || hasModifier(node, ModifierFlags.Static | ModifierFlags.Abstract))) { + return grammarErrorOnNode(node.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context); + } + } + function checkGrammarTopLevelElementForRequiredDeclareModifier(node: Node): boolean { + // A declare modifier is required for any top level .d.ts declaration except export=, export default, export as namespace + // interfaces and imports categories: + // + // DeclarationElement: + // ExportAssignment + // export_opt InterfaceDeclaration + // export_opt TypeAliasDeclaration + // export_opt ImportDeclaration + // export_opt ExternalImportDeclaration + // export_opt AmbientDeclaration + // + // TODO: The spec needs to be amended to reflect this grammar. + if (node.kind === SyntaxKind.InterfaceDeclaration || + node.kind === SyntaxKind.TypeAliasDeclaration || + node.kind === SyntaxKind.ImportDeclaration || + node.kind === SyntaxKind.ImportEqualsDeclaration || + node.kind === SyntaxKind.ExportDeclaration || + node.kind === SyntaxKind.ExportAssignment || + node.kind === SyntaxKind.NamespaceExportDeclaration || + hasModifier(node, ModifierFlags.Ambient | ModifierFlags.Export | ModifierFlags.Default)) { return false; } - - function getAmbientModules(): Symbol[] { - if (!ambientModulesCache) { - ambientModulesCache = []; - globals.forEach((global, sym) => { - // No need to `unescapeLeadingUnderscores`, an escaped symbol is never an ambient module. - if (ambientModuleSymbolRegex.test(sym as string)) { - ambientModulesCache!.push(global); - } - }); + return grammarErrorOnFirstToken(node, Diagnostics.Top_level_declarations_in_d_ts_files_must_start_with_either_a_declare_or_export_modifier); + } + function checkGrammarTopLevelElementsForRequiredDeclareModifier(file: SourceFile): boolean { + for (const decl of file.statements) { + if (isDeclaration(decl) || decl.kind === SyntaxKind.VariableStatement) { + if (checkGrammarTopLevelElementForRequiredDeclareModifier(decl)) { + return true; + } } - return ambientModulesCache; } - - function checkGrammarImportCallExpression(node: ImportCall): boolean { - if (moduleKind === ModuleKind.ES2015) { - return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_are_only_supported_when_the_module_flag_is_set_to_esnext_commonjs_amd_system_or_umd); - } - - if (node.typeArguments) { - return grammarErrorOnNode(node, Diagnostics.Dynamic_import_cannot_have_type_arguments); + return false; + } + function checkGrammarSourceFile(node: SourceFile): boolean { + return !!(node.flags & NodeFlags.Ambient) && checkGrammarTopLevelElementsForRequiredDeclareModifier(node); + } + function checkGrammarStatementInAmbientContext(node: Node): boolean { + if (node.flags & NodeFlags.Ambient) { + // Find containing block which is either Block, ModuleBlock, SourceFile + const links = getNodeLinks(node); + if (!links.hasReportedStatementInAmbientContext && (isFunctionLike(node.parent) || isAccessor(node.parent))) { + return getNodeLinks(node).hasReportedStatementInAmbientContext = grammarErrorOnFirstToken(node, Diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts); } - - const nodeArguments = node.arguments; - if (nodeArguments.length !== 1) { - return grammarErrorOnNode(node, Diagnostics.Dynamic_import_must_have_one_specifier_as_an_argument); + // We are either parented by another statement, or some sort of block. + // If we're in a block, we only want to really report an error once + // to prevent noisiness. So use a bit on the block to indicate if + // this has already been reported, and don't report if it has. + // + if (node.parent.kind === SyntaxKind.Block || node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { + const links = getNodeLinks(node.parent); + // Check if the containing block ever report this error + if (!links.hasReportedStatementInAmbientContext) { + return links.hasReportedStatementInAmbientContext = grammarErrorOnFirstToken(node, Diagnostics.Statements_are_not_allowed_in_ambient_contexts); + } } - checkGrammarForDisallowedTrailingComma(nodeArguments); - // see: parseArgumentOrArrayLiteralElement...we use this function which parse arguments of callExpression to parse specifier for dynamic import. - // parseArgumentOrArrayLiteralElement allows spread element to be in an argument list which is not allowed as specifier in dynamic import. - if (isSpreadElement(nodeArguments[0])) { - return grammarErrorOnNode(nodeArguments[0], Diagnostics.Specifier_of_dynamic_import_cannot_be_spread_element); + else { + // We must be parented by a statement. If so, there's no need + // to report the error as our parent will have already done it. + // Debug.assert(isStatement(node.parent)); } - return false; } + return false; } - - function isNotAccessor(declaration: Declaration): boolean { - // Accessors check for their own matching duplicates, and in contexts where they are valid, there are already duplicate identifier checks - return !isAccessor(declaration); + function checkGrammarNumericLiteral(node: NumericLiteral): boolean { + // Grammar checking + if (node.numericLiteralFlags & TokenFlags.Octal) { + let diagnosticMessage: DiagnosticMessage | undefined; + if (languageVersion >= ScriptTarget.ES5) { + diagnosticMessage = Diagnostics.Octal_literals_are_not_available_when_targeting_ECMAScript_5_and_higher_Use_the_syntax_0; + } + else if (isChildOfNodeWithKind(node, SyntaxKind.LiteralType)) { + diagnosticMessage = Diagnostics.Octal_literal_types_must_use_ES2015_syntax_Use_the_syntax_0; + } + else if (isChildOfNodeWithKind(node, SyntaxKind.EnumMember)) { + diagnosticMessage = Diagnostics.Octal_literals_are_not_allowed_in_enums_members_initializer_Use_the_syntax_0; + } + if (diagnosticMessage) { + const withMinus = isPrefixUnaryExpression(node.parent) && node.parent.operator === SyntaxKind.MinusToken; + const literal = (withMinus ? "-" : "") + "0o" + node.text; + return grammarErrorOnNode(withMinus ? node.parent : node, diagnosticMessage, literal); + } + } + // Realism (size) checking + checkNumericLiteralValueSize(node); + return false; } - - function isNotOverload(declaration: Declaration): boolean { - return (declaration.kind !== SyntaxKind.FunctionDeclaration && declaration.kind !== SyntaxKind.MethodDeclaration) || - !!(declaration as FunctionDeclaration).body; + function checkNumericLiteralValueSize(node: NumericLiteral) { + // Scientific notation (e.g. 2e54 and 1e00000000010) can't be converted to bigint + // Literals with 15 or fewer characters aren't long enough to reach past 2^53 - 1 + // Fractional numbers (e.g. 9000000000000000.001) are inherently imprecise anyway + if (node.numericLiteralFlags & TokenFlags.Scientific || node.text.length <= 15 || node.text.indexOf(".") !== -1) { + return; + } + // We can't rely on the runtime to accurately store and compare extremely large numeric values + // Even for internal use, we use getTextOfNode: https://github.com/microsoft/TypeScript/issues/33298 + // Thus, if the runtime claims a too-large number is lower than Number.MAX_SAFE_INTEGER, + // it's likely addition operations on it will fail too + const apparentValue = +getTextOfNode(node); + if (apparentValue <= 2 ** 53 - 1 && apparentValue + 1 > apparentValue) { + return; + } + addErrorOrSuggestion(/*isError*/ false, createDiagnosticForNode(node, Diagnostics.Numeric_literals_with_absolute_values_equal_to_2_53_or_greater_are_too_large_to_be_represented_accurately_as_integers)); } - - /** Like 'isDeclarationName', but returns true for LHS of `import { x as y }` or `export { x as y }`. */ - function isDeclarationNameOrImportPropertyName(name: Node): boolean { - switch (name.parent.kind) { - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: - return isIdentifier(name); - default: - return isDeclarationName(name); + function checkGrammarBigIntLiteral(node: BigIntLiteral): boolean { + const literalType = isLiteralTypeNode(node.parent) || + isPrefixUnaryExpression(node.parent) && isLiteralTypeNode(node.parent.parent); + if (!literalType) { + if (languageVersion < ScriptTarget.ESNext) { + if (grammarErrorOnNode(node, Diagnostics.BigInt_literals_are_not_available_when_targeting_lower_than_ESNext)) { + return true; + } + } } + return false; } - - function isSomeImportDeclaration(decl: Node): boolean { - switch (decl.kind) { - case SyntaxKind.ImportClause: // For default import - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.NamespaceImport: - case SyntaxKind.ImportSpecifier: // For rename import `x as y` - return true; - case SyntaxKind.Identifier: - // For regular import, `decl` is an Identifier under the ImportSpecifier. - return decl.parent.kind === SyntaxKind.ImportSpecifier; - default: - return false; + function grammarErrorAfterFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const span = getSpanOfTokenAtPosition(sourceFile, node.pos); + diagnostics.add(createFileDiagnostic(sourceFile, textSpanEnd(span), /*length*/ 0, message, arg0, arg1, arg2)); + return true; } + return false; } - - namespace JsxNames { - export const JSX = "JSX" as __String; - export const IntrinsicElements = "IntrinsicElements" as __String; - export const ElementClass = "ElementClass" as __String; - export const ElementAttributesPropertyNameContainer = "ElementAttributesProperty" as __String; // TODO: Deprecate and remove support - export const ElementChildrenAttributeNameContainer = "ElementChildrenAttribute" as __String; - export const Element = "Element" as __String; - export const IntrinsicAttributes = "IntrinsicAttributes" as __String; - export const IntrinsicClassAttributes = "IntrinsicClassAttributes" as __String; - export const LibraryManagedAttributes = "LibraryManagedAttributes" as __String; + function getAmbientModules(): ts.Symbol[] { + if (!ambientModulesCache) { + ambientModulesCache = []; + globals.forEach((global, sym) => { + // No need to `unescapeLeadingUnderscores`, an escaped symbol is never an ambient module. + if (ambientModuleSymbolRegex.test(sym as string)) { + ambientModulesCache!.push(global); + } + }); + } + return ambientModulesCache; } - - function getIterationTypesKeyFromIterationTypeKind(typeKind: IterationTypeKind) { - switch (typeKind) { - case IterationTypeKind.Yield: return "yieldType"; - case IterationTypeKind.Return: return "returnType"; - case IterationTypeKind.Next: return "nextType"; + function checkGrammarImportCallExpression(node: ImportCall): boolean { + if (moduleKind === ModuleKind.ES2015) { + return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_are_only_supported_when_the_module_flag_is_set_to_esnext_commonjs_amd_system_or_umd); + } + if (node.typeArguments) { + return grammarErrorOnNode(node, Diagnostics.Dynamic_import_cannot_have_type_arguments); } + const nodeArguments = node.arguments; + if (nodeArguments.length !== 1) { + return grammarErrorOnNode(node, Diagnostics.Dynamic_import_must_have_one_specifier_as_an_argument); + } + checkGrammarForDisallowedTrailingComma(nodeArguments); + // see: parseArgumentOrArrayLiteralElement...we use this function which parse arguments of callExpression to parse specifier for dynamic import. + // parseArgumentOrArrayLiteralElement allows spread element to be in an argument list which is not allowed as specifier in dynamic import. + if (isSpreadElement(nodeArguments[0])) { + return grammarErrorOnNode(nodeArguments[0], Diagnostics.Specifier_of_dynamic_import_cannot_be_spread_element); + } + return false; } - - export function signatureHasRestParameter(s: Signature) { - return !!(s.flags & SignatureFlags.HasRestParameter); +} +/* @internal */ +function isNotAccessor(declaration: Declaration): boolean { + // Accessors check for their own matching duplicates, and in contexts where they are valid, there are already duplicate identifier checks + return !isAccessor(declaration); +} +/* @internal */ +function isNotOverload(declaration: Declaration): boolean { + return (declaration.kind !== SyntaxKind.FunctionDeclaration && declaration.kind !== SyntaxKind.MethodDeclaration) || + !!(declaration as FunctionDeclaration).body; +} +/** Like 'isDeclarationName', but returns true for LHS of `import { x as y }` or `export { x as y }`. */ +/* @internal */ +function isDeclarationNameOrImportPropertyName(name: Node): boolean { + switch (name.parent.kind) { + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: + return isIdentifier(name); + default: + return isDeclarationName(name); } - - export function signatureHasLiteralTypes(s: Signature) { - return !!(s.flags & SignatureFlags.HasLiteralTypes); +} +/* @internal */ +function isSomeImportDeclaration(decl: Node): boolean { + switch (decl.kind) { + case SyntaxKind.ImportClause: // For default import + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportSpecifier: // For rename import `x as y` + return true; + case SyntaxKind.Identifier: + // For regular import, `decl` is an Identifier under the ImportSpecifier. + return decl.parent.kind === SyntaxKind.ImportSpecifier; + default: + return false; } - +} +/* @internal */ +namespace JsxNames { + export const JSX = ("JSX" as __String); + export const IntrinsicElements = ("IntrinsicElements" as __String); + export const ElementClass = ("ElementClass" as __String); + export const ElementAttributesPropertyNameContainer = ("ElementAttributesProperty" as __String); // TODO: Deprecate and remove support + export const ElementChildrenAttributeNameContainer = ("ElementChildrenAttribute" as __String); + export const Element = ("Element" as __String); + export const IntrinsicAttributes = ("IntrinsicAttributes" as __String); + export const IntrinsicClassAttributes = ("IntrinsicClassAttributes" as __String); + export const LibraryManagedAttributes = ("LibraryManagedAttributes" as __String); +} +/* @internal */ +function getIterationTypesKeyFromIterationTypeKind(typeKind: IterationTypeKind) { + switch (typeKind) { + case IterationTypeKind.Yield: return "yieldType"; + case IterationTypeKind.Return: return "returnType"; + case IterationTypeKind.Next: return "nextType"; + } +} +/* @internal */ +export function signatureHasRestParameter(s: Signature) { + return !!(s.flags & SignatureFlags.HasRestParameter); +} +/* @internal */ +export function signatureHasLiteralTypes(s: Signature) { + return !!(s.flags & SignatureFlags.HasLiteralTypes); } diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 8bf71d7301d8a..bd81c6db00844 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1,3153 +1,2814 @@ -namespace ts { - /* @internal */ - export const compileOnSaveCommandLineOption: CommandLineOption = { name: "compileOnSave", type: "boolean" }; - - // NOTE: The order here is important to default lib ordering as entries will have the same - // order in the generated program (see `getDefaultLibPriority` in program.ts). This - // order also affects overload resolution when a type declared in one lib is - // augmented in another lib. - const libEntries: [string, string][] = [ - // JavaScript only - ["es5", "lib.es5.d.ts"], - ["es6", "lib.es2015.d.ts"], - ["es2015", "lib.es2015.d.ts"], - ["es7", "lib.es2016.d.ts"], - ["es2016", "lib.es2016.d.ts"], - ["es2017", "lib.es2017.d.ts"], - ["es2018", "lib.es2018.d.ts"], - ["es2019", "lib.es2019.d.ts"], - ["es2020", "lib.es2020.d.ts"], - ["esnext", "lib.esnext.d.ts"], - // Host only - ["dom", "lib.dom.d.ts"], - ["dom.iterable", "lib.dom.iterable.d.ts"], - ["webworker", "lib.webworker.d.ts"], - ["webworker.importscripts", "lib.webworker.importscripts.d.ts"], - ["scripthost", "lib.scripthost.d.ts"], - // ES2015 Or ESNext By-feature options - ["es2015.core", "lib.es2015.core.d.ts"], - ["es2015.collection", "lib.es2015.collection.d.ts"], - ["es2015.generator", "lib.es2015.generator.d.ts"], - ["es2015.iterable", "lib.es2015.iterable.d.ts"], - ["es2015.promise", "lib.es2015.promise.d.ts"], - ["es2015.proxy", "lib.es2015.proxy.d.ts"], - ["es2015.reflect", "lib.es2015.reflect.d.ts"], - ["es2015.symbol", "lib.es2015.symbol.d.ts"], - ["es2015.symbol.wellknown", "lib.es2015.symbol.wellknown.d.ts"], - ["es2016.array.include", "lib.es2016.array.include.d.ts"], - ["es2017.object", "lib.es2017.object.d.ts"], - ["es2017.sharedmemory", "lib.es2017.sharedmemory.d.ts"], - ["es2017.string", "lib.es2017.string.d.ts"], - ["es2017.intl", "lib.es2017.intl.d.ts"], - ["es2017.typedarrays", "lib.es2017.typedarrays.d.ts"], - ["es2018.asyncgenerator", "lib.es2018.asyncgenerator.d.ts"], - ["es2018.asynciterable", "lib.es2018.asynciterable.d.ts"], - ["es2018.intl", "lib.es2018.intl.d.ts"], - ["es2018.promise", "lib.es2018.promise.d.ts"], - ["es2018.regexp", "lib.es2018.regexp.d.ts"], - ["es2019.array", "lib.es2019.array.d.ts"], - ["es2019.object", "lib.es2019.object.d.ts"], - ["es2019.string", "lib.es2019.string.d.ts"], - ["es2019.symbol", "lib.es2019.symbol.d.ts"], - ["es2020.string", "lib.es2020.string.d.ts"], - ["es2020.symbol.wellknown", "lib.es2020.symbol.wellknown.d.ts"], - ["esnext.array", "lib.es2019.array.d.ts"], - ["esnext.symbol", "lib.es2019.symbol.d.ts"], - ["esnext.asynciterable", "lib.es2018.asynciterable.d.ts"], - ["esnext.intl", "lib.esnext.intl.d.ts"], - ["esnext.bigint", "lib.esnext.bigint.d.ts"] - ]; - - /** - * An array of supported "lib" reference file names used to determine the order for inclusion - * when referenced, as well as for spelling suggestions. This ensures the correct ordering for - * overload resolution when a type declared in one lib is extended by another. - */ - /* @internal */ - export const libs = libEntries.map(entry => entry[0]); - - /** - * A map of lib names to lib files. This map is used both for parsing the "lib" command line - * option as well as for resolving lib reference directives. - */ - /* @internal */ - export const libMap = createMapFromEntries(libEntries); - - // Watch related options - /* @internal */ - export const optionsForWatch: CommandLineOption[] = [ - { - name: "watchFile", - type: createMapFromTemplate({ - fixedpollinginterval: WatchFileKind.FixedPollingInterval, - prioritypollinginterval: WatchFileKind.PriorityPollingInterval, - dynamicprioritypolling: WatchFileKind.DynamicPriorityPolling, - usefsevents: WatchFileKind.UseFsEvents, - usefseventsonparentdirectory: WatchFileKind.UseFsEventsOnParentDirectory, - }), - category: Diagnostics.Advanced_Options, - description: Diagnostics.Specify_strategy_for_watching_file_Colon_FixedPollingInterval_default_PriorityPollingInterval_DynamicPriorityPolling_UseFsEvents_UseFsEventsOnParentDirectory, - }, - { - name: "watchDirectory", - type: createMapFromTemplate({ - usefsevents: WatchDirectoryKind.UseFsEvents, - fixedpollinginterval: WatchDirectoryKind.FixedPollingInterval, - dynamicprioritypolling: WatchDirectoryKind.DynamicPriorityPolling, - }), - category: Diagnostics.Advanced_Options, - description: Diagnostics.Specify_strategy_for_watching_directory_on_platforms_that_don_t_support_recursive_watching_natively_Colon_UseFsEvents_default_FixedPollingInterval_DynamicPriorityPolling, - }, - { - name: "fallbackPolling", - type: createMapFromTemplate({ - fixedinterval: PollingWatchKind.FixedInterval, - priorityinterval: PollingWatchKind.PriorityInterval, - dynamicpriority: PollingWatchKind.DynamicPriority, - }), - category: Diagnostics.Advanced_Options, - description: Diagnostics.Specify_strategy_for_creating_a_polling_watch_when_it_fails_to_create_using_file_system_events_Colon_FixedInterval_default_PriorityInterval_DynamicPriority, - }, - { - name: "synchronousWatchDirectory", - type: "boolean", - category: Diagnostics.Advanced_Options, - description: Diagnostics.Synchronously_call_callbacks_and_update_the_state_of_directory_watchers_on_platforms_that_don_t_support_recursive_watching_natively, - }, - ]; - - /* @internal */ - export const commonOptionsWithBuild: CommandLineOption[] = [ - { - name: "help", - shortName: "h", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - description: Diagnostics.Print_this_message, - }, - { - name: "help", - shortName: "?", - type: "boolean" - }, - { - name: "watch", - shortName: "w", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - description: Diagnostics.Watch_input_files, - }, - { - name: "preserveWatchOutput", - type: "boolean", - showInSimplifiedHelpView: false, - category: Diagnostics.Command_line_Options, - description: Diagnostics.Whether_to_keep_outdated_console_output_in_watch_mode_instead_of_clearing_the_screen, - }, - { - name: "listFiles", - type: "boolean", - category: Diagnostics.Advanced_Options, - description: Diagnostics.Print_names_of_files_part_of_the_compilation - }, - { - name: "listEmittedFiles", - type: "boolean", - category: Diagnostics.Advanced_Options, - description: Diagnostics.Print_names_of_generated_files_part_of_the_compilation - }, - { - name: "pretty", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - description: Diagnostics.Stylize_errors_and_messages_using_color_and_context_experimental - }, - - { - name: "traceResolution", - type: "boolean", - category: Diagnostics.Advanced_Options, - description: Diagnostics.Enable_tracing_of_the_name_resolution_process - }, - { - name: "diagnostics", - type: "boolean", - category: Diagnostics.Advanced_Options, - description: Diagnostics.Show_diagnostic_information - }, - { - name: "extendedDiagnostics", - type: "boolean", - category: Diagnostics.Advanced_Options, - description: Diagnostics.Show_verbose_diagnostic_information - }, - { - name: "generateCpuProfile", - type: "string", - isFilePath: true, - paramType: Diagnostics.FILE_OR_DIRECTORY, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Generates_a_CPU_profile - }, - { - name: "incremental", - shortName: "i", - type: "boolean", - category: Diagnostics.Basic_Options, - description: Diagnostics.Enable_incremental_compilation, - transpileOptionValue: undefined - }, - { - name: "locale", - type: "string", - category: Diagnostics.Advanced_Options, - description: Diagnostics.The_locale_used_when_displaying_messages_to_the_user_e_g_en_us - }, - ]; - - /* @internal */ - export const optionDeclarations: CommandLineOption[] = [ - // CommandLine only options - ...commonOptionsWithBuild, - { - name: "all", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - description: Diagnostics.Show_all_compiler_options, - }, - { - name: "version", - shortName: "v", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - description: Diagnostics.Print_the_compiler_s_version, - }, - { - name: "init", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - description: Diagnostics.Initializes_a_TypeScript_project_and_creates_a_tsconfig_json_file, - }, - { - name: "project", - shortName: "p", - type: "string", - isFilePath: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - paramType: Diagnostics.FILE_OR_DIRECTORY, - description: Diagnostics.Compile_the_project_given_the_path_to_its_configuration_file_or_to_a_folder_with_a_tsconfig_json, - }, - { - name: "build", - type: "boolean", - shortName: "b", - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - description: Diagnostics.Build_one_or_more_projects_and_their_dependencies_if_out_of_date - }, - { - name: "showConfig", - type: "boolean", - category: Diagnostics.Command_line_Options, - isCommandLineOnly: true, - description: Diagnostics.Print_the_final_configuration_instead_of_building - }, - { - name: "listFilesOnly", - type: "boolean", - category: Diagnostics.Command_line_Options, - affectsSemanticDiagnostics: true, - affectsEmit: true, - isCommandLineOnly: true, - description: Diagnostics.Print_names_of_files_that_are_part_of_the_compilation_and_then_stop_processing - }, - - // Basic - { - name: "target", - shortName: "t", - type: createMapFromTemplate({ - es3: ScriptTarget.ES3, - es5: ScriptTarget.ES5, - es6: ScriptTarget.ES2015, - es2015: ScriptTarget.ES2015, - es2016: ScriptTarget.ES2016, - es2017: ScriptTarget.ES2017, - es2018: ScriptTarget.ES2018, - es2019: ScriptTarget.ES2019, - es2020: ScriptTarget.ES2020, - esnext: ScriptTarget.ESNext, - }), - affectsSourceFile: true, - affectsModuleResolution: true, - affectsEmit: true, - paramType: Diagnostics.VERSION, - showInSimplifiedHelpView: true, - category: Diagnostics.Basic_Options, - description: Diagnostics.Specify_ECMAScript_target_version_Colon_ES3_default_ES5_ES2015_ES2016_ES2017_ES2018_ES2019_or_ESNEXT, - }, - { - name: "module", - shortName: "m", - type: createMapFromTemplate({ - none: ModuleKind.None, - commonjs: ModuleKind.CommonJS, - amd: ModuleKind.AMD, - system: ModuleKind.System, - umd: ModuleKind.UMD, - es6: ModuleKind.ES2015, - es2015: ModuleKind.ES2015, - esnext: ModuleKind.ESNext - }), - affectsModuleResolution: true, - affectsEmit: true, - paramType: Diagnostics.KIND, - showInSimplifiedHelpView: true, - category: Diagnostics.Basic_Options, - description: Diagnostics.Specify_module_code_generation_Colon_none_commonjs_amd_system_umd_es2015_or_ESNext, - }, - { +import { CommandLineOption, createMapFromEntries, createMapFromTemplate, WatchFileKind, Diagnostics, WatchDirectoryKind, PollingWatchKind, ScriptTarget, ModuleKind, JsxEmit, ModuleResolutionKind, NewLineKind, hasProperty, createMap, forEach, CompilerOptions, TypeAcquisition, CommandLineOptionOfCustomType, Diagnostic, createCompilerDiagnostic, DiagnosticMessage, arrayFrom, Push, CommandLineOptionOfListType, startsWith, map, mapDefined, CompilerOptionsValue, TsConfigSourceFile, DidYouMeanOptionsDiagnostics, getSpellingSuggestion, WatchOptions, CharacterCodes, sys, ParsedCommandLine, BuildOptions, ParseConfigHost, parseJsonText, toPath, createGetCanonicalFileName, getNormalizedAbsolutePath, getDirectoryPath, isString, arrayToMap, TsConfigOnlyOption, PropertyName, Expression, JsonSourceFile, ObjectLiteralExpression, SyntaxKind, createDiagnosticForNodeInSourceFile, getTextOfPropertyName, unescapeLeadingUnderscores, NodeArray, filter, StringLiteral, NumericLiteral, PrefixUnaryExpression, ArrayLiteralExpression, Node, isStringLiteral, isStringDoubleQuoted, isArray, ProjectReference, getRelativePathFromFile, length, getFileMatcherPatterns, getRegexFromPattern, forEachEntry, extend, Debug, createMultiMap, getLocaleSpecificMessage, Path, FileExtensionInfo, normalizeSlashes, ExpandResult, firstDefined, getTsConfigPropArray, ConfigFileSpecs, filterMutate, assign, isRootedDiskPath, endsWith, Extension, nodeModuleNameResolver, combinePaths, toLowerCase, getBaseFileName, convertToRelativePath, identity, normalizePath, getSupportedExtensions, getSuppoertedExtensionsWithJsonIfResolveJsonModule, fileExtensionIs, getRegularExpressionsForWildcards, emptyArray, findIndex, getTsConfigPropArrayElementValue, MapLike, WatchDirectoryFlags, getRegularExpressionForWildcard, containsPath, isImplicitGlob, getExtensionPriority, adjustExtensionPriority, ExtensionPriority, changeExtension, getNextLowestExtensionPriority } from "./ts"; +import * as ts from "./ts"; +/* @internal */ +export const compileOnSaveCommandLineOption: CommandLineOption = { name: "compileOnSave", type: "boolean" }; +// NOTE: The order here is important to default lib ordering as entries will have the same +// order in the generated program (see `getDefaultLibPriority` in program.ts). This +// order also affects overload resolution when a type declared in one lib is +// augmented in another lib. +const libEntries: [string, string][] = [ + // JavaScript only + ["es5", "lib.es5.d.ts"], + ["es6", "lib.es2015.d.ts"], + ["es2015", "lib.es2015.d.ts"], + ["es7", "lib.es2016.d.ts"], + ["es2016", "lib.es2016.d.ts"], + ["es2017", "lib.es2017.d.ts"], + ["es2018", "lib.es2018.d.ts"], + ["es2019", "lib.es2019.d.ts"], + ["es2020", "lib.es2020.d.ts"], + ["esnext", "lib.esnext.d.ts"], + // Host only + ["dom", "lib.dom.d.ts"], + ["dom.iterable", "lib.dom.iterable.d.ts"], + ["webworker", "lib.webworker.d.ts"], + ["webworker.importscripts", "lib.webworker.importscripts.d.ts"], + ["scripthost", "lib.scripthost.d.ts"], + // ES2015 Or ESNext By-feature options + ["es2015.core", "lib.es2015.core.d.ts"], + ["es2015.collection", "lib.es2015.collection.d.ts"], + ["es2015.generator", "lib.es2015.generator.d.ts"], + ["es2015.iterable", "lib.es2015.iterable.d.ts"], + ["es2015.promise", "lib.es2015.promise.d.ts"], + ["es2015.proxy", "lib.es2015.proxy.d.ts"], + ["es2015.reflect", "lib.es2015.reflect.d.ts"], + ["es2015.symbol", "lib.es2015.symbol.d.ts"], + ["es2015.symbol.wellknown", "lib.es2015.symbol.wellknown.d.ts"], + ["es2016.array.include", "lib.es2016.array.include.d.ts"], + ["es2017.object", "lib.es2017.object.d.ts"], + ["es2017.sharedmemory", "lib.es2017.sharedmemory.d.ts"], + ["es2017.string", "lib.es2017.string.d.ts"], + ["es2017.intl", "lib.es2017.intl.d.ts"], + ["es2017.typedarrays", "lib.es2017.typedarrays.d.ts"], + ["es2018.asyncgenerator", "lib.es2018.asyncgenerator.d.ts"], + ["es2018.asynciterable", "lib.es2018.asynciterable.d.ts"], + ["es2018.intl", "lib.es2018.intl.d.ts"], + ["es2018.promise", "lib.es2018.promise.d.ts"], + ["es2018.regexp", "lib.es2018.regexp.d.ts"], + ["es2019.array", "lib.es2019.array.d.ts"], + ["es2019.object", "lib.es2019.object.d.ts"], + ["es2019.string", "lib.es2019.string.d.ts"], + ["es2019.symbol", "lib.es2019.symbol.d.ts"], + ["es2020.string", "lib.es2020.string.d.ts"], + ["es2020.symbol.wellknown", "lib.es2020.symbol.wellknown.d.ts"], + ["esnext.array", "lib.es2019.array.d.ts"], + ["esnext.symbol", "lib.es2019.symbol.d.ts"], + ["esnext.asynciterable", "lib.es2018.asynciterable.d.ts"], + ["esnext.intl", "lib.esnext.intl.d.ts"], + ["esnext.bigint", "lib.esnext.bigint.d.ts"] +]; +/** + * An array of supported "lib" reference file names used to determine the order for inclusion + * when referenced, as well as for spelling suggestions. This ensures the correct ordering for + * overload resolution when a type declared in one lib is extended by another. + */ +/* @internal */ +export const libs = libEntries.map(entry => entry[0]); +/** + * A map of lib names to lib files. This map is used both for parsing the "lib" command line + * option as well as for resolving lib reference directives. + */ +/* @internal */ +export const libMap = createMapFromEntries(libEntries); +// Watch related options +/* @internal */ +export const optionsForWatch: CommandLineOption[] = [ + { + name: "watchFile", + type: createMapFromTemplate({ + fixedpollinginterval: WatchFileKind.FixedPollingInterval, + prioritypollinginterval: WatchFileKind.PriorityPollingInterval, + dynamicprioritypolling: WatchFileKind.DynamicPriorityPolling, + usefsevents: WatchFileKind.UseFsEvents, + usefseventsonparentdirectory: WatchFileKind.UseFsEventsOnParentDirectory, + }), + category: Diagnostics.Advanced_Options, + description: Diagnostics.Specify_strategy_for_watching_file_Colon_FixedPollingInterval_default_PriorityPollingInterval_DynamicPriorityPolling_UseFsEvents_UseFsEventsOnParentDirectory, + }, + { + name: "watchDirectory", + type: createMapFromTemplate({ + usefsevents: WatchDirectoryKind.UseFsEvents, + fixedpollinginterval: WatchDirectoryKind.FixedPollingInterval, + dynamicprioritypolling: WatchDirectoryKind.DynamicPriorityPolling, + }), + category: Diagnostics.Advanced_Options, + description: Diagnostics.Specify_strategy_for_watching_directory_on_platforms_that_don_t_support_recursive_watching_natively_Colon_UseFsEvents_default_FixedPollingInterval_DynamicPriorityPolling, + }, + { + name: "fallbackPolling", + type: createMapFromTemplate({ + fixedinterval: PollingWatchKind.FixedInterval, + priorityinterval: PollingWatchKind.PriorityInterval, + dynamicpriority: PollingWatchKind.DynamicPriority, + }), + category: Diagnostics.Advanced_Options, + description: Diagnostics.Specify_strategy_for_creating_a_polling_watch_when_it_fails_to_create_using_file_system_events_Colon_FixedInterval_default_PriorityInterval_DynamicPriority, + }, + { + name: "synchronousWatchDirectory", + type: "boolean", + category: Diagnostics.Advanced_Options, + description: Diagnostics.Synchronously_call_callbacks_and_update_the_state_of_directory_watchers_on_platforms_that_don_t_support_recursive_watching_natively, + }, +]; +/* @internal */ +export const commonOptionsWithBuild: CommandLineOption[] = [ + { + name: "help", + shortName: "h", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Print_this_message, + }, + { + name: "help", + shortName: "?", + type: "boolean" + }, + { + name: "watch", + shortName: "w", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Watch_input_files, + }, + { + name: "preserveWatchOutput", + type: "boolean", + showInSimplifiedHelpView: false, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Whether_to_keep_outdated_console_output_in_watch_mode_instead_of_clearing_the_screen, + }, + { + name: "listFiles", + type: "boolean", + category: Diagnostics.Advanced_Options, + description: Diagnostics.Print_names_of_files_part_of_the_compilation + }, + { + name: "listEmittedFiles", + type: "boolean", + category: Diagnostics.Advanced_Options, + description: Diagnostics.Print_names_of_generated_files_part_of_the_compilation + }, + { + name: "pretty", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Stylize_errors_and_messages_using_color_and_context_experimental + }, + { + name: "traceResolution", + type: "boolean", + category: Diagnostics.Advanced_Options, + description: Diagnostics.Enable_tracing_of_the_name_resolution_process + }, + { + name: "diagnostics", + type: "boolean", + category: Diagnostics.Advanced_Options, + description: Diagnostics.Show_diagnostic_information + }, + { + name: "extendedDiagnostics", + type: "boolean", + category: Diagnostics.Advanced_Options, + description: Diagnostics.Show_verbose_diagnostic_information + }, + { + name: "generateCpuProfile", + type: "string", + isFilePath: true, + paramType: Diagnostics.FILE_OR_DIRECTORY, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Generates_a_CPU_profile + }, + { + name: "incremental", + shortName: "i", + type: "boolean", + category: Diagnostics.Basic_Options, + description: Diagnostics.Enable_incremental_compilation, + transpileOptionValue: undefined + }, + { + name: "locale", + type: "string", + category: Diagnostics.Advanced_Options, + description: Diagnostics.The_locale_used_when_displaying_messages_to_the_user_e_g_en_us + }, +]; +/* @internal */ +export const optionDeclarations: CommandLineOption[] = [ + // CommandLine only options + ...commonOptionsWithBuild, + { + name: "all", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Show_all_compiler_options, + }, + { + name: "version", + shortName: "v", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Print_the_compiler_s_version, + }, + { + name: "init", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Initializes_a_TypeScript_project_and_creates_a_tsconfig_json_file, + }, + { + name: "project", + shortName: "p", + type: "string", + isFilePath: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + paramType: Diagnostics.FILE_OR_DIRECTORY, + description: Diagnostics.Compile_the_project_given_the_path_to_its_configuration_file_or_to_a_folder_with_a_tsconfig_json, + }, + { + name: "build", + type: "boolean", + shortName: "b", + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Build_one_or_more_projects_and_their_dependencies_if_out_of_date + }, + { + name: "showConfig", + type: "boolean", + category: Diagnostics.Command_line_Options, + isCommandLineOnly: true, + description: Diagnostics.Print_the_final_configuration_instead_of_building + }, + { + name: "listFilesOnly", + type: "boolean", + category: Diagnostics.Command_line_Options, + affectsSemanticDiagnostics: true, + affectsEmit: true, + isCommandLineOnly: true, + description: Diagnostics.Print_names_of_files_that_are_part_of_the_compilation_and_then_stop_processing + }, + // Basic + { + name: "target", + shortName: "t", + type: createMapFromTemplate({ + es3: ScriptTarget.ES3, + es5: ScriptTarget.ES5, + es6: ScriptTarget.ES2015, + es2015: ScriptTarget.ES2015, + es2016: ScriptTarget.ES2016, + es2017: ScriptTarget.ES2017, + es2018: ScriptTarget.ES2018, + es2019: ScriptTarget.ES2019, + es2020: ScriptTarget.ES2020, + esnext: ScriptTarget.ESNext, + }), + affectsSourceFile: true, + affectsModuleResolution: true, + affectsEmit: true, + paramType: Diagnostics.VERSION, + showInSimplifiedHelpView: true, + category: Diagnostics.Basic_Options, + description: Diagnostics.Specify_ECMAScript_target_version_Colon_ES3_default_ES5_ES2015_ES2016_ES2017_ES2018_ES2019_or_ESNEXT, + }, + { + name: "module", + shortName: "m", + type: createMapFromTemplate({ + none: ModuleKind.None, + commonjs: ModuleKind.CommonJS, + amd: ModuleKind.AMD, + system: ModuleKind.System, + umd: ModuleKind.UMD, + es6: ModuleKind.ES2015, + es2015: ModuleKind.ES2015, + esnext: ModuleKind.ESNext + }), + affectsModuleResolution: true, + affectsEmit: true, + paramType: Diagnostics.KIND, + showInSimplifiedHelpView: true, + category: Diagnostics.Basic_Options, + description: Diagnostics.Specify_module_code_generation_Colon_none_commonjs_amd_system_umd_es2015_or_ESNext, + }, + { + name: "lib", + type: "list", + element: { name: "lib", - type: "list", - element: { - name: "lib", - type: libMap - }, - affectsModuleResolution: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Basic_Options, - description: Diagnostics.Specify_library_files_to_be_included_in_the_compilation, - transpileOptionValue: undefined - }, - { - name: "allowJs", - type: "boolean", - affectsModuleResolution: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Basic_Options, - description: Diagnostics.Allow_javascript_files_to_be_compiled - }, - { - name: "checkJs", - type: "boolean", - category: Diagnostics.Basic_Options, - description: Diagnostics.Report_errors_in_js_files - }, - { - name: "jsx", - type: createMapFromTemplate({ - "preserve": JsxEmit.Preserve, - "react-native": JsxEmit.ReactNative, - "react": JsxEmit.React - }), - affectsSourceFile: true, - paramType: Diagnostics.KIND, - showInSimplifiedHelpView: true, - category: Diagnostics.Basic_Options, - description: Diagnostics.Specify_JSX_code_generation_Colon_preserve_react_native_or_react, - }, - { - name: "declaration", - shortName: "d", - type: "boolean", - affectsEmit: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Basic_Options, - description: Diagnostics.Generates_corresponding_d_ts_file, - transpileOptionValue: undefined - }, - { - name: "declarationMap", - type: "boolean", - affectsEmit: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Basic_Options, - description: Diagnostics.Generates_a_sourcemap_for_each_corresponding_d_ts_file, - transpileOptionValue: undefined - }, - { - name: "emitDeclarationOnly", - type: "boolean", - affectsEmit: true, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Only_emit_d_ts_declaration_files, - transpileOptionValue: undefined - }, - { - name: "sourceMap", - type: "boolean", - affectsEmit: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Basic_Options, - description: Diagnostics.Generates_corresponding_map_file, - }, - { - name: "outFile", - type: "string", - affectsEmit: true, - isFilePath: true, - paramType: Diagnostics.FILE, - showInSimplifiedHelpView: true, - category: Diagnostics.Basic_Options, - description: Diagnostics.Concatenate_and_emit_output_to_single_file, - transpileOptionValue: undefined - }, - { - name: "outDir", - type: "string", - affectsEmit: true, - isFilePath: true, - paramType: Diagnostics.DIRECTORY, - showInSimplifiedHelpView: true, - category: Diagnostics.Basic_Options, - description: Diagnostics.Redirect_output_structure_to_the_directory, - }, - { - name: "rootDir", - type: "string", - affectsEmit: true, - isFilePath: true, - paramType: Diagnostics.LOCATION, - category: Diagnostics.Basic_Options, - description: Diagnostics.Specify_the_root_directory_of_input_files_Use_to_control_the_output_directory_structure_with_outDir, - }, - { - name: "composite", - type: "boolean", - affectsEmit: true, - isTSConfigOnly: true, - category: Diagnostics.Basic_Options, - description: Diagnostics.Enable_project_compilation, - transpileOptionValue: undefined - }, - { - name: "tsBuildInfoFile", - type: "string", - affectsEmit: true, - isFilePath: true, - paramType: Diagnostics.FILE, - category: Diagnostics.Basic_Options, - description: Diagnostics.Specify_file_to_store_incremental_compilation_information, - transpileOptionValue: undefined - }, - { - name: "removeComments", - type: "boolean", - affectsEmit: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Basic_Options, - description: Diagnostics.Do_not_emit_comments_to_output, - }, - { - name: "noEmit", - type: "boolean", - affectsEmit: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Basic_Options, - description: Diagnostics.Do_not_emit_outputs, - transpileOptionValue: undefined - }, - { - name: "importHelpers", - type: "boolean", - affectsEmit: true, - category: Diagnostics.Basic_Options, - description: Diagnostics.Import_emit_helpers_from_tslib - }, - { - name: "downlevelIteration", - type: "boolean", - affectsEmit: true, - category: Diagnostics.Basic_Options, - description: Diagnostics.Provide_full_support_for_iterables_in_for_of_spread_and_destructuring_when_targeting_ES5_or_ES3 - }, - { - name: "isolatedModules", - type: "boolean", - category: Diagnostics.Basic_Options, - description: Diagnostics.Transpile_each_file_as_a_separate_module_similar_to_ts_transpileModule, - transpileOptionValue: true - }, - - // Strict Type Checks - { - name: "strict", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.Strict_Type_Checking_Options, - description: Diagnostics.Enable_all_strict_type_checking_options - }, - { - name: "noImplicitAny", - type: "boolean", - affectsSemanticDiagnostics: true, - strictFlag: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Strict_Type_Checking_Options, - description: Diagnostics.Raise_error_on_expressions_and_declarations_with_an_implied_any_type - }, - { - name: "strictNullChecks", - type: "boolean", - affectsSemanticDiagnostics: true, - strictFlag: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Strict_Type_Checking_Options, - description: Diagnostics.Enable_strict_null_checks - }, - { - name: "strictFunctionTypes", - type: "boolean", - affectsSemanticDiagnostics: true, - strictFlag: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Strict_Type_Checking_Options, - description: Diagnostics.Enable_strict_checking_of_function_types - }, - { - name: "strictBindCallApply", - type: "boolean", - strictFlag: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Strict_Type_Checking_Options, - description: Diagnostics.Enable_strict_bind_call_and_apply_methods_on_functions - }, - { - name: "strictPropertyInitialization", - type: "boolean", - affectsSemanticDiagnostics: true, - strictFlag: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Strict_Type_Checking_Options, - description: Diagnostics.Enable_strict_checking_of_property_initialization_in_classes - }, - { - name: "noImplicitThis", - type: "boolean", - affectsSemanticDiagnostics: true, - strictFlag: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Strict_Type_Checking_Options, - description: Diagnostics.Raise_error_on_this_expressions_with_an_implied_any_type, - }, - { - name: "alwaysStrict", - type: "boolean", - affectsSourceFile: true, - strictFlag: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Strict_Type_Checking_Options, - description: Diagnostics.Parse_in_strict_mode_and_emit_use_strict_for_each_source_file - }, - - // Additional Checks - { - name: "noUnusedLocals", - type: "boolean", - affectsSemanticDiagnostics: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Additional_Checks, - description: Diagnostics.Report_errors_on_unused_locals, - }, - { - name: "noUnusedParameters", - type: "boolean", - affectsSemanticDiagnostics: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Additional_Checks, - description: Diagnostics.Report_errors_on_unused_parameters, - }, - { - name: "noImplicitReturns", - type: "boolean", - affectsSemanticDiagnostics: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Additional_Checks, - description: Diagnostics.Report_error_when_not_all_code_paths_in_function_return_a_value - }, - { - name: "noFallthroughCasesInSwitch", - type: "boolean", - affectsBindDiagnostics: true, - affectsSemanticDiagnostics: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Additional_Checks, - description: Diagnostics.Report_errors_for_fallthrough_cases_in_switch_statement - }, - - // Module Resolution - { - name: "moduleResolution", - type: createMapFromTemplate({ - node: ModuleResolutionKind.NodeJs, - classic: ModuleResolutionKind.Classic, - }), - affectsModuleResolution: true, - paramType: Diagnostics.STRATEGY, - category: Diagnostics.Module_Resolution_Options, - description: Diagnostics.Specify_module_resolution_strategy_Colon_node_Node_js_or_classic_TypeScript_pre_1_6, - }, - { - name: "baseUrl", - type: "string", - affectsModuleResolution: true, - isFilePath: true, - category: Diagnostics.Module_Resolution_Options, - description: Diagnostics.Base_directory_to_resolve_non_absolute_module_names - }, - { - // this option can only be specified in tsconfig.json - // use type = object to copy the value as-is - name: "paths", - type: "object", - affectsModuleResolution: true, - isTSConfigOnly: true, - category: Diagnostics.Module_Resolution_Options, - description: Diagnostics.A_series_of_entries_which_re_map_imports_to_lookup_locations_relative_to_the_baseUrl, - transpileOptionValue: undefined - }, - { - // this option can only be specified in tsconfig.json - // use type = object to copy the value as-is + type: libMap + }, + affectsModuleResolution: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Basic_Options, + description: Diagnostics.Specify_library_files_to_be_included_in_the_compilation, + transpileOptionValue: undefined + }, + { + name: "allowJs", + type: "boolean", + affectsModuleResolution: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Basic_Options, + description: Diagnostics.Allow_javascript_files_to_be_compiled + }, + { + name: "checkJs", + type: "boolean", + category: Diagnostics.Basic_Options, + description: Diagnostics.Report_errors_in_js_files + }, + { + name: "jsx", + type: createMapFromTemplate({ + "preserve": JsxEmit.Preserve, + "react-native": JsxEmit.ReactNative, + "react": JsxEmit.React + }), + affectsSourceFile: true, + paramType: Diagnostics.KIND, + showInSimplifiedHelpView: true, + category: Diagnostics.Basic_Options, + description: Diagnostics.Specify_JSX_code_generation_Colon_preserve_react_native_or_react, + }, + { + name: "declaration", + shortName: "d", + type: "boolean", + affectsEmit: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Basic_Options, + description: Diagnostics.Generates_corresponding_d_ts_file, + transpileOptionValue: undefined + }, + { + name: "declarationMap", + type: "boolean", + affectsEmit: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Basic_Options, + description: Diagnostics.Generates_a_sourcemap_for_each_corresponding_d_ts_file, + transpileOptionValue: undefined + }, + { + name: "emitDeclarationOnly", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Only_emit_d_ts_declaration_files, + transpileOptionValue: undefined + }, + { + name: "sourceMap", + type: "boolean", + affectsEmit: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Basic_Options, + description: Diagnostics.Generates_corresponding_map_file, + }, + { + name: "outFile", + type: "string", + affectsEmit: true, + isFilePath: true, + paramType: Diagnostics.FILE, + showInSimplifiedHelpView: true, + category: Diagnostics.Basic_Options, + description: Diagnostics.Concatenate_and_emit_output_to_single_file, + transpileOptionValue: undefined + }, + { + name: "outDir", + type: "string", + affectsEmit: true, + isFilePath: true, + paramType: Diagnostics.DIRECTORY, + showInSimplifiedHelpView: true, + category: Diagnostics.Basic_Options, + description: Diagnostics.Redirect_output_structure_to_the_directory, + }, + { + name: "rootDir", + type: "string", + affectsEmit: true, + isFilePath: true, + paramType: Diagnostics.LOCATION, + category: Diagnostics.Basic_Options, + description: Diagnostics.Specify_the_root_directory_of_input_files_Use_to_control_the_output_directory_structure_with_outDir, + }, + { + name: "composite", + type: "boolean", + affectsEmit: true, + isTSConfigOnly: true, + category: Diagnostics.Basic_Options, + description: Diagnostics.Enable_project_compilation, + transpileOptionValue: undefined + }, + { + name: "tsBuildInfoFile", + type: "string", + affectsEmit: true, + isFilePath: true, + paramType: Diagnostics.FILE, + category: Diagnostics.Basic_Options, + description: Diagnostics.Specify_file_to_store_incremental_compilation_information, + transpileOptionValue: undefined + }, + { + name: "removeComments", + type: "boolean", + affectsEmit: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Basic_Options, + description: Diagnostics.Do_not_emit_comments_to_output, + }, + { + name: "noEmit", + type: "boolean", + affectsEmit: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Basic_Options, + description: Diagnostics.Do_not_emit_outputs, + transpileOptionValue: undefined + }, + { + name: "importHelpers", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Basic_Options, + description: Diagnostics.Import_emit_helpers_from_tslib + }, + { + name: "downlevelIteration", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Basic_Options, + description: Diagnostics.Provide_full_support_for_iterables_in_for_of_spread_and_destructuring_when_targeting_ES5_or_ES3 + }, + { + name: "isolatedModules", + type: "boolean", + category: Diagnostics.Basic_Options, + description: Diagnostics.Transpile_each_file_as_a_separate_module_similar_to_ts_transpileModule, + transpileOptionValue: true + }, + // Strict Type Checks + { + name: "strict", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Strict_Type_Checking_Options, + description: Diagnostics.Enable_all_strict_type_checking_options + }, + { + name: "noImplicitAny", + type: "boolean", + affectsSemanticDiagnostics: true, + strictFlag: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Strict_Type_Checking_Options, + description: Diagnostics.Raise_error_on_expressions_and_declarations_with_an_implied_any_type + }, + { + name: "strictNullChecks", + type: "boolean", + affectsSemanticDiagnostics: true, + strictFlag: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Strict_Type_Checking_Options, + description: Diagnostics.Enable_strict_null_checks + }, + { + name: "strictFunctionTypes", + type: "boolean", + affectsSemanticDiagnostics: true, + strictFlag: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Strict_Type_Checking_Options, + description: Diagnostics.Enable_strict_checking_of_function_types + }, + { + name: "strictBindCallApply", + type: "boolean", + strictFlag: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Strict_Type_Checking_Options, + description: Diagnostics.Enable_strict_bind_call_and_apply_methods_on_functions + }, + { + name: "strictPropertyInitialization", + type: "boolean", + affectsSemanticDiagnostics: true, + strictFlag: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Strict_Type_Checking_Options, + description: Diagnostics.Enable_strict_checking_of_property_initialization_in_classes + }, + { + name: "noImplicitThis", + type: "boolean", + affectsSemanticDiagnostics: true, + strictFlag: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Strict_Type_Checking_Options, + description: Diagnostics.Raise_error_on_this_expressions_with_an_implied_any_type, + }, + { + name: "alwaysStrict", + type: "boolean", + affectsSourceFile: true, + strictFlag: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Strict_Type_Checking_Options, + description: Diagnostics.Parse_in_strict_mode_and_emit_use_strict_for_each_source_file + }, + // Additional Checks + { + name: "noUnusedLocals", + type: "boolean", + affectsSemanticDiagnostics: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Additional_Checks, + description: Diagnostics.Report_errors_on_unused_locals, + }, + { + name: "noUnusedParameters", + type: "boolean", + affectsSemanticDiagnostics: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Additional_Checks, + description: Diagnostics.Report_errors_on_unused_parameters, + }, + { + name: "noImplicitReturns", + type: "boolean", + affectsSemanticDiagnostics: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Additional_Checks, + description: Diagnostics.Report_error_when_not_all_code_paths_in_function_return_a_value + }, + { + name: "noFallthroughCasesInSwitch", + type: "boolean", + affectsBindDiagnostics: true, + affectsSemanticDiagnostics: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Additional_Checks, + description: Diagnostics.Report_errors_for_fallthrough_cases_in_switch_statement + }, + // Module Resolution + { + name: "moduleResolution", + type: createMapFromTemplate({ + node: ModuleResolutionKind.NodeJs, + classic: ModuleResolutionKind.Classic, + }), + affectsModuleResolution: true, + paramType: Diagnostics.STRATEGY, + category: Diagnostics.Module_Resolution_Options, + description: Diagnostics.Specify_module_resolution_strategy_Colon_node_Node_js_or_classic_TypeScript_pre_1_6, + }, + { + name: "baseUrl", + type: "string", + affectsModuleResolution: true, + isFilePath: true, + category: Diagnostics.Module_Resolution_Options, + description: Diagnostics.Base_directory_to_resolve_non_absolute_module_names + }, + { + // this option can only be specified in tsconfig.json + // use type = object to copy the value as-is + name: "paths", + type: "object", + affectsModuleResolution: true, + isTSConfigOnly: true, + category: Diagnostics.Module_Resolution_Options, + description: Diagnostics.A_series_of_entries_which_re_map_imports_to_lookup_locations_relative_to_the_baseUrl, + transpileOptionValue: undefined + }, + { + // this option can only be specified in tsconfig.json + // use type = object to copy the value as-is + name: "rootDirs", + type: "list", + isTSConfigOnly: true, + element: { name: "rootDirs", - type: "list", - isTSConfigOnly: true, - element: { - name: "rootDirs", - type: "string", - isFilePath: true - }, - affectsModuleResolution: true, - category: Diagnostics.Module_Resolution_Options, - description: Diagnostics.List_of_root_folders_whose_combined_content_represents_the_structure_of_the_project_at_runtime, - transpileOptionValue: undefined - }, - { - name: "typeRoots", - type: "list", - element: { - name: "typeRoots", - type: "string", - isFilePath: true - }, - affectsModuleResolution: true, - category: Diagnostics.Module_Resolution_Options, - description: Diagnostics.List_of_folders_to_include_type_definitions_from - }, - { - name: "types", - type: "list", - element: { - name: "types", - type: "string" - }, - affectsModuleResolution: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Module_Resolution_Options, - description: Diagnostics.Type_declaration_files_to_be_included_in_compilation, - transpileOptionValue: undefined - }, - { - name: "allowSyntheticDefaultImports", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Module_Resolution_Options, - description: Diagnostics.Allow_default_imports_from_modules_with_no_default_export_This_does_not_affect_code_emit_just_typechecking - }, - { - name: "esModuleInterop", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsEmit: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Module_Resolution_Options, - description: Diagnostics.Enables_emit_interoperability_between_CommonJS_and_ES_Modules_via_creation_of_namespace_objects_for_all_imports_Implies_allowSyntheticDefaultImports - }, - { - name: "preserveSymlinks", - type: "boolean", - category: Diagnostics.Module_Resolution_Options, - description: Diagnostics.Do_not_resolve_the_real_path_of_symlinks, - }, - { - name: "allowUmdGlobalAccess", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Module_Resolution_Options, - description: Diagnostics.Allow_accessing_UMD_globals_from_modules, - }, - - // Source Maps - { - name: "sourceRoot", - type: "string", - affectsEmit: true, - paramType: Diagnostics.LOCATION, - category: Diagnostics.Source_Map_Options, - description: Diagnostics.Specify_the_location_where_debugger_should_locate_TypeScript_files_instead_of_source_locations, - }, - { - name: "mapRoot", - type: "string", - affectsEmit: true, - paramType: Diagnostics.LOCATION, - category: Diagnostics.Source_Map_Options, - description: Diagnostics.Specify_the_location_where_debugger_should_locate_map_files_instead_of_generated_locations, - }, - { - name: "inlineSourceMap", - type: "boolean", - affectsEmit: true, - category: Diagnostics.Source_Map_Options, - description: Diagnostics.Emit_a_single_file_with_source_maps_instead_of_having_a_separate_file - }, - { - name: "inlineSources", - type: "boolean", - affectsEmit: true, - category: Diagnostics.Source_Map_Options, - description: Diagnostics.Emit_the_source_alongside_the_sourcemaps_within_a_single_file_requires_inlineSourceMap_or_sourceMap_to_be_set - }, - - // Experimental - { - name: "experimentalDecorators", - type: "boolean", - category: Diagnostics.Experimental_Options, - description: Diagnostics.Enables_experimental_support_for_ES7_decorators - }, - { - name: "emitDecoratorMetadata", - type: "boolean", - category: Diagnostics.Experimental_Options, - description: Diagnostics.Enables_experimental_support_for_emitting_type_metadata_for_decorators - }, - - // Advanced - { - name: "jsxFactory", - type: "string", - category: Diagnostics.Advanced_Options, - description: Diagnostics.Specify_the_JSX_factory_function_to_use_when_targeting_react_JSX_emit_e_g_React_createElement_or_h - }, - { - name: "resolveJsonModule", - type: "boolean", - category: Diagnostics.Advanced_Options, - description: Diagnostics.Include_modules_imported_with_json_extension - }, - - { - name: "out", - type: "string", - affectsEmit: true, - isFilePath: false, // This is intentionally broken to support compatability with existing tsconfig files - // for correct behaviour, please use outFile - category: Diagnostics.Advanced_Options, - paramType: Diagnostics.FILE, - description: Diagnostics.Deprecated_Use_outFile_instead_Concatenate_and_emit_output_to_single_file, - transpileOptionValue: undefined - }, - { - name: "reactNamespace", - type: "string", - affectsEmit: true, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Deprecated_Use_jsxFactory_instead_Specify_the_object_invoked_for_createElement_when_targeting_react_JSX_emit - }, - { - name: "skipDefaultLibCheck", - type: "boolean", - category: Diagnostics.Advanced_Options, - description: Diagnostics.Deprecated_Use_skipLibCheck_instead_Skip_type_checking_of_default_library_declaration_files - }, - { - name: "charset", type: "string", - category: Diagnostics.Advanced_Options, - description: Diagnostics.The_character_set_of_the_input_files - }, - { - name: "emitBOM", - type: "boolean", - affectsEmit: true, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Emit_a_UTF_8_Byte_Order_Mark_BOM_in_the_beginning_of_output_files - }, - { - name: "newLine", - type: createMapFromTemplate({ - crlf: NewLineKind.CarriageReturnLineFeed, - lf: NewLineKind.LineFeed - }), - affectsEmit: true, - paramType: Diagnostics.NEWLINE, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Specify_the_end_of_line_sequence_to_be_used_when_emitting_files_Colon_CRLF_dos_or_LF_unix, - }, - { - name: "noErrorTruncation", - type: "boolean", - category: Diagnostics.Advanced_Options, - description: Diagnostics.Do_not_truncate_error_messages - }, - { - name: "noLib", - type: "boolean", - affectsModuleResolution: true, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Do_not_include_the_default_library_file_lib_d_ts, - // We are not returning a sourceFile for lib file when asked by the program, - // so pass --noLib to avoid reporting a file not found error. - transpileOptionValue: true - }, - { - name: "noResolve", - type: "boolean", - affectsModuleResolution: true, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Do_not_add_triple_slash_references_or_imported_modules_to_the_list_of_compiled_files, - // We are not doing a full typecheck, we are not resolving the whole context, - // so pass --noResolve to avoid reporting missing file errors. - transpileOptionValue: true - }, - { - name: "stripInternal", - type: "boolean", - affectsEmit: true, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Do_not_emit_declarations_for_code_that_has_an_internal_annotation, - }, - { - name: "disableSizeLimit", - type: "boolean", - affectsSourceFile: true, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Disable_size_limitations_on_JavaScript_projects - }, - { - name: "disableSourceOfProjectReferenceRedirect", - type: "boolean", - isTSConfigOnly: true, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Disable_use_of_source_files_instead_of_declaration_files_from_referenced_projects - }, - { - name: "disableSolutionSearching", - type: "boolean", - isTSConfigOnly: true, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Disable_solution_searching_for_this_project - }, - { - name: "noImplicitUseStrict", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Do_not_emit_use_strict_directives_in_module_output - }, - { - name: "noEmitHelpers", - type: "boolean", - affectsEmit: true, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Do_not_generate_custom_helper_functions_like_extends_in_compiled_output - }, - { - name: "noEmitOnError", - type: "boolean", - affectsEmit: true, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Do_not_emit_outputs_if_any_errors_were_reported, - transpileOptionValue: undefined - }, - { - name: "preserveConstEnums", - type: "boolean", - affectsEmit: true, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Do_not_erase_const_enum_declarations_in_generated_code - }, - { - name: "declarationDir", + isFilePath: true + }, + affectsModuleResolution: true, + category: Diagnostics.Module_Resolution_Options, + description: Diagnostics.List_of_root_folders_whose_combined_content_represents_the_structure_of_the_project_at_runtime, + transpileOptionValue: undefined + }, + { + name: "typeRoots", + type: "list", + element: { + name: "typeRoots", type: "string", - affectsEmit: true, - isFilePath: true, - paramType: Diagnostics.DIRECTORY, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Output_directory_for_generated_declaration_files, - transpileOptionValue: undefined - }, - { - name: "skipLibCheck", - type: "boolean", - category: Diagnostics.Advanced_Options, - description: Diagnostics.Skip_type_checking_of_declaration_files, - }, - { - name: "allowUnusedLabels", - type: "boolean", - affectsBindDiagnostics: true, - affectsSemanticDiagnostics: true, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Do_not_report_errors_on_unused_labels - }, - { - name: "allowUnreachableCode", - type: "boolean", - affectsBindDiagnostics: true, - affectsSemanticDiagnostics: true, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Do_not_report_errors_on_unreachable_code - }, - { - name: "suppressExcessPropertyErrors", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Suppress_excess_property_checks_for_object_literals, - }, - { - name: "suppressImplicitAnyIndexErrors", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Suppress_noImplicitAny_errors_for_indexing_objects_lacking_index_signatures, - }, - { - name: "forceConsistentCasingInFileNames", - type: "boolean", - category: Diagnostics.Advanced_Options, - description: Diagnostics.Disallow_inconsistently_cased_references_to_the_same_file - }, - { - name: "maxNodeModuleJsDepth", - type: "number", - affectsModuleResolution: true, - category: Diagnostics.Advanced_Options, - description: Diagnostics.The_maximum_dependency_depth_to_search_under_node_modules_and_load_JavaScript_files - }, - { - name: "noStrictGenericChecks", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Disable_strict_checking_of_generic_signatures_in_function_types, - }, - { - name: "useDefineForClassFields", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Advanced_Options, - description: Diagnostics.Emit_class_fields_with_Define_instead_of_Set, - }, - { - name: "keyofStringsOnly", - type: "boolean", - category: Diagnostics.Advanced_Options, - description: Diagnostics.Resolve_keyof_to_string_valued_property_names_only_no_numbers_or_symbols, - }, - { - // A list of plugins to load in the language service - name: "plugins", - type: "list", - isTSConfigOnly: true, - element: { - name: "plugin", - type: "object" - }, - description: Diagnostics.List_of_language_service_plugins - }, - ]; - - /* @internal */ - export const semanticDiagnosticsOptionDeclarations: readonly CommandLineOption[] = - optionDeclarations.filter(option => !!option.affectsSemanticDiagnostics); - - /* @internal */ - export const affectsEmitOptionDeclarations: readonly CommandLineOption[] = - optionDeclarations.filter(option => !!option.affectsEmit); - - /* @internal */ - export const moduleResolutionOptionDeclarations: readonly CommandLineOption[] = - optionDeclarations.filter(option => !!option.affectsModuleResolution); - - /* @internal */ - export const sourceFileAffectingCompilerOptions: readonly CommandLineOption[] = optionDeclarations.filter(option => - !!option.affectsSourceFile || !!option.affectsModuleResolution || !!option.affectsBindDiagnostics); - - /* @internal */ - export const transpileOptionValueCompilerOptions: readonly CommandLineOption[] = optionDeclarations.filter(option => - hasProperty(option, "transpileOptionValue")); - - /* @internal */ - export const buildOpts: CommandLineOption[] = [ - ...commonOptionsWithBuild, - { - name: "verbose", - shortName: "v", - category: Diagnostics.Command_line_Options, - description: Diagnostics.Enable_verbose_logging, - type: "boolean" - }, - { - name: "dry", - shortName: "d", - category: Diagnostics.Command_line_Options, - description: Diagnostics.Show_what_would_be_built_or_deleted_if_specified_with_clean, - type: "boolean" - }, - { - name: "force", - shortName: "f", - category: Diagnostics.Command_line_Options, - description: Diagnostics.Build_all_projects_including_those_that_appear_to_be_up_to_date, - type: "boolean" - }, - { - name: "clean", - category: Diagnostics.Command_line_Options, - description: Diagnostics.Delete_the_outputs_of_all_projects, - type: "boolean" - } - ]; - - /* @internal */ - export const typeAcquisitionDeclarations: CommandLineOption[] = [ - { - /* @deprecated typingOptions.enableAutoDiscovery - * Use typeAcquisition.enable instead. - */ - name: "enableAutoDiscovery", - type: "boolean", - }, - { - name: "enable", - type: "boolean", - }, - { + isFilePath: true + }, + affectsModuleResolution: true, + category: Diagnostics.Module_Resolution_Options, + description: Diagnostics.List_of_folders_to_include_type_definitions_from + }, + { + name: "types", + type: "list", + element: { + name: "types", + type: "string" + }, + affectsModuleResolution: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Module_Resolution_Options, + description: Diagnostics.Type_declaration_files_to_be_included_in_compilation, + transpileOptionValue: undefined + }, + { + name: "allowSyntheticDefaultImports", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Module_Resolution_Options, + description: Diagnostics.Allow_default_imports_from_modules_with_no_default_export_This_does_not_affect_code_emit_just_typechecking + }, + { + name: "esModuleInterop", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsEmit: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Module_Resolution_Options, + description: Diagnostics.Enables_emit_interoperability_between_CommonJS_and_ES_Modules_via_creation_of_namespace_objects_for_all_imports_Implies_allowSyntheticDefaultImports + }, + { + name: "preserveSymlinks", + type: "boolean", + category: Diagnostics.Module_Resolution_Options, + description: Diagnostics.Do_not_resolve_the_real_path_of_symlinks, + }, + { + name: "allowUmdGlobalAccess", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Module_Resolution_Options, + description: Diagnostics.Allow_accessing_UMD_globals_from_modules, + }, + // Source Maps + { + name: "sourceRoot", + type: "string", + affectsEmit: true, + paramType: Diagnostics.LOCATION, + category: Diagnostics.Source_Map_Options, + description: Diagnostics.Specify_the_location_where_debugger_should_locate_TypeScript_files_instead_of_source_locations, + }, + { + name: "mapRoot", + type: "string", + affectsEmit: true, + paramType: Diagnostics.LOCATION, + category: Diagnostics.Source_Map_Options, + description: Diagnostics.Specify_the_location_where_debugger_should_locate_map_files_instead_of_generated_locations, + }, + { + name: "inlineSourceMap", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Source_Map_Options, + description: Diagnostics.Emit_a_single_file_with_source_maps_instead_of_having_a_separate_file + }, + { + name: "inlineSources", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Source_Map_Options, + description: Diagnostics.Emit_the_source_alongside_the_sourcemaps_within_a_single_file_requires_inlineSourceMap_or_sourceMap_to_be_set + }, + // Experimental + { + name: "experimentalDecorators", + type: "boolean", + category: Diagnostics.Experimental_Options, + description: Diagnostics.Enables_experimental_support_for_ES7_decorators + }, + { + name: "emitDecoratorMetadata", + type: "boolean", + category: Diagnostics.Experimental_Options, + description: Diagnostics.Enables_experimental_support_for_emitting_type_metadata_for_decorators + }, + // Advanced + { + name: "jsxFactory", + type: "string", + category: Diagnostics.Advanced_Options, + description: Diagnostics.Specify_the_JSX_factory_function_to_use_when_targeting_react_JSX_emit_e_g_React_createElement_or_h + }, + { + name: "resolveJsonModule", + type: "boolean", + category: Diagnostics.Advanced_Options, + description: Diagnostics.Include_modules_imported_with_json_extension + }, + { + name: "out", + type: "string", + affectsEmit: true, + isFilePath: false, + // for correct behaviour, please use outFile + category: Diagnostics.Advanced_Options, + paramType: Diagnostics.FILE, + description: Diagnostics.Deprecated_Use_outFile_instead_Concatenate_and_emit_output_to_single_file, + transpileOptionValue: undefined + }, + { + name: "reactNamespace", + type: "string", + affectsEmit: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Deprecated_Use_jsxFactory_instead_Specify_the_object_invoked_for_createElement_when_targeting_react_JSX_emit + }, + { + name: "skipDefaultLibCheck", + type: "boolean", + category: Diagnostics.Advanced_Options, + description: Diagnostics.Deprecated_Use_skipLibCheck_instead_Skip_type_checking_of_default_library_declaration_files + }, + { + name: "charset", + type: "string", + category: Diagnostics.Advanced_Options, + description: Diagnostics.The_character_set_of_the_input_files + }, + { + name: "emitBOM", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Emit_a_UTF_8_Byte_Order_Mark_BOM_in_the_beginning_of_output_files + }, + { + name: "newLine", + type: createMapFromTemplate({ + crlf: NewLineKind.CarriageReturnLineFeed, + lf: NewLineKind.LineFeed + }), + affectsEmit: true, + paramType: Diagnostics.NEWLINE, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Specify_the_end_of_line_sequence_to_be_used_when_emitting_files_Colon_CRLF_dos_or_LF_unix, + }, + { + name: "noErrorTruncation", + type: "boolean", + category: Diagnostics.Advanced_Options, + description: Diagnostics.Do_not_truncate_error_messages + }, + { + name: "noLib", + type: "boolean", + affectsModuleResolution: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Do_not_include_the_default_library_file_lib_d_ts, + // We are not returning a sourceFile for lib file when asked by the program, + // so pass --noLib to avoid reporting a file not found error. + transpileOptionValue: true + }, + { + name: "noResolve", + type: "boolean", + affectsModuleResolution: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Do_not_add_triple_slash_references_or_imported_modules_to_the_list_of_compiled_files, + // We are not doing a full typecheck, we are not resolving the whole context, + // so pass --noResolve to avoid reporting missing file errors. + transpileOptionValue: true + }, + { + name: "stripInternal", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Do_not_emit_declarations_for_code_that_has_an_internal_annotation, + }, + { + name: "disableSizeLimit", + type: "boolean", + affectsSourceFile: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Disable_size_limitations_on_JavaScript_projects + }, + { + name: "disableSourceOfProjectReferenceRedirect", + type: "boolean", + isTSConfigOnly: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Disable_use_of_source_files_instead_of_declaration_files_from_referenced_projects + }, + { + name: "disableSolutionSearching", + type: "boolean", + isTSConfigOnly: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Disable_solution_searching_for_this_project + }, + { + name: "noImplicitUseStrict", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Do_not_emit_use_strict_directives_in_module_output + }, + { + name: "noEmitHelpers", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Do_not_generate_custom_helper_functions_like_extends_in_compiled_output + }, + { + name: "noEmitOnError", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Do_not_emit_outputs_if_any_errors_were_reported, + transpileOptionValue: undefined + }, + { + name: "preserveConstEnums", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Do_not_erase_const_enum_declarations_in_generated_code + }, + { + name: "declarationDir", + type: "string", + affectsEmit: true, + isFilePath: true, + paramType: Diagnostics.DIRECTORY, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Output_directory_for_generated_declaration_files, + transpileOptionValue: undefined + }, + { + name: "skipLibCheck", + type: "boolean", + category: Diagnostics.Advanced_Options, + description: Diagnostics.Skip_type_checking_of_declaration_files, + }, + { + name: "allowUnusedLabels", + type: "boolean", + affectsBindDiagnostics: true, + affectsSemanticDiagnostics: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Do_not_report_errors_on_unused_labels + }, + { + name: "allowUnreachableCode", + type: "boolean", + affectsBindDiagnostics: true, + affectsSemanticDiagnostics: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Do_not_report_errors_on_unreachable_code + }, + { + name: "suppressExcessPropertyErrors", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Suppress_excess_property_checks_for_object_literals, + }, + { + name: "suppressImplicitAnyIndexErrors", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Suppress_noImplicitAny_errors_for_indexing_objects_lacking_index_signatures, + }, + { + name: "forceConsistentCasingInFileNames", + type: "boolean", + category: Diagnostics.Advanced_Options, + description: Diagnostics.Disallow_inconsistently_cased_references_to_the_same_file + }, + { + name: "maxNodeModuleJsDepth", + type: "number", + affectsModuleResolution: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.The_maximum_dependency_depth_to_search_under_node_modules_and_load_JavaScript_files + }, + { + name: "noStrictGenericChecks", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Disable_strict_checking_of_generic_signatures_in_function_types, + }, + { + name: "useDefineForClassFields", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Emit_class_fields_with_Define_instead_of_Set, + }, + { + name: "keyofStringsOnly", + type: "boolean", + category: Diagnostics.Advanced_Options, + description: Diagnostics.Resolve_keyof_to_string_valued_property_names_only_no_numbers_or_symbols, + }, + { + // A list of plugins to load in the language service + name: "plugins", + type: "list", + isTSConfigOnly: true, + element: { + name: "plugin", + type: "object" + }, + description: Diagnostics.List_of_language_service_plugins + }, +]; +/* @internal */ +export const semanticDiagnosticsOptionDeclarations: readonly CommandLineOption[] = optionDeclarations.filter(option => !!option.affectsSemanticDiagnostics); +/* @internal */ +export const affectsEmitOptionDeclarations: readonly CommandLineOption[] = optionDeclarations.filter(option => !!option.affectsEmit); +/* @internal */ +export const moduleResolutionOptionDeclarations: readonly CommandLineOption[] = optionDeclarations.filter(option => !!option.affectsModuleResolution); +/* @internal */ +export const sourceFileAffectingCompilerOptions: readonly CommandLineOption[] = optionDeclarations.filter(option => !!option.affectsSourceFile || !!option.affectsModuleResolution || !!option.affectsBindDiagnostics); +/* @internal */ +export const transpileOptionValueCompilerOptions: readonly CommandLineOption[] = optionDeclarations.filter(option => hasProperty(option, "transpileOptionValue")); +/* @internal */ +export const buildOpts: CommandLineOption[] = [ + ...commonOptionsWithBuild, + { + name: "verbose", + shortName: "v", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Enable_verbose_logging, + type: "boolean" + }, + { + name: "dry", + shortName: "d", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Show_what_would_be_built_or_deleted_if_specified_with_clean, + type: "boolean" + }, + { + name: "force", + shortName: "f", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Build_all_projects_including_those_that_appear_to_be_up_to_date, + type: "boolean" + }, + { + name: "clean", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Delete_the_outputs_of_all_projects, + type: "boolean" + } +]; +/* @internal */ +export const typeAcquisitionDeclarations: CommandLineOption[] = [ + { + /* @deprecated typingOptions.enableAutoDiscovery + * Use typeAcquisition.enable instead. + */ + name: "enableAutoDiscovery", + type: "boolean", + }, + { + name: "enable", + type: "boolean", + }, + { + name: "include", + type: "list", + element: { name: "include", - type: "list", - element: { - name: "include", - type: "string" - } - }, - { - name: "exclude", - type: "list", - element: { - name: "exclude", - type: "string" - } - } - ]; - - /* @internal */ - export interface OptionsNameMap { - optionsNameMap: Map; - shortOptionNames: Map; - } - - /*@internal*/ - export function createOptionNameMap(optionDeclarations: readonly CommandLineOption[]): OptionsNameMap { - const optionsNameMap = createMap(); - const shortOptionNames = createMap(); - forEach(optionDeclarations, option => { - optionsNameMap.set(option.name.toLowerCase(), option); - if (option.shortName) { - shortOptionNames.set(option.shortName, option.name); - } - }); - - return { optionsNameMap, shortOptionNames }; - } - - let optionsNameMapCache: OptionsNameMap; - - /* @internal */ - export function getOptionsNameMap(): OptionsNameMap { - return optionsNameMapCache || (optionsNameMapCache = createOptionNameMap(optionDeclarations)); - } - - /* @internal */ - export const defaultInitCompilerOptions: CompilerOptions = { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - strict: true, - esModuleInterop: true, - forceConsistentCasingInFileNames: true - }; - - /* @internal */ - export function convertEnableAutoDiscoveryToEnable(typeAcquisition: TypeAcquisition): TypeAcquisition { - // Convert deprecated typingOptions.enableAutoDiscovery to typeAcquisition.enable - if (typeAcquisition && typeAcquisition.enableAutoDiscovery !== undefined && typeAcquisition.enable === undefined) { - return { - enable: typeAcquisition.enableAutoDiscovery, - include: typeAcquisition.include || [], - exclude: typeAcquisition.exclude || [] - }; - } - return typeAcquisition; - } - - /* @internal */ - export function createCompilerDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType): Diagnostic { - return createDiagnosticForInvalidCustomType(opt, createCompilerDiagnostic); - } - - function createDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType, createDiagnostic: (message: DiagnosticMessage, arg0: string, arg1: string) => Diagnostic): Diagnostic { - const namesOfType = arrayFrom(opt.type.keys()).map(key => `'${key}'`).join(", "); - return createDiagnostic(Diagnostics.Argument_for_0_option_must_be_Colon_1, `--${opt.name}`, namesOfType); - } - - /* @internal */ - export function parseCustomTypeOption(opt: CommandLineOptionOfCustomType, value: string, errors: Push) { - return convertJsonOptionOfCustomType(opt, trimString(value || ""), errors); - } - - /* @internal */ - export function parseListTypeOption(opt: CommandLineOptionOfListType, value = "", errors: Push): (string | number)[] | undefined { - value = trimString(value); - if (startsWith(value, "-")) { - return undefined; - } - if (value === "") { - return []; + type: "string" } - const values = value.split(","); - switch (opt.element.type) { - case "number": - return map(values, parseInt); - case "string": - return map(values, v => v || ""); - default: - return mapDefined(values, v => parseCustomTypeOption(opt.element, v, errors)); + }, + { + name: "exclude", + type: "list", + element: { + name: "exclude", + type: "string" } } - - interface OptionsBase { - [option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined; - } - - interface ParseCommandLineWorkerDiagnostics extends DidYouMeanOptionsDiagnostics { - getOptionsNameMap: () => OptionsNameMap; - optionTypeMismatchDiagnostic: DiagnosticMessage; - } - - function getOptionName(option: CommandLineOption) { - return option.name; - } - - function createUnknownOptionError( - unknownOption: string, - diagnostics: DidYouMeanOptionsDiagnostics, - createDiagnostics: (message: DiagnosticMessage, arg0: string, arg1?: string) => Diagnostic, - unknownOptionErrorText?: string - ) { - const possibleOption = getSpellingSuggestion(unknownOption, diagnostics.optionDeclarations, getOptionName); - return possibleOption ? - createDiagnostics(diagnostics.unknownDidYouMeanDiagnostic, unknownOptionErrorText || unknownOption, possibleOption.name) : - createDiagnostics(diagnostics.unknownOptionDiagnostic, unknownOptionErrorText || unknownOption); - } - - function parseCommandLineWorker( - diagnostics: ParseCommandLineWorkerDiagnostics, - commandLine: readonly string[], - readFile?: (path: string) => string | undefined) { - const options = {} as OptionsBase; - let watchOptions: WatchOptions | undefined; - const fileNames: string[] = []; - const errors: Diagnostic[] = []; - - parseStrings(commandLine); +]; +/* @internal */ +export interface OptionsNameMap { + optionsNameMap: ts.Map; + shortOptionNames: ts.Map; +} +/*@internal*/ +export function createOptionNameMap(optionDeclarations: readonly CommandLineOption[]): OptionsNameMap { + const optionsNameMap = createMap(); + const shortOptionNames = createMap(); + forEach(optionDeclarations, option => { + optionsNameMap.set(option.name.toLowerCase(), option); + if (option.shortName) { + shortOptionNames.set(option.shortName, option.name); + } + }); + return { optionsNameMap, shortOptionNames }; +} +let optionsNameMapCache: OptionsNameMap; +/* @internal */ +export function getOptionsNameMap(): OptionsNameMap { + return optionsNameMapCache || (optionsNameMapCache = createOptionNameMap(optionDeclarations)); +} +/* @internal */ +export const defaultInitCompilerOptions: CompilerOptions = { + module: ModuleKind.CommonJS, + target: ScriptTarget.ES5, + strict: true, + esModuleInterop: true, + forceConsistentCasingInFileNames: true +}; +/* @internal */ +export function convertEnableAutoDiscoveryToEnable(typeAcquisition: TypeAcquisition): TypeAcquisition { + // Convert deprecated typingOptions.enableAutoDiscovery to typeAcquisition.enable + if (typeAcquisition && typeAcquisition.enableAutoDiscovery !== undefined && typeAcquisition.enable === undefined) { return { - options, - watchOptions, - fileNames, - errors + enable: typeAcquisition.enableAutoDiscovery, + include: typeAcquisition.include || [], + exclude: typeAcquisition.exclude || [] }; - - function parseStrings(args: readonly string[]) { - let i = 0; - while (i < args.length) { - const s = args[i]; - i++; - if (s.charCodeAt(0) === CharacterCodes.at) { - parseResponseFile(s.slice(1)); + } + return typeAcquisition; +} +/* @internal */ +export function createCompilerDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType): Diagnostic { + return createDiagnosticForInvalidCustomType(opt, createCompilerDiagnostic); +} +function createDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType, createDiagnostic: (message: DiagnosticMessage, arg0: string, arg1: string) => Diagnostic): Diagnostic { + const namesOfType = arrayFrom(opt.type.keys()).map(key => `'${key}'`).join(", "); + return createDiagnostic(Diagnostics.Argument_for_0_option_must_be_Colon_1, `--${opt.name}`, namesOfType); +} +/* @internal */ +export function parseCustomTypeOption(opt: CommandLineOptionOfCustomType, value: string, errors: Push) { + return convertJsonOptionOfCustomType(opt, trimString(value || ""), errors); +} +/* @internal */ +export function parseListTypeOption(opt: CommandLineOptionOfListType, value = "", errors: Push): (string | number)[] | undefined { + value = trimString(value); + if (startsWith(value, "-")) { + return undefined; + } + if (value === "") { + return []; + } + const values = value.split(","); + switch (opt.element.type) { + case "number": + return map(values, parseInt); + case "string": + return map(values, v => v || ""); + default: + return mapDefined(values, v => parseCustomTypeOption((opt.element), v, errors)); + } +} +interface OptionsBase { + [option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined; +} +interface ParseCommandLineWorkerDiagnostics extends DidYouMeanOptionsDiagnostics { + getOptionsNameMap: () => OptionsNameMap; + optionTypeMismatchDiagnostic: DiagnosticMessage; +} +function getOptionName(option: CommandLineOption) { + return option.name; +} +function createUnknownOptionError(unknownOption: string, diagnostics: DidYouMeanOptionsDiagnostics, createDiagnostics: (message: DiagnosticMessage, arg0: string, arg1?: string) => Diagnostic, unknownOptionErrorText?: string) { + const possibleOption = getSpellingSuggestion(unknownOption, diagnostics.optionDeclarations, getOptionName); + return possibleOption ? + createDiagnostics(diagnostics.unknownDidYouMeanDiagnostic, unknownOptionErrorText || unknownOption, possibleOption.name) : + createDiagnostics(diagnostics.unknownOptionDiagnostic, unknownOptionErrorText || unknownOption); +} +function parseCommandLineWorker(diagnostics: ParseCommandLineWorkerDiagnostics, commandLine: readonly string[], readFile?: (path: string) => string | undefined) { + const options = {} as OptionsBase; + let watchOptions: WatchOptions | undefined; + const fileNames: string[] = []; + const errors: Diagnostic[] = []; + parseStrings(commandLine); + return { + options, + watchOptions, + fileNames, + errors + }; + function parseStrings(args: readonly string[]) { + let i = 0; + while (i < args.length) { + const s = args[i]; + i++; + if (s.charCodeAt(0) === CharacterCodes.at) { + parseResponseFile(s.slice(1)); + } + else if (s.charCodeAt(0) === CharacterCodes.minus) { + const inputOptionName = s.slice(s.charCodeAt(1) === CharacterCodes.minus ? 2 : 1); + const opt = getOptionDeclarationFromName(diagnostics.getOptionsNameMap, inputOptionName, /*allowShort*/ true); + if (opt) { + i = parseOptionValue(args, i, diagnostics, opt, options, errors); } - else if (s.charCodeAt(0) === CharacterCodes.minus) { - const inputOptionName = s.slice(s.charCodeAt(1) === CharacterCodes.minus ? 2 : 1); - const opt = getOptionDeclarationFromName(diagnostics.getOptionsNameMap, inputOptionName, /*allowShort*/ true); - if (opt) { - i = parseOptionValue(args, i, diagnostics, opt, options, errors); + else { + const watchOpt = getOptionDeclarationFromName(watchOptionsDidYouMeanDiagnostics.getOptionsNameMap, inputOptionName, /*allowShort*/ true); + if (watchOpt) { + i = parseOptionValue(args, i, watchOptionsDidYouMeanDiagnostics, watchOpt, watchOptions || (watchOptions = {}), errors); } else { - const watchOpt = getOptionDeclarationFromName(watchOptionsDidYouMeanDiagnostics.getOptionsNameMap, inputOptionName, /*allowShort*/ true); - if (watchOpt) { - i = parseOptionValue(args, i, watchOptionsDidYouMeanDiagnostics, watchOpt, watchOptions || (watchOptions = {}), errors); - } - else { - errors.push(createUnknownOptionError(inputOptionName, diagnostics, createCompilerDiagnostic, s)); - } + errors.push(createUnknownOptionError(inputOptionName, diagnostics, createCompilerDiagnostic, s)); } } - else { - fileNames.push(s); - } } - } - - function parseResponseFile(fileName: string) { - const text = readFile ? readFile(fileName) : sys.readFile(fileName); - - if (!text) { - errors.push(createCompilerDiagnostic(Diagnostics.File_0_not_found, fileName)); - return; + else { + fileNames.push(s); } - - const args: string[] = []; - let pos = 0; - while (true) { - while (pos < text.length && text.charCodeAt(pos) <= CharacterCodes.space) pos++; - if (pos >= text.length) break; - const start = pos; - if (text.charCodeAt(start) === CharacterCodes.doubleQuote) { + } + } + function parseResponseFile(fileName: string) { + const text = readFile ? readFile(fileName) : sys.readFile(fileName); + if (!text) { + errors.push(createCompilerDiagnostic(Diagnostics.File_0_not_found, fileName)); + return; + } + const args: string[] = []; + let pos = 0; + while (true) { + while (pos < text.length && text.charCodeAt(pos) <= CharacterCodes.space) + pos++; + if (pos >= text.length) + break; + const start = pos; + if (text.charCodeAt(start) === CharacterCodes.doubleQuote) { + pos++; + while (pos < text.length && text.charCodeAt(pos) !== CharacterCodes.doubleQuote) + pos++; + if (pos < text.length) { + args.push(text.substring(start + 1, pos)); pos++; - while (pos < text.length && text.charCodeAt(pos) !== CharacterCodes.doubleQuote) pos++; - if (pos < text.length) { - args.push(text.substring(start + 1, pos)); - pos++; - } - else { - errors.push(createCompilerDiagnostic(Diagnostics.Unterminated_quoted_string_in_response_file_0, fileName)); - } } else { - while (text.charCodeAt(pos) > CharacterCodes.space) pos++; - args.push(text.substring(start, pos)); + errors.push(createCompilerDiagnostic(Diagnostics.Unterminated_quoted_string_in_response_file_0, fileName)); } } - parseStrings(args); + else { + while (text.charCodeAt(pos) > CharacterCodes.space) + pos++; + args.push(text.substring(start, pos)); + } } + parseStrings(args); } - - function parseOptionValue( - args: readonly string[], - i: number, - diagnostics: ParseCommandLineWorkerDiagnostics, - opt: CommandLineOption, - options: OptionsBase, - errors: Diagnostic[] - ) { - if (opt.isTSConfigOnly) { - errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file, opt.name)); +} +function parseOptionValue(args: readonly string[], i: number, diagnostics: ParseCommandLineWorkerDiagnostics, opt: CommandLineOption, options: OptionsBase, errors: Diagnostic[]) { + if (opt.isTSConfigOnly) { + errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file, opt.name)); + } + else { + // Check to see if no argument was provided (e.g. "--locale" is the last command-line argument). + if (!args[i] && opt.type !== "boolean") { + errors.push(createCompilerDiagnostic(diagnostics.optionTypeMismatchDiagnostic, opt.name, getCompilerOptionValueTypeString(opt))); } - else { - // Check to see if no argument was provided (e.g. "--locale" is the last command-line argument). - if (!args[i] && opt.type !== "boolean") { - errors.push(createCompilerDiagnostic(diagnostics.optionTypeMismatchDiagnostic, opt.name, getCompilerOptionValueTypeString(opt))); - } - - switch (opt.type) { - case "number": - options[opt.name] = parseInt(args[i]); - i++; - break; - case "boolean": - // boolean flag has optional value true, false, others - const optValue = args[i]; - options[opt.name] = optValue !== "false"; - // consume next argument as boolean flag value - if (optValue === "false" || optValue === "true") { - i++; - } - break; - case "string": - options[opt.name] = args[i] || ""; + switch (opt.type) { + case "number": + options[opt.name] = parseInt(args[i]); + i++; + break; + case "boolean": + // boolean flag has optional value true, false, others + const optValue = args[i]; + options[opt.name] = optValue !== "false"; + // consume next argument as boolean flag value + if (optValue === "false" || optValue === "true") { i++; - break; - case "list": - const result = parseListTypeOption(opt, args[i], errors); - options[opt.name] = result || []; - if (result) { - i++; - } - break; - // If not a primitive, the possible types are specified in what is effectively a map of options. - default: - options[opt.name] = parseCustomTypeOption(opt, args[i], errors); + } + break; + case "string": + options[opt.name] = args[i] || ""; + i++; + break; + case "list": + const result = parseListTypeOption(opt, args[i], errors); + options[opt.name] = result || []; + if (result) { i++; - break; - } - } - return i; - } - - const compilerOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = { - getOptionsNameMap, - optionDeclarations, - unknownOptionDiagnostic: Diagnostics.Unknown_compiler_option_0, - unknownDidYouMeanDiagnostic: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1, - optionTypeMismatchDiagnostic: Diagnostics.Compiler_option_0_expects_an_argument - }; - export function parseCommandLine(commandLine: readonly string[], readFile?: (path: string) => string | undefined): ParsedCommandLine { - return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile); - } - - /** @internal */ - export function getOptionFromName(optionName: string, allowShort?: boolean): CommandLineOption | undefined { - return getOptionDeclarationFromName(getOptionsNameMap, optionName, allowShort); - } - - function getOptionDeclarationFromName(getOptionNameMap: () => OptionsNameMap, optionName: string, allowShort = false): CommandLineOption | undefined { - optionName = optionName.toLowerCase(); - const { optionsNameMap, shortOptionNames } = getOptionNameMap(); - // Try to translate short option names to their full equivalents. - if (allowShort) { - const short = shortOptionNames.get(optionName); - if (short !== undefined) { - optionName = short; - } - } - return optionsNameMap.get(optionName); - } - - /*@internal*/ - export interface ParsedBuildCommand { - buildOptions: BuildOptions; - watchOptions: WatchOptions | undefined; - projects: string[]; - errors: Diagnostic[]; - } - - let buildOptionsNameMapCache: OptionsNameMap; - function getBuildOptionsNameMap(): OptionsNameMap { - return buildOptionsNameMapCache || (buildOptionsNameMapCache = createOptionNameMap(buildOpts)); - } - - const buildOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = { - getOptionsNameMap: getBuildOptionsNameMap, - optionDeclarations: buildOpts, - unknownOptionDiagnostic: Diagnostics.Unknown_build_option_0, - unknownDidYouMeanDiagnostic: Diagnostics.Unknown_build_option_0_Did_you_mean_1, - optionTypeMismatchDiagnostic: Diagnostics.Build_option_0_requires_a_value_of_type_1 - }; - - /*@internal*/ - export function parseBuildCommand(args: readonly string[]): ParsedBuildCommand { - const { options, watchOptions, fileNames: projects, errors } = parseCommandLineWorker( - buildOptionsDidYouMeanDiagnostics, - args - ); - const buildOptions = options as BuildOptions; - - if (projects.length === 0) { - // tsc -b invoked with no extra arguments; act as if invoked with "tsc -b ." - projects.push("."); - } - - // Nonsensical combinations - if (buildOptions.clean && buildOptions.force) { - errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "force")); - } - if (buildOptions.clean && buildOptions.verbose) { - errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "verbose")); - } - if (buildOptions.clean && buildOptions.watch) { - errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "watch")); - } - if (buildOptions.watch && buildOptions.dry) { - errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "watch", "dry")); + } + break; + // If not a primitive, the possible types are specified in what is effectively a map of options. + default: + options[opt.name] = parseCustomTypeOption((opt), args[i], errors); + i++; + break; } - - return { buildOptions, watchOptions, projects, errors }; - } - - /* @internal */ - export function getDiagnosticText(_message: DiagnosticMessage, ..._args: any[]): string { - const diagnostic = createCompilerDiagnostic.apply(undefined, arguments); - return diagnostic.messageText; - } - - export type DiagnosticReporter = (diagnostic: Diagnostic) => void; - /** - * Reports config file diagnostics - */ - export interface ConfigFileDiagnosticsReporter { - /** - * Reports unrecoverable error when parsing config file - */ - onUnRecoverableConfigFileDiagnostic: DiagnosticReporter; - } - - /** - * Interface extending ParseConfigHost to support ParseConfigFile that reads config file and reports errors - */ - export interface ParseConfigFileHost extends ParseConfigHost, ConfigFileDiagnosticsReporter { - getCurrentDirectory(): string; } - - /** - * Reads the config file, reports errors if any and exits if the config file cannot be found - */ - export function getParsedCommandLineOfConfigFile( - configFileName: string, - optionsToExtend: CompilerOptions, - host: ParseConfigFileHost, - extendedConfigCache?: Map, - watchOptionsToExtend?: WatchOptions - ): ParsedCommandLine | undefined { - let configFileText: string | undefined; - try { - configFileText = host.readFile(configFileName); - } - catch (e) { - const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message); - host.onUnRecoverableConfigFileDiagnostic(error); - return undefined; - } - if (!configFileText) { - const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName); - host.onUnRecoverableConfigFileDiagnostic(error); - return undefined; + return i; +} +const compilerOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = { + getOptionsNameMap, + optionDeclarations, + unknownOptionDiagnostic: Diagnostics.Unknown_compiler_option_0, + unknownDidYouMeanDiagnostic: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1, + optionTypeMismatchDiagnostic: Diagnostics.Compiler_option_0_expects_an_argument +}; +export function parseCommandLine(commandLine: readonly string[], readFile?: (path: string) => string | undefined): ParsedCommandLine { + return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile); +} +/** @internal */ +export function getOptionFromName(optionName: string, allowShort?: boolean): CommandLineOption | undefined { + return getOptionDeclarationFromName(getOptionsNameMap, optionName, allowShort); +} +function getOptionDeclarationFromName(getOptionNameMap: () => OptionsNameMap, optionName: string, allowShort = false): CommandLineOption | undefined { + optionName = optionName.toLowerCase(); + const { optionsNameMap, shortOptionNames } = getOptionNameMap(); + // Try to translate short option names to their full equivalents. + if (allowShort) { + const short = shortOptionNames.get(optionName); + if (short !== undefined) { + optionName = short; } - - const result = parseJsonText(configFileName, configFileText); - const cwd = host.getCurrentDirectory(); - result.path = toPath(configFileName, cwd, createGetCanonicalFileName(host.useCaseSensitiveFileNames)); - result.resolvedPath = result.path; - result.originalFileName = result.fileName; - return parseJsonSourceFileConfigFileContent( - result, - host, - getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), - optionsToExtend, - getNormalizedAbsolutePath(configFileName, cwd), - /*resolutionStack*/ undefined, - /*extraFileExtension*/ undefined, - extendedConfigCache, - watchOptionsToExtend - ); - } - - /** - * Read tsconfig.json file - * @param fileName The path to the config file - */ - export function readConfigFile(fileName: string, readFile: (path: string) => string | undefined): { config?: any; error?: Diagnostic } { - const textOrDiagnostic = tryReadFile(fileName, readFile); - return isString(textOrDiagnostic) ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic }; - } - - /** - * Parse the text of the tsconfig.json file - * @param fileName The path to the config file - * @param jsonText The text of the config file - */ - export function parseConfigFileTextToJson(fileName: string, jsonText: string): { config?: any; error?: Diagnostic } { - const jsonSourceFile = parseJsonText(fileName, jsonText); - return { - config: convertToObject(jsonSourceFile, jsonSourceFile.parseDiagnostics), - error: jsonSourceFile.parseDiagnostics.length ? jsonSourceFile.parseDiagnostics[0] : undefined - }; } - + return optionsNameMap.get(optionName); +} +/*@internal*/ +export interface ParsedBuildCommand { + buildOptions: BuildOptions; + watchOptions: WatchOptions | undefined; + projects: string[]; + errors: Diagnostic[]; +} +let buildOptionsNameMapCache: OptionsNameMap; +function getBuildOptionsNameMap(): OptionsNameMap { + return buildOptionsNameMapCache || (buildOptionsNameMapCache = createOptionNameMap(buildOpts)); +} +const buildOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = { + getOptionsNameMap: getBuildOptionsNameMap, + optionDeclarations: buildOpts, + unknownOptionDiagnostic: Diagnostics.Unknown_build_option_0, + unknownDidYouMeanDiagnostic: Diagnostics.Unknown_build_option_0_Did_you_mean_1, + optionTypeMismatchDiagnostic: Diagnostics.Build_option_0_requires_a_value_of_type_1 +}; +/*@internal*/ +export function parseBuildCommand(args: readonly string[]): ParsedBuildCommand { + const { options, watchOptions, fileNames: projects, errors } = parseCommandLineWorker(buildOptionsDidYouMeanDiagnostics, args); + const buildOptions = (options as BuildOptions); + if (projects.length === 0) { + // tsc -b invoked with no extra arguments; act as if invoked with "tsc -b ." + projects.push("."); + } + // Nonsensical combinations + if (buildOptions.clean && buildOptions.force) { + errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "force")); + } + if (buildOptions.clean && buildOptions.verbose) { + errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "verbose")); + } + if (buildOptions.clean && buildOptions.watch) { + errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "watch")); + } + if (buildOptions.watch && buildOptions.dry) { + errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "watch", "dry")); + } + return { buildOptions, watchOptions, projects, errors }; +} +/* @internal */ +export function getDiagnosticText(_message: DiagnosticMessage, ..._args: any[]): string { + const diagnostic = createCompilerDiagnostic.apply(undefined, arguments); + return diagnostic.messageText; +} +export type DiagnosticReporter = (diagnostic: Diagnostic) => void; +/** + * Reports config file diagnostics + */ +export interface ConfigFileDiagnosticsReporter { /** - * Read tsconfig.json file - * @param fileName The path to the config file + * Reports unrecoverable error when parsing config file */ - export function readJsonConfigFile(fileName: string, readFile: (path: string) => string | undefined): TsConfigSourceFile { - const textOrDiagnostic = tryReadFile(fileName, readFile); - return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : { parseDiagnostics: [textOrDiagnostic] }; - } - - function tryReadFile(fileName: string, readFile: (path: string) => string | undefined): string | Diagnostic { - let text: string | undefined; - try { - text = readFile(fileName); - } - catch (e) { - return createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message); - } - return text === undefined ? createCompilerDiagnostic(Diagnostics.The_specified_path_does_not_exist_Colon_0, fileName) : text; + onUnRecoverableConfigFileDiagnostic: DiagnosticReporter; +} +/** + * Interface extending ParseConfigHost to support ParseConfigFile that reads config file and reports errors + */ +export interface ParseConfigFileHost extends ParseConfigHost, ConfigFileDiagnosticsReporter { + getCurrentDirectory(): string; +} +/** + * Reads the config file, reports errors if any and exits if the config file cannot be found + */ +export function getParsedCommandLineOfConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: ParseConfigFileHost, extendedConfigCache?: ts.Map, watchOptionsToExtend?: WatchOptions): ParsedCommandLine | undefined { + let configFileText: string | undefined; + try { + configFileText = host.readFile(configFileName); + } + catch (e) { + const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message); + host.onUnRecoverableConfigFileDiagnostic(error); + return undefined; } - - function commandLineOptionsToMap(options: readonly CommandLineOption[]) { - return arrayToMap(options, getOptionName); + if (!configFileText) { + const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName); + host.onUnRecoverableConfigFileDiagnostic(error); + return undefined; } - - const typeAcquisitionDidYouMeanDiagnostics: DidYouMeanOptionsDiagnostics = { - optionDeclarations: typeAcquisitionDeclarations, - unknownOptionDiagnostic: Diagnostics.Unknown_type_acquisition_option_0, - unknownDidYouMeanDiagnostic: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1, - }; - - let watchOptionsNameMapCache: OptionsNameMap; - function getWatchOptionsNameMap(): OptionsNameMap { - return watchOptionsNameMapCache || (watchOptionsNameMapCache = createOptionNameMap(optionsForWatch)); - } - const watchOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = { - getOptionsNameMap: getWatchOptionsNameMap, - optionDeclarations: optionsForWatch, - unknownOptionDiagnostic: Diagnostics.Unknown_watch_option_0, - unknownDidYouMeanDiagnostic: Diagnostics.Unknown_watch_option_0_Did_you_mean_1, - optionTypeMismatchDiagnostic: Diagnostics.Watch_option_0_requires_a_value_of_type_1 + const result = parseJsonText(configFileName, configFileText); + const cwd = host.getCurrentDirectory(); + result.path = toPath(configFileName, cwd, createGetCanonicalFileName(host.useCaseSensitiveFileNames)); + result.resolvedPath = result.path; + result.originalFileName = result.fileName; + return parseJsonSourceFileConfigFileContent(result, host, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd), + /*resolutionStack*/ undefined, + /*extraFileExtension*/ undefined, extendedConfigCache, watchOptionsToExtend); +} +/** + * Read tsconfig.json file + * @param fileName The path to the config file + */ +export function readConfigFile(fileName: string, readFile: (path: string) => string | undefined): { + config?: any; + error?: Diagnostic; +} { + const textOrDiagnostic = tryReadFile(fileName, readFile); + return isString(textOrDiagnostic) ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic }; +} +/** + * Parse the text of the tsconfig.json file + * @param fileName The path to the config file + * @param jsonText The text of the config file + */ +export function parseConfigFileTextToJson(fileName: string, jsonText: string): { + config?: any; + error?: Diagnostic; +} { + const jsonSourceFile = parseJsonText(fileName, jsonText); + return { + config: convertToObject(jsonSourceFile, jsonSourceFile.parseDiagnostics), + error: jsonSourceFile.parseDiagnostics.length ? jsonSourceFile.parseDiagnostics[0] : undefined }; - - let commandLineCompilerOptionsMapCache: Map; - function getCommandLineCompilerOptionsMap() { - return commandLineCompilerOptionsMapCache || (commandLineCompilerOptionsMapCache = commandLineOptionsToMap(optionDeclarations)); - } - let commandLineWatchOptionsMapCache: Map; - function getCommandLineWatchOptionsMap() { - return commandLineWatchOptionsMapCache || (commandLineWatchOptionsMapCache = commandLineOptionsToMap(optionsForWatch)); - } - let commandLineTypeAcquisitionMapCache: Map; - function getCommandLineTypeAcquisitionMap() { - return commandLineTypeAcquisitionMapCache || (commandLineTypeAcquisitionMapCache = commandLineOptionsToMap(typeAcquisitionDeclarations)); - } - - let _tsconfigRootOptions: TsConfigOnlyOption; - function getTsconfigRootOptionsMap() { - if (_tsconfigRootOptions === undefined) { - _tsconfigRootOptions = { - name: undefined!, // should never be needed since this is root - type: "object", - elementOptions: commandLineOptionsToMap([ - { - name: "compilerOptions", - type: "object", - elementOptions: getCommandLineCompilerOptionsMap(), - extraKeyDiagnostics: compilerOptionsDidYouMeanDiagnostics, - }, - { - name: "watchOptions", - type: "object", - elementOptions: getCommandLineWatchOptionsMap(), - extraKeyDiagnostics: watchOptionsDidYouMeanDiagnostics, - }, - { - name: "typingOptions", - type: "object", - elementOptions: getCommandLineTypeAcquisitionMap(), - extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics, - }, - { - name: "typeAcquisition", - type: "object", - elementOptions: getCommandLineTypeAcquisitionMap(), - extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics - }, - { - name: "extends", - type: "string" - }, - { +} +/** + * Read tsconfig.json file + * @param fileName The path to the config file + */ +export function readJsonConfigFile(fileName: string, readFile: (path: string) => string | undefined): TsConfigSourceFile { + const textOrDiagnostic = tryReadFile(fileName, readFile); + return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : { parseDiagnostics: [textOrDiagnostic] }; +} +function tryReadFile(fileName: string, readFile: (path: string) => string | undefined): string | Diagnostic { + let text: string | undefined; + try { + text = readFile(fileName); + } + catch (e) { + return createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message); + } + return text === undefined ? createCompilerDiagnostic(Diagnostics.The_specified_path_does_not_exist_Colon_0, fileName) : text; +} +function commandLineOptionsToMap(options: readonly CommandLineOption[]) { + return arrayToMap(options, getOptionName); +} +const typeAcquisitionDidYouMeanDiagnostics: DidYouMeanOptionsDiagnostics = { + optionDeclarations: typeAcquisitionDeclarations, + unknownOptionDiagnostic: Diagnostics.Unknown_type_acquisition_option_0, + unknownDidYouMeanDiagnostic: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1, +}; +let watchOptionsNameMapCache: OptionsNameMap; +function getWatchOptionsNameMap(): OptionsNameMap { + return watchOptionsNameMapCache || (watchOptionsNameMapCache = createOptionNameMap(optionsForWatch)); +} +const watchOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = { + getOptionsNameMap: getWatchOptionsNameMap, + optionDeclarations: optionsForWatch, + unknownOptionDiagnostic: Diagnostics.Unknown_watch_option_0, + unknownDidYouMeanDiagnostic: Diagnostics.Unknown_watch_option_0_Did_you_mean_1, + optionTypeMismatchDiagnostic: Diagnostics.Watch_option_0_requires_a_value_of_type_1 +}; +let commandLineCompilerOptionsMapCache: ts.Map; +function getCommandLineCompilerOptionsMap() { + return commandLineCompilerOptionsMapCache || (commandLineCompilerOptionsMapCache = commandLineOptionsToMap(optionDeclarations)); +} +let commandLineWatchOptionsMapCache: ts.Map; +function getCommandLineWatchOptionsMap() { + return commandLineWatchOptionsMapCache || (commandLineWatchOptionsMapCache = commandLineOptionsToMap(optionsForWatch)); +} +let commandLineTypeAcquisitionMapCache: ts.Map; +function getCommandLineTypeAcquisitionMap() { + return commandLineTypeAcquisitionMapCache || (commandLineTypeAcquisitionMapCache = commandLineOptionsToMap(typeAcquisitionDeclarations)); +} +let _tsconfigRootOptions: TsConfigOnlyOption; +function getTsconfigRootOptionsMap() { + if (_tsconfigRootOptions === undefined) { + _tsconfigRootOptions = { + name: undefined!, + type: "object", + elementOptions: commandLineOptionsToMap([ + { + name: "compilerOptions", + type: "object", + elementOptions: getCommandLineCompilerOptionsMap(), + extraKeyDiagnostics: compilerOptionsDidYouMeanDiagnostics, + }, + { + name: "watchOptions", + type: "object", + elementOptions: getCommandLineWatchOptionsMap(), + extraKeyDiagnostics: watchOptionsDidYouMeanDiagnostics, + }, + { + name: "typingOptions", + type: "object", + elementOptions: getCommandLineTypeAcquisitionMap(), + extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics, + }, + { + name: "typeAcquisition", + type: "object", + elementOptions: getCommandLineTypeAcquisitionMap(), + extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics + }, + { + name: "extends", + type: "string" + }, + { + name: "references", + type: "list", + element: { name: "references", - type: "list", - element: { - name: "references", - type: "object" - } - }, - { + type: "object" + } + }, + { + name: "files", + type: "list", + element: { name: "files", - type: "list", - element: { - name: "files", - type: "string" - } - }, - { + type: "string" + } + }, + { + name: "include", + type: "list", + element: { name: "include", - type: "list", - element: { - name: "include", - type: "string" - } - }, - { + type: "string" + } + }, + { + name: "exclude", + type: "list", + element: { name: "exclude", - type: "list", - element: { - name: "exclude", - type: "string" - } - }, - compileOnSaveCommandLineOption - ]) - }; - } - return _tsconfigRootOptions; - } - - /*@internal*/ - interface JsonConversionNotifier { - /** - * Notifies parent option object is being set with the optionKey and a valid optionValue - * Currently it notifies only if there is element with type object (parentOption) and - * has element's option declarations map associated with it - * @param parentOption parent option name in which the option and value are being set - * @param option option declaration which is being set with the value - * @param value value of the option - */ - onSetValidOptionKeyValueInParent(parentOption: string, option: CommandLineOption, value: CompilerOptionsValue): void; - /** - * Notify when valid root key value option is being set - * @param key option key - * @param keyNode node corresponding to node in the source file - * @param value computed value of the key - * @param ValueNode node corresponding to value in the source file - */ - onSetValidOptionKeyValueInRoot(key: string, keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression): void; - /** - * Notify when unknown root key value option is being set - * @param key option key - * @param keyNode node corresponding to node in the source file - * @param value computed value of the key - * @param ValueNode node corresponding to value in the source file - */ - onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression): void; + type: "string" + } + }, + compileOnSaveCommandLineOption + ]) + }; } - + return _tsconfigRootOptions; +} +/*@internal*/ +interface JsonConversionNotifier { /** - * Convert the json syntax tree into the json value + * Notifies parent option object is being set with the optionKey and a valid optionValue + * Currently it notifies only if there is element with type object (parentOption) and + * has element's option declarations map associated with it + * @param parentOption parent option name in which the option and value are being set + * @param option option declaration which is being set with the value + * @param value value of the option */ - export function convertToObject(sourceFile: JsonSourceFile, errors: Push): any { - return convertToObjectWorker(sourceFile, errors, /*returnValue*/ true, /*knownRootOptions*/ undefined, /*jsonConversionNotifier*/ undefined); - } - + onSetValidOptionKeyValueInParent(parentOption: string, option: CommandLineOption, value: CompilerOptionsValue): void; /** - * Convert the json syntax tree into the json value and report errors - * This returns the json value (apart from checking errors) only if returnValue provided is true. - * Otherwise it just checks the errors and returns undefined + * Notify when valid root key value option is being set + * @param key option key + * @param keyNode node corresponding to node in the source file + * @param value computed value of the key + * @param ValueNode node corresponding to value in the source file */ - /*@internal*/ - export function convertToObjectWorker( - sourceFile: JsonSourceFile, - errors: Push, - returnValue: boolean, - knownRootOptions: CommandLineOption | undefined, - jsonConversionNotifier: JsonConversionNotifier | undefined): any { - if (!sourceFile.statements.length) { - return returnValue ? {} : undefined; - } - - return convertPropertyValueToJson(sourceFile.statements[0].expression, knownRootOptions); - - function isRootOptionMap(knownOptions: Map | undefined) { - return knownRootOptions && (knownRootOptions as TsConfigOnlyOption).elementOptions === knownOptions; - } - - function convertObjectLiteralExpressionToJson( - node: ObjectLiteralExpression, - knownOptions: Map | undefined, - extraKeyDiagnostics: DidYouMeanOptionsDiagnostics | undefined, - parentOption: string | undefined - ): any { - const result: any = returnValue ? {} : undefined; - for (const element of node.properties) { - if (element.kind !== SyntaxKind.PropertyAssignment) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element, Diagnostics.Property_assignment_expected)); - continue; - } - - if (element.questionToken) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.questionToken, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, "?")); + onSetValidOptionKeyValueInRoot(key: string, keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression): void; + /** + * Notify when unknown root key value option is being set + * @param key option key + * @param keyNode node corresponding to node in the source file + * @param value computed value of the key + * @param ValueNode node corresponding to value in the source file + */ + onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression): void; +} +/** + * Convert the json syntax tree into the json value + */ +export function convertToObject(sourceFile: JsonSourceFile, errors: Push): any { + return convertToObjectWorker(sourceFile, errors, /*returnValue*/ true, /*knownRootOptions*/ undefined, /*jsonConversionNotifier*/ undefined); +} +/** + * Convert the json syntax tree into the json value and report errors + * This returns the json value (apart from checking errors) only if returnValue provided is true. + * Otherwise it just checks the errors and returns undefined + */ +/*@internal*/ +export function convertToObjectWorker(sourceFile: JsonSourceFile, errors: Push, returnValue: boolean, knownRootOptions: CommandLineOption | undefined, jsonConversionNotifier: JsonConversionNotifier | undefined): any { + if (!sourceFile.statements.length) { + return returnValue ? {} : undefined; + } + return convertPropertyValueToJson(sourceFile.statements[0].expression, knownRootOptions); + function isRootOptionMap(knownOptions: ts.Map | undefined) { + return knownRootOptions && (knownRootOptions as TsConfigOnlyOption).elementOptions === knownOptions; + } + function convertObjectLiteralExpressionToJson(node: ObjectLiteralExpression, knownOptions: ts.Map | undefined, extraKeyDiagnostics: DidYouMeanOptionsDiagnostics | undefined, parentOption: string | undefined): any { + const result: any = returnValue ? {} : undefined; + for (const element of node.properties) { + if (element.kind !== SyntaxKind.PropertyAssignment) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element, Diagnostics.Property_assignment_expected)); + continue; + } + if (element.questionToken) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.questionToken, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, "?")); + } + if (!isDoubleQuotedString(element.name)) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, Diagnostics.String_literal_with_double_quotes_expected)); + } + const textOfKey = getTextOfPropertyName(element.name); + const keyText = textOfKey && unescapeLeadingUnderscores(textOfKey); + const option = keyText && knownOptions ? knownOptions.get(keyText) : undefined; + if (keyText && extraKeyDiagnostics && !option) { + if (knownOptions) { + errors.push(createUnknownOptionError(keyText, extraKeyDiagnostics, (message, arg0, arg1) => createDiagnosticForNodeInSourceFile(sourceFile, element.name, message, arg0, arg1))); } - if (!isDoubleQuotedString(element.name)) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, Diagnostics.String_literal_with_double_quotes_expected)); + else { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, extraKeyDiagnostics.unknownOptionDiagnostic, keyText)); } - - const textOfKey = getTextOfPropertyName(element.name); - const keyText = textOfKey && unescapeLeadingUnderscores(textOfKey); - const option = keyText && knownOptions ? knownOptions.get(keyText) : undefined; - if (keyText && extraKeyDiagnostics && !option) { - if (knownOptions) { - errors.push(createUnknownOptionError( - keyText, - extraKeyDiagnostics, - (message, arg0, arg1) => createDiagnosticForNodeInSourceFile(sourceFile, element.name, message, arg0, arg1) - )); - } - else { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, extraKeyDiagnostics.unknownOptionDiagnostic, keyText)); - } + } + const value = convertPropertyValueToJson(element.initializer, option); + if (typeof keyText !== "undefined") { + if (returnValue) { + result[keyText] = value; } - const value = convertPropertyValueToJson(element.initializer, option); - if (typeof keyText !== "undefined") { - if (returnValue) { - result[keyText] = value; + // Notify key value set, if user asked for it + if (jsonConversionNotifier && + // Current callbacks are only on known parent option or if we are setting values in the root + (parentOption || isRootOptionMap(knownOptions))) { + const isValidOptionValue = isCompilerOptionsValue(option, value); + if (parentOption) { + if (isValidOptionValue) { + // Notify option set in the parent if its a valid option value + jsonConversionNotifier.onSetValidOptionKeyValueInParent(parentOption, option!, value); + } } - // Notify key value set, if user asked for it - if (jsonConversionNotifier && - // Current callbacks are only on known parent option or if we are setting values in the root - (parentOption || isRootOptionMap(knownOptions))) { - const isValidOptionValue = isCompilerOptionsValue(option, value); - if (parentOption) { - if (isValidOptionValue) { - // Notify option set in the parent if its a valid option value - jsonConversionNotifier.onSetValidOptionKeyValueInParent(parentOption, option!, value); - } + else if (isRootOptionMap(knownOptions)) { + if (isValidOptionValue) { + // Notify about the valid root key value being set + jsonConversionNotifier.onSetValidOptionKeyValueInRoot(keyText, element.name, value, element.initializer); } - else if (isRootOptionMap(knownOptions)) { - if (isValidOptionValue) { - // Notify about the valid root key value being set - jsonConversionNotifier.onSetValidOptionKeyValueInRoot(keyText, element.name, value, element.initializer); - } - else if (!option) { - // Notify about the unknown root key value being set - jsonConversionNotifier.onSetUnknownOptionKeyValueInRoot(keyText, element.name, value, element.initializer); - } + else if (!option) { + // Notify about the unknown root key value being set + jsonConversionNotifier.onSetUnknownOptionKeyValueInRoot(keyText, element.name, value, element.initializer); } } } } - return result; } - - function convertArrayLiteralExpressionToJson( - elements: NodeArray, - elementOption: CommandLineOption | undefined - ): any[] | void { - if (!returnValue) { - return elements.forEach(element => convertPropertyValueToJson(element, elementOption)); - } - - // Filter out invalid values - return filter(elements.map(element => convertPropertyValueToJson(element, elementOption)), v => v !== undefined); + return result; + } + function convertArrayLiteralExpressionToJson(elements: NodeArray, elementOption: CommandLineOption | undefined): any[] | void { + if (!returnValue) { + return elements.forEach(element => convertPropertyValueToJson(element, elementOption)); } - - function convertPropertyValueToJson(valueExpression: Expression, option: CommandLineOption | undefined): any { - switch (valueExpression.kind) { - case SyntaxKind.TrueKeyword: - reportInvalidOptionValue(option && option.type !== "boolean"); - return true; - - case SyntaxKind.FalseKeyword: - reportInvalidOptionValue(option && option.type !== "boolean"); - return false; - - case SyntaxKind.NullKeyword: - reportInvalidOptionValue(option && option.name === "extends"); // "extends" is the only option we don't allow null/undefined for - return null; // eslint-disable-line no-null/no-null - - case SyntaxKind.StringLiteral: - if (!isDoubleQuotedString(valueExpression)) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.String_literal_with_double_quotes_expected)); - } - reportInvalidOptionValue(option && (isString(option.type) && option.type !== "string")); - const text = (valueExpression).text; - if (option && !isString(option.type)) { - const customOption = option; - // Validate custom option type - if (!customOption.type.has(text.toLowerCase())) { - errors.push( - createDiagnosticForInvalidCustomType( - customOption, - (message, arg0, arg1) => createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, message, arg0, arg1) - ) - ); - } - } - return text; - - case SyntaxKind.NumericLiteral: - reportInvalidOptionValue(option && option.type !== "number"); - return Number((valueExpression).text); - - case SyntaxKind.PrefixUnaryExpression: - if ((valueExpression).operator !== SyntaxKind.MinusToken || (valueExpression).operand.kind !== SyntaxKind.NumericLiteral) { - break; // not valid JSON syntax - } - reportInvalidOptionValue(option && option.type !== "number"); - return -Number(((valueExpression).operand).text); - - case SyntaxKind.ObjectLiteralExpression: - reportInvalidOptionValue(option && option.type !== "object"); - const objectLiteralExpression = valueExpression; - - // Currently having element option declaration in the tsconfig with type "object" - // determines if it needs onSetValidOptionKeyValueInParent callback or not - // At moment there are only "compilerOptions", "typeAcquisition" and "typingOptions" - // that satifies it and need it to modify options set in them (for normalizing file paths) - // vs what we set in the json - // If need arises, we can modify this interface and callbacks as needed - if (option) { - const { elementOptions, extraKeyDiagnostics, name: optionName } = option; - return convertObjectLiteralExpressionToJson(objectLiteralExpression, - elementOptions, extraKeyDiagnostics, optionName); - } - else { - return convertObjectLiteralExpressionToJson( - objectLiteralExpression, /* knownOptions*/ undefined, - /*extraKeyDiagnosticMessage */ undefined, /*parentOption*/ undefined); + // Filter out invalid values + return filter(elements.map(element => convertPropertyValueToJson(element, elementOption)), v => v !== undefined); + } + function convertPropertyValueToJson(valueExpression: Expression, option: CommandLineOption | undefined): any { + switch (valueExpression.kind) { + case SyntaxKind.TrueKeyword: + reportInvalidOptionValue(option && option.type !== "boolean"); + return true; + case SyntaxKind.FalseKeyword: + reportInvalidOptionValue(option && option.type !== "boolean"); + return false; + case SyntaxKind.NullKeyword: + reportInvalidOptionValue(option && option.name === "extends"); // "extends" is the only option we don't allow null/undefined for + return null; // eslint-disable-line no-null/no-null + case SyntaxKind.StringLiteral: + if (!isDoubleQuotedString(valueExpression)) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.String_literal_with_double_quotes_expected)); + } + reportInvalidOptionValue(option && (isString(option.type) && option.type !== "string")); + const text = (valueExpression).text; + if (option && !isString(option.type)) { + const customOption = (option); + // Validate custom option type + if (!customOption.type.has(text.toLowerCase())) { + errors.push(createDiagnosticForInvalidCustomType(customOption, (message, arg0, arg1) => createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, message, arg0, arg1))); } - - case SyntaxKind.ArrayLiteralExpression: - reportInvalidOptionValue(option && option.type !== "list"); - return convertArrayLiteralExpressionToJson( - (valueExpression).elements, - option && (option).element); - } - - // Not in expected format - if (option) { - reportInvalidOptionValue(/*isError*/ true); - } - else { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.Property_value_can_only_be_string_literal_numeric_literal_true_false_null_object_literal_or_array_literal)); - } - - return undefined; - - function reportInvalidOptionValue(isError: boolean | undefined) { - if (isError) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.Compiler_option_0_requires_a_value_of_type_1, option!.name, getCompilerOptionValueTypeString(option!))); } - } - } - - function isDoubleQuotedString(node: Node): boolean { - return isStringLiteral(node) && isStringDoubleQuoted(node, sourceFile); + return text; + case SyntaxKind.NumericLiteral: + reportInvalidOptionValue(option && option.type !== "number"); + return Number((valueExpression).text); + case SyntaxKind.PrefixUnaryExpression: + if ((valueExpression).operator !== SyntaxKind.MinusToken || (valueExpression).operand.kind !== SyntaxKind.NumericLiteral) { + break; // not valid JSON syntax + } + reportInvalidOptionValue(option && option.type !== "number"); + return -Number(((valueExpression).operand).text); + case SyntaxKind.ObjectLiteralExpression: + reportInvalidOptionValue(option && option.type !== "object"); + const objectLiteralExpression = (valueExpression); + // Currently having element option declaration in the tsconfig with type "object" + // determines if it needs onSetValidOptionKeyValueInParent callback or not + // At moment there are only "compilerOptions", "typeAcquisition" and "typingOptions" + // that satifies it and need it to modify options set in them (for normalizing file paths) + // vs what we set in the json + // If need arises, we can modify this interface and callbacks as needed + if (option) { + const { elementOptions, extraKeyDiagnostics, name: optionName } = (option); + return convertObjectLiteralExpressionToJson(objectLiteralExpression, elementOptions, extraKeyDiagnostics, optionName); + } + else { + return convertObjectLiteralExpressionToJson(objectLiteralExpression, /* knownOptions*/ undefined, + /*extraKeyDiagnosticMessage */ undefined, /*parentOption*/ undefined); + } + case SyntaxKind.ArrayLiteralExpression: + reportInvalidOptionValue(option && option.type !== "list"); + return convertArrayLiteralExpressionToJson((valueExpression).elements, option && (option).element); } - } - - function getCompilerOptionValueTypeString(option: CommandLineOption) { - return option.type === "list" ? - "Array" : - isString(option.type) ? option.type : "string"; - } - - function isCompilerOptionsValue(option: CommandLineOption | undefined, value: any): value is CompilerOptionsValue { + // Not in expected format if (option) { - if (isNullOrUndefined(value)) return true; // All options are undefinable/nullable - if (option.type === "list") { - return isArray(value); + reportInvalidOptionValue(/*isError*/ true); + } + else { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.Property_value_can_only_be_string_literal_numeric_literal_true_false_null_object_literal_or_array_literal)); + } + return undefined; + function reportInvalidOptionValue(isError: boolean | undefined) { + if (isError) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.Compiler_option_0_requires_a_value_of_type_1, option!.name, getCompilerOptionValueTypeString(option!))); } - const expectedType = isString(option.type) ? option.type : "string"; - return typeof value === expectedType; } - return false; } - - /** @internal */ - export interface TSConfig { - compilerOptions: CompilerOptions; - compileOnSave: boolean | undefined; - exclude?: readonly string[]; - files: readonly string[] | undefined; - include?: readonly string[]; - references: readonly ProjectReference[] | undefined; - } - - /** @internal */ - export interface ConvertToTSConfigHost { - getCurrentDirectory(): string; - useCaseSensitiveFileNames: boolean; - } - - /** - * Generate an uncommented, complete tsconfig for use with "--showConfig" - * @param configParseResult options to be generated into tsconfig.json - * @param configFileName name of the parsed config file - output paths will be generated relative to this - * @param host provides current directory and case sensitivity services - */ - /** @internal */ - export function convertToTSConfig(configParseResult: ParsedCommandLine, configFileName: string, host: ConvertToTSConfigHost): TSConfig { - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); - const files = map( - filter( - configParseResult.fileNames, - (!configParseResult.configFileSpecs || !configParseResult.configFileSpecs.validatedIncludeSpecs) ? _ => true : matchesSpecs( - configFileName, - configParseResult.configFileSpecs.validatedIncludeSpecs, - configParseResult.configFileSpecs.validatedExcludeSpecs, - host, - ) - ), - f => getRelativePathFromFile(getNormalizedAbsolutePath(configFileName, host.getCurrentDirectory()), getNormalizedAbsolutePath(f, host.getCurrentDirectory()), getCanonicalFileName) - ); - const optionMap = serializeCompilerOptions(configParseResult.options, { configFilePath: getNormalizedAbsolutePath(configFileName, host.getCurrentDirectory()), useCaseSensitiveFileNames: host.useCaseSensitiveFileNames }); - const watchOptionMap = configParseResult.watchOptions && serializeWatchOptions(configParseResult.watchOptions); - const config = { - compilerOptions: { - ...optionMapToObject(optionMap), - showConfig: undefined, - configFile: undefined, - configFilePath: undefined, - help: undefined, - init: undefined, - listFiles: undefined, - listEmittedFiles: undefined, - project: undefined, - build: undefined, - version: undefined, - }, - watchOptions: watchOptionMap && optionMapToObject(watchOptionMap), - references: map(configParseResult.projectReferences, r => ({ ...r, path: r.originalPath ? r.originalPath : "", originalPath: undefined })), - files: length(files) ? files : undefined, - ...(configParseResult.configFileSpecs ? { - include: filterSameAsDefaultInclude(configParseResult.configFileSpecs.validatedIncludeSpecs), - exclude: configParseResult.configFileSpecs.validatedExcludeSpecs - } : {}), - compileOnSave: !!configParseResult.compileOnSave ? true : undefined - }; - return config; + function isDoubleQuotedString(node: Node): boolean { + return isStringLiteral(node) && isStringDoubleQuoted(node, sourceFile); } - - function optionMapToObject(optionMap: Map): object { - return { - ...arrayFrom(optionMap.entries()).reduce((prev, cur) => ({ ...prev, [cur[0]]: cur[1] }), {}), - }; +} +function getCompilerOptionValueTypeString(option: CommandLineOption) { + return option.type === "list" ? + "Array" : + isString(option.type) ? option.type : "string"; +} +function isCompilerOptionsValue(option: CommandLineOption | undefined, value: any): value is CompilerOptionsValue { + if (option) { + if (isNullOrUndefined(value)) + return true; // All options are undefinable/nullable + if (option.type === "list") { + return isArray(value); + } + const expectedType = isString(option.type) ? option.type : "string"; + return typeof value === expectedType; } - - function filterSameAsDefaultInclude(specs: readonly string[] | undefined) { - if (!length(specs)) return undefined; - if (length(specs) !== 1) return specs; - if (specs![0] === "**/*") return undefined; + return false; +} +/** @internal */ +export interface TSConfig { + compilerOptions: CompilerOptions; + compileOnSave: boolean | undefined; + exclude?: readonly string[]; + files: readonly string[] | undefined; + include?: readonly string[]; + references: readonly ProjectReference[] | undefined; +} +/** @internal */ +export interface ConvertToTSConfigHost { + getCurrentDirectory(): string; + useCaseSensitiveFileNames: boolean; +} +/** + * Generate an uncommented, complete tsconfig for use with "--showConfig" + * @param configParseResult options to be generated into tsconfig.json + * @param configFileName name of the parsed config file - output paths will be generated relative to this + * @param host provides current directory and case sensitivity services + */ +/** @internal */ +export function convertToTSConfig(configParseResult: ParsedCommandLine, configFileName: string, host: ConvertToTSConfigHost): TSConfig { + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); + const files = map(filter(configParseResult.fileNames, (!configParseResult.configFileSpecs || !configParseResult.configFileSpecs.validatedIncludeSpecs) ? _ => true : matchesSpecs(configFileName, configParseResult.configFileSpecs.validatedIncludeSpecs, configParseResult.configFileSpecs.validatedExcludeSpecs, host)), f => getRelativePathFromFile(getNormalizedAbsolutePath(configFileName, host.getCurrentDirectory()), getNormalizedAbsolutePath(f, host.getCurrentDirectory()), getCanonicalFileName)); + const optionMap = serializeCompilerOptions(configParseResult.options, { configFilePath: getNormalizedAbsolutePath(configFileName, host.getCurrentDirectory()), useCaseSensitiveFileNames: host.useCaseSensitiveFileNames }); + const watchOptionMap = configParseResult.watchOptions && serializeWatchOptions(configParseResult.watchOptions); + const config = { + compilerOptions: { + ...optionMapToObject(optionMap), + showConfig: undefined, + configFile: undefined, + configFilePath: undefined, + help: undefined, + init: undefined, + listFiles: undefined, + listEmittedFiles: undefined, + project: undefined, + build: undefined, + version: undefined, + }, + watchOptions: watchOptionMap && optionMapToObject(watchOptionMap), + references: map(configParseResult.projectReferences, r => ({ ...r, path: r.originalPath ? r.originalPath : "", originalPath: undefined })), + files: length(files) ? files : undefined, + ...(configParseResult.configFileSpecs ? { + include: filterSameAsDefaultInclude(configParseResult.configFileSpecs.validatedIncludeSpecs), + exclude: configParseResult.configFileSpecs.validatedExcludeSpecs + } : {}), + compileOnSave: !!configParseResult.compileOnSave ? true : undefined + }; + return config; +} +function optionMapToObject(optionMap: ts.Map): object { + return { + ...arrayFrom(optionMap.entries()).reduce((prev, cur) => ({ ...prev, [cur[0]]: cur[1] }), {}), + }; +} +function filterSameAsDefaultInclude(specs: readonly string[] | undefined) { + if (!length(specs)) + return undefined; + if (length(specs) !== 1) return specs; - } - - function matchesSpecs(path: string, includeSpecs: readonly string[] | undefined, excludeSpecs: readonly string[] | undefined, host: ConvertToTSConfigHost): (path: string) => boolean { - if (!includeSpecs) return _ => true; - const patterns = getFileMatcherPatterns(path, excludeSpecs, includeSpecs, host.useCaseSensitiveFileNames, host.getCurrentDirectory()); - const excludeRe = patterns.excludePattern && getRegexFromPattern(patterns.excludePattern, host.useCaseSensitiveFileNames); - const includeRe = patterns.includeFilePattern && getRegexFromPattern(patterns.includeFilePattern, host.useCaseSensitiveFileNames); - if (includeRe) { - if (excludeRe) { - return path => !(includeRe.test(path) && !excludeRe.test(path)); - } - return path => !includeRe.test(path); - } + if (specs![0] === "**/*") + return undefined; + return specs; +} +function matchesSpecs(path: string, includeSpecs: readonly string[] | undefined, excludeSpecs: readonly string[] | undefined, host: ConvertToTSConfigHost): (path: string) => boolean { + if (!includeSpecs) + return _ => true; + const patterns = getFileMatcherPatterns(path, excludeSpecs, includeSpecs, host.useCaseSensitiveFileNames, host.getCurrentDirectory()); + const excludeRe = patterns.excludePattern && getRegexFromPattern(patterns.excludePattern, host.useCaseSensitiveFileNames); + const includeRe = patterns.includeFilePattern && getRegexFromPattern(patterns.includeFilePattern, host.useCaseSensitiveFileNames); + if (includeRe) { if (excludeRe) { - return path => excludeRe.test(path); + return path => !(includeRe.test(path) && !excludeRe.test(path)); } - return _ => true; + return path => !includeRe.test(path); } - - function getCustomTypeMapOfCommandLineOption(optionDefinition: CommandLineOption): Map | undefined { - if (optionDefinition.type === "string" || optionDefinition.type === "number" || optionDefinition.type === "boolean" || optionDefinition.type === "object") { - // this is of a type CommandLineOptionOfPrimitiveType - return undefined; - } - else if (optionDefinition.type === "list") { - return getCustomTypeMapOfCommandLineOption(optionDefinition.element); - } - else { - return (optionDefinition).type; - } + if (excludeRe) { + return path => excludeRe.test(path); } - - function getNameOfCompilerOptionValue(value: CompilerOptionsValue, customTypeMap: Map): string | undefined { - // There is a typeMap associated with this command-line option so use it to map value back to its name - return forEachEntry(customTypeMap, (mapValue, key) => { - if (mapValue === value) { - return key; - } - }); + return _ => true; +} +function getCustomTypeMapOfCommandLineOption(optionDefinition: CommandLineOption): ts.Map | undefined { + if (optionDefinition.type === "string" || optionDefinition.type === "number" || optionDefinition.type === "boolean" || optionDefinition.type === "object") { + // this is of a type CommandLineOptionOfPrimitiveType + return undefined; } - - function serializeCompilerOptions( - options: CompilerOptions, - pathOptions?: { configFilePath: string, useCaseSensitiveFileNames: boolean } - ): Map { - return serializeOptionBaseObject(options, getOptionsNameMap(), pathOptions); - } - - function serializeWatchOptions(options: WatchOptions) { - return serializeOptionBaseObject(options, getWatchOptionsNameMap()); - } - - function serializeOptionBaseObject( - options: OptionsBase, - { optionsNameMap }: OptionsNameMap, - pathOptions?: { configFilePath: string, useCaseSensitiveFileNames: boolean } - ): Map { - const result = createMap(); - const getCanonicalFileName = pathOptions && createGetCanonicalFileName(pathOptions.useCaseSensitiveFileNames); - - for (const name in options) { - if (hasProperty(options, name)) { - // tsconfig only options cannot be specified via command line, - // so we can assume that only types that can appear here string | number | boolean - if (optionsNameMap.has(name) && optionsNameMap.get(name)!.category === Diagnostics.Command_line_Options) { - continue; - } - const value = options[name]; - const optionDefinition = optionsNameMap.get(name.toLowerCase()); - if (optionDefinition) { - const customTypeMap = getCustomTypeMapOfCommandLineOption(optionDefinition); - if (!customTypeMap) { - // There is no map associated with this compiler option then use the value as-is - // This is the case if the value is expect to be string, number, boolean or list of string - if (pathOptions && optionDefinition.isFilePath) { - result.set(name, getRelativePathFromFile(pathOptions.configFilePath, getNormalizedAbsolutePath(value as string, getDirectoryPath(pathOptions.configFilePath)), getCanonicalFileName!)); - } - else { - result.set(name, value); - } - } - else { - if (optionDefinition.type === "list") { - result.set(name, (value as readonly (string | number)[]).map(element => getNameOfCompilerOptionValue(element, customTypeMap)!)); // TODO: GH#18217 - } - else { - // There is a typeMap associated with this command-line option so use it to map value back to its name - result.set(name, getNameOfCompilerOptionValue(value, customTypeMap)); - } - } - } - } - } - return result; + else if (optionDefinition.type === "list") { + return getCustomTypeMapOfCommandLineOption(optionDefinition.element); } - - /** - * Generate tsconfig configuration when running command line "--init" - * @param options commandlineOptions to be generated into tsconfig.json - * @param fileNames array of filenames to be generated into tsconfig.json - */ - /* @internal */ - export function generateTSConfig(options: CompilerOptions, fileNames: readonly string[], newLine: string): string { - const compilerOptions = extend(options, defaultInitCompilerOptions); - const compilerOptionsMap = serializeCompilerOptions(compilerOptions); - return writeConfigurations(); - - function getDefaultValueForOption(option: CommandLineOption) { - switch (option.type) { - case "number": - return 1; - case "boolean": - return true; - case "string": - return option.isFilePath ? "./" : ""; - case "list": - return []; - case "object": - return {}; - default: - const iterResult = option.type.keys().next(); - if (!iterResult.done) return iterResult.value; - return Debug.fail("Expected 'option.type' to have entries."); - } - } - - function makePadding(paddingLength: number): string { - return Array(paddingLength + 1).join(" "); - } - - function isAllowedOption({ category, name }: CommandLineOption): boolean { - // Skip options which do not have a category or have category `Command_line_Options` - // Exclude all possible `Advanced_Options` in tsconfig.json which were NOT defined in command line - return category !== undefined - && category !== Diagnostics.Command_line_Options - && (category !== Diagnostics.Advanced_Options || compilerOptionsMap.has(name)); + else { + return (optionDefinition).type; + } +} +function getNameOfCompilerOptionValue(value: CompilerOptionsValue, customTypeMap: ts.Map): string | undefined { + // There is a typeMap associated with this command-line option so use it to map value back to its name + return forEachEntry(customTypeMap, (mapValue, key) => { + if (mapValue === value) { + return key; } - - function writeConfigurations() { - // Filter applicable options to place in the file - const categorizedOptions = createMultiMap(); - for (const option of optionDeclarations) { - const { category } = option; - - if (isAllowedOption(option)) { - categorizedOptions.add(getLocaleSpecificMessage(category!), option); - } + }); +} +function serializeCompilerOptions(options: CompilerOptions, pathOptions?: { + configFilePath: string; + useCaseSensitiveFileNames: boolean; +}): ts.Map { + return serializeOptionBaseObject(options, getOptionsNameMap(), pathOptions); +} +function serializeWatchOptions(options: WatchOptions) { + return serializeOptionBaseObject(options, getWatchOptionsNameMap()); +} +function serializeOptionBaseObject(options: OptionsBase, { optionsNameMap }: OptionsNameMap, pathOptions?: { + configFilePath: string; + useCaseSensitiveFileNames: boolean; +}): ts.Map { + const result = createMap(); + const getCanonicalFileName = pathOptions && createGetCanonicalFileName(pathOptions.useCaseSensitiveFileNames); + for (const name in options) { + if (hasProperty(options, name)) { + // tsconfig only options cannot be specified via command line, + // so we can assume that only types that can appear here string | number | boolean + if (optionsNameMap.has(name) && optionsNameMap.get(name)!.category === Diagnostics.Command_line_Options) { + continue; } - - // Serialize all options and their descriptions - let marginLength = 0; - let seenKnownKeys = 0; - const nameColumn: string[] = []; - const descriptionColumn: string[] = []; - categorizedOptions.forEach((options, category) => { - if (nameColumn.length !== 0) { - nameColumn.push(""); - descriptionColumn.push(""); - } - nameColumn.push(`/* ${category} */`); - descriptionColumn.push(""); - for (const option of options) { - let optionName; - if (compilerOptionsMap.has(option.name)) { - optionName = `"${option.name}": ${JSON.stringify(compilerOptionsMap.get(option.name))}${(seenKnownKeys += 1) === compilerOptionsMap.size ? "" : ","}`; + const value = (options[name]); + const optionDefinition = optionsNameMap.get(name.toLowerCase()); + if (optionDefinition) { + const customTypeMap = getCustomTypeMapOfCommandLineOption(optionDefinition); + if (!customTypeMap) { + // There is no map associated with this compiler option then use the value as-is + // This is the case if the value is expect to be string, number, boolean or list of string + if (pathOptions && optionDefinition.isFilePath) { + result.set(name, getRelativePathFromFile(pathOptions.configFilePath, getNormalizedAbsolutePath((value as string), getDirectoryPath(pathOptions.configFilePath)), (getCanonicalFileName!))); } else { - optionName = `// "${option.name}": ${JSON.stringify(getDefaultValueForOption(option))},`; + result.set(name, value); } - nameColumn.push(optionName); - descriptionColumn.push(`/* ${option.description && getLocaleSpecificMessage(option.description) || option.name} */`); - marginLength = Math.max(optionName.length, marginLength); } - }); - - // Write the output - const tab = makePadding(2); - const result: string[] = []; - result.push(`{`); - result.push(`${tab}"compilerOptions": {`); - // Print out each row, aligning all the descriptions on the same column. - for (let i = 0; i < nameColumn.length; i++) { - const optionName = nameColumn[i]; - const description = descriptionColumn[i]; - result.push(optionName && `${tab}${tab}${optionName}${description && (makePadding(marginLength - optionName.length + 2) + description)}`); - } - if (fileNames.length) { - result.push(`${tab}},`); - result.push(`${tab}"files": [`); - for (let i = 0; i < fileNames.length; i++) { - result.push(`${tab}${tab}${JSON.stringify(fileNames[i])}${i === fileNames.length - 1 ? "" : ","}`); + else { + if (optionDefinition.type === "list") { + result.set(name, (value as readonly (string | number)[]).map(element => getNameOfCompilerOptionValue(element, customTypeMap)!)); // TODO: GH#18217 + } + else { + // There is a typeMap associated with this command-line option so use it to map value back to its name + result.set(name, getNameOfCompilerOptionValue(value, customTypeMap)); + } } - result.push(`${tab}]`); - } - else { - result.push(`${tab}}`); } - result.push(`}`); - - return result.join(newLine) + newLine; } } - - /* @internal */ - export function convertToOptionsWithAbsolutePaths(options: CompilerOptions, toAbsolutePath: (path: string) => string) { - const result: CompilerOptions = {}; - const optionsNameMap = getOptionsNameMap().optionsNameMap; - - for (const name in options) { - if (hasProperty(options, name)) { - result[name] = convertToOptionValueWithAbsolutePaths( - optionsNameMap.get(name.toLowerCase()), - options[name] as CompilerOptionsValue, - toAbsolutePath - ); + return result; +} +/** + * Generate tsconfig configuration when running command line "--init" + * @param options commandlineOptions to be generated into tsconfig.json + * @param fileNames array of filenames to be generated into tsconfig.json + */ +/* @internal */ +export function generateTSConfig(options: CompilerOptions, fileNames: readonly string[], newLine: string): string { + const compilerOptions = extend(options, defaultInitCompilerOptions); + const compilerOptionsMap = serializeCompilerOptions(compilerOptions); + return writeConfigurations(); + function getDefaultValueForOption(option: CommandLineOption) { + switch (option.type) { + case "number": + return 1; + case "boolean": + return true; + case "string": + return option.isFilePath ? "./" : ""; + case "list": + return []; + case "object": + return {}; + default: + const iterResult = option.type.keys().next(); + if (!iterResult.done) + return iterResult.value; + return Debug.fail("Expected 'option.type' to have entries."); + } + } + function makePadding(paddingLength: number): string { + return Array(paddingLength + 1).join(" "); + } + function isAllowedOption({ category, name }: CommandLineOption): boolean { + // Skip options which do not have a category or have category `Command_line_Options` + // Exclude all possible `Advanced_Options` in tsconfig.json which were NOT defined in command line + return category !== undefined + && category !== Diagnostics.Command_line_Options + && (category !== Diagnostics.Advanced_Options || compilerOptionsMap.has(name)); + } + function writeConfigurations() { + // Filter applicable options to place in the file + const categorizedOptions = createMultiMap(); + for (const option of optionDeclarations) { + const { category } = option; + if (isAllowedOption(option)) { + categorizedOptions.add(getLocaleSpecificMessage((category!)), option); } } - if (result.configFilePath) { - result.configFilePath = toAbsolutePath(result.configFilePath); - } - return result; - } - - function convertToOptionValueWithAbsolutePaths(option: CommandLineOption | undefined, value: CompilerOptionsValue, toAbsolutePath: (path: string) => string) { - if (option) { - if (option.type === "list") { - const values = value as readonly (string | number)[]; - if (option.element.isFilePath && values.length) { - return values.map(toAbsolutePath); + // Serialize all options and their descriptions + let marginLength = 0; + let seenKnownKeys = 0; + const nameColumn: string[] = []; + const descriptionColumn: string[] = []; + categorizedOptions.forEach((options, category) => { + if (nameColumn.length !== 0) { + nameColumn.push(""); + descriptionColumn.push(""); + } + nameColumn.push(`/* ${category} */`); + descriptionColumn.push(""); + for (const option of options) { + let optionName; + if (compilerOptionsMap.has(option.name)) { + optionName = `"${option.name}": ${JSON.stringify(compilerOptionsMap.get(option.name))}${(seenKnownKeys += 1) === compilerOptionsMap.size ? "" : ","}`; + } + else { + optionName = `// "${option.name}": ${JSON.stringify(getDefaultValueForOption(option))},`; } + nameColumn.push(optionName); + descriptionColumn.push(`/* ${option.description && getLocaleSpecificMessage(option.description) || option.name} */`); + marginLength = Math.max(optionName.length, marginLength); } - else if (option.isFilePath) { - return toAbsolutePath(value as string); + }); + // Write the output + const tab = makePadding(2); + const result: string[] = []; + result.push(`{`); + result.push(`${tab}"compilerOptions": {`); + // Print out each row, aligning all the descriptions on the same column. + for (let i = 0; i < nameColumn.length; i++) { + const optionName = nameColumn[i]; + const description = descriptionColumn[i]; + result.push(optionName && `${tab}${tab}${optionName}${description && (makePadding(marginLength - optionName.length + 2) + description)}`); + } + if (fileNames.length) { + result.push(`${tab}},`); + result.push(`${tab}"files": [`); + for (let i = 0; i < fileNames.length; i++) { + result.push(`${tab}${tab}${JSON.stringify(fileNames[i])}${i === fileNames.length - 1 ? "" : ","}`); } + result.push(`${tab}]`); } - return value; - } - - /** - * Parse the contents of a config file (tsconfig.json). - * @param json The contents of the config file to parse - * @param host Instance of ParseConfigHost used to enumerate files in folder. - * @param basePath A root directory to resolve relative path entries in the config - * file to. e.g. outDir - */ - export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map, existingWatchOptions?: WatchOptions): ParsedCommandLine { - return parseJsonConfigFileContentWorker(json, /*sourceFile*/ undefined, host, basePath, existingOptions, existingWatchOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache); + else { + result.push(`${tab}}`); + } + result.push(`}`); + return result.join(newLine) + newLine; } - - /** - * Parse the contents of a config file (tsconfig.json). - * @param jsonNode The contents of the config file to parse - * @param host Instance of ParseConfigHost used to enumerate files in folder. - * @param basePath A root directory to resolve relative path entries in the config - * file to. e.g. outDir - */ - export function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map, existingWatchOptions?: WatchOptions): ParsedCommandLine { - return parseJsonConfigFileContentWorker(/*json*/ undefined, sourceFile, host, basePath, existingOptions, existingWatchOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache); - } - - /*@internal*/ - export function setConfigFileInOptions(options: CompilerOptions, configFile: TsConfigSourceFile | undefined) { - if (configFile) { - Object.defineProperty(options, "configFile", { enumerable: false, writable: false, value: configFile }); +} +/* @internal */ +export function convertToOptionsWithAbsolutePaths(options: CompilerOptions, toAbsolutePath: (path: string) => string) { + const result: CompilerOptions = {}; + const optionsNameMap = getOptionsNameMap().optionsNameMap; + for (const name in options) { + if (hasProperty(options, name)) { + result[name] = convertToOptionValueWithAbsolutePaths(optionsNameMap.get(name.toLowerCase()), (options[name] as CompilerOptionsValue), toAbsolutePath); } } - - function isNullOrUndefined(x: any): x is null | undefined { - return x === undefined || x === null; // eslint-disable-line no-null/no-null + if (result.configFilePath) { + result.configFilePath = toAbsolutePath(result.configFilePath); } - - function directoryOfCombinedPath(fileName: string, basePath: string) { - // Use the `getNormalizedAbsolutePath` function to avoid canonicalizing the path, as it must remain noncanonical - // until consistent casing errors are reported - return getDirectoryPath(getNormalizedAbsolutePath(fileName, basePath)); + return result; +} +function convertToOptionValueWithAbsolutePaths(option: CommandLineOption | undefined, value: CompilerOptionsValue, toAbsolutePath: (path: string) => string) { + if (option) { + if (option.type === "list") { + const values = value as readonly (string | number)[]; + if (option.element.isFilePath && values.length) { + return values.map(toAbsolutePath); + } + } + else if (option.isFilePath) { + return toAbsolutePath(value as string); + } } - - /** - * Parse the contents of a config file from json or json source file (tsconfig.json). - * @param json The contents of the config file to parse - * @param sourceFile sourceFile corresponding to the Json - * @param host Instance of ParseConfigHost used to enumerate files in folder. - * @param basePath A root directory to resolve relative path entries in the config - * file to. e.g. outDir - * @param resolutionStack Only present for backwards-compatibility. Should be empty. - */ - function parseJsonConfigFileContentWorker( - json: any, - sourceFile: TsConfigSourceFile | undefined, - host: ParseConfigHost, - basePath: string, - existingOptions: CompilerOptions = {}, - existingWatchOptions: WatchOptions | undefined, - configFileName?: string, - resolutionStack: Path[] = [], - extraFileExtensions: readonly FileExtensionInfo[] = [], - extendedConfigCache?: Map - ): ParsedCommandLine { - Debug.assert((json === undefined && sourceFile !== undefined) || (json !== undefined && sourceFile === undefined)); - const errors: Diagnostic[] = []; - - const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStack, errors, extendedConfigCache); - const { raw } = parsedConfig; - const options = extend(existingOptions, parsedConfig.options || {}); - const watchOptions = existingWatchOptions && parsedConfig.watchOptions ? - extend(existingWatchOptions, parsedConfig.watchOptions) : - parsedConfig.watchOptions || existingWatchOptions; - - options.configFilePath = configFileName && normalizeSlashes(configFileName); - setConfigFileInOptions(options, sourceFile); - let projectReferences: ProjectReference[] | undefined; - const { fileNames, wildcardDirectories, spec } = getFileNames(); - return { - options, - watchOptions, - fileNames, - projectReferences, - typeAcquisition: parsedConfig.typeAcquisition || getDefaultTypeAcquisition(), - raw, - errors, - wildcardDirectories, - compileOnSave: !!raw.compileOnSave, - configFileSpecs: spec - }; - - function getFileNames(): ExpandResult { - let filesSpecs: readonly string[] | undefined; - if (hasProperty(raw, "files") && !isNullOrUndefined(raw.files)) { - if (isArray(raw.files)) { - filesSpecs = raw.files; - const hasReferences = hasProperty(raw, "references") && !isNullOrUndefined(raw.references); - const hasZeroOrNoReferences = !hasReferences || raw.references.length === 0; - const hasExtends = hasProperty(raw, "extends"); - if (filesSpecs.length === 0 && hasZeroOrNoReferences && !hasExtends) { - if (sourceFile) { - const fileName = configFileName || "tsconfig.json"; - const diagnosticMessage = Diagnostics.The_files_list_in_config_file_0_is_empty; - const nodeValue = firstDefined(getTsConfigPropArray(sourceFile, "files"), property => property.initializer); - const error = nodeValue - ? createDiagnosticForNodeInSourceFile(sourceFile, nodeValue, diagnosticMessage, fileName) - : createCompilerDiagnostic(diagnosticMessage, fileName); - errors.push(error); - } - else { - createCompilerDiagnosticOnlyIfJson(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json"); - } + return value; +} +/** + * Parse the contents of a config file (tsconfig.json). + * @param json The contents of the config file to parse + * @param host Instance of ParseConfigHost used to enumerate files in folder. + * @param basePath A root directory to resolve relative path entries in the config + * file to. e.g. outDir + */ +export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: ts.Map, existingWatchOptions?: WatchOptions): ParsedCommandLine { + return parseJsonConfigFileContentWorker(json, /*sourceFile*/ undefined, host, basePath, existingOptions, existingWatchOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache); +} +/** + * Parse the contents of a config file (tsconfig.json). + * @param jsonNode The contents of the config file to parse + * @param host Instance of ParseConfigHost used to enumerate files in folder. + * @param basePath A root directory to resolve relative path entries in the config + * file to. e.g. outDir + */ +export function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: ts.Map, existingWatchOptions?: WatchOptions): ParsedCommandLine { + return parseJsonConfigFileContentWorker(/*json*/ undefined, sourceFile, host, basePath, existingOptions, existingWatchOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache); +} +/*@internal*/ +export function setConfigFileInOptions(options: CompilerOptions, configFile: TsConfigSourceFile | undefined) { + if (configFile) { + Object.defineProperty(options, "configFile", { enumerable: false, writable: false, value: configFile }); + } +} +function isNullOrUndefined(x: any): x is null | undefined { + return x === undefined || x === null; // eslint-disable-line no-null/no-null +} +function directoryOfCombinedPath(fileName: string, basePath: string) { + // Use the `getNormalizedAbsolutePath` function to avoid canonicalizing the path, as it must remain noncanonical + // until consistent casing errors are reported + return getDirectoryPath(getNormalizedAbsolutePath(fileName, basePath)); +} +/** + * Parse the contents of a config file from json or json source file (tsconfig.json). + * @param json The contents of the config file to parse + * @param sourceFile sourceFile corresponding to the Json + * @param host Instance of ParseConfigHost used to enumerate files in folder. + * @param basePath A root directory to resolve relative path entries in the config + * file to. e.g. outDir + * @param resolutionStack Only present for backwards-compatibility. Should be empty. + */ +function parseJsonConfigFileContentWorker(json: any, sourceFile: TsConfigSourceFile | undefined, host: ParseConfigHost, basePath: string, existingOptions: CompilerOptions = {}, existingWatchOptions: WatchOptions | undefined, configFileName?: string, resolutionStack: Path[] = [], extraFileExtensions: readonly FileExtensionInfo[] = [], extendedConfigCache?: ts.Map): ParsedCommandLine { + Debug.assert((json === undefined && sourceFile !== undefined) || (json !== undefined && sourceFile === undefined)); + const errors: Diagnostic[] = []; + const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStack, errors, extendedConfigCache); + const { raw } = parsedConfig; + const options = extend(existingOptions, parsedConfig.options || {}); + const watchOptions = existingWatchOptions && parsedConfig.watchOptions ? + extend(existingWatchOptions, parsedConfig.watchOptions) : + parsedConfig.watchOptions || existingWatchOptions; + options.configFilePath = configFileName && normalizeSlashes(configFileName); + setConfigFileInOptions(options, sourceFile); + let projectReferences: ProjectReference[] | undefined; + const { fileNames, wildcardDirectories, spec } = getFileNames(); + return { + options, + watchOptions, + fileNames, + projectReferences, + typeAcquisition: parsedConfig.typeAcquisition || getDefaultTypeAcquisition(), + raw, + errors, + wildcardDirectories, + compileOnSave: !!raw.compileOnSave, + configFileSpecs: spec + }; + function getFileNames(): ExpandResult { + let filesSpecs: readonly string[] | undefined; + if (hasProperty(raw, "files") && !isNullOrUndefined(raw.files)) { + if (isArray(raw.files)) { + filesSpecs = raw.files; + const hasReferences = hasProperty(raw, "references") && !isNullOrUndefined(raw.references); + const hasZeroOrNoReferences = !hasReferences || raw.references.length === 0; + const hasExtends = hasProperty(raw, "extends"); + if (filesSpecs.length === 0 && hasZeroOrNoReferences && !hasExtends) { + if (sourceFile) { + const fileName = configFileName || "tsconfig.json"; + const diagnosticMessage = Diagnostics.The_files_list_in_config_file_0_is_empty; + const nodeValue = firstDefined(getTsConfigPropArray(sourceFile, "files"), property => property.initializer); + const error = nodeValue + ? createDiagnosticForNodeInSourceFile(sourceFile, nodeValue, diagnosticMessage, fileName) + : createCompilerDiagnostic(diagnosticMessage, fileName); + errors.push(error); + } + else { + createCompilerDiagnosticOnlyIfJson(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json"); } - } - else { - createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "files", "Array"); } } - - let includeSpecs: readonly string[] | undefined; - if (hasProperty(raw, "include") && !isNullOrUndefined(raw.include)) { - if (isArray(raw.include)) { - includeSpecs = raw.include; - } - else { - createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "include", "Array"); - } + else { + createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "files", "Array"); } - - let excludeSpecs: readonly string[] | undefined; - if (hasProperty(raw, "exclude") && !isNullOrUndefined(raw.exclude)) { - if (isArray(raw.exclude)) { - excludeSpecs = raw.exclude; - } - else { - createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "exclude", "Array"); - } + } + let includeSpecs: readonly string[] | undefined; + if (hasProperty(raw, "include") && !isNullOrUndefined(raw.include)) { + if (isArray(raw.include)) { + includeSpecs = raw.include; } - else if (raw.compilerOptions) { - const outDir = raw.compilerOptions.outDir; - const declarationDir = raw.compilerOptions.declarationDir; - - if (outDir || declarationDir) { - excludeSpecs = [outDir, declarationDir].filter(d => !!d); - } + else { + createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "include", "Array"); + } + } + let excludeSpecs: readonly string[] | undefined; + if (hasProperty(raw, "exclude") && !isNullOrUndefined(raw.exclude)) { + if (isArray(raw.exclude)) { + excludeSpecs = raw.exclude; } - - if (filesSpecs === undefined && includeSpecs === undefined) { - includeSpecs = ["**/*"]; + else { + createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "exclude", "Array"); } - - const result = matchFileNames(filesSpecs, includeSpecs, excludeSpecs, configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath, options, host, errors, extraFileExtensions, sourceFile); - if (shouldReportNoInputFiles(result, canJsonReportNoInutFiles(raw), resolutionStack)) { - errors.push(getErrorForNoInputFiles(result.spec, configFileName)); + } + else if (raw.compilerOptions) { + const outDir = raw.compilerOptions.outDir; + const declarationDir = raw.compilerOptions.declarationDir; + if (outDir || declarationDir) { + excludeSpecs = [outDir, declarationDir].filter(d => !!d); } - - if (hasProperty(raw, "references") && !isNullOrUndefined(raw.references)) { - if (isArray(raw.references)) { - for (const ref of raw.references) { - if (typeof ref.path !== "string") { - createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "reference.path", "string"); - } - else { - (projectReferences || (projectReferences = [])).push({ - path: getNormalizedAbsolutePath(ref.path, basePath), - originalPath: ref.path, - prepend: ref.prepend, - circular: ref.circular - }); - } + } + if (filesSpecs === undefined && includeSpecs === undefined) { + includeSpecs = ["**/*"]; + } + const result = matchFileNames(filesSpecs, includeSpecs, excludeSpecs, configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath, options, host, errors, extraFileExtensions, sourceFile); + if (shouldReportNoInputFiles(result, canJsonReportNoInutFiles(raw), resolutionStack)) { + errors.push(getErrorForNoInputFiles(result.spec, configFileName)); + } + if (hasProperty(raw, "references") && !isNullOrUndefined(raw.references)) { + if (isArray(raw.references)) { + for (const ref of raw.references) { + if (typeof ref.path !== "string") { + createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "reference.path", "string"); + } + else { + (projectReferences || (projectReferences = [])).push({ + path: getNormalizedAbsolutePath(ref.path, basePath), + originalPath: ref.path, + prepend: ref.prepend, + circular: ref.circular + }); } - } - else { - createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "references", "Array"); } } - - return result; - } - - function createCompilerDiagnosticOnlyIfJson(message: DiagnosticMessage, arg0?: string, arg1?: string) { - if (!sourceFile) { - errors.push(createCompilerDiagnostic(message, arg0, arg1)); + else { + createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "references", "Array"); } } + return result; } - - function isErrorNoInputFiles(error: Diagnostic) { - return error.code === Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code; - } - - function getErrorForNoInputFiles({ includeSpecs, excludeSpecs }: ConfigFileSpecs, configFileName: string | undefined) { - return createCompilerDiagnostic( - Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - configFileName || "tsconfig.json", - JSON.stringify(includeSpecs || []), - JSON.stringify(excludeSpecs || [])); - } - - function shouldReportNoInputFiles(result: ExpandResult, canJsonReportNoInutFiles: boolean, resolutionStack?: Path[]) { - return result.fileNames.length === 0 && canJsonReportNoInutFiles && (!resolutionStack || resolutionStack.length === 0); - } - - /*@internal*/ - export function canJsonReportNoInutFiles(raw: any) { - return !hasProperty(raw, "files") && !hasProperty(raw, "references"); - } - - /*@internal*/ - export function updateErrorForNoInputFiles(result: ExpandResult, configFileName: string, configFileSpecs: ConfigFileSpecs, configParseDiagnostics: Diagnostic[], canJsonReportNoInutFiles: boolean) { - const existingErrors = configParseDiagnostics.length; - if (shouldReportNoInputFiles(result, canJsonReportNoInutFiles)) { - configParseDiagnostics.push(getErrorForNoInputFiles(configFileSpecs, configFileName)); - } - else { - filterMutate(configParseDiagnostics, error => !isErrorNoInputFiles(error)); + function createCompilerDiagnosticOnlyIfJson(message: DiagnosticMessage, arg0?: string, arg1?: string) { + if (!sourceFile) { + errors.push(createCompilerDiagnostic(message, arg0, arg1)); } - return existingErrors !== configParseDiagnostics.length; - } - - export interface ParsedTsconfig { - raw: any; - options?: CompilerOptions; - watchOptions?: WatchOptions; - typeAcquisition?: TypeAcquisition; - /** - * Note that the case of the config path has not yet been normalized, as no files have been imported into the project yet - */ - extendedConfigPath?: string; } - - function isSuccessfulParsedTsconfig(value: ParsedTsconfig) { - return !!value.options; +} +function isErrorNoInputFiles(error: Diagnostic) { + return error.code === Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code; +} +function getErrorForNoInputFiles({ includeSpecs, excludeSpecs }: ConfigFileSpecs, configFileName: string | undefined) { + return createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, configFileName || "tsconfig.json", JSON.stringify(includeSpecs || []), JSON.stringify(excludeSpecs || [])); +} +function shouldReportNoInputFiles(result: ExpandResult, canJsonReportNoInutFiles: boolean, resolutionStack?: Path[]) { + return result.fileNames.length === 0 && canJsonReportNoInutFiles && (!resolutionStack || resolutionStack.length === 0); +} +/*@internal*/ +export function canJsonReportNoInutFiles(raw: any) { + return !hasProperty(raw, "files") && !hasProperty(raw, "references"); +} +/*@internal*/ +export function updateErrorForNoInputFiles(result: ExpandResult, configFileName: string, configFileSpecs: ConfigFileSpecs, configParseDiagnostics: Diagnostic[], canJsonReportNoInutFiles: boolean) { + const existingErrors = configParseDiagnostics.length; + if (shouldReportNoInputFiles(result, canJsonReportNoInutFiles)) { + configParseDiagnostics.push(getErrorForNoInputFiles(configFileSpecs, configFileName)); + } + else { + filterMutate(configParseDiagnostics, error => !isErrorNoInputFiles(error)); } - + return existingErrors !== configParseDiagnostics.length; +} +export interface ParsedTsconfig { + raw: any; + options?: CompilerOptions; + watchOptions?: WatchOptions; + typeAcquisition?: TypeAcquisition; /** - * This *just* extracts options/include/exclude/files out of a config file. - * It does *not* resolve the included files. + * Note that the case of the config path has not yet been normalized, as no files have been imported into the project yet */ - function parseConfig( - json: any, - sourceFile: TsConfigSourceFile | undefined, - host: ParseConfigHost, - basePath: string, - configFileName: string | undefined, - resolutionStack: string[], - errors: Push, - extendedConfigCache?: Map - ): ParsedTsconfig { - basePath = normalizeSlashes(basePath); - const resolvedPath = getNormalizedAbsolutePath(configFileName || "", basePath); - - if (resolutionStack.indexOf(resolvedPath) >= 0) { - errors.push(createCompilerDiagnostic(Diagnostics.Circularity_detected_while_resolving_configuration_Colon_0, [...resolutionStack, resolvedPath].join(" -> "))); - return { raw: json || convertToObject(sourceFile!, errors) }; - } - - const ownConfig = json ? - parseOwnConfigOfJson(json, host, basePath, configFileName, errors) : - parseOwnConfigOfJsonSourceFile(sourceFile!, host, basePath, configFileName, errors); - - if (ownConfig.extendedConfigPath) { - // copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios. - resolutionStack = resolutionStack.concat([resolvedPath]); - const extendedConfig = getExtendedConfig(sourceFile, ownConfig.extendedConfigPath, host, basePath, resolutionStack, errors, extendedConfigCache); - if (extendedConfig && isSuccessfulParsedTsconfig(extendedConfig)) { - const baseRaw = extendedConfig.raw; - const raw = ownConfig.raw; - const setPropertyInRawIfNotUndefined = (propertyName: string) => { - const value = raw[propertyName] || baseRaw[propertyName]; - if (value) { - raw[propertyName] = value; - } - }; - setPropertyInRawIfNotUndefined("include"); - setPropertyInRawIfNotUndefined("exclude"); - setPropertyInRawIfNotUndefined("files"); - if (raw.compileOnSave === undefined) { - raw.compileOnSave = baseRaw.compileOnSave; + extendedConfigPath?: string; +} +function isSuccessfulParsedTsconfig(value: ParsedTsconfig) { + return !!value.options; +} +/** + * This *just* extracts options/include/exclude/files out of a config file. + * It does *not* resolve the included files. + */ +function parseConfig(json: any, sourceFile: TsConfigSourceFile | undefined, host: ParseConfigHost, basePath: string, configFileName: string | undefined, resolutionStack: string[], errors: Push, extendedConfigCache?: ts.Map): ParsedTsconfig { + basePath = normalizeSlashes(basePath); + const resolvedPath = getNormalizedAbsolutePath(configFileName || "", basePath); + if (resolutionStack.indexOf(resolvedPath) >= 0) { + errors.push(createCompilerDiagnostic(Diagnostics.Circularity_detected_while_resolving_configuration_Colon_0, [...resolutionStack, resolvedPath].join(" -> "))); + return { raw: json || convertToObject(sourceFile!, errors) }; + } + const ownConfig = json ? + parseOwnConfigOfJson(json, host, basePath, configFileName, errors) : + parseOwnConfigOfJsonSourceFile(sourceFile!, host, basePath, configFileName, errors); + if (ownConfig.extendedConfigPath) { + // copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios. + resolutionStack = resolutionStack.concat([resolvedPath]); + const extendedConfig = getExtendedConfig(sourceFile, ownConfig.extendedConfigPath, host, basePath, resolutionStack, errors, extendedConfigCache); + if (extendedConfig && isSuccessfulParsedTsconfig(extendedConfig)) { + const baseRaw = extendedConfig.raw; + const raw = ownConfig.raw; + const setPropertyInRawIfNotUndefined = (propertyName: string) => { + const value = raw[propertyName] || baseRaw[propertyName]; + if (value) { + raw[propertyName] = value; } - ownConfig.options = assign({}, extendedConfig.options, ownConfig.options); - ownConfig.watchOptions = ownConfig.watchOptions && extendedConfig.watchOptions ? - assign({}, extendedConfig.watchOptions, ownConfig.watchOptions) : - ownConfig.watchOptions || extendedConfig.watchOptions; - // TODO extend type typeAcquisition + }; + setPropertyInRawIfNotUndefined("include"); + setPropertyInRawIfNotUndefined("exclude"); + setPropertyInRawIfNotUndefined("files"); + if (raw.compileOnSave === undefined) { + raw.compileOnSave = baseRaw.compileOnSave; } + ownConfig.options = assign({}, extendedConfig.options, ownConfig.options); + ownConfig.watchOptions = ownConfig.watchOptions && extendedConfig.watchOptions ? + assign({}, extendedConfig.watchOptions, ownConfig.watchOptions) : + ownConfig.watchOptions || extendedConfig.watchOptions; + // TODO extend type typeAcquisition } - - return ownConfig; - } - - function parseOwnConfigOfJson( - json: any, - host: ParseConfigHost, - basePath: string, - configFileName: string | undefined, - errors: Push - ): ParsedTsconfig { - if (hasProperty(json, "excludes")) { - errors.push(createCompilerDiagnostic(Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); + } + return ownConfig; +} +function parseOwnConfigOfJson(json: any, host: ParseConfigHost, basePath: string, configFileName: string | undefined, errors: Push): ParsedTsconfig { + if (hasProperty(json, "excludes")) { + errors.push(createCompilerDiagnostic(Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); + } + const options = convertCompilerOptionsFromJsonWorker(json.compilerOptions, basePath, errors, configFileName); + // typingOptions has been deprecated and is only supported for backward compatibility purposes. + // It should be removed in future releases - use typeAcquisition instead. + const typeAcquisition = convertTypeAcquisitionFromJsonWorker(json.typeAcquisition || json.typingOptions, basePath, errors, configFileName); + const watchOptions = convertWatchOptionsFromJsonWorker(json.watchOptions, basePath, errors); + json.compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors); + let extendedConfigPath: string | undefined; + if (json.extends) { + if (!isString(json.extends)) { + errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string")); } - - const options = convertCompilerOptionsFromJsonWorker(json.compilerOptions, basePath, errors, configFileName); - // typingOptions has been deprecated and is only supported for backward compatibility purposes. - // It should be removed in future releases - use typeAcquisition instead. - const typeAcquisition = convertTypeAcquisitionFromJsonWorker(json.typeAcquisition || json.typingOptions, basePath, errors, configFileName); - const watchOptions = convertWatchOptionsFromJsonWorker(json.watchOptions, basePath, errors); - json.compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors); - let extendedConfigPath: string | undefined; - - if (json.extends) { - if (!isString(json.extends)) { - errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string")); - } - else { - const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath; - extendedConfigPath = getExtendsConfigPath(json.extends, host, newBase, errors, createCompilerDiagnostic); - } + else { + const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath; + extendedConfigPath = getExtendsConfigPath(json.extends, host, newBase, errors, createCompilerDiagnostic); } - return { raw: json, options, watchOptions, typeAcquisition, extendedConfigPath }; - } - - function parseOwnConfigOfJsonSourceFile( - sourceFile: TsConfigSourceFile, - host: ParseConfigHost, - basePath: string, - configFileName: string | undefined, - errors: Push - ): ParsedTsconfig { - const options = getDefaultCompilerOptions(configFileName); - let typeAcquisition: TypeAcquisition | undefined, typingOptionstypeAcquisition: TypeAcquisition | undefined; - let watchOptions: WatchOptions | undefined; - let extendedConfigPath: string | undefined; - - const optionsIterator: JsonConversionNotifier = { - onSetValidOptionKeyValueInParent(parentOption: string, option: CommandLineOption, value: CompilerOptionsValue) { - let currentOption; - switch (parentOption) { - case "compilerOptions": - currentOption = options; - break; - case "watchOptions": - currentOption = (watchOptions || (watchOptions = {})); - break; - case "typeAcquisition": - currentOption = (typeAcquisition || (typeAcquisition = getDefaultTypeAcquisition(configFileName))); - break; - case "typingOptions": - currentOption = (typingOptionstypeAcquisition || (typingOptionstypeAcquisition = getDefaultTypeAcquisition(configFileName))); - break; - default: - Debug.fail("Unknown option"); - } - - currentOption[option.name] = normalizeOptionValue(option, basePath, value); - }, - onSetValidOptionKeyValueInRoot(key: string, _keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression) { - switch (key) { - case "extends": - const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath; - extendedConfigPath = getExtendsConfigPath( - value, - host, - newBase, - errors, - (message, arg0) => - createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0) - ); - return; - } - }, - onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, _value: CompilerOptionsValue, _valueNode: Expression) { - if (key === "excludes") { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, keyNode, Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); - } + } + return { raw: json, options, watchOptions, typeAcquisition, extendedConfigPath }; +} +function parseOwnConfigOfJsonSourceFile(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, configFileName: string | undefined, errors: Push): ParsedTsconfig { + const options = getDefaultCompilerOptions(configFileName); + let typeAcquisition: TypeAcquisition | undefined, typingOptionstypeAcquisition: TypeAcquisition | undefined; + let watchOptions: WatchOptions | undefined; + let extendedConfigPath: string | undefined; + const optionsIterator: JsonConversionNotifier = { + onSetValidOptionKeyValueInParent(parentOption: string, option: CommandLineOption, value: CompilerOptionsValue) { + let currentOption; + switch (parentOption) { + case "compilerOptions": + currentOption = options; + break; + case "watchOptions": + currentOption = (watchOptions || (watchOptions = {})); + break; + case "typeAcquisition": + currentOption = (typeAcquisition || (typeAcquisition = getDefaultTypeAcquisition(configFileName))); + break; + case "typingOptions": + currentOption = (typingOptionstypeAcquisition || (typingOptionstypeAcquisition = getDefaultTypeAcquisition(configFileName))); + break; + default: + Debug.fail("Unknown option"); } - }; - const json = convertToObjectWorker(sourceFile, errors, /*returnValue*/ true, getTsconfigRootOptionsMap(), optionsIterator); - - if (!typeAcquisition) { - if (typingOptionstypeAcquisition) { - typeAcquisition = (typingOptionstypeAcquisition.enableAutoDiscovery !== undefined) ? - { - enable: typingOptionstypeAcquisition.enableAutoDiscovery, - include: typingOptionstypeAcquisition.include, - exclude: typingOptionstypeAcquisition.exclude - } : - typingOptionstypeAcquisition; + currentOption[option.name] = normalizeOptionValue(option, basePath, value); + }, + onSetValidOptionKeyValueInRoot(key: string, _keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression) { + switch (key) { + case "extends": + const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath; + extendedConfigPath = getExtendsConfigPath((value), host, newBase, errors, (message, arg0) => createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0)); + return; } - else { - typeAcquisition = getDefaultTypeAcquisition(configFileName); + }, + onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, _value: CompilerOptionsValue, _valueNode: Expression) { + if (key === "excludes") { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, keyNode, Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); } } - - return { raw: json, options, watchOptions, typeAcquisition, extendedConfigPath }; - } - - function getExtendsConfigPath( - extendedConfig: string, - host: ParseConfigHost, - basePath: string, - errors: Push, - createDiagnostic: (message: DiagnosticMessage, arg1?: string) => Diagnostic) { - extendedConfig = normalizeSlashes(extendedConfig); - if (isRootedDiskPath(extendedConfig) || startsWith(extendedConfig, "./") || startsWith(extendedConfig, "../")) { - let extendedConfigPath = getNormalizedAbsolutePath(extendedConfig, basePath); - if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, Extension.Json)) { - extendedConfigPath = `${extendedConfigPath}.json`; - if (!host.fileExists(extendedConfigPath)) { - errors.push(createDiagnostic(Diagnostics.File_0_not_found, extendedConfig)); - return undefined; - } - } - return extendedConfigPath; + }; + const json = convertToObjectWorker(sourceFile, errors, /*returnValue*/ true, getTsconfigRootOptionsMap(), optionsIterator); + if (!typeAcquisition) { + if (typingOptionstypeAcquisition) { + typeAcquisition = (typingOptionstypeAcquisition.enableAutoDiscovery !== undefined) ? + { + enable: typingOptionstypeAcquisition.enableAutoDiscovery, + include: typingOptionstypeAcquisition.include, + exclude: typingOptionstypeAcquisition.exclude + } : + typingOptionstypeAcquisition; } - // If the path isn't a rooted or relative path, resolve like a module - const resolved = nodeModuleNameResolver(extendedConfig, combinePaths(basePath, "tsconfig.json"), { moduleResolution: ModuleResolutionKind.NodeJs }, host, /*cache*/ undefined, /*projectRefs*/ undefined, /*lookupConfig*/ true); - if (resolved.resolvedModule) { - return resolved.resolvedModule.resolvedFileName; + else { + typeAcquisition = getDefaultTypeAcquisition(configFileName); } - errors.push(createDiagnostic(Diagnostics.File_0_not_found, extendedConfig)); - return undefined; } - - export interface ExtendedConfigCacheEntry { - extendedResult: TsConfigSourceFile; - extendedConfig: ParsedTsconfig | undefined; - } - - function getExtendedConfig( - sourceFile: TsConfigSourceFile | undefined, - extendedConfigPath: string, - host: ParseConfigHost, - basePath: string, - resolutionStack: string[], - errors: Push, - extendedConfigCache?: Map - ): ParsedTsconfig | undefined { - const path = host.useCaseSensitiveFileNames ? extendedConfigPath : toLowerCase(extendedConfigPath); - let value: ExtendedConfigCacheEntry | undefined; - let extendedResult: TsConfigSourceFile; - let extendedConfig: ParsedTsconfig | undefined; - if (extendedConfigCache && (value = extendedConfigCache.get(path))) { - ({ extendedResult, extendedConfig } = value); - } - else { - extendedResult = readJsonConfigFile(extendedConfigPath, path => host.readFile(path)); - if (!extendedResult.parseDiagnostics.length) { - const extendedDirname = getDirectoryPath(extendedConfigPath); - extendedConfig = parseConfig(/*json*/ undefined, extendedResult, host, extendedDirname, - getBaseFileName(extendedConfigPath), resolutionStack, errors, extendedConfigCache); - - if (isSuccessfulParsedTsconfig(extendedConfig)) { - // Update the paths to reflect base path - const relativeDifference = convertToRelativePath(extendedDirname, basePath, identity); - const updatePath = (path: string) => isRootedDiskPath(path) ? path : combinePaths(relativeDifference, path); - const mapPropertiesInRawIfNotUndefined = (propertyName: string) => { - if (raw[propertyName]) { - raw[propertyName] = map(raw[propertyName], updatePath); - } - }; - - const { raw } = extendedConfig; - mapPropertiesInRawIfNotUndefined("include"); - mapPropertiesInRawIfNotUndefined("exclude"); - mapPropertiesInRawIfNotUndefined("files"); - } - } - if (extendedConfigCache) { - extendedConfigCache.set(path, { extendedResult, extendedConfig }); + return { raw: json, options, watchOptions, typeAcquisition, extendedConfigPath }; +} +function getExtendsConfigPath(extendedConfig: string, host: ParseConfigHost, basePath: string, errors: Push, createDiagnostic: (message: DiagnosticMessage, arg1?: string) => Diagnostic) { + extendedConfig = normalizeSlashes(extendedConfig); + if (isRootedDiskPath(extendedConfig) || startsWith(extendedConfig, "./") || startsWith(extendedConfig, "../")) { + let extendedConfigPath = getNormalizedAbsolutePath(extendedConfig, basePath); + if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, Extension.Json)) { + extendedConfigPath = `${extendedConfigPath}.json`; + if (!host.fileExists(extendedConfigPath)) { + errors.push(createDiagnostic(Diagnostics.File_0_not_found, extendedConfig)); + return undefined; } } - if (sourceFile) { - sourceFile.extendedSourceFiles = [extendedResult.fileName]; - if (extendedResult.extendedSourceFiles) { - sourceFile.extendedSourceFiles.push(...extendedResult.extendedSourceFiles); + return extendedConfigPath; + } + // If the path isn't a rooted or relative path, resolve like a module + const resolved = nodeModuleNameResolver(extendedConfig, combinePaths(basePath, "tsconfig.json"), { moduleResolution: ModuleResolutionKind.NodeJs }, host, /*cache*/ undefined, /*projectRefs*/ undefined, /*lookupConfig*/ true); + if (resolved.resolvedModule) { + return resolved.resolvedModule.resolvedFileName; + } + errors.push(createDiagnostic(Diagnostics.File_0_not_found, extendedConfig)); + return undefined; +} +export interface ExtendedConfigCacheEntry { + extendedResult: TsConfigSourceFile; + extendedConfig: ParsedTsconfig | undefined; +} +function getExtendedConfig(sourceFile: TsConfigSourceFile | undefined, extendedConfigPath: string, host: ParseConfigHost, basePath: string, resolutionStack: string[], errors: Push, extendedConfigCache?: ts.Map): ParsedTsconfig | undefined { + const path = host.useCaseSensitiveFileNames ? extendedConfigPath : toLowerCase(extendedConfigPath); + let value: ExtendedConfigCacheEntry | undefined; + let extendedResult: TsConfigSourceFile; + let extendedConfig: ParsedTsconfig | undefined; + if (extendedConfigCache && (value = extendedConfigCache.get(path))) { + ({ extendedResult, extendedConfig } = value); + } + else { + extendedResult = readJsonConfigFile(extendedConfigPath, path => host.readFile(path)); + if (!extendedResult.parseDiagnostics.length) { + const extendedDirname = getDirectoryPath(extendedConfigPath); + extendedConfig = parseConfig(/*json*/ undefined, extendedResult, host, extendedDirname, getBaseFileName(extendedConfigPath), resolutionStack, errors, extendedConfigCache); + if (isSuccessfulParsedTsconfig(extendedConfig)) { + // Update the paths to reflect base path + const relativeDifference = convertToRelativePath(extendedDirname, basePath, identity); + const updatePath = (path: string) => isRootedDiskPath(path) ? path : combinePaths(relativeDifference, path); + const mapPropertiesInRawIfNotUndefined = (propertyName: string) => { + if (raw[propertyName]) { + raw[propertyName] = map(raw[propertyName], updatePath); + } + }; + const { raw } = extendedConfig; + mapPropertiesInRawIfNotUndefined("include"); + mapPropertiesInRawIfNotUndefined("exclude"); + mapPropertiesInRawIfNotUndefined("files"); } } - if (extendedResult.parseDiagnostics.length) { - errors.push(...extendedResult.parseDiagnostics); - return undefined; + if (extendedConfigCache) { + extendedConfigCache.set(path, { extendedResult, extendedConfig }); } - return extendedConfig!; } - - function convertCompileOnSaveOptionFromJson(jsonOption: any, basePath: string, errors: Push): boolean { - if (!hasProperty(jsonOption, compileOnSaveCommandLineOption.name)) { - return false; - } - const result = convertJsonOption(compileOnSaveCommandLineOption, jsonOption.compileOnSave, basePath, errors); - return typeof result === "boolean" && result; - } - - export function convertCompilerOptionsFromJson(jsonOptions: any, basePath: string, configFileName?: string): { options: CompilerOptions, errors: Diagnostic[] } { - const errors: Diagnostic[] = []; - const options = convertCompilerOptionsFromJsonWorker(jsonOptions, basePath, errors, configFileName); - return { options, errors }; - } - - export function convertTypeAcquisitionFromJson(jsonOptions: any, basePath: string, configFileName?: string): { options: TypeAcquisition, errors: Diagnostic[] } { - const errors: Diagnostic[] = []; - const options = convertTypeAcquisitionFromJsonWorker(jsonOptions, basePath, errors, configFileName); - return { options, errors }; - } - - function getDefaultCompilerOptions(configFileName?: string) { - const options: CompilerOptions = configFileName && getBaseFileName(configFileName) === "jsconfig.json" - ? { allowJs: true, maxNodeModuleJsDepth: 2, allowSyntheticDefaultImports: true, skipLibCheck: true, noEmit: true } - : {}; - return options; - } - - function convertCompilerOptionsFromJsonWorker(jsonOptions: any, - basePath: string, errors: Push, configFileName?: string): CompilerOptions { - - const options = getDefaultCompilerOptions(configFileName); - convertOptionsFromJson(getCommandLineCompilerOptionsMap(), jsonOptions, basePath, options, compilerOptionsDidYouMeanDiagnostics, errors); - if (configFileName) { - options.configFilePath = normalizeSlashes(configFileName); - } - return options; - } - - function getDefaultTypeAcquisition(configFileName?: string): TypeAcquisition { - return { enable: !!configFileName && getBaseFileName(configFileName) === "jsconfig.json", include: [], exclude: [] }; - } - - function convertTypeAcquisitionFromJsonWorker(jsonOptions: any, - basePath: string, errors: Push, configFileName?: string): TypeAcquisition { - - const options = getDefaultTypeAcquisition(configFileName); - const typeAcquisition = convertEnableAutoDiscoveryToEnable(jsonOptions); - - convertOptionsFromJson(getCommandLineTypeAcquisitionMap(), typeAcquisition, basePath, options, typeAcquisitionDidYouMeanDiagnostics, errors); - return options; - } - - function convertWatchOptionsFromJsonWorker(jsonOptions: any, basePath: string, errors: Push): WatchOptions | undefined { - return convertOptionsFromJson(getCommandLineWatchOptionsMap(), jsonOptions, basePath, /*defaultOptions*/ undefined, watchOptionsDidYouMeanDiagnostics, errors); - } - - function convertOptionsFromJson(optionsNameMap: Map, jsonOptions: any, basePath: string, - defaultOptions: undefined, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push): WatchOptions | undefined; - function convertOptionsFromJson(optionsNameMap: Map, jsonOptions: any, basePath: string, - defaultOptions: CompilerOptions | TypeAcquisition, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push): CompilerOptions | TypeAcquisition; - function convertOptionsFromJson(optionsNameMap: Map, jsonOptions: any, basePath: string, - defaultOptions: CompilerOptions | TypeAcquisition | WatchOptions | undefined, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push) { - - if (!jsonOptions) { - return; - } - - for (const id in jsonOptions) { - const opt = optionsNameMap.get(id); - if (opt) { - (defaultOptions || (defaultOptions = {}))[opt.name] = convertJsonOption(opt, jsonOptions[id], basePath, errors); - } - else { - errors.push(createUnknownOptionError(id, diagnostics, createCompilerDiagnostic)); - } + if (sourceFile) { + sourceFile.extendedSourceFiles = [extendedResult.fileName]; + if (extendedResult.extendedSourceFiles) { + sourceFile.extendedSourceFiles.push(...extendedResult.extendedSourceFiles); } - return defaultOptions; - } - - function convertJsonOption(opt: CommandLineOption, value: any, basePath: string, errors: Push): CompilerOptionsValue { - if (isCompilerOptionsValue(opt, value)) { - const optType = opt.type; - if (optType === "list" && isArray(value)) { - return convertJsonOptionOfListType(opt, value, basePath, errors); - } - else if (!isString(optType)) { - return convertJsonOptionOfCustomType(opt, value, errors); - } - return normalizeNonListOptionValue(opt, basePath, value); + } + if (extendedResult.parseDiagnostics.length) { + errors.push(...extendedResult.parseDiagnostics); + return undefined; + } + return extendedConfig!; +} +function convertCompileOnSaveOptionFromJson(jsonOption: any, basePath: string, errors: Push): boolean { + if (!hasProperty(jsonOption, compileOnSaveCommandLineOption.name)) { + return false; + } + const result = convertJsonOption(compileOnSaveCommandLineOption, jsonOption.compileOnSave, basePath, errors); + return typeof result === "boolean" && result; +} +export function convertCompilerOptionsFromJson(jsonOptions: any, basePath: string, configFileName?: string): { + options: CompilerOptions; + errors: Diagnostic[]; +} { + const errors: Diagnostic[] = []; + const options = convertCompilerOptionsFromJsonWorker(jsonOptions, basePath, errors, configFileName); + return { options, errors }; +} +export function convertTypeAcquisitionFromJson(jsonOptions: any, basePath: string, configFileName?: string): { + options: TypeAcquisition; + errors: Diagnostic[]; +} { + const errors: Diagnostic[] = []; + const options = convertTypeAcquisitionFromJsonWorker(jsonOptions, basePath, errors, configFileName); + return { options, errors }; +} +function getDefaultCompilerOptions(configFileName?: string) { + const options: CompilerOptions = configFileName && getBaseFileName(configFileName) === "jsconfig.json" + ? { allowJs: true, maxNodeModuleJsDepth: 2, allowSyntheticDefaultImports: true, skipLibCheck: true, noEmit: true } + : {}; + return options; +} +function convertCompilerOptionsFromJsonWorker(jsonOptions: any, basePath: string, errors: Push, configFileName?: string): CompilerOptions { + const options = getDefaultCompilerOptions(configFileName); + convertOptionsFromJson(getCommandLineCompilerOptionsMap(), jsonOptions, basePath, options, compilerOptionsDidYouMeanDiagnostics, errors); + if (configFileName) { + options.configFilePath = normalizeSlashes(configFileName); + } + return options; +} +function getDefaultTypeAcquisition(configFileName?: string): TypeAcquisition { + return { enable: !!configFileName && getBaseFileName(configFileName) === "jsconfig.json", include: [], exclude: [] }; +} +function convertTypeAcquisitionFromJsonWorker(jsonOptions: any, basePath: string, errors: Push, configFileName?: string): TypeAcquisition { + const options = getDefaultTypeAcquisition(configFileName); + const typeAcquisition = convertEnableAutoDiscoveryToEnable(jsonOptions); + convertOptionsFromJson(getCommandLineTypeAcquisitionMap(), typeAcquisition, basePath, options, typeAcquisitionDidYouMeanDiagnostics, errors); + return options; +} +function convertWatchOptionsFromJsonWorker(jsonOptions: any, basePath: string, errors: Push): WatchOptions | undefined { + return convertOptionsFromJson(getCommandLineWatchOptionsMap(), jsonOptions, basePath, /*defaultOptions*/ undefined, watchOptionsDidYouMeanDiagnostics, errors); +} +function convertOptionsFromJson(optionsNameMap: ts.Map, jsonOptions: any, basePath: string, defaultOptions: undefined, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push): WatchOptions | undefined; +function convertOptionsFromJson(optionsNameMap: ts.Map, jsonOptions: any, basePath: string, defaultOptions: CompilerOptions | TypeAcquisition, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push): CompilerOptions | TypeAcquisition; +function convertOptionsFromJson(optionsNameMap: ts.Map, jsonOptions: any, basePath: string, defaultOptions: CompilerOptions | TypeAcquisition | WatchOptions | undefined, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push) { + if (!jsonOptions) { + return; + } + for (const id in jsonOptions) { + const opt = optionsNameMap.get(id); + if (opt) { + (defaultOptions || (defaultOptions = {}))[opt.name] = convertJsonOption(opt, jsonOptions[id], basePath, errors); } else { - errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, opt.name, getCompilerOptionValueTypeString(opt))); + errors.push(createUnknownOptionError(id, diagnostics, createCompilerDiagnostic)); } } - - function normalizeOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue { - if (isNullOrUndefined(value)) return undefined; - if (option.type === "list") { - const listOption = option; - if (listOption.element.isFilePath || !isString(listOption.element.type)) { - return filter(map(value, v => normalizeOptionValue(listOption.element, basePath, v)), v => !!v); - } - return value; + return defaultOptions; +} +function convertJsonOption(opt: CommandLineOption, value: any, basePath: string, errors: Push): CompilerOptionsValue { + if (isCompilerOptionsValue(opt, value)) { + const optType = opt.type; + if (optType === "list" && isArray(value)) { + return convertJsonOptionOfListType((opt), value, basePath, errors); } - else if (!isString(option.type)) { - return option.type.get(isString(value) ? value.toLowerCase() : value); + else if (!isString(optType)) { + return convertJsonOptionOfCustomType((opt), (value), errors); } - return normalizeNonListOptionValue(option, basePath, value); - } - - function normalizeNonListOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue { - if (option.isFilePath) { - value = getNormalizedAbsolutePath(value, basePath); - if (value === "") { - value = "."; - } + return normalizeNonListOptionValue(opt, basePath, value); + } + else { + errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, opt.name, getCompilerOptionValueTypeString(opt))); + } +} +function normalizeOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue { + if (isNullOrUndefined(value)) + return undefined; + if (option.type === "list") { + const listOption = option; + if (listOption.element.isFilePath || !isString(listOption.element.type)) { + return filter(map(value, v => normalizeOptionValue(listOption.element, basePath, v)), v => !!v); } return value; } - - function convertJsonOptionOfCustomType(opt: CommandLineOptionOfCustomType, value: string, errors: Push) { - if (isNullOrUndefined(value)) return undefined; - const key = value.toLowerCase(); - const val = opt.type.get(key); - if (val !== undefined) { - return val; - } - else { - errors.push(createCompilerDiagnosticForInvalidCustomType(opt)); + else if (!isString(option.type)) { + return option.type.get(isString(value) ? value.toLowerCase() : value); + } + return normalizeNonListOptionValue(option, basePath, value); +} +function normalizeNonListOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue { + if (option.isFilePath) { + value = getNormalizedAbsolutePath(value, basePath); + if (value === "") { + value = "."; } } - - function convertJsonOptionOfListType(option: CommandLineOptionOfListType, values: readonly any[], basePath: string, errors: Push): any[] { - return filter(map(values, v => convertJsonOption(option.element, v, basePath, errors)), v => !!v); + return value; +} +function convertJsonOptionOfCustomType(opt: CommandLineOptionOfCustomType, value: string, errors: Push) { + if (isNullOrUndefined(value)) + return undefined; + const key = value.toLowerCase(); + const val = opt.type.get(key); + if (val !== undefined) { + return val; } - - function trimString(s: string) { - return typeof s.trim === "function" ? s.trim() : s.replace(/^[\s]+|[\s]+$/g, ""); + else { + errors.push(createCompilerDiagnosticForInvalidCustomType(opt)); } - - /** - * Tests for a path that ends in a recursive directory wildcard. - * Matches **, \**, **\, and \**\, but not a**b. - * - * NOTE: used \ in place of / above to avoid issues with multiline comments. - * - * Breakdown: - * (^|\/) # matches either the beginning of the string or a directory separator. - * \*\* # matches the recursive directory wildcard "**". - * \/?$ # matches an optional trailing directory separator at the end of the string. - */ - const invalidTrailingRecursionPattern = /(^|\/)\*\*\/?$/; - - /** - * Tests for a path where .. appears after a recursive directory wildcard. - * Matches **\..\*, **\a\..\*, and **\.., but not ..\**\* - * - * NOTE: used \ in place of / above to avoid issues with multiline comments. - * - * Breakdown: - * (^|\/) # matches either the beginning of the string or a directory separator. - * \*\*\/ # matches a recursive directory wildcard "**" followed by a directory separator. - * (.*\/)? # optionally matches any number of characters followed by a directory separator. - * \.\. # matches a parent directory path component ".." - * ($|\/) # matches either the end of the string or a directory separator. - */ - const invalidDotDotAfterRecursiveWildcardPattern = /(^|\/)\*\*\/(.*\/)?\.\.($|\/)/; - - /** - * Tests for a path containing a wildcard character in a directory component of the path. - * Matches \*\, \?\, and \a*b\, but not \a\ or \a\*. - * - * NOTE: used \ in place of / above to avoid issues with multiline comments. - * - * Breakdown: - * \/ # matches a directory separator. - * [^/]*? # matches any number of characters excluding directory separators (non-greedy). - * [*?] # matches either a wildcard character (* or ?) - * [^/]* # matches any number of characters excluding directory separators (greedy). - * \/ # matches a directory separator. - */ - const watchRecursivePattern = /\/[^/]*?[*?][^/]*\//; - - /** - * Matches the portion of a wildcard path that does not contain wildcards. - * Matches \a of \a\*, or \a\b\c of \a\b\c\?\d. - * - * NOTE: used \ in place of / above to avoid issues with multiline comments. - * - * Breakdown: - * ^ # matches the beginning of the string - * [^*?]* # matches any number of non-wildcard characters - * (?=\/[^/]*[*?]) # lookahead that matches a directory separator followed by - * # a path component that contains at least one wildcard character (* or ?). - */ - const wildcardDirectoryPattern = /^[^*?]*(?=\/[^/]*[*?])/; - - /** - * Expands an array of file specifications. - * - * @param filesSpecs The literal file names to include. - * @param includeSpecs The wildcard file specifications to include. - * @param excludeSpecs The wildcard file specifications to exclude. - * @param basePath The base path for any relative file specifications. - * @param options Compiler options. - * @param host The host used to resolve files and directories. - * @param errors An array for diagnostic reporting. - */ - function matchFileNames( - filesSpecs: readonly string[] | undefined, - includeSpecs: readonly string[] | undefined, - excludeSpecs: readonly string[] | undefined, - basePath: string, - options: CompilerOptions, - host: ParseConfigHost, - errors: Push, - extraFileExtensions: readonly FileExtensionInfo[], - jsonSourceFile: TsConfigSourceFile | undefined - ): ExpandResult { - basePath = normalizePath(basePath); - let validatedIncludeSpecs: readonly string[] | undefined, validatedExcludeSpecs: readonly string[] | undefined; - - // The exclude spec list is converted into a regular expression, which allows us to quickly - // test whether a file or directory should be excluded before recursively traversing the - // file system. - - if (includeSpecs) { - validatedIncludeSpecs = validateSpecs(includeSpecs, errors, /*allowTrailingRecursion*/ false, jsonSourceFile, "include"); - } - - if (excludeSpecs) { - validatedExcludeSpecs = validateSpecs(excludeSpecs, errors, /*allowTrailingRecursion*/ true, jsonSourceFile, "exclude"); - } - - // Wildcard directories (provided as part of a wildcard path) are stored in a - // file map that marks whether it was a regular wildcard match (with a `*` or `?` token), - // or a recursive directory. This information is used by filesystem watchers to monitor for - // new entries in these paths. - const wildcardDirectories = getWildcardDirectories(validatedIncludeSpecs, validatedExcludeSpecs, basePath, host.useCaseSensitiveFileNames); - - const spec: ConfigFileSpecs = { filesSpecs, includeSpecs, excludeSpecs, validatedIncludeSpecs, validatedExcludeSpecs, wildcardDirectories }; - return getFileNamesFromConfigSpecs(spec, basePath, options, host, extraFileExtensions); - } - - /** - * Gets the file names from the provided config file specs that contain, files, include, exclude and - * other properties needed to resolve the file names - * @param spec The config file specs extracted with file names to include, wildcards to include/exclude and other details - * @param basePath The base path for any relative file specifications. - * @param options Compiler options. - * @param host The host used to resolve files and directories. - * @param extraFileExtensions optionaly file extra file extension information from host - */ - /* @internal */ - export function getFileNamesFromConfigSpecs(spec: ConfigFileSpecs, basePath: string, options: CompilerOptions, host: ParseConfigHost, extraFileExtensions: readonly FileExtensionInfo[] = []): ExpandResult { - basePath = normalizePath(basePath); - - const keyMapper = host.useCaseSensitiveFileNames ? identity : toLowerCase; - - // Literal file names (provided via the "files" array in tsconfig.json) are stored in a - // file map with a possibly case insensitive key. We use this map later when when including - // wildcard paths. - const literalFileMap = createMap(); - - // Wildcard paths (provided via the "includes" array in tsconfig.json) are stored in a - // file map with a possibly case insensitive key. We use this map to store paths matched - // via wildcard, and to handle extension priority. - const wildcardFileMap = createMap(); - - // Wildcard paths of json files (provided via the "includes" array in tsconfig.json) are stored in a - // file map with a possibly case insensitive key. We use this map to store paths matched - // via wildcard of *.json kind - const wildCardJsonFileMap = createMap(); - const { filesSpecs, validatedIncludeSpecs, validatedExcludeSpecs, wildcardDirectories } = spec; - - // Rather than requery this for each file and filespec, we query the supported extensions - // once and store it on the expansion context. - const supportedExtensions = getSupportedExtensions(options, extraFileExtensions); - const supportedExtensionsWithJsonIfResolveJsonModule = getSuppoertedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions); - - // Literal files are always included verbatim. An "include" or "exclude" specification cannot - // remove a literal file. - if (filesSpecs) { - for (const fileName of filesSpecs) { - const file = getNormalizedAbsolutePath(fileName, basePath); - literalFileMap.set(keyMapper(file), file); - } - } - - let jsonOnlyIncludeRegexes: readonly RegExp[] | undefined; - if (validatedIncludeSpecs && validatedIncludeSpecs.length > 0) { - for (const file of host.readDirectory(basePath, supportedExtensionsWithJsonIfResolveJsonModule, validatedExcludeSpecs, validatedIncludeSpecs, /*depth*/ undefined)) { - if (fileExtensionIs(file, Extension.Json)) { - // Valid only if *.json specified - if (!jsonOnlyIncludeRegexes) { - const includes = validatedIncludeSpecs.filter(s => endsWith(s, Extension.Json)); - const includeFilePatterns = map(getRegularExpressionsForWildcards(includes, basePath, "files"), pattern => `^${pattern}$`); - jsonOnlyIncludeRegexes = includeFilePatterns ? includeFilePatterns.map(pattern => getRegexFromPattern(pattern, host.useCaseSensitiveFileNames)) : emptyArray; - } - const includeIndex = findIndex(jsonOnlyIncludeRegexes, re => re.test(file)); - if (includeIndex !== -1) { - const key = keyMapper(file); - if (!literalFileMap.has(key) && !wildCardJsonFileMap.has(key)) { - wildCardJsonFileMap.set(key, file); - } - } - continue; - } - // If we have already included a literal or wildcard path with a - // higher priority extension, we should skip this file. - // - // This handles cases where we may encounter both .ts and - // .d.ts (or .js if "allowJs" is enabled) in the same - // directory when they are compilation outputs. - if (hasFileWithHigherPriorityExtension(file, literalFileMap, wildcardFileMap, supportedExtensions, keyMapper)) { - continue; +} +function convertJsonOptionOfListType(option: CommandLineOptionOfListType, values: readonly any[], basePath: string, errors: Push): any[] { + return filter(map(values, v => convertJsonOption(option.element, v, basePath, errors)), v => !!v); +} +function trimString(s: string) { + return typeof s.trim === "function" ? s.trim() : s.replace(/^[\s]+|[\s]+$/g, ""); +} +/** + * Tests for a path that ends in a recursive directory wildcard. + * Matches **, \**, **\, and \**\, but not a**b. + * + * NOTE: used \ in place of / above to avoid issues with multiline comments. + * + * Breakdown: + * (^|\/) # matches either the beginning of the string or a directory separator. + * \*\* # matches the recursive directory wildcard "**". + * \/?$ # matches an optional trailing directory separator at the end of the string. + */ +const invalidTrailingRecursionPattern = /(^|\/)\*\*\/?$/; +/** + * Tests for a path where .. appears after a recursive directory wildcard. + * Matches **\..\*, **\a\..\*, and **\.., but not ..\**\* + * + * NOTE: used \ in place of / above to avoid issues with multiline comments. + * + * Breakdown: + * (^|\/) # matches either the beginning of the string or a directory separator. + * \*\*\/ # matches a recursive directory wildcard "**" followed by a directory separator. + * (.*\/)? # optionally matches any number of characters followed by a directory separator. + * \.\. # matches a parent directory path component ".." + * ($|\/) # matches either the end of the string or a directory separator. + */ +const invalidDotDotAfterRecursiveWildcardPattern = /(^|\/)\*\*\/(.*\/)?\.\.($|\/)/; +/** + * Tests for a path containing a wildcard character in a directory component of the path. + * Matches \*\, \?\, and \a*b\, but not \a\ or \a\*. + * + * NOTE: used \ in place of / above to avoid issues with multiline comments. + * + * Breakdown: + * \/ # matches a directory separator. + * [^/]*? # matches any number of characters excluding directory separators (non-greedy). + * [*?] # matches either a wildcard character (* or ?) + * [^/]* # matches any number of characters excluding directory separators (greedy). + * \/ # matches a directory separator. + */ +const watchRecursivePattern = /\/[^/]*?[*?][^/]*\//; +/** + * Matches the portion of a wildcard path that does not contain wildcards. + * Matches \a of \a\*, or \a\b\c of \a\b\c\?\d. + * + * NOTE: used \ in place of / above to avoid issues with multiline comments. + * + * Breakdown: + * ^ # matches the beginning of the string + * [^*?]* # matches any number of non-wildcard characters + * (?=\/[^/]*[*?]) # lookahead that matches a directory separator followed by + * # a path component that contains at least one wildcard character (* or ?). + */ +const wildcardDirectoryPattern = /^[^*?]*(?=\/[^/]*[*?])/; +/** + * Expands an array of file specifications. + * + * @param filesSpecs The literal file names to include. + * @param includeSpecs The wildcard file specifications to include. + * @param excludeSpecs The wildcard file specifications to exclude. + * @param basePath The base path for any relative file specifications. + * @param options Compiler options. + * @param host The host used to resolve files and directories. + * @param errors An array for diagnostic reporting. + */ +function matchFileNames(filesSpecs: readonly string[] | undefined, includeSpecs: readonly string[] | undefined, excludeSpecs: readonly string[] | undefined, basePath: string, options: CompilerOptions, host: ParseConfigHost, errors: Push, extraFileExtensions: readonly FileExtensionInfo[], jsonSourceFile: TsConfigSourceFile | undefined): ExpandResult { + basePath = normalizePath(basePath); + let validatedIncludeSpecs: readonly string[] | undefined, validatedExcludeSpecs: readonly string[] | undefined; + // The exclude spec list is converted into a regular expression, which allows us to quickly + // test whether a file or directory should be excluded before recursively traversing the + // file system. + if (includeSpecs) { + validatedIncludeSpecs = validateSpecs(includeSpecs, errors, /*allowTrailingRecursion*/ false, jsonSourceFile, "include"); + } + if (excludeSpecs) { + validatedExcludeSpecs = validateSpecs(excludeSpecs, errors, /*allowTrailingRecursion*/ true, jsonSourceFile, "exclude"); + } + // Wildcard directories (provided as part of a wildcard path) are stored in a + // file map that marks whether it was a regular wildcard match (with a `*` or `?` token), + // or a recursive directory. This information is used by filesystem watchers to monitor for + // new entries in these paths. + const wildcardDirectories = getWildcardDirectories(validatedIncludeSpecs, validatedExcludeSpecs, basePath, host.useCaseSensitiveFileNames); + const spec: ConfigFileSpecs = { filesSpecs, includeSpecs, excludeSpecs, validatedIncludeSpecs, validatedExcludeSpecs, wildcardDirectories }; + return getFileNamesFromConfigSpecs(spec, basePath, options, host, extraFileExtensions); +} +/** + * Gets the file names from the provided config file specs that contain, files, include, exclude and + * other properties needed to resolve the file names + * @param spec The config file specs extracted with file names to include, wildcards to include/exclude and other details + * @param basePath The base path for any relative file specifications. + * @param options Compiler options. + * @param host The host used to resolve files and directories. + * @param extraFileExtensions optionaly file extra file extension information from host + */ +/* @internal */ +export function getFileNamesFromConfigSpecs(spec: ConfigFileSpecs, basePath: string, options: CompilerOptions, host: ParseConfigHost, extraFileExtensions: readonly FileExtensionInfo[] = []): ExpandResult { + basePath = normalizePath(basePath); + const keyMapper = host.useCaseSensitiveFileNames ? identity : toLowerCase; + // Literal file names (provided via the "files" array in tsconfig.json) are stored in a + // file map with a possibly case insensitive key. We use this map later when when including + // wildcard paths. + const literalFileMap = createMap(); + // Wildcard paths (provided via the "includes" array in tsconfig.json) are stored in a + // file map with a possibly case insensitive key. We use this map to store paths matched + // via wildcard, and to handle extension priority. + const wildcardFileMap = createMap(); + // Wildcard paths of json files (provided via the "includes" array in tsconfig.json) are stored in a + // file map with a possibly case insensitive key. We use this map to store paths matched + // via wildcard of *.json kind + const wildCardJsonFileMap = createMap(); + const { filesSpecs, validatedIncludeSpecs, validatedExcludeSpecs, wildcardDirectories } = spec; + // Rather than requery this for each file and filespec, we query the supported extensions + // once and store it on the expansion context. + const supportedExtensions = getSupportedExtensions(options, extraFileExtensions); + const supportedExtensionsWithJsonIfResolveJsonModule = getSuppoertedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions); + // Literal files are always included verbatim. An "include" or "exclude" specification cannot + // remove a literal file. + if (filesSpecs) { + for (const fileName of filesSpecs) { + const file = getNormalizedAbsolutePath(fileName, basePath); + literalFileMap.set(keyMapper(file), file); + } + } + let jsonOnlyIncludeRegexes: readonly RegExp[] | undefined; + if (validatedIncludeSpecs && validatedIncludeSpecs.length > 0) { + for (const file of host.readDirectory(basePath, supportedExtensionsWithJsonIfResolveJsonModule, validatedExcludeSpecs, validatedIncludeSpecs, /*depth*/ undefined)) { + if (fileExtensionIs(file, Extension.Json)) { + // Valid only if *.json specified + if (!jsonOnlyIncludeRegexes) { + const includes = validatedIncludeSpecs.filter(s => endsWith(s, Extension.Json)); + const includeFilePatterns = map(getRegularExpressionsForWildcards(includes, basePath, "files"), pattern => `^${pattern}$`); + jsonOnlyIncludeRegexes = includeFilePatterns ? includeFilePatterns.map(pattern => getRegexFromPattern(pattern, host.useCaseSensitiveFileNames)) : emptyArray; } - - // We may have included a wildcard path with a lower priority - // extension due to the user-defined order of entries in the - // "include" array. If there is a lower priority extension in the - // same directory, we should remove it. - removeWildcardFilesWithLowerPriorityExtension(file, wildcardFileMap, supportedExtensions, keyMapper); - - const key = keyMapper(file); - if (!literalFileMap.has(key) && !wildcardFileMap.has(key)) { - wildcardFileMap.set(key, file); + const includeIndex = findIndex(jsonOnlyIncludeRegexes, re => re.test(file)); + if (includeIndex !== -1) { + const key = keyMapper(file); + if (!literalFileMap.has(key) && !wildCardJsonFileMap.has(key)) { + wildCardJsonFileMap.set(key, file); + } } + continue; } - } - - const literalFiles = arrayFrom(literalFileMap.values()); - const wildcardFiles = arrayFrom(wildcardFileMap.values()); - - return { - fileNames: literalFiles.concat(wildcardFiles, arrayFrom(wildCardJsonFileMap.values())), - wildcardDirectories, - spec - }; - } - - function validateSpecs(specs: readonly string[], errors: Push, allowTrailingRecursion: boolean, jsonSourceFile: TsConfigSourceFile | undefined, specKey: string): readonly string[] { - return specs.filter(spec => { - const diag = specToDiagnostic(spec, allowTrailingRecursion); - if (diag !== undefined) { - errors.push(createDiagnostic(diag, spec)); + // If we have already included a literal or wildcard path with a + // higher priority extension, we should skip this file. + // + // This handles cases where we may encounter both .ts and + // .d.ts (or .js if "allowJs" is enabled) in the same + // directory when they are compilation outputs. + if (hasFileWithHigherPriorityExtension(file, literalFileMap, wildcardFileMap, supportedExtensions, keyMapper)) { + continue; + } + // We may have included a wildcard path with a lower priority + // extension due to the user-defined order of entries in the + // "include" array. If there is a lower priority extension in the + // same directory, we should remove it. + removeWildcardFilesWithLowerPriorityExtension(file, wildcardFileMap, supportedExtensions, keyMapper); + const key = keyMapper(file); + if (!literalFileMap.has(key) && !wildcardFileMap.has(key)) { + wildcardFileMap.set(key, file); } - return diag === undefined; - }); - - function createDiagnostic(message: DiagnosticMessage, spec: string): Diagnostic { - const element = getTsConfigPropArrayElementValue(jsonSourceFile, specKey, spec); - return element ? - createDiagnosticForNodeInSourceFile(jsonSourceFile!, element, message, spec) : - createCompilerDiagnostic(message, spec); } } - - function specToDiagnostic(spec: string, allowTrailingRecursion: boolean): DiagnosticMessage | undefined { - if (!allowTrailingRecursion && invalidTrailingRecursionPattern.test(spec)) { - return Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0; - } - else if (invalidDotDotAfterRecursiveWildcardPattern.test(spec)) { - return Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0; - } + const literalFiles = arrayFrom(literalFileMap.values()); + const wildcardFiles = arrayFrom(wildcardFileMap.values()); + return { + fileNames: literalFiles.concat(wildcardFiles, arrayFrom(wildCardJsonFileMap.values())), + wildcardDirectories, + spec + }; +} +function validateSpecs(specs: readonly string[], errors: Push, allowTrailingRecursion: boolean, jsonSourceFile: TsConfigSourceFile | undefined, specKey: string): readonly string[] { + return specs.filter(spec => { + const diag = specToDiagnostic(spec, allowTrailingRecursion); + if (diag !== undefined) { + errors.push(createDiagnostic(diag, spec)); + } + return diag === undefined; + }); + function createDiagnostic(message: DiagnosticMessage, spec: string): Diagnostic { + const element = getTsConfigPropArrayElementValue(jsonSourceFile, specKey, spec); + return element ? + createDiagnosticForNodeInSourceFile((jsonSourceFile!), element, message, spec) : + createCompilerDiagnostic(message, spec); } - - /** - * Gets directories in a set of include patterns that should be watched for changes. - */ - function getWildcardDirectories(include: readonly string[] | undefined, exclude: readonly string[] | undefined, path: string, useCaseSensitiveFileNames: boolean): MapLike { - // We watch a directory recursively if it contains a wildcard anywhere in a directory segment - // of the pattern: - // - // /a/b/**/d - Watch /a/b recursively to catch changes to any d in any subfolder recursively - // /a/b/*/d - Watch /a/b recursively to catch any d in any immediate subfolder, even if a new subfolder is added - // /a/b - Watch /a/b recursively to catch changes to anything in any recursive subfoler - // - // We watch a directory without recursion if it contains a wildcard in the file segment of - // the pattern: - // - // /a/b/* - Watch /a/b directly to catch any new file - // /a/b/a?z - Watch /a/b directly to catch any new file matching a?z - const rawExcludeRegex = getRegularExpressionForWildcard(exclude, path, "exclude"); - const excludeRegex = rawExcludeRegex && new RegExp(rawExcludeRegex, useCaseSensitiveFileNames ? "" : "i"); - const wildcardDirectories: MapLike = {}; - if (include !== undefined) { - const recursiveKeys: string[] = []; - for (const file of include) { - const spec = normalizePath(combinePaths(path, file)); - if (excludeRegex && excludeRegex.test(spec)) { - continue; - } - - const match = getWildcardDirectoryFromSpec(spec, useCaseSensitiveFileNames); - if (match) { - const { key, flags } = match; - const existingFlags = wildcardDirectories[key]; - if (existingFlags === undefined || existingFlags < flags) { - wildcardDirectories[key] = flags; - if (flags === WatchDirectoryFlags.Recursive) { - recursiveKeys.push(key); - } +} +function specToDiagnostic(spec: string, allowTrailingRecursion: boolean): DiagnosticMessage | undefined { + if (!allowTrailingRecursion && invalidTrailingRecursionPattern.test(spec)) { + return Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0; + } + else if (invalidDotDotAfterRecursiveWildcardPattern.test(spec)) { + return Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0; + } +} +/** + * Gets directories in a set of include patterns that should be watched for changes. + */ +function getWildcardDirectories(include: readonly string[] | undefined, exclude: readonly string[] | undefined, path: string, useCaseSensitiveFileNames: boolean): MapLike { + // We watch a directory recursively if it contains a wildcard anywhere in a directory segment + // of the pattern: + // + // /a/b/**/d - Watch /a/b recursively to catch changes to any d in any subfolder recursively + // /a/b/*/d - Watch /a/b recursively to catch any d in any immediate subfolder, even if a new subfolder is added + // /a/b - Watch /a/b recursively to catch changes to anything in any recursive subfoler + // + // We watch a directory without recursion if it contains a wildcard in the file segment of + // the pattern: + // + // /a/b/* - Watch /a/b directly to catch any new file + // /a/b/a?z - Watch /a/b directly to catch any new file matching a?z + const rawExcludeRegex = getRegularExpressionForWildcard(exclude, path, "exclude"); + const excludeRegex = rawExcludeRegex && new RegExp(rawExcludeRegex, useCaseSensitiveFileNames ? "" : "i"); + const wildcardDirectories: MapLike = {}; + if (include !== undefined) { + const recursiveKeys: string[] = []; + for (const file of include) { + const spec = normalizePath(combinePaths(path, file)); + if (excludeRegex && excludeRegex.test(spec)) { + continue; + } + const match = getWildcardDirectoryFromSpec(spec, useCaseSensitiveFileNames); + if (match) { + const { key, flags } = match; + const existingFlags = wildcardDirectories[key]; + if (existingFlags === undefined || existingFlags < flags) { + wildcardDirectories[key] = flags; + if (flags === WatchDirectoryFlags.Recursive) { + recursiveKeys.push(key); } } } - - // Remove any subpaths under an existing recursively watched directory. - for (const key in wildcardDirectories) { - if (hasProperty(wildcardDirectories, key)) { - for (const recursiveKey of recursiveKeys) { - if (key !== recursiveKey && containsPath(recursiveKey, key, path, !useCaseSensitiveFileNames)) { - delete wildcardDirectories[key]; - } + } + // Remove any subpaths under an existing recursively watched directory. + for (const key in wildcardDirectories) { + if (hasProperty(wildcardDirectories, key)) { + for (const recursiveKey of recursiveKeys) { + if (key !== recursiveKey && containsPath(recursiveKey, key, path, !useCaseSensitiveFileNames)) { + delete wildcardDirectories[key]; } } } } - - return wildcardDirectories; - } - - function getWildcardDirectoryFromSpec(spec: string, useCaseSensitiveFileNames: boolean): { key: string, flags: WatchDirectoryFlags } | undefined { - const match = wildcardDirectoryPattern.exec(spec); - if (match) { - return { - key: useCaseSensitiveFileNames ? match[0] : match[0].toLowerCase(), - flags: watchRecursivePattern.test(spec) ? WatchDirectoryFlags.Recursive : WatchDirectoryFlags.None - }; - } - if (isImplicitGlob(spec)) { - return { key: spec, flags: WatchDirectoryFlags.Recursive }; - } - return undefined; } - - /** - * Determines whether a literal or wildcard file has already been included that has a higher - * extension priority. - * - * @param file The path to the file. - * @param extensionPriority The priority of the extension. - * @param context The expansion context. - */ - function hasFileWithHigherPriorityExtension(file: string, literalFiles: Map, wildcardFiles: Map, extensions: readonly string[], keyMapper: (value: string) => string) { - const extensionPriority = getExtensionPriority(file, extensions); - const adjustedExtensionPriority = adjustExtensionPriority(extensionPriority, extensions); - for (let i = ExtensionPriority.Highest; i < adjustedExtensionPriority; i++) { - const higherPriorityExtension = extensions[i]; - const higherPriorityPath = keyMapper(changeExtension(file, higherPriorityExtension)); - if (literalFiles.has(higherPriorityPath) || wildcardFiles.has(higherPriorityPath)) { - return true; - } - } - - return false; + return wildcardDirectories; +} +function getWildcardDirectoryFromSpec(spec: string, useCaseSensitiveFileNames: boolean): { + key: string; + flags: WatchDirectoryFlags; +} | undefined { + const match = wildcardDirectoryPattern.exec(spec); + if (match) { + return { + key: useCaseSensitiveFileNames ? match[0] : match[0].toLowerCase(), + flags: watchRecursivePattern.test(spec) ? WatchDirectoryFlags.Recursive : WatchDirectoryFlags.None + }; } - - /** - * Removes files included via wildcard expansion with a lower extension priority that have - * already been included. - * - * @param file The path to the file. - * @param extensionPriority The priority of the extension. - * @param context The expansion context. - */ - function removeWildcardFilesWithLowerPriorityExtension(file: string, wildcardFiles: Map, extensions: readonly string[], keyMapper: (value: string) => string) { - const extensionPriority = getExtensionPriority(file, extensions); - const nextExtensionPriority = getNextLowestExtensionPriority(extensionPriority, extensions); - for (let i = nextExtensionPriority; i < extensions.length; i++) { - const lowerPriorityExtension = extensions[i]; - const lowerPriorityPath = keyMapper(changeExtension(file, lowerPriorityExtension)); - wildcardFiles.delete(lowerPriorityPath); - } + if (isImplicitGlob(spec)) { + return { key: spec, flags: WatchDirectoryFlags.Recursive }; } - - /** - * Produces a cleaned version of compiler options with personally identifying info (aka, paths) removed. - * Also converts enum values back to strings. - */ - /* @internal */ - export function convertCompilerOptionsForTelemetry(opts: CompilerOptions): CompilerOptions { - const out: CompilerOptions = {}; - for (const key in opts) { - if (opts.hasOwnProperty(key)) { - const type = getOptionFromName(key); - if (type !== undefined) { // Ignore unknown options - out[key] = getOptionValueWithEmptyStrings(opts[key], type); - } + return undefined; +} +/** + * Determines whether a literal or wildcard file has already been included that has a higher + * extension priority. + * + * @param file The path to the file. + * @param extensionPriority The priority of the extension. + * @param context The expansion context. + */ +function hasFileWithHigherPriorityExtension(file: string, literalFiles: ts.Map, wildcardFiles: ts.Map, extensions: readonly string[], keyMapper: (value: string) => string) { + const extensionPriority = getExtensionPriority(file, extensions); + const adjustedExtensionPriority = adjustExtensionPriority(extensionPriority, extensions); + for (let i = ExtensionPriority.Highest; i < adjustedExtensionPriority; i++) { + const higherPriorityExtension = extensions[i]; + const higherPriorityPath = keyMapper(changeExtension(file, higherPriorityExtension)); + if (literalFiles.has(higherPriorityPath) || wildcardFiles.has(higherPriorityPath)) { + return true; + } + } + return false; +} +/** + * Removes files included via wildcard expansion with a lower extension priority that have + * already been included. + * + * @param file The path to the file. + * @param extensionPriority The priority of the extension. + * @param context The expansion context. + */ +function removeWildcardFilesWithLowerPriorityExtension(file: string, wildcardFiles: ts.Map, extensions: readonly string[], keyMapper: (value: string) => string) { + const extensionPriority = getExtensionPriority(file, extensions); + const nextExtensionPriority = getNextLowestExtensionPriority(extensionPriority, extensions); + for (let i = nextExtensionPriority; i < extensions.length; i++) { + const lowerPriorityExtension = extensions[i]; + const lowerPriorityPath = keyMapper(changeExtension(file, lowerPriorityExtension)); + wildcardFiles.delete(lowerPriorityPath); + } +} +/** + * Produces a cleaned version of compiler options with personally identifying info (aka, paths) removed. + * Also converts enum values back to strings. + */ +/* @internal */ +export function convertCompilerOptionsForTelemetry(opts: CompilerOptions): CompilerOptions { + const out: CompilerOptions = {}; + for (const key in opts) { + if (opts.hasOwnProperty(key)) { + const type = getOptionFromName(key); + if (type !== undefined) { // Ignore unknown options + out[key] = getOptionValueWithEmptyStrings(opts[key], type); } } - return out; } - - function getOptionValueWithEmptyStrings(value: any, option: CommandLineOption): {} { - switch (option.type) { - case "object": // "paths". Can't get any useful information from the value since we blank out strings, so just return "". - return ""; - case "string": // Could be any arbitrary string -- use empty string instead. - return ""; - case "number": // Allow numbers, but be sure to check it's actually a number. - return typeof value === "number" ? value : ""; - case "boolean": - return typeof value === "boolean" ? value : ""; - case "list": - const elementType = option.element; - return isArray(value) ? value.map(v => getOptionValueWithEmptyStrings(v, elementType)) : ""; - default: - return forEachEntry(option.type, (optionEnumValue, optionStringValue) => { - if (optionEnumValue === value) { - return optionStringValue; - } - })!; // TODO: GH#18217 - } + return out; +} +function getOptionValueWithEmptyStrings(value: any, option: CommandLineOption): {} { + switch (option.type) { + case "object": // "paths". Can't get any useful information from the value since we blank out strings, so just return "". + return ""; + case "string": // Could be any arbitrary string -- use empty string instead. + return ""; + case "number": // Allow numbers, but be sure to check it's actually a number. + return typeof value === "number" ? value : ""; + case "boolean": + return typeof value === "boolean" ? value : ""; + case "list": + const elementType = option.element; + return isArray(value) ? value.map(v => getOptionValueWithEmptyStrings(v, elementType)) : ""; + default: + return forEachEntry(option.type, (optionEnumValue, optionStringValue) => { + if (optionEnumValue === value) { + return optionStringValue; + } + })!; // TODO: GH#18217 } } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 7a3fcac973aac..960eeba61961a 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1,348 +1,405 @@ - -/* @internal */ -namespace ts { - - export const emptyArray: never[] = [] as never[]; - - /** Create a new map. */ - export function createMap(): Map { - return new Map(); - } - - /** Create a new map from an array of entries. */ - export function createMapFromEntries(entries: [string, T][]): Map { - const map = createMap(); - for (const [key, value] of entries) { - map.set(key, value); - } - return map; - } - - /** Create a new map from a template object is provided, the map will copy entries from it. */ - export function createMapFromTemplate(template: MapLike): Map { - const map: Map = new Map(); - - // Copies keys/values from template. Note that for..in will not throw if - // template is undefined, and instead will just exit the loop. - for (const key in template) { - if (hasOwnProperty.call(template, key)) { - map.set(key, template[key]); - } - } - - return map; - } - - export function length(array: readonly any[] | undefined): number { - return array ? array.length : 0; - } - - /** - * Iterates through 'array' by index and performs the callback on each element of array until the callback - * returns a truthy value, then returns that value. - * If no such value is found, the callback is applied to each element of array and undefined is returned. - */ - export function forEach(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { - if (array) { - for (let i = 0; i < array.length; i++) { - const result = callback(array[i], i); - if (result) { - return result; - } - } +import { MapLike, Debug, EqualityComparer, Comparer, SortedReadonlyArray, Comparison, SortedArray, Push, UnderscoreEscapedMap, __String } from "./ts"; +import * as ts from "./ts"; +/* @internal */ +export const emptyArray: never[] = [] as never[]; +/** Create a new map. */ +/* @internal */ +export function createMap(): ts.Map { + return new ts.Map(); +} +/** Create a new map from an array of entries. */ +/* @internal */ +export function createMapFromEntries(entries: [string, T][]): ts.Map { + const map = createMap(); + for (const [key, value] of entries) { + map.set(key, value); + } + return map; +} +/** Create a new map from a template object is provided, the map will copy entries from it. */ +/* @internal */ +export function createMapFromTemplate(template: MapLike): ts.Map { + const map: ts.Map = new ts.Map(); + // Copies keys/values from template. Note that for..in will not throw if + // template is undefined, and instead will just exit the loop. + for (const key in template) { + if (hasOwnProperty.call(template, key)) { + map.set(key, template[key]); } - return undefined; } - - /** - * Like `forEach`, but iterates in reverse order. - */ - export function forEachRight(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { - if (array) { - for (let i = array.length - 1; i >= 0; i--) { - const result = callback(array[i], i); - if (result) { - return result; - } + return map; +} +/* @internal */ +export function length(array: readonly any[] | undefined): number { + return array ? array.length : 0; +} +/** + * Iterates through 'array' by index and performs the callback on each element of array until the callback + * returns a truthy value, then returns that value. + * If no such value is found, the callback is applied to each element of array and undefined is returned. + */ +/* @internal */ +export function forEach(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { + if (array) { + for (let i = 0; i < array.length; i++) { + const result = callback(array[i], i); + if (result) { + return result; } } - return undefined; } - - /** Like `forEach`, but suitable for use with numbers and strings (which may be falsy). */ - export function firstDefined(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { - if (array === undefined) { - return undefined; - } - - for (let i = 0; i < array.length; i++) { + return undefined; +} +/** + * Like `forEach`, but iterates in reverse order. + */ +/* @internal */ +export function forEachRight(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { + if (array) { + for (let i = array.length - 1; i >= 0; i--) { const result = callback(array[i], i); - if (result !== undefined) { + if (result) { return result; } } + } + return undefined; +} +/** Like `forEach`, but suitable for use with numbers and strings (which may be falsy). */ +/* @internal */ +export function firstDefined(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { + if (array === undefined) { return undefined; } - - export function firstDefinedIterator(iter: Iterator, callback: (element: T) => U | undefined): U | undefined { - while (true) { - const iterResult = iter.next(); - if (iterResult.done) { - return undefined; - } - const result = callback(iterResult.value); - if (result !== undefined) { - return result; - } + for (let i = 0; i < array.length; i++) { + const result = callback(array[i], i); + if (result !== undefined) { + return result; } } - - export function zipWith(arrayA: readonly T[], arrayB: readonly U[], callback: (a: T, b: U, index: number) => V): V[] { - const result: V[] = []; - Debug.assertEqual(arrayA.length, arrayB.length); - for (let i = 0; i < arrayA.length; i++) { - result.push(callback(arrayA[i], arrayB[i], i)); + return undefined; +} +/* @internal */ +export function firstDefinedIterator(iter: ts.Iterator, callback: (element: T) => U | undefined): U | undefined { + while (true) { + const iterResult = iter.next(); + if (iterResult.done) { + return undefined; + } + const result = callback(iterResult.value); + if (result !== undefined) { + return result; } - return result; } - - export function zipToIterator(arrayA: readonly T[], arrayB: readonly U[]): Iterator<[T, U]> { - Debug.assertEqual(arrayA.length, arrayB.length); - let i = 0; - return { - next() { - if (i === arrayA.length) { - return { value: undefined as never, done: true }; - } - i++; - return { value: [arrayA[i - 1], arrayB[i - 1]] as [T, U], done: false }; +} +/* @internal */ +export function zipWith(arrayA: readonly T[], arrayB: readonly U[], callback: (a: T, b: U, index: number) => V): V[] { + const result: V[] = []; + Debug.assertEqual(arrayA.length, arrayB.length); + for (let i = 0; i < arrayA.length; i++) { + result.push(callback(arrayA[i], arrayB[i], i)); + } + return result; +} +/* @internal */ +export function zipToIterator(arrayA: readonly T[], arrayB: readonly U[]): ts.Iterator<[T, U]> { + Debug.assertEqual(arrayA.length, arrayB.length); + let i = 0; + return { + next() { + if (i === arrayA.length) { + return { value: undefined as never, done: true }; + } + i++; + return { value: [arrayA[i - 1], arrayB[i - 1]] as [T, U], done: false }; + } + }; +} +/* @internal */ +export function zipToMap(keys: readonly string[], values: readonly T[]): ts.Map { + Debug.assert(keys.length === values.length); + const map = createMap(); + for (let i = 0; i < keys.length; ++i) { + map.set(keys[i], values[i]); + } + return map; +} +/** + * Iterates through `array` by index and performs the callback on each element of array until the callback + * returns a falsey value, then returns false. + * If no such value is found, the callback is applied to each element of array and `true` is returned. + */ +/* @internal */ +export function every(array: readonly T[], callback: (element: T, index: number) => boolean): boolean { + if (array) { + for (let i = 0; i < array.length; i++) { + if (!callback(array[i], i)) { + return false; } - }; + } } - - export function zipToMap(keys: readonly string[], values: readonly T[]): Map { - Debug.assert(keys.length === values.length); - const map = createMap(); - for (let i = 0; i < keys.length; ++i) { - map.set(keys[i], values[i]); + return true; +} +/** Works like Array.prototype.find, returning `undefined` if no element satisfying the predicate is found. */ +/* @internal */ +export function find(array: readonly T[], predicate: (element: T, index: number) => element is U): U | undefined; +/* @internal */ +export function find(array: readonly T[], predicate: (element: T, index: number) => boolean): T | undefined; +/* @internal */ +export function find(array: readonly T[], predicate: (element: T, index: number) => boolean): T | undefined { + for (let i = 0; i < array.length; i++) { + const value = array[i]; + if (predicate(value, i)) { + return value; } - return map; } - - /** - * Iterates through `array` by index and performs the callback on each element of array until the callback - * returns a falsey value, then returns false. - * If no such value is found, the callback is applied to each element of array and `true` is returned. - */ - export function every(array: readonly T[], callback: (element: T, index: number) => boolean): boolean { - if (array) { - for (let i = 0; i < array.length; i++) { - if (!callback(array[i], i)) { - return false; - } - } + return undefined; +} +/* @internal */ +export function findLast(array: readonly T[], predicate: (element: T, index: number) => element is U): U | undefined; +/* @internal */ +export function findLast(array: readonly T[], predicate: (element: T, index: number) => boolean): T | undefined; +/* @internal */ +export function findLast(array: readonly T[], predicate: (element: T, index: number) => boolean): T | undefined { + for (let i = array.length - 1; i >= 0; i--) { + const value = array[i]; + if (predicate(value, i)) { + return value; } - - return true; } - - /** Works like Array.prototype.find, returning `undefined` if no element satisfying the predicate is found. */ - export function find(array: readonly T[], predicate: (element: T, index: number) => element is U): U | undefined; - export function find(array: readonly T[], predicate: (element: T, index: number) => boolean): T | undefined; - export function find(array: readonly T[], predicate: (element: T, index: number) => boolean): T | undefined { - for (let i = 0; i < array.length; i++) { - const value = array[i]; - if (predicate(value, i)) { - return value; - } + return undefined; +} +/** Works like Array.prototype.findIndex, returning `-1` if no element satisfying the predicate is found. */ +/* @internal */ +export function findIndex(array: readonly T[], predicate: (element: T, index: number) => boolean, startIndex?: number): number { + for (let i = startIndex || 0; i < array.length; i++) { + if (predicate(array[i], i)) { + return i; } - return undefined; } - - export function findLast(array: readonly T[], predicate: (element: T, index: number) => element is U): U | undefined; - export function findLast(array: readonly T[], predicate: (element: T, index: number) => boolean): T | undefined; - export function findLast(array: readonly T[], predicate: (element: T, index: number) => boolean): T | undefined { - for (let i = array.length - 1; i >= 0; i--) { - const value = array[i]; - if (predicate(value, i)) { - return value; - } + return -1; +} +/* @internal */ +export function findLastIndex(array: readonly T[], predicate: (element: T, index: number) => boolean, startIndex?: number): number { + for (let i = startIndex === undefined ? array.length - 1 : startIndex; i >= 0; i--) { + if (predicate(array[i], i)) { + return i; + } + } + return -1; +} +/** + * Returns the first truthy result of `callback`, or else fails. + * This is like `forEach`, but never returns undefined. + */ +/* @internal */ +export function findMap(array: readonly T[], callback: (element: T, index: number) => U | undefined): U { + for (let i = 0; i < array.length; i++) { + const result = callback(array[i], i); + if (result) { + return result; } - return undefined; } - - /** Works like Array.prototype.findIndex, returning `-1` if no element satisfying the predicate is found. */ - export function findIndex(array: readonly T[], predicate: (element: T, index: number) => boolean, startIndex?: number): number { - for (let i = startIndex || 0; i < array.length; i++) { - if (predicate(array[i], i)) { - return i; + return Debug.fail(); +} +/* @internal */ +export function contains(array: readonly T[] | undefined, value: T, equalityComparer: EqualityComparer = equateValues): boolean { + if (array) { + for (const v of array) { + if (equalityComparer(v, value)) { + return true; } } - return -1; } - - export function findLastIndex(array: readonly T[], predicate: (element: T, index: number) => boolean, startIndex?: number): number { - for (let i = startIndex === undefined ? array.length - 1 : startIndex; i >= 0; i--) { - if (predicate(array[i], i)) { - return i; - } + return false; +} +/* @internal */ +export function arraysEqual(a: readonly T[], b: readonly T[], equalityComparer: EqualityComparer = equateValues): boolean { + return a.length === b.length && a.every((x, i) => equalityComparer(x, b[i])); +} +/* @internal */ +export function indexOfAnyCharCode(text: string, charCodes: readonly number[], start?: number): number { + for (let i = start || 0; i < text.length; i++) { + if (contains(charCodes, text.charCodeAt(i))) { + return i; } - return -1; } - - /** - * Returns the first truthy result of `callback`, or else fails. - * This is like `forEach`, but never returns undefined. - */ - export function findMap(array: readonly T[], callback: (element: T, index: number) => U | undefined): U { + return -1; +} +/* @internal */ +export function countWhere(array: readonly T[], predicate: (x: T, i: number) => boolean): number { + let count = 0; + if (array) { for (let i = 0; i < array.length; i++) { - const result = callback(array[i], i); - if (result) { - return result; + const v = array[i]; + if (predicate(v, i)) { + count++; } } - return Debug.fail(); } - - export function contains(array: readonly T[] | undefined, value: T, equalityComparer: EqualityComparer = equateValues): boolean { - if (array) { - for (const v of array) { - if (equalityComparer(v, value)) { - return true; + return count; +} +/** + * Filters an array by a predicate function. Returns the same array instance if the predicate is + * true for all elements, otherwise returns a new array instance containing the filtered subset. + */ +/* @internal */ +export function filter(array: T[], f: (x: T) => x is U): U[]; +/* @internal */ +export function filter(array: T[], f: (x: T) => boolean): T[]; +/* @internal */ +export function filter(array: readonly T[], f: (x: T) => x is U): readonly U[]; +/* @internal */ +export function filter(array: readonly T[], f: (x: T) => boolean): readonly T[]; +/* @internal */ +export function filter(array: T[] | undefined, f: (x: T) => x is U): U[] | undefined; +/* @internal */ +export function filter(array: T[] | undefined, f: (x: T) => boolean): T[] | undefined; +/* @internal */ +export function filter(array: readonly T[] | undefined, f: (x: T) => x is U): readonly U[] | undefined; +/* @internal */ +export function filter(array: readonly T[] | undefined, f: (x: T) => boolean): readonly T[] | undefined; +/* @internal */ +export function filter(array: readonly T[] | undefined, f: (x: T) => boolean): readonly T[] | undefined { + if (array) { + const len = array.length; + let i = 0; + while (i < len && f(array[i])) + i++; + if (i < len) { + const result = array.slice(0, i); + i++; + while (i < len) { + const item = array[i]; + if (f(item)) { + result.push(item); } + i++; } + return result; } - return false; - } - - export function arraysEqual(a: readonly T[], b: readonly T[], equalityComparer: EqualityComparer = equateValues): boolean { - return a.length === b.length && a.every((x, i) => equalityComparer(x, b[i])); } - - export function indexOfAnyCharCode(text: string, charCodes: readonly number[], start?: number): number { - for (let i = start || 0; i < text.length; i++) { - if (contains(charCodes, text.charCodeAt(i))) { - return i; - } + return array; +} +/* @internal */ +export function filterMutate(array: T[], f: (x: T, i: number, array: T[]) => boolean): void { + let outIndex = 0; + for (let i = 0; i < array.length; i++) { + if (f(array[i], i, array)) { + array[outIndex] = array[i]; + outIndex++; } - return -1; } - - export function countWhere(array: readonly T[], predicate: (x: T, i: number) => boolean): number { - let count = 0; - if (array) { - for (let i = 0; i < array.length; i++) { - const v = array[i]; - if (predicate(v, i)) { - count++; - } - } + array.length = outIndex; +} +/* @internal */ +export function clear(array: {}[]): void { + array.length = 0; +} +/* @internal */ +export function map(array: readonly T[], f: (x: T, i: number) => U): U[]; +/* @internal */ +export function map(array: readonly T[] | undefined, f: (x: T, i: number) => U): U[] | undefined; +/* @internal */ +export function map(array: readonly T[] | undefined, f: (x: T, i: number) => U): U[] | undefined { + let result: U[] | undefined; + if (array) { + result = []; + for (let i = 0; i < array.length; i++) { + result.push(f(array[i], i)); } - return count; } - - /** - * Filters an array by a predicate function. Returns the same array instance if the predicate is - * true for all elements, otherwise returns a new array instance containing the filtered subset. - */ - export function filter(array: T[], f: (x: T) => x is U): U[]; - export function filter(array: T[], f: (x: T) => boolean): T[]; - export function filter(array: readonly T[], f: (x: T) => x is U): readonly U[]; - export function filter(array: readonly T[], f: (x: T) => boolean): readonly T[]; - export function filter(array: T[] | undefined, f: (x: T) => x is U): U[] | undefined; - export function filter(array: T[] | undefined, f: (x: T) => boolean): T[] | undefined; - export function filter(array: readonly T[] | undefined, f: (x: T) => x is U): readonly U[] | undefined; - export function filter(array: readonly T[] | undefined, f: (x: T) => boolean): readonly T[] | undefined; - export function filter(array: readonly T[] | undefined, f: (x: T) => boolean): readonly T[] | undefined { - if (array) { - const len = array.length; - let i = 0; - while (i < len && f(array[i])) i++; - if (i < len) { + return result; +} +/* @internal */ +export function mapIterator(iter: ts.Iterator, mapFn: (x: T) => U): ts.Iterator { + return { + next() { + const iterRes = iter.next(); + return iterRes.done ? iterRes as { + done: true; + value: never; + } : { value: mapFn(iterRes.value), done: false }; + } + }; +} +// Maps from T to T and avoids allocation if all elements map to themselves +/* @internal */ +export function sameMap(array: T[], f: (x: T, i: number) => T): T[]; +/* @internal */ +export function sameMap(array: readonly T[], f: (x: T, i: number) => T): readonly T[]; +/* @internal */ +export function sameMap(array: T[] | undefined, f: (x: T, i: number) => T): T[] | undefined; +/* @internal */ +export function sameMap(array: readonly T[] | undefined, f: (x: T, i: number) => T): readonly T[] | undefined; +/* @internal */ +export function sameMap(array: readonly T[] | undefined, f: (x: T, i: number) => T): readonly T[] | undefined { + if (array) { + for (let i = 0; i < array.length; i++) { + const item = array[i]; + const mapped = f(item, i); + if (item !== mapped) { const result = array.slice(0, i); - i++; - while (i < len) { - const item = array[i]; - if (f(item)) { - result.push(item); - } - i++; + result.push(mapped); + for (i++; i < array.length; i++) { + result.push(f(array[i], i)); } return result; } } - return array; } - - export function filterMutate(array: T[], f: (x: T, i: number, array: T[]) => boolean): void { - let outIndex = 0; - for (let i = 0; i < array.length; i++) { - if (f(array[i], i, array)) { - array[outIndex] = array[i]; - outIndex++; + return array; +} +/** + * Flattens an array containing a mix of array or non-array elements. + * + * @param array The array to flatten. + */ +/* @internal */ +export function flatten(array: T[][] | readonly (T | readonly T[] | undefined)[]): T[] { + const result = []; + for (const v of array) { + if (v) { + if (isArray(v)) { + addRange(result, v); } - } - array.length = outIndex; - } - - export function clear(array: {}[]): void { - array.length = 0; - } - - export function map(array: readonly T[], f: (x: T, i: number) => U): U[]; - export function map(array: readonly T[] | undefined, f: (x: T, i: number) => U): U[] | undefined; - export function map(array: readonly T[] | undefined, f: (x: T, i: number) => U): U[] | undefined { - let result: U[] | undefined; - if (array) { - result = []; - for (let i = 0; i < array.length; i++) { - result.push(f(array[i], i)); + else { + result.push(v); } } - return result; } - - - export function mapIterator(iter: Iterator, mapFn: (x: T) => U): Iterator { - return { - next() { - const iterRes = iter.next(); - return iterRes.done ? iterRes as { done: true, value: never } : { value: mapFn(iterRes.value), done: false }; - } - }; - } - - // Maps from T to T and avoids allocation if all elements map to themselves - export function sameMap(array: T[], f: (x: T, i: number) => T): T[]; - export function sameMap(array: readonly T[], f: (x: T, i: number) => T): readonly T[]; - export function sameMap(array: T[] | undefined, f: (x: T, i: number) => T): T[] | undefined; - export function sameMap(array: readonly T[] | undefined, f: (x: T, i: number) => T): readonly T[] | undefined; - export function sameMap(array: readonly T[] | undefined, f: (x: T, i: number) => T): readonly T[] | undefined { - if (array) { - for (let i = 0; i < array.length; i++) { - const item = array[i]; - const mapped = f(item, i); - if (item !== mapped) { - const result = array.slice(0, i); - result.push(mapped); - for (i++; i < array.length; i++) { - result.push(f(array[i], i)); - } - return result; + return result; +} +/** + * Maps an array. If the mapped value is an array, it is spread into the result. + * + * @param array The array to map. + * @param mapfn The callback used to map the result into one or more values. + */ +/* @internal */ +export function flatMap(array: readonly T[] | undefined, mapfn: (x: T, i: number) => U | readonly U[] | undefined): readonly U[] { + let result: U[] | undefined; + if (array) { + for (let i = 0; i < array.length; i++) { + const v = mapfn(array[i], i); + if (v) { + if (isArray(v)) { + result = addRange(result, v); + } + else { + result = append(result, v); } } } - return array; } - - /** - * Flattens an array containing a mix of array or non-array elements. - * - * @param array The array to flatten. - */ - export function flatten(array: T[][] | readonly (T | readonly T[] | undefined)[]): T[] { - const result = []; - for (const v of array) { + return result || emptyArray; +} +/* @internal */ +export function flatMapToMutable(array: readonly T[] | undefined, mapfn: (x: T, i: number) => U | readonly U[] | undefined): U[] { + const result: U[] = []; + if (array) { + for (let i = 0; i < array.length; i++) { + const v = mapfn(array[i], i); if (v) { if (isArray(v)) { addRange(result, v); @@ -352,564 +409,544 @@ namespace ts { } } } - return result; } - - /** - * Maps an array. If the mapped value is an array, it is spread into the result. - * - * @param array The array to map. - * @param mapfn The callback used to map the result into one or more values. - */ - export function flatMap(array: readonly T[] | undefined, mapfn: (x: T, i: number) => U | readonly U[] | undefined): readonly U[] { - let result: U[] | undefined; - if (array) { - for (let i = 0; i < array.length; i++) { - const v = mapfn(array[i], i); - if (v) { - if (isArray(v)) { - result = addRange(result, v); - } - else { - result = append(result, v); - } + return result; +} +/* @internal */ +export function flatMapIterator(iter: ts.Iterator, mapfn: (x: T) => readonly U[] | ts.Iterator | undefined): ts.Iterator { + const first = iter.next(); + if (first.done) { + return emptyIterator; + } + let currentIter = getIterator(first.value); + return { + next() { + while (true) { + const currentRes = currentIter.next(); + if (!currentRes.done) { + return currentRes; } - } - } - return result || emptyArray; - } - - export function flatMapToMutable(array: readonly T[] | undefined, mapfn: (x: T, i: number) => U | readonly U[] | undefined): U[] { - const result: U[] = []; - if (array) { - for (let i = 0; i < array.length; i++) { - const v = mapfn(array[i], i); - if (v) { - if (isArray(v)) { - addRange(result, v); - } - else { - result.push(v); - } + const iterRes = iter.next(); + if (iterRes.done) { + return iterRes as { + done: true; + value: never; + }; } - } - } - return result; - } - - export function flatMapIterator(iter: Iterator, mapfn: (x: T) => readonly U[] | Iterator | undefined): Iterator { - const first = iter.next(); - if (first.done) { - return emptyIterator; - } - let currentIter = getIterator(first.value); - return { - next() { - while (true) { - const currentRes = currentIter.next(); - if (!currentRes.done) { - return currentRes; - } - const iterRes = iter.next(); - if (iterRes.done) { - return iterRes as { done: true, value: never }; - } - currentIter = getIterator(iterRes.value); + currentIter = getIterator(iterRes.value); + } + }, + }; + function getIterator(x: T): ts.Iterator { + const res = mapfn(x); + return res === undefined ? emptyIterator : isArray(res) ? arrayIterator(res) : res; + } +} +/** + * Maps an array. If the mapped value is an array, it is spread into the result. + * Avoids allocation if all elements map to themselves. + * + * @param array The array to map. + * @param mapfn The callback used to map the result into one or more values. + */ +/* @internal */ +export function sameFlatMap(array: T[], mapfn: (x: T, i: number) => T | readonly T[]): T[]; +/* @internal */ +export function sameFlatMap(array: readonly T[], mapfn: (x: T, i: number) => T | readonly T[]): readonly T[]; +/* @internal */ +export function sameFlatMap(array: T[], mapfn: (x: T, i: number) => T | T[]): T[] { + let result: T[] | undefined; + if (array) { + for (let i = 0; i < array.length; i++) { + const item = array[i]; + const mapped = mapfn(item, i); + if (result || item !== mapped || isArray(mapped)) { + if (!result) { + result = array.slice(0, i); } - }, - }; - - function getIterator(x: T): Iterator { - const res = mapfn(x); - return res === undefined ? emptyIterator : isArray(res) ? arrayIterator(res) : res; - } - } - - /** - * Maps an array. If the mapped value is an array, it is spread into the result. - * Avoids allocation if all elements map to themselves. - * - * @param array The array to map. - * @param mapfn The callback used to map the result into one or more values. - */ - export function sameFlatMap(array: T[], mapfn: (x: T, i: number) => T | readonly T[]): T[]; - export function sameFlatMap(array: readonly T[], mapfn: (x: T, i: number) => T | readonly T[]): readonly T[]; - export function sameFlatMap(array: T[], mapfn: (x: T, i: number) => T | T[]): T[] { - let result: T[] | undefined; - if (array) { - for (let i = 0; i < array.length; i++) { - const item = array[i]; - const mapped = mapfn(item, i); - if (result || item !== mapped || isArray(mapped)) { - if (!result) { - result = array.slice(0, i); - } - if (isArray(mapped)) { - addRange(result, mapped); - } - else { - result.push(mapped); - } + if (isArray(mapped)) { + addRange(result, mapped); + } + else { + result.push(mapped); } } } - return result || array; } - - export function mapAllOrFail(array: readonly T[], mapFn: (x: T, i: number) => U | undefined): U[] | undefined { - const result: U[] = []; + return result || array; +} +/* @internal */ +export function mapAllOrFail(array: readonly T[], mapFn: (x: T, i: number) => U | undefined): U[] | undefined { + const result: U[] = []; + for (let i = 0; i < array.length; i++) { + const mapped = mapFn(array[i], i); + if (mapped === undefined) { + return undefined; + } + result.push(mapped); + } + return result; +} +/* @internal */ +export function mapDefined(array: readonly T[] | undefined, mapFn: (x: T, i: number) => U | undefined): U[] { + const result: U[] = []; + if (array) { for (let i = 0; i < array.length; i++) { const mapped = mapFn(array[i], i); - if (mapped === undefined) { - return undefined; + if (mapped !== undefined) { + result.push(mapped); } - result.push(mapped); } - return result; } - - export function mapDefined(array: readonly T[] | undefined, mapFn: (x: T, i: number) => U | undefined): U[] { - const result: U[] = []; - if (array) { - for (let i = 0; i < array.length; i++) { - const mapped = mapFn(array[i], i); - if (mapped !== undefined) { - result.push(mapped); + return result; +} +/* @internal */ +export function mapDefinedIterator(iter: ts.Iterator, mapFn: (x: T) => U | undefined): ts.Iterator { + return { + next() { + while (true) { + const res = iter.next(); + if (res.done) { + return res as { + done: true; + value: never; + }; } - } - } - return result; - } - - export function mapDefinedIterator(iter: Iterator, mapFn: (x: T) => U | undefined): Iterator { - return { - next() { - while (true) { - const res = iter.next(); - if (res.done) { - return res as { done: true, value: never }; - } - const value = mapFn(res.value); - if (value !== undefined) { - return { value, done: false }; - } + const value = mapFn(res.value); + if (value !== undefined) { + return { value, done: false }; } } - }; - } - - export function mapDefinedMap(map: ReadonlyMap, mapValue: (value: T, key: string) => U | undefined, mapKey: (key: string) => string = identity): Map { - const result = createMap(); - map.forEach((value, key) => { - const mapped = mapValue(value, key); - if (mapped !== undefined) { - result.set(mapKey(key), mapped); - } - }); - return result; - } - - export const emptyIterator: Iterator = { next: () => ({ value: undefined as never, done: true }) }; - - export function singleIterator(value: T): Iterator { - let done = false; - return { - next() { - const wasDone = done; - done = true; - return wasDone ? { value: undefined as never, done: true } : { value, done: false }; - } - }; - } - - /** - * Maps contiguous spans of values with the same key. - * - * @param array The array to map. - * @param keyfn A callback used to select the key for an element. - * @param mapfn A callback used to map a contiguous chunk of values to a single value. - */ - export function spanMap(array: readonly T[], keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[]; - export function spanMap(array: readonly T[] | undefined, keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[] | undefined; - export function spanMap(array: readonly T[] | undefined, keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[] | undefined { - let result: U[] | undefined; - if (array) { - result = []; - const len = array.length; - let previousKey: K | undefined; - let key: K | undefined; - let start = 0; - let pos = 0; - while (start < len) { - while (pos < len) { - const value = array[pos]; - key = keyfn(value, pos); - if (pos === 0) { - previousKey = key; - } - else if (key !== previousKey) { - break; - } - - pos++; + } + }; +} +/* @internal */ +export function mapDefinedMap(map: ts.ReadonlyMap, mapValue: (value: T, key: string) => U | undefined, mapKey: (key: string) => string = identity): ts.Map { + const result = createMap(); + map.forEach((value, key) => { + const mapped = mapValue(value, key); + if (mapped !== undefined) { + result.set(mapKey(key), mapped); + } + }); + return result; +} +/* @internal */ +export const emptyIterator: ts.Iterator = { next: () => ({ value: undefined as never, done: true }) }; +/* @internal */ +export function singleIterator(value: T): ts.Iterator { + let done = false; + return { + next() { + const wasDone = done; + done = true; + return wasDone ? { value: undefined as never, done: true } : { value, done: false }; + } + }; +} +/** + * Maps contiguous spans of values with the same key. + * + * @param array The array to map. + * @param keyfn A callback used to select the key for an element. + * @param mapfn A callback used to map a contiguous chunk of values to a single value. + */ +/* @internal */ +export function spanMap(array: readonly T[], keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[]; +/* @internal */ +export function spanMap(array: readonly T[] | undefined, keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[] | undefined; +/* @internal */ +export function spanMap(array: readonly T[] | undefined, keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[] | undefined { + let result: U[] | undefined; + if (array) { + result = []; + const len = array.length; + let previousKey: K | undefined; + let key: K | undefined; + let start = 0; + let pos = 0; + while (start < len) { + while (pos < len) { + const value = array[pos]; + key = keyfn(value, pos); + if (pos === 0) { + previousKey = key; } - - if (start < pos) { - const v = mapfn(array.slice(start, pos), previousKey!, start, pos); - if (v) { - result.push(v); - } - - start = pos; + else if (key !== previousKey) { + break; } - - previousKey = key; pos++; } - } - - return result; - } - - export function mapEntries(map: ReadonlyMap, f: (key: string, value: T) => [string, U]): Map; - export function mapEntries(map: ReadonlyMap | undefined, f: (key: string, value: T) => [string, U]): Map | undefined; - export function mapEntries(map: ReadonlyMap | undefined, f: (key: string, value: T) => [string, U]): Map | undefined { - if (!map) { - return undefined; - } - - const result = createMap(); - map.forEach((value, key) => { - const [newKey, newValue] = f(key, value); - result.set(newKey, newValue); - }); - return result; - } - export function some(array: readonly T[] | undefined): array is readonly T[]; - export function some(array: readonly T[] | undefined, predicate: (value: T) => boolean): boolean; - export function some(array: readonly T[] | undefined, predicate?: (value: T) => boolean): boolean { - if (array) { - if (predicate) { - for (const v of array) { - if (predicate(v)) { - return true; - } + if (start < pos) { + const v = mapfn(array.slice(start, pos), previousKey!, start, pos); + if (v) { + result.push(v); } + start = pos; } - else { - return array.length > 0; - } + previousKey = key; + pos++; } - return false; } - - /** Calls the callback with (start, afterEnd) index pairs for each range where 'pred' is true. */ - export function getRangesWhere(arr: readonly T[], pred: (t: T) => boolean, cb: (start: number, afterEnd: number) => void): void { - let start: number | undefined; - for (let i = 0; i < arr.length; i++) { - if (pred(arr[i])) { - start = start === undefined ? i : start; - } - else { - if (start !== undefined) { - cb(start, i); - start = undefined; + return result; +} +/* @internal */ +export function mapEntries(map: ts.ReadonlyMap, f: (key: string, value: T) => [string, U]): ts.Map; +/* @internal */ +export function mapEntries(map: ts.ReadonlyMap | undefined, f: (key: string, value: T) => [string, U]): ts.Map | undefined; +/* @internal */ +export function mapEntries(map: ts.ReadonlyMap | undefined, f: (key: string, value: T) => [string, U]): ts.Map | undefined { + if (!map) { + return undefined; + } + const result = createMap(); + map.forEach((value, key) => { + const [newKey, newValue] = f(key, value); + result.set(newKey, newValue); + }); + return result; +} +/* @internal */ +export function some(array: readonly T[] | undefined): array is readonly T[]; +/* @internal */ +export function some(array: readonly T[] | undefined, predicate: (value: T) => boolean): boolean; +/* @internal */ +export function some(array: readonly T[] | undefined, predicate?: (value: T) => boolean): boolean { + if (array) { + if (predicate) { + for (const v of array) { + if (predicate(v)) { + return true; } } } - if (start !== undefined) cb(start, arr.length); - } - - export function concatenate(array1: T[], array2: T[]): T[]; - export function concatenate(array1: readonly T[], array2: readonly T[]): readonly T[]; - export function concatenate(array1: T[] | undefined, array2: T[] | undefined): T[]; - export function concatenate(array1: readonly T[] | undefined, array2: readonly T[] | undefined): readonly T[]; - export function concatenate(array1: T[], array2: T[]): T[] { - if (!some(array2)) return array1; - if (!some(array1)) return array2; - return [...array1, ...array2]; - } - - function deduplicateRelational(array: readonly T[], equalityComparer: EqualityComparer, comparer: Comparer) { - // Perform a stable sort of the array. This ensures the first entry in a list of - // duplicates remains the first entry in the result. - const indices = array.map((_, i) => i); - stableSortIndices(array, indices, comparer); - - let last = array[indices[0]]; - const deduplicated: number[] = [indices[0]]; - for (let i = 1; i < indices.length; i++) { - const index = indices[i]; - const item = array[index]; - if (!equalityComparer(last, item)) { - deduplicated.push(index); - last = item; - } + else { + return array.length > 0; } - - // restore original order - deduplicated.sort(); - return deduplicated.map(i => array[i]); } - - function deduplicateEquality(array: readonly T[], equalityComparer: EqualityComparer) { - const result: T[] = []; - for (const item of array) { - pushIfUnique(result, item, equalityComparer); + return false; +} +/** Calls the callback with (start, afterEnd) index pairs for each range where 'pred' is true. */ +/* @internal */ +export function getRangesWhere(arr: readonly T[], pred: (t: T) => boolean, cb: (start: number, afterEnd: number) => void): void { + let start: number | undefined; + for (let i = 0; i < arr.length; i++) { + if (pred(arr[i])) { + start = start === undefined ? i : start; + } + else { + if (start !== undefined) { + cb(start, i); + start = undefined; + } } - return result; } - - /** - * Deduplicates an unsorted array. - * @param equalityComparer An `EqualityComparer` used to determine if two values are duplicates. - * @param comparer An optional `Comparer` used to sort entries before comparison, though the - * result will remain in the original order in `array`. - */ - export function deduplicate(array: readonly T[], equalityComparer: EqualityComparer, comparer?: Comparer): T[] { - return array.length === 0 ? [] : - array.length === 1 ? array.slice() : + if (start !== undefined) + cb(start, arr.length); +} +/* @internal */ +export function concatenate(array1: T[], array2: T[]): T[]; +/* @internal */ +export function concatenate(array1: readonly T[], array2: readonly T[]): readonly T[]; +/* @internal */ +export function concatenate(array1: T[] | undefined, array2: T[] | undefined): T[]; +/* @internal */ +export function concatenate(array1: readonly T[] | undefined, array2: readonly T[] | undefined): readonly T[]; +/* @internal */ +export function concatenate(array1: T[], array2: T[]): T[] { + if (!some(array2)) + return array1; + if (!some(array1)) + return array2; + return [...array1, ...array2]; +} +/* @internal */ +function deduplicateRelational(array: readonly T[], equalityComparer: EqualityComparer, comparer: Comparer) { + // Perform a stable sort of the array. This ensures the first entry in a list of + // duplicates remains the first entry in the result. + const indices = array.map((_, i) => i); + stableSortIndices(array, indices, comparer); + let last = array[indices[0]]; + const deduplicated: number[] = [indices[0]]; + for (let i = 1; i < indices.length; i++) { + const index = indices[i]; + const item = array[index]; + if (!equalityComparer(last, item)) { + deduplicated.push(index); + last = item; + } + } + // restore original order + deduplicated.sort(); + return deduplicated.map(i => array[i]); +} +/* @internal */ +function deduplicateEquality(array: readonly T[], equalityComparer: EqualityComparer) { + const result: T[] = []; + for (const item of array) { + pushIfUnique(result, item, equalityComparer); + } + return result; +} +/** + * Deduplicates an unsorted array. + * @param equalityComparer An `EqualityComparer` used to determine if two values are duplicates. + * @param comparer An optional `Comparer` used to sort entries before comparison, though the + * result will remain in the original order in `array`. + */ +/* @internal */ +export function deduplicate(array: readonly T[], equalityComparer: EqualityComparer, comparer?: Comparer): T[] { + return array.length === 0 ? [] : + array.length === 1 ? array.slice() : comparer ? deduplicateRelational(array, equalityComparer, comparer) : - deduplicateEquality(array, equalityComparer); + deduplicateEquality(array, equalityComparer); +} +/** + * Deduplicates an array that has already been sorted. + */ +/* @internal */ +function deduplicateSorted(array: SortedReadonlyArray, comparer: EqualityComparer | Comparer): SortedReadonlyArray { + if (array.length === 0) + return emptyArray as any as SortedReadonlyArray; + let last = array[0]; + const deduplicated: T[] = [last]; + for (let i = 1; i < array.length; i++) { + const next = array[i]; + switch (comparer(next, last)) { + // equality comparison + case true: + // relational comparison + // falls through + case Comparison.EqualTo: + continue; + case Comparison.LessThan: + // If `array` is sorted, `next` should **never** be less than `last`. + return Debug.fail("Array is unsorted."); + } + deduplicated.push(last = next); + } + return deduplicated as any as SortedReadonlyArray; +} +/* @internal */ +export function insertSorted(array: SortedArray, insert: T, compare: Comparer): void { + if (array.length === 0) { + array.push(insert); + return; } - - /** - * Deduplicates an array that has already been sorted. - */ - function deduplicateSorted(array: SortedReadonlyArray, comparer: EqualityComparer | Comparer): SortedReadonlyArray { - if (array.length === 0) return emptyArray as any as SortedReadonlyArray; - - let last = array[0]; - const deduplicated: T[] = [last]; - for (let i = 1; i < array.length; i++) { - const next = array[i]; - switch (comparer(next, last)) { - // equality comparison - case true: - - // relational comparison - // falls through - case Comparison.EqualTo: - continue; - - case Comparison.LessThan: - // If `array` is sorted, `next` should **never** be less than `last`. - return Debug.fail("Array is unsorted."); - } - - deduplicated.push(last = next); - } - - return deduplicated as any as SortedReadonlyArray; + const insertIndex = binarySearch(array, insert, identity, compare); + if (insertIndex < 0) { + array.splice(~insertIndex, 0, insert); } - - export function insertSorted(array: SortedArray, insert: T, compare: Comparer): void { - if (array.length === 0) { - array.push(insert); - return; - } - - const insertIndex = binarySearch(array, insert, identity, compare); - if (insertIndex < 0) { - array.splice(~insertIndex, 0, insert); - } +} +/* @internal */ +export function sortAndDeduplicate(array: readonly string[]): SortedReadonlyArray; +/* @internal */ +export function sortAndDeduplicate(array: readonly T[], comparer: Comparer, equalityComparer?: EqualityComparer): SortedReadonlyArray; +/* @internal */ +export function sortAndDeduplicate(array: readonly T[], comparer?: Comparer, equalityComparer?: EqualityComparer): SortedReadonlyArray { + return deduplicateSorted(sort(array, comparer), equalityComparer || comparer || (compareStringsCaseSensitive as any as Comparer)); +} +/* @internal */ +export function arrayIsEqualTo(array1: readonly T[] | undefined, array2: readonly T[] | undefined, equalityComparer: (a: T, b: T, index: number) => boolean = equateValues): boolean { + if (!array1 || !array2) { + return array1 === array2; } - - export function sortAndDeduplicate(array: readonly string[]): SortedReadonlyArray; - export function sortAndDeduplicate(array: readonly T[], comparer: Comparer, equalityComparer?: EqualityComparer): SortedReadonlyArray; - export function sortAndDeduplicate(array: readonly T[], comparer?: Comparer, equalityComparer?: EqualityComparer): SortedReadonlyArray { - return deduplicateSorted(sort(array, comparer), equalityComparer || comparer || compareStringsCaseSensitive as any as Comparer); + if (array1.length !== array2.length) { + return false; } - - export function arrayIsEqualTo(array1: readonly T[] | undefined, array2: readonly T[] | undefined, equalityComparer: (a: T, b: T, index: number) => boolean = equateValues): boolean { - if (!array1 || !array2) { - return array1 === array2; - } - - if (array1.length !== array2.length) { + for (let i = 0; i < array1.length; i++) { + if (!equalityComparer(array1[i], array2[i], i)) { return false; } - - for (let i = 0; i < array1.length; i++) { - if (!equalityComparer(array1[i], array2[i], i)) { - return false; - } - } - - return true; - } - - /** - * Compacts an array, removing any falsey elements. - */ - export function compact(array: (T | undefined | null | false | 0 | "")[]): T[]; - export function compact(array: readonly (T | undefined | null | false | 0 | "")[]): readonly T[]; - // ESLint thinks these can be combined with the above - they cannot; they'd produce higher-priority inferences and prevent the falsey types from being stripped - export function compact(array: T[]): T[]; // eslint-disable-line @typescript-eslint/unified-signatures - export function compact(array: readonly T[]): readonly T[]; // eslint-disable-line @typescript-eslint/unified-signatures - export function compact(array: T[]): T[] { - let result: T[] | undefined; - if (array) { - for (let i = 0; i < array.length; i++) { - const v = array[i]; - if (result || !v) { - if (!result) { - result = array.slice(0, i); - } - if (v) { - result.push(v); - } - } - } - } - return result || array; } - - /** - * Gets the relative complement of `arrayA` with respect to `arrayB`, returning the elements that - * are not present in `arrayA` but are present in `arrayB`. Assumes both arrays are sorted - * based on the provided comparer. - */ - export function relativeComplement(arrayA: T[] | undefined, arrayB: T[] | undefined, comparer: Comparer): T[] | undefined { - if (!arrayB || !arrayA || arrayB.length === 0 || arrayA.length === 0) return arrayB; - const result: T[] = []; - loopB: for (let offsetA = 0, offsetB = 0; offsetB < arrayB.length; offsetB++) { - if (offsetB > 0) { - // Ensure `arrayB` is properly sorted. - Debug.assertGreaterThanOrEqual(comparer(arrayB[offsetB], arrayB[offsetB - 1]), Comparison.EqualTo); - } - - loopA: for (const startA = offsetA; offsetA < arrayA.length; offsetA++) { - if (offsetA > startA) { - // Ensure `arrayA` is properly sorted. We only need to perform this check if - // `offsetA` has changed since we entered the loop. - Debug.assertGreaterThanOrEqual(comparer(arrayA[offsetA], arrayA[offsetA - 1]), Comparison.EqualTo); + return true; +} +/** + * Compacts an array, removing any falsey elements. + */ +/* @internal */ +export function compact(array: (T | undefined | null | false | 0 | "")[]): T[]; +/* @internal */ +export function compact(array: readonly (T | undefined | null | false | 0 | "")[]): readonly T[]; +// ESLint thinks these can be combined with the above - they cannot; they'd produce higher-priority inferences and prevent the falsey types from being stripped +/* @internal */ +export function compact(array: T[]): T[]; // eslint-disable-line @typescript-eslint/unified-signatures +/* @internal */ +export function compact(array: readonly T[]): readonly T[]; // eslint-disable-line @typescript-eslint/unified-signatures +/* @internal */ +export function compact(array: T[]): T[] { + let result: T[] | undefined; + if (array) { + for (let i = 0; i < array.length; i++) { + const v = array[i]; + if (result || !v) { + if (!result) { + result = array.slice(0, i); } - - switch (comparer(arrayB[offsetB], arrayA[offsetA])) { - case Comparison.LessThan: - // If B is less than A, B does not exist in arrayA. Add B to the result and - // move to the next element in arrayB without changing the current position - // in arrayA. - result.push(arrayB[offsetB]); - continue loopB; - case Comparison.EqualTo: - // If B is equal to A, B exists in arrayA. Move to the next element in - // arrayB without adding B to the result or changing the current position - // in arrayA. - continue loopB; - case Comparison.GreaterThan: - // If B is greater than A, we need to keep looking for B in arrayA. Move to - // the next element in arrayA and recheck. - continue loopA; + if (v) { + result.push(v); } } } - return result; } - - export function sum, K extends string>(array: readonly T[], prop: K): number { - let result = 0; - for (const v of array) { - result += v[prop]; + return result || array; +} +/** + * Gets the relative complement of `arrayA` with respect to `arrayB`, returning the elements that + * are not present in `arrayA` but are present in `arrayB`. Assumes both arrays are sorted + * based on the provided comparer. + */ +/* @internal */ +export function relativeComplement(arrayA: T[] | undefined, arrayB: T[] | undefined, comparer: Comparer): T[] | undefined { + if (!arrayB || !arrayA || arrayB.length === 0 || arrayA.length === 0) + return arrayB; + const result: T[] = []; + loopB: for (let offsetA = 0, offsetB = 0; offsetB < arrayB.length; offsetB++) { + if (offsetB > 0) { + // Ensure `arrayB` is properly sorted. + Debug.assertGreaterThanOrEqual(comparer(arrayB[offsetB], arrayB[offsetB - 1]), Comparison.EqualTo); + } + loopA: for (const startA = offsetA; offsetA < arrayA.length; offsetA++) { + if (offsetA > startA) { + // Ensure `arrayA` is properly sorted. We only need to perform this check if + // `offsetA` has changed since we entered the loop. + Debug.assertGreaterThanOrEqual(comparer(arrayA[offsetA], arrayA[offsetA - 1]), Comparison.EqualTo); + } + switch (comparer(arrayB[offsetB], arrayA[offsetA])) { + case Comparison.LessThan: + // If B is less than A, B does not exist in arrayA. Add B to the result and + // move to the next element in arrayB without changing the current position + // in arrayA. + result.push(arrayB[offsetB]); + continue loopB; + case Comparison.EqualTo: + // If B is equal to A, B exists in arrayA. Move to the next element in + // arrayB without adding B to the result or changing the current position + // in arrayA. + continue loopB; + case Comparison.GreaterThan: + // If B is greater than A, we need to keep looking for B in arrayA. Move to + // the next element in arrayA and recheck. + continue loopA; + } } - return result; } - - /** - * Appends a value to an array, returning the array. - * - * @param to The array to which `value` is to be appended. If `to` is `undefined`, a new array - * is created if `value` was appended. - * @param value The value to append to the array. If `value` is `undefined`, nothing is - * appended. - */ - export function append[number] | undefined>(to: TArray, value: TValue): [undefined, undefined] extends [TArray, TValue] ? TArray : NonNullable[number][]; - export function append(to: T[], value: T | undefined): T[]; - export function append(to: T[] | undefined, value: T): T[]; - export function append(to: T[] | undefined, value: T | undefined): T[] | undefined; - export function append(to: Push, value: T | undefined): void; - export function append(to: T[], value: T | undefined): T[] | undefined { - if (value === undefined) return to; - if (to === undefined) return [value]; - to.push(value); + return result; +} +/* @internal */ +export function sum, K extends string>(array: readonly T[], prop: K): number { + let result = 0; + for (const v of array) { + result += v[prop]; + } + return result; +} +/** + * Appends a value to an array, returning the array. + * + * @param to The array to which `value` is to be appended. If `to` is `undefined`, a new array + * is created if `value` was appended. + * @param value The value to append to the array. If `value` is `undefined`, nothing is + * appended. + */ +/* @internal */ +export function append[number] | undefined>(to: TArray, value: TValue): [undefined, undefined] extends [TArray, TValue] ? TArray : NonNullable[number][]; +/* @internal */ +export function append(to: T[], value: T | undefined): T[]; +/* @internal */ +export function append(to: T[] | undefined, value: T): T[]; +/* @internal */ +export function append(to: T[] | undefined, value: T | undefined): T[] | undefined; +/* @internal */ +export function append(to: Push, value: T | undefined): void; +/* @internal */ +export function append(to: T[], value: T | undefined): T[] | undefined { + if (value === undefined) return to; - } - - /** - * Gets the actual offset into an array for a relative offset. Negative offsets indicate a - * position offset from the end of the array. - */ - function toOffset(array: readonly any[], offset: number) { - return offset < 0 ? array.length + offset : offset; - } - - /** - * Appends a range of value to an array, returning the array. - * - * @param to The array to which `value` is to be appended. If `to` is `undefined`, a new array - * is created if `value` was appended. - * @param from The values to append to the array. If `from` is `undefined`, nothing is - * appended. If an element of `from` is `undefined`, that element is not appended. - * @param start The offset in `from` at which to start copying values. - * @param end The offset in `from` at which to stop copying values (non-inclusive). - */ - export function addRange(to: T[], from: readonly T[] | undefined, start?: number, end?: number): T[]; - export function addRange(to: T[] | undefined, from: readonly T[] | undefined, start?: number, end?: number): T[] | undefined; - export function addRange(to: T[] | undefined, from: readonly T[] | undefined, start?: number, end?: number): T[] | undefined { - if (from === undefined || from.length === 0) return to; - if (to === undefined) return from.slice(start, end); - start = start === undefined ? 0 : toOffset(from, start); - end = end === undefined ? from.length : toOffset(from, end); - for (let i = start; i < end && i < from.length; i++) { - if (from[i] !== undefined) { - to.push(from[i]); - } - } + if (to === undefined) + return [value]; + to.push(value); + return to; +} +/** + * Gets the actual offset into an array for a relative offset. Negative offsets indicate a + * position offset from the end of the array. + */ +/* @internal */ +function toOffset(array: readonly any[], offset: number) { + return offset < 0 ? array.length + offset : offset; +} +/** + * Appends a range of value to an array, returning the array. + * + * @param to The array to which `value` is to be appended. If `to` is `undefined`, a new array + * is created if `value` was appended. + * @param from The values to append to the array. If `from` is `undefined`, nothing is + * appended. If an element of `from` is `undefined`, that element is not appended. + * @param start The offset in `from` at which to start copying values. + * @param end The offset in `from` at which to stop copying values (non-inclusive). + */ +/* @internal */ +export function addRange(to: T[], from: readonly T[] | undefined, start?: number, end?: number): T[]; +/* @internal */ +export function addRange(to: T[] | undefined, from: readonly T[] | undefined, start?: number, end?: number): T[] | undefined; +/* @internal */ +export function addRange(to: T[] | undefined, from: readonly T[] | undefined, start?: number, end?: number): T[] | undefined { + if (from === undefined || from.length === 0) return to; + if (to === undefined) + return from.slice(start, end); + start = start === undefined ? 0 : toOffset(from, start); + end = end === undefined ? from.length : toOffset(from, end); + for (let i = start; i < end && i < from.length; i++) { + if (from[i] !== undefined) { + to.push(from[i]); + } + } + return to; +} +/** + * @return Whether the value was added. + */ +/* @internal */ +export function pushIfUnique(array: T[], toAdd: T, equalityComparer?: EqualityComparer): boolean { + if (contains(array, toAdd, equalityComparer)) { + return false; } - - /** - * @return Whether the value was added. - */ - export function pushIfUnique(array: T[], toAdd: T, equalityComparer?: EqualityComparer): boolean { - if (contains(array, toAdd, equalityComparer)) { - return false; - } - else { - array.push(toAdd); - return true; - } - } - - /** - * Unlike `pushIfUnique`, this can take `undefined` as an input, and returns a new array. - */ - export function appendIfUnique(array: T[] | undefined, toAdd: T, equalityComparer?: EqualityComparer): T[] { - if (array) { - pushIfUnique(array, toAdd, equalityComparer); - return array; - } - else { - return [toAdd]; - } + else { + array.push(toAdd); + return true; } - - function stableSortIndices(array: readonly T[], indices: number[], comparer: Comparer) { - // sort indices by value then position - indices.sort((x, y) => comparer(array[x], array[y]) || compareValues(x, y)); +} +/** + * Unlike `pushIfUnique`, this can take `undefined` as an input, and returns a new array. + */ +/* @internal */ +export function appendIfUnique(array: T[] | undefined, toAdd: T, equalityComparer?: EqualityComparer): T[] { + if (array) { + pushIfUnique(array, toAdd, equalityComparer); + return array; } - - /** - * Returns a new sorted array. - */ - export function sort(array: readonly T[], comparer?: Comparer): SortedReadonlyArray { - return (array.length === 0 ? array : array.slice().sort(comparer)) as SortedReadonlyArray; + else { + return [toAdd]; } - - export function arrayIterator(array: readonly T[]): Iterator { - let i = 0; - return { next: () => { +} +/* @internal */ +function stableSortIndices(array: readonly T[], indices: number[], comparer: Comparer) { + // sort indices by value then position + indices.sort((x, y) => comparer(array[x], array[y]) || compareValues(x, y)); +} +/** + * Returns a new sorted array. + */ +/* @internal */ +export function sort(array: readonly T[], comparer?: Comparer): SortedReadonlyArray { + return (array.length === 0 ? array : array.slice().sort(comparer)) as SortedReadonlyArray; +} +/* @internal */ +export function arrayIterator(array: readonly T[]): ts.Iterator { + let i = 0; + return { next: () => { if (i === array.length) { return { value: undefined as never, done: true }; } @@ -917,1076 +954,1088 @@ namespace ts { i++; return { value: array[i - 1], done: false }; } - }}; - } - - export function arrayReverseIterator(array: readonly T[]): Iterator { - let i = array.length; - return { - next: () => { - if (i === 0) { - return { value: undefined as never, done: true }; - } - else { - i--; - return { value: array[i], done: false }; - } + } }; +} +/* @internal */ +export function arrayReverseIterator(array: readonly T[]): ts.Iterator { + let i = array.length; + return { + next: () => { + if (i === 0) { + return { value: undefined as never, done: true }; } - }; - } - - /** - * Stable sort of an array. Elements equal to each other maintain their relative position in the array. - */ - export function stableSort(array: readonly T[], comparer: Comparer): SortedReadonlyArray { - const indices = array.map((_, i) => i); - stableSortIndices(array, indices, comparer); - return indices.map(i => array[i]) as SortedArray as SortedReadonlyArray; - } - - export function rangeEquals(array1: readonly T[], array2: readonly T[], pos: number, end: number) { - while (pos < end) { - if (array1[pos] !== array2[pos]) { - return false; + else { + i--; + return { value: array[i], done: false }; } - pos++; } - return true; - } - - /** - * Returns the element at a specific offset in an array if non-empty, `undefined` otherwise. - * A negative offset indicates the element should be retrieved from the end of the array. - */ - export function elementAt(array: readonly T[] | undefined, offset: number): T | undefined { - if (array) { - offset = toOffset(array, offset); - if (offset < array.length) { - return array[offset]; - } + }; +} +/** + * Stable sort of an array. Elements equal to each other maintain their relative position in the array. + */ +/* @internal */ +export function stableSort(array: readonly T[], comparer: Comparer): SortedReadonlyArray { + const indices = array.map((_, i) => i); + stableSortIndices(array, indices, comparer); + return indices.map(i => array[i]) as SortedArray as SortedReadonlyArray; +} +/* @internal */ +export function rangeEquals(array1: readonly T[], array2: readonly T[], pos: number, end: number) { + while (pos < end) { + if (array1[pos] !== array2[pos]) { + return false; } - return undefined; - } - - /** - * Returns the first element of an array if non-empty, `undefined` otherwise. - */ - export function firstOrUndefined(array: readonly T[]): T | undefined { - return array.length === 0 ? undefined : array[0]; + pos++; } - - export function first(array: readonly T[]): T { - Debug.assert(array.length !== 0); - return array[0]; - } - - /** - * Returns the last element of an array if non-empty, `undefined` otherwise. - */ - export function lastOrUndefined(array: readonly T[]): T | undefined { - return array.length === 0 ? undefined : array[array.length - 1]; - } - - export function last(array: readonly T[]): T { - Debug.assert(array.length !== 0); - return array[array.length - 1]; - } - - /** - * Returns the only element of an array if it contains only one element, `undefined` otherwise. - */ - export function singleOrUndefined(array: readonly T[] | undefined): T | undefined { - return array && array.length === 1 - ? array[0] - : undefined; - } - - /** - * Returns the only element of an array if it contains only one element; otherwise, returns the - * array. - */ - export function singleOrMany(array: T[]): T | T[]; - export function singleOrMany(array: readonly T[]): T | readonly T[]; - export function singleOrMany(array: T[] | undefined): T | T[] | undefined; - export function singleOrMany(array: readonly T[] | undefined): T | readonly T[] | undefined; - export function singleOrMany(array: readonly T[] | undefined): T | readonly T[] | undefined { - return array && array.length === 1 - ? array[0] - : array; - } - - export function replaceElement(array: readonly T[], index: number, value: T): T[] { - const result = array.slice(0); - result[index] = value; - return result; - } - - /** - * Performs a binary search, finding the index at which `value` occurs in `array`. - * If no such index is found, returns the 2's-complement of first index at which - * `array[index]` exceeds `value`. - * @param array A sorted array whose first element must be no larger than number - * @param value The value to be searched for in the array. - * @param keySelector A callback used to select the search key from `value` and each element of - * `array`. - * @param keyComparer A callback used to compare two keys in a sorted array. - * @param offset An offset into `array` at which to start the search. - */ - export function binarySearch(array: readonly T[], value: T, keySelector: (v: T) => U, keyComparer: Comparer, offset?: number): number { - return binarySearchKey(array, keySelector(value), keySelector, keyComparer, offset); + return true; +} +/** + * Returns the element at a specific offset in an array if non-empty, `undefined` otherwise. + * A negative offset indicates the element should be retrieved from the end of the array. + */ +/* @internal */ +export function elementAt(array: readonly T[] | undefined, offset: number): T | undefined { + if (array) { + offset = toOffset(array, offset); + if (offset < array.length) { + return array[offset]; + } + } + return undefined; +} +/** + * Returns the first element of an array if non-empty, `undefined` otherwise. + */ +/* @internal */ +export function firstOrUndefined(array: readonly T[]): T | undefined { + return array.length === 0 ? undefined : array[0]; +} +/* @internal */ +export function first(array: readonly T[]): T { + Debug.assert(array.length !== 0); + return array[0]; +} +/** + * Returns the last element of an array if non-empty, `undefined` otherwise. + */ +/* @internal */ +export function lastOrUndefined(array: readonly T[]): T | undefined { + return array.length === 0 ? undefined : array[array.length - 1]; +} +/* @internal */ +export function last(array: readonly T[]): T { + Debug.assert(array.length !== 0); + return array[array.length - 1]; +} +/** + * Returns the only element of an array if it contains only one element, `undefined` otherwise. + */ +/* @internal */ +export function singleOrUndefined(array: readonly T[] | undefined): T | undefined { + return array && array.length === 1 + ? array[0] + : undefined; +} +/** + * Returns the only element of an array if it contains only one element; otherwise, returns the + * array. + */ +/* @internal */ +export function singleOrMany(array: T[]): T | T[]; +/* @internal */ +export function singleOrMany(array: readonly T[]): T | readonly T[]; +/* @internal */ +export function singleOrMany(array: T[] | undefined): T | T[] | undefined; +/* @internal */ +export function singleOrMany(array: readonly T[] | undefined): T | readonly T[] | undefined; +/* @internal */ +export function singleOrMany(array: readonly T[] | undefined): T | readonly T[] | undefined { + return array && array.length === 1 + ? array[0] + : array; +} +/* @internal */ +export function replaceElement(array: readonly T[], index: number, value: T): T[] { + const result = array.slice(0); + result[index] = value; + return result; +} +/** + * Performs a binary search, finding the index at which `value` occurs in `array`. + * If no such index is found, returns the 2's-complement of first index at which + * `array[index]` exceeds `value`. + * @param array A sorted array whose first element must be no larger than number + * @param value The value to be searched for in the array. + * @param keySelector A callback used to select the search key from `value` and each element of + * `array`. + * @param keyComparer A callback used to compare two keys in a sorted array. + * @param offset An offset into `array` at which to start the search. + */ +/* @internal */ +export function binarySearch(array: readonly T[], value: T, keySelector: (v: T) => U, keyComparer: Comparer, offset?: number): number { + return binarySearchKey(array, keySelector(value), keySelector, keyComparer, offset); +} +/** + * Performs a binary search, finding the index at which an object with `key` occurs in `array`. + * If no such index is found, returns the 2's-complement of first index at which + * `array[index]` exceeds `key`. + * @param array A sorted array whose first element must be no larger than number + * @param key The key to be searched for in the array. + * @param keySelector A callback used to select the search key from each element of `array`. + * @param keyComparer A callback used to compare two keys in a sorted array. + * @param offset An offset into `array` at which to start the search. + */ +/* @internal */ +export function binarySearchKey(array: readonly T[], key: U, keySelector: (v: T) => U, keyComparer: Comparer, offset?: number): number { + if (!some(array)) { + return -1; } - - /** - * Performs a binary search, finding the index at which an object with `key` occurs in `array`. - * If no such index is found, returns the 2's-complement of first index at which - * `array[index]` exceeds `key`. - * @param array A sorted array whose first element must be no larger than number - * @param key The key to be searched for in the array. - * @param keySelector A callback used to select the search key from each element of `array`. - * @param keyComparer A callback used to compare two keys in a sorted array. - * @param offset An offset into `array` at which to start the search. - */ - export function binarySearchKey(array: readonly T[], key: U, keySelector: (v: T) => U, keyComparer: Comparer, offset?: number): number { - if (!some(array)) { - return -1; - } - - let low = offset || 0; - let high = array.length - 1; - while (low <= high) { - const middle = low + ((high - low) >> 1); - const midKey = keySelector(array[middle]); - switch (keyComparer(midKey, key)) { - case Comparison.LessThan: - low = middle + 1; - break; - case Comparison.EqualTo: - return middle; - case Comparison.GreaterThan: - high = middle - 1; - break; + let low = offset || 0; + let high = array.length - 1; + while (low <= high) { + const middle = low + ((high - low) >> 1); + const midKey = keySelector(array[middle]); + switch (keyComparer(midKey, key)) { + case Comparison.LessThan: + low = middle + 1; + break; + case Comparison.EqualTo: + return middle; + case Comparison.GreaterThan: + high = middle - 1; + break; + } + } + return ~low; +} +/* @internal */ +export function reduceLeft(array: readonly T[] | undefined, f: (memo: U, value: T, i: number) => U, initial: U, start?: number, count?: number): U; +/* @internal */ +export function reduceLeft(array: readonly T[], f: (memo: T, value: T, i: number) => T): T | undefined; +/* @internal */ +export function reduceLeft(array: T[], f: (memo: T, value: T, i: number) => T, initial?: T, start?: number, count?: number): T | undefined { + if (array && array.length > 0) { + const size = array.length; + if (size > 0) { + let pos = start === undefined || start < 0 ? 0 : start; + const end = count === undefined || pos + count > size - 1 ? size - 1 : pos + count; + let result: T; + if (arguments.length <= 2) { + result = array[pos]; + pos++; } - } - - return ~low; - } - - export function reduceLeft(array: readonly T[] | undefined, f: (memo: U, value: T, i: number) => U, initial: U, start?: number, count?: number): U; - export function reduceLeft(array: readonly T[], f: (memo: T, value: T, i: number) => T): T | undefined; - export function reduceLeft(array: T[], f: (memo: T, value: T, i: number) => T, initial?: T, start?: number, count?: number): T | undefined { - if (array && array.length > 0) { - const size = array.length; - if (size > 0) { - let pos = start === undefined || start < 0 ? 0 : start; - const end = count === undefined || pos + count > size - 1 ? size - 1 : pos + count; - let result: T; - if (arguments.length <= 2) { - result = array[pos]; - pos++; - } - else { - result = initial!; - } - while (pos <= end) { - result = f(result, array[pos], pos); - pos++; - } - return result; + else { + result = initial!; } - } - return initial; - } - - const hasOwnProperty = Object.prototype.hasOwnProperty; - - /** - * Indicates whether a map-like contains an own property with the specified key. - * - * @param map A map-like. - * @param key A property key. - */ - export function hasProperty(map: MapLike, key: string): boolean { - return hasOwnProperty.call(map, key); - } - - /** - * Gets the value of an owned property in a map-like. - * - * @param map A map-like. - * @param key A property key. - */ - export function getProperty(map: MapLike, key: string): T | undefined { - return hasOwnProperty.call(map, key) ? map[key] : undefined; - } - - /** - * Gets the owned, enumerable property keys of a map-like. - */ - export function getOwnKeys(map: MapLike): string[] { - const keys: string[] = []; - for (const key in map) { - if (hasOwnProperty.call(map, key)) { - keys.push(key); + while (pos <= end) { + result = f(result, array[pos], pos); + pos++; } + return result; } - - return keys; } - - export function getAllKeys(obj: object): string[] { - const result: string[] = []; - do { - const names = Object.getOwnPropertyNames(obj); - for (const name of names) { - pushIfUnique(result, name); - } - } while (obj = Object.getPrototypeOf(obj)); - return result; - } - - export function getOwnValues(sparseArray: T[]): T[] { - const values: T[] = []; - for (const key in sparseArray) { - if (hasOwnProperty.call(sparseArray, key)) { - values.push(sparseArray[key]); - } - } - - return values; - } - - /** Shims `Array.from`. */ - export function arrayFrom(iterator: Iterator | IterableIterator, map: (t: T) => U): U[]; - export function arrayFrom(iterator: Iterator | IterableIterator): T[]; - export function arrayFrom(iterator: Iterator | IterableIterator, map?: (t: T) => U): (T | U)[] { - const result: (T | U)[] = []; - for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) { - result.push(map ? map(iterResult.value) : iterResult.value); - } - return result; - } - - - export function assign(t: T, ...args: (T | undefined)[]) { - for (const arg of args) { - if (arg === undefined) continue; - for (const p in arg) { - if (hasProperty(arg, p)) { - t[p] = arg[p]; - } - } + return initial; +} +/* @internal */ +const hasOwnProperty = Object.prototype.hasOwnProperty; +/** + * Indicates whether a map-like contains an own property with the specified key. + * + * @param map A map-like. + * @param key A property key. + */ +/* @internal */ +export function hasProperty(map: MapLike, key: string): boolean { + return hasOwnProperty.call(map, key); +} +/** + * Gets the value of an owned property in a map-like. + * + * @param map A map-like. + * @param key A property key. + */ +/* @internal */ +export function getProperty(map: MapLike, key: string): T | undefined { + return hasOwnProperty.call(map, key) ? map[key] : undefined; +} +/** + * Gets the owned, enumerable property keys of a map-like. + */ +/* @internal */ +export function getOwnKeys(map: MapLike): string[] { + const keys: string[] = []; + for (const key in map) { + if (hasOwnProperty.call(map, key)) { + keys.push(key); } - return t; } - - /** - * Performs a shallow equality comparison of the contents of two map-likes. - * - * @param left A map-like whose properties should be compared. - * @param right A map-like whose properties should be compared. - */ - export function equalOwnProperties(left: MapLike | undefined, right: MapLike | undefined, equalityComparer: EqualityComparer = equateValues) { - if (left === right) return true; - if (!left || !right) return false; - for (const key in left) { - if (hasOwnProperty.call(left, key)) { - if (!hasOwnProperty.call(right, key)) return false; - if (!equalityComparer(left[key], right[key])) return false; - } - } - - for (const key in right) { - if (hasOwnProperty.call(right, key)) { - if (!hasOwnProperty.call(left, key)) return false; - } + return keys; +} +/* @internal */ +export function getAllKeys(obj: object): string[] { + const result: string[] = []; + do { + const names = Object.getOwnPropertyNames(obj); + for (const name of names) { + pushIfUnique(result, name); + } + } while (obj = Object.getPrototypeOf(obj)); + return result; +} +/* @internal */ +export function getOwnValues(sparseArray: T[]): T[] { + const values: T[] = []; + for (const key in sparseArray) { + if (hasOwnProperty.call(sparseArray, key)) { + values.push(sparseArray[key]); } - - return true; } - - /** - * Creates a map from the elements of an array. - * - * @param array the array of input elements. - * @param makeKey a function that produces a key for a given element. - * - * This function makes no effort to avoid collisions; if any two elements produce - * the same key with the given 'makeKey' function, then the element with the higher - * index in the array will be the one associated with the produced key. - */ - export function arrayToMap(array: readonly T[], makeKey: (value: T) => string | undefined): Map; - export function arrayToMap(array: readonly T[], makeKey: (value: T) => string | undefined, makeValue: (value: T) => U): Map; - export function arrayToMap(array: readonly T[], makeKey: (value: T) => string | undefined, makeValue: (value: T) => T | U = identity): Map { - const result = createMap(); - for (const value of array) { - const key = makeKey(value); - if (key !== undefined) result.set(key, makeValue(value)); - } - return result; - } - - export function arrayToNumericMap(array: readonly T[], makeKey: (value: T) => number): T[]; - export function arrayToNumericMap(array: readonly T[], makeKey: (value: T) => number, makeValue: (value: T) => U): U[]; - export function arrayToNumericMap(array: readonly T[], makeKey: (value: T) => number, makeValue: (value: T) => T | U = identity): (T | U)[] { - const result: (T | U)[] = []; - for (const value of array) { - result[makeKey(value)] = makeValue(value); - } - return result; - } - - export function arrayToMultiMap(values: readonly T[], makeKey: (value: T) => string): MultiMap; - export function arrayToMultiMap(values: readonly T[], makeKey: (value: T) => string, makeValue: (value: T) => U): MultiMap; - export function arrayToMultiMap(values: readonly T[], makeKey: (value: T) => string, makeValue: (value: T) => T | U = identity): MultiMap { - const result = createMultiMap(); - for (const value of values) { - result.add(makeKey(value), makeValue(value)); - } - return result; - } - - export function group(values: readonly T[], getGroupId: (value: T) => string): readonly (readonly T[])[] { - return arrayFrom(arrayToMultiMap(values, getGroupId).values()); - } - - export function clone(object: T): T { - const result: any = {}; - for (const id in object) { - if (hasOwnProperty.call(object, id)) { - result[id] = (object)[id]; - } - } - return result; + return values; +} +/** Shims `Array.from`. */ +/* @internal */ +export function arrayFrom(iterator: ts.Iterator | IterableIterator, map: (t: T) => U): U[]; +/* @internal */ +export function arrayFrom(iterator: ts.Iterator | IterableIterator): T[]; +/* @internal */ +export function arrayFrom(iterator: ts.Iterator | IterableIterator, map?: (t: T) => U): (T | U)[] { + const result: (T | U)[] = []; + for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) { + result.push(map ? map(iterResult.value) : iterResult.value); } - - export function extend(first: T1, second: T2): T1 & T2 { - const result: T1 & T2 = {}; - for (const id in second) { - if (hasOwnProperty.call(second, id)) { - (result as any)[id] = (second as any)[id]; - } - } - - for (const id in first) { - if (hasOwnProperty.call(first, id)) { - (result as any)[id] = (first as any)[id]; - } + return result; +} +/* @internal */ +export function assign(t: T, ...args: (T | undefined)[]) { + for (const arg of args) { + if (arg === undefined) + continue; + for (const p in arg) { + if (hasProperty(arg, p)) { + t[p] = arg[p]; + } + } + } + return t; +} +/** + * Performs a shallow equality comparison of the contents of two map-likes. + * + * @param left A map-like whose properties should be compared. + * @param right A map-like whose properties should be compared. + */ +/* @internal */ +export function equalOwnProperties(left: MapLike | undefined, right: MapLike | undefined, equalityComparer: EqualityComparer = equateValues) { + if (left === right) + return true; + if (!left || !right) + return false; + for (const key in left) { + if (hasOwnProperty.call(left, key)) { + if (!hasOwnProperty.call(right, key)) + return false; + if (!equalityComparer(left[key], right[key])) + return false; } - - return result; } - - export function copyProperties(first: T1, second: T2) { - for (const id in second) { - if (hasOwnProperty.call(second, id)) { - (first as any)[id] = second[id]; - } + for (const key in right) { + if (hasOwnProperty.call(right, key)) { + if (!hasOwnProperty.call(left, key)) + return false; } } - - export function maybeBind(obj: T, fn: ((this: T, ...args: A) => R) | undefined): ((...args: A) => R) | undefined { - return fn ? fn.bind(obj) : undefined; - } - - export function mapMap(map: Map, f: (t: T, key: string) => [string, U]): Map; - export function mapMap(map: UnderscoreEscapedMap, f: (t: T, key: __String) => [string, U]): Map; - export function mapMap(map: Map | UnderscoreEscapedMap, f: ((t: T, key: string) => [string, U]) | ((t: T, key: __String) => [string, U])): Map { - const result = createMap(); - map.forEach((t: T, key: string & __String) => result.set(...(f(t, key)))); - return result; - } - - export interface MultiMap extends Map { - /** - * Adds the value to an array of values associated with the key, and returns the array. - * Creates the array if it does not already exist. - */ - add(key: string, value: T): T[]; - /** - * Removes a value from an array of values associated with the key. - * Does not preserve the order of those values. - * Does nothing if `key` is not in `map`, or `value` is not in `map[key]`. - */ - remove(key: string, value: T): void; - } - - export function createMultiMap(): MultiMap { - const map = createMap() as MultiMap; - map.add = multiMapAdd; - map.remove = multiMapRemove; - return map; - } - function multiMapAdd(this: MultiMap, key: string, value: T) { - let values = this.get(key); - if (values) { - values.push(value); - } - else { - this.set(key, values = [value]); + return true; +} +/** + * Creates a map from the elements of an array. + * + * @param array the array of input elements. + * @param makeKey a function that produces a key for a given element. + * + * This function makes no effort to avoid collisions; if any two elements produce + * the same key with the given 'makeKey' function, then the element with the higher + * index in the array will be the one associated with the produced key. + */ +/* @internal */ +export function arrayToMap(array: readonly T[], makeKey: (value: T) => string | undefined): ts.Map; +/* @internal */ +export function arrayToMap(array: readonly T[], makeKey: (value: T) => string | undefined, makeValue: (value: T) => U): ts.Map; +/* @internal */ +export function arrayToMap(array: readonly T[], makeKey: (value: T) => string | undefined, makeValue: (value: T) => T | U = identity): ts.Map { + const result = createMap(); + for (const value of array) { + const key = makeKey(value); + if (key !== undefined) + result.set(key, makeValue(value)); + } + return result; +} +/* @internal */ +export function arrayToNumericMap(array: readonly T[], makeKey: (value: T) => number): T[]; +/* @internal */ +export function arrayToNumericMap(array: readonly T[], makeKey: (value: T) => number, makeValue: (value: T) => U): U[]; +/* @internal */ +export function arrayToNumericMap(array: readonly T[], makeKey: (value: T) => number, makeValue: (value: T) => T | U = identity): (T | U)[] { + const result: (T | U)[] = []; + for (const value of array) { + result[makeKey(value)] = makeValue(value); + } + return result; +} +/* @internal */ +export function arrayToMultiMap(values: readonly T[], makeKey: (value: T) => string): MultiMap; +/* @internal */ +export function arrayToMultiMap(values: readonly T[], makeKey: (value: T) => string, makeValue: (value: T) => U): MultiMap; +/* @internal */ +export function arrayToMultiMap(values: readonly T[], makeKey: (value: T) => string, makeValue: (value: T) => T | U = identity): MultiMap { + const result = createMultiMap(); + for (const value of values) { + result.add(makeKey(value), makeValue(value)); + } + return result; +} +/* @internal */ +export function group(values: readonly T[], getGroupId: (value: T) => string): readonly (readonly T[])[] { + return arrayFrom(arrayToMultiMap(values, getGroupId).values()); +} +/* @internal */ +export function clone(object: T): T { + const result: any = {}; + for (const id in object) { + if (hasOwnProperty.call(object, id)) { + result[id] = (object)[id]; } - return values; } - function multiMapRemove(this: MultiMap, key: string, value: T) { - const values = this.get(key); - if (values) { - unorderedRemoveItem(values, value); - if (!values.length) { - this.delete(key); - } + return result; +} +/* @internal */ +export function extend(first: T1, second: T2): T1 & T2 { + const result: T1 & T2 = {}; + for (const id in second) { + if (hasOwnProperty.call(second, id)) { + (result as any)[id] = (second as any)[id]; } } - - /** - * Tests whether a value is an array. - */ - export function isArray(value: any): value is readonly {}[] { - return Array.isArray ? Array.isArray(value) : value instanceof Array; + for (const id in first) { + if (hasOwnProperty.call(first, id)) { + (result as any)[id] = (first as any)[id]; + } } - - export function toArray(value: T | T[]): T[]; - export function toArray(value: T | readonly T[]): readonly T[]; - export function toArray(value: T | T[]): T[] { - return isArray(value) ? value : [value]; + return result; +} +/* @internal */ +export function copyProperties(first: T1, second: T2) { + for (const id in second) { + if (hasOwnProperty.call(second, id)) { + (first as any)[id] = second[id]; + } } - +} +/* @internal */ +export function maybeBind(obj: T, fn: ((this: T, ...args: A) => R) | undefined): ((...args: A) => R) | undefined { + return fn ? fn.bind(obj) : undefined; +} +/* @internal */ +export function mapMap(map: ts.Map, f: (t: T, key: string) => [string, U]): ts.Map; +/* @internal */ +export function mapMap(map: UnderscoreEscapedMap, f: (t: T, key: __String) => [string, U]): ts.Map; +/* @internal */ +export function mapMap(map: ts.Map | UnderscoreEscapedMap, f: ((t: T, key: string) => [string, U]) | ((t: T, key: __String) => [string, U])): ts.Map { + const result = createMap(); + map.forEach((t: T, key: string & __String) => result.set(...(f(t, key)))); + return result; +} +/* @internal */ +export interface MultiMap extends ts.Map { /** - * Tests whether a value is string + * Adds the value to an array of values associated with the key, and returns the array. + * Creates the array if it does not already exist. */ - export function isString(text: unknown): text is string { - return typeof text === "string"; - } - export function isNumber(x: unknown): x is number { - return typeof x === "number"; - } - - export function tryCast(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined; - export function tryCast(value: T, test: (value: T) => boolean): T | undefined; - export function tryCast(value: T, test: (value: T) => boolean): T | undefined { - return value !== undefined && test(value) ? value : undefined; - } - - export function cast(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut { - if (value !== undefined && test(value)) return value; - - return Debug.fail(`Invalid cast. The supplied value ${value} did not pass the test '${Debug.getFunctionName(test)}'.`); - } - - /** Does nothing. */ - export function noop(_?: {} | null | undefined): void { } - - /** Do nothing and return false */ - export function returnFalse(): false { return false; } - - /** Do nothing and return true */ - export function returnTrue(): true { return true; } - - /** Do nothing and return undefined */ - export function returnUndefined(): undefined { return undefined; } - - /** Returns its argument. */ - export function identity(x: T) { return x; } - - /** Returns lower case string */ - export function toLowerCase(x: string) { return x.toLowerCase(); } - - /** Throws an error because a function is not implemented. */ - export function notImplemented(): never { - throw new Error("Not implemented"); - } - - export function memoize(callback: () => T): () => T { - let value: T; - return () => { - if (callback) { - value = callback(); - callback = undefined!; - } - return value; - }; - } - + add(key: string, value: T): T[]; /** - * High-order function, composes functions. Note that functions are composed inside-out; - * for example, `compose(a, b)` is the equivalent of `x => b(a(x))`. - * - * @param args The functions to compose. + * Removes a value from an array of values associated with the key. + * Does not preserve the order of those values. + * Does nothing if `key` is not in `map`, or `value` is not in `map[key]`. */ - export function compose(...args: ((t: T) => T)[]): (t: T) => T; - export function compose(a: (t: T) => T, b: (t: T) => T, c: (t: T) => T, d: (t: T) => T, e: (t: T) => T): (t: T) => T { - if (!!e) { - const args: ((t: T) => T)[] = []; - for (let i = 0; i < arguments.length; i++) { - args[i] = arguments[i]; - } - - return t => reduceLeft(args, (u, f) => f(u), t); - } - else if (d) { - return t => d(c(b(a(t)))); - } - else if (c) { - return t => c(b(a(t))); - } - else if (b) { - return t => b(a(t)); - } - else if (a) { - return t => a(t); - } - else { - return t => t; - } + remove(key: string, value: T): void; +} +/* @internal */ +export function createMultiMap(): MultiMap { + const map = createMap() as MultiMap; + map.add = multiMapAdd; + map.remove = multiMapRemove; + return map; +} +/* @internal */ +function multiMapAdd(this: MultiMap, key: string, value: T) { + let values = this.get(key); + if (values) { + values.push(value); } - - export const enum AssertionLevel { - None = 0, - Normal = 1, - Aggressive = 2, - VeryAggressive = 3, + else { + this.set(key, values = [value]); } - - /** - * Safer version of `Function` which should not be called. - * Every function should be assignable to this, but this should not be assignable to every function. - */ - export type AnyFunction = (...args: never[]) => void; - export type AnyConstructor = new (...args: unknown[]) => unknown; - - export function equateValues(a: T, b: T) { - return a === b; - } - - /** - * Compare the equality of two strings using a case-sensitive ordinal comparison. - * - * Case-sensitive comparisons compare both strings one code-point at a time using the integer - * value of each code-point after applying `toUpperCase` to each string. We always map both - * strings to their upper-case form as some unicode characters do not properly round-trip to - * lowercase (such as `ẞ` (German sharp capital s)). - */ - export function equateStringsCaseInsensitive(a: string, b: string) { - return a === b - || a !== undefined - && b !== undefined - && a.toUpperCase() === b.toUpperCase(); + return values; +} +/* @internal */ +function multiMapRemove(this: MultiMap, key: string, value: T) { + const values = this.get(key); + if (values) { + unorderedRemoveItem(values, value); + if (!values.length) { + this.delete(key); + } + } +} +/** + * Tests whether a value is an array. + */ +/* @internal */ +export function isArray(value: any): value is readonly {}[] { + return Array.isArray ? Array.isArray(value) : value instanceof Array; +} +/* @internal */ +export function toArray(value: T | T[]): T[]; +/* @internal */ +export function toArray(value: T | readonly T[]): readonly T[]; +/* @internal */ +export function toArray(value: T | T[]): T[] { + return isArray(value) ? value : [value]; +} +/** + * Tests whether a value is string + */ +/* @internal */ +export function isString(text: unknown): text is string { + return typeof text === "string"; +} +/* @internal */ +export function isNumber(x: unknown): x is number { + return typeof x === "number"; +} +/* @internal */ +export function tryCast(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined; +/* @internal */ +export function tryCast(value: T, test: (value: T) => boolean): T | undefined; +/* @internal */ +export function tryCast(value: T, test: (value: T) => boolean): T | undefined { + return value !== undefined && test(value) ? value : undefined; +} +/* @internal */ +export function cast(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut { + if (value !== undefined && test(value)) + return value; + return Debug.fail(`Invalid cast. The supplied value ${value} did not pass the test '${Debug.getFunctionName(test)}'.`); +} +/** Does nothing. */ +/* @internal */ +export function noop(_?: {} | null | undefined): void { } +/** Do nothing and return false */ +/* @internal */ +export function returnFalse(): false { return false; } +/** Do nothing and return true */ +/* @internal */ +export function returnTrue(): true { return true; } +/** Do nothing and return undefined */ +/* @internal */ +export function returnUndefined(): undefined { return undefined; } +/** Returns its argument. */ +/* @internal */ +export function identity(x: T) { return x; } +/** Returns lower case string */ +/* @internal */ +export function toLowerCase(x: string) { return x.toLowerCase(); } +/** Throws an error because a function is not implemented. */ +/* @internal */ +export function notImplemented(): never { + throw new Error("Not implemented"); +} +/* @internal */ +export function memoize(callback: () => T): () => T { + let value: T; + return () => { + if (callback) { + value = callback(); + callback = undefined!; + } + return value; + }; +} +/** + * High-order function, composes functions. Note that functions are composed inside-out; + * for example, `compose(a, b)` is the equivalent of `x => b(a(x))`. + * + * @param args The functions to compose. + */ +/* @internal */ +export function compose(...args: ((t: T) => T)[]): (t: T) => T; +/* @internal */ +export function compose(a: (t: T) => T, b: (t: T) => T, c: (t: T) => T, d: (t: T) => T, e: (t: T) => T): (t: T) => T { + if (!!e) { + const args: ((t: T) => T)[] = []; + for (let i = 0; i < arguments.length; i++) { + args[i] = arguments[i]; + } + return t => reduceLeft(args, (u, f) => f(u), t); } - - /** - * Compare the equality of two strings using a case-sensitive ordinal comparison. - * - * Case-sensitive comparisons compare both strings one code-point at a time using the - * integer value of each code-point. - */ - export function equateStringsCaseSensitive(a: string, b: string) { - return equateValues(a, b); - } - - function compareComparableValues(a: string | undefined, b: string | undefined): Comparison; - function compareComparableValues(a: number | undefined, b: number | undefined): Comparison; - function compareComparableValues(a: string | number | undefined, b: string | number | undefined) { - return a === b ? Comparison.EqualTo : - a === undefined ? Comparison.LessThan : - b === undefined ? Comparison.GreaterThan : - a < b ? Comparison.LessThan : - Comparison.GreaterThan; + else if (d) { + return t => d(c(b(a(t)))); } - - /** - * Compare two numeric values for their order relative to each other. - * To compare strings, use any of the `compareStrings` functions. - */ - export function compareValues(a: number | undefined, b: number | undefined): Comparison { - return compareComparableValues(a, b); + else if (c) { + return t => c(b(a(t))); } - - export function min(a: T, b: T, compare: Comparer): T { - return compare(a, b) === Comparison.LessThan ? a : b; + else if (b) { + return t => b(a(t)); } - - /** - * Compare two strings using a case-insensitive ordinal comparison. - * - * Ordinal comparisons are based on the difference between the unicode code points of both - * strings. Characters with multiple unicode representations are considered unequal. Ordinal - * comparisons provide predictable ordering, but place "a" after "B". - * - * Case-insensitive comparisons compare both strings one code-point at a time using the integer - * value of each code-point after applying `toUpperCase` to each string. We always map both - * strings to their upper-case form as some unicode characters do not properly round-trip to - * lowercase (such as `ẞ` (German sharp capital s)). - */ - export function compareStringsCaseInsensitive(a: string, b: string) { - if (a === b) return Comparison.EqualTo; - if (a === undefined) return Comparison.LessThan; - if (b === undefined) return Comparison.GreaterThan; - a = a.toUpperCase(); - b = b.toUpperCase(); - return a < b ? Comparison.LessThan : a > b ? Comparison.GreaterThan : Comparison.EqualTo; - } - - /** - * Compare two strings using a case-sensitive ordinal comparison. - * - * Ordinal comparisons are based on the difference between the unicode code points of both - * strings. Characters with multiple unicode representations are considered unequal. Ordinal - * comparisons provide predictable ordering, but place "a" after "B". - * - * Case-sensitive comparisons compare both strings one code-point at a time using the integer - * value of each code-point. - */ - export function compareStringsCaseSensitive(a: string | undefined, b: string | undefined): Comparison { - return compareComparableValues(a, b); + else if (a) { + return t => a(t); } - - export function getStringComparer(ignoreCase?: boolean) { - return ignoreCase ? compareStringsCaseInsensitive : compareStringsCaseSensitive; + else { + return t => t; } - - /** - * Creates a string comparer for use with string collation in the UI. - */ - const createUIStringComparer = (() => { - let defaultComparer: Comparer | undefined; - let enUSComparer: Comparer | undefined; - - const stringComparerFactory = getStringComparerFactory(); - return createStringComparer; - - function compareWithCallback(a: string | undefined, b: string | undefined, comparer: (a: string, b: string) => number) { - if (a === b) return Comparison.EqualTo; - if (a === undefined) return Comparison.LessThan; - if (b === undefined) return Comparison.GreaterThan; - const value = comparer(a, b); - return value < 0 ? Comparison.LessThan : value > 0 ? Comparison.GreaterThan : Comparison.EqualTo; - } - - function createIntlCollatorStringComparer(locale: string | undefined): Comparer { - // Intl.Collator.prototype.compare is bound to the collator. See NOTE in - // http://www.ecma-international.org/ecma-402/2.0/#sec-Intl.Collator.prototype.compare - const comparer = new Intl.Collator(locale, { usage: "sort", sensitivity: "variant" }).compare; - return (a, b) => compareWithCallback(a, b, comparer); - } - - function createLocaleCompareStringComparer(locale: string | undefined): Comparer { - // if the locale is not the default locale (`undefined`), use the fallback comparer. - if (locale !== undefined) return createFallbackStringComparer(); - - return (a, b) => compareWithCallback(a, b, compareStrings); - - function compareStrings(a: string, b: string) { - return a.localeCompare(b); - } - } - - function createFallbackStringComparer(): Comparer { - // An ordinal comparison puts "A" after "b", but for the UI we want "A" before "b". - // We first sort case insensitively. So "Aaa" will come before "baa". - // Then we sort case sensitively, so "aaa" will come before "Aaa". - // - // For case insensitive comparisons we always map both strings to their - // upper-case form as some unicode characters do not properly round-trip to - // lowercase (such as `ẞ` (German sharp capital s)). - return (a, b) => compareWithCallback(a, b, compareDictionaryOrder); - - function compareDictionaryOrder(a: string, b: string) { - return compareStrings(a.toUpperCase(), b.toUpperCase()) || compareStrings(a, b); - } - - function compareStrings(a: string, b: string) { - return a < b ? Comparison.LessThan : a > b ? Comparison.GreaterThan : Comparison.EqualTo; - } - } - - function getStringComparerFactory() { - // If the host supports Intl, we use it for comparisons using the default locale. - if (typeof Intl === "object" && typeof Intl.Collator === "function") { - return createIntlCollatorStringComparer; - } - - // If the host does not support Intl, we fall back to localeCompare. - // localeCompare in Node v0.10 is just an ordinal comparison, so don't use it. - if (typeof String.prototype.localeCompare === "function" && - typeof String.prototype.toLocaleUpperCase === "function" && - "a".localeCompare("B") < 0) { - return createLocaleCompareStringComparer; - } - - // Otherwise, fall back to ordinal comparison: - return createFallbackStringComparer; - } - - function createStringComparer(locale: string | undefined) { - // Hold onto common string comparers. This avoids constantly reallocating comparers during - // tests. - if (locale === undefined) { - return defaultComparer || (defaultComparer = stringComparerFactory(locale)); - } - else if (locale === "en-US") { - return enUSComparer || (enUSComparer = stringComparerFactory(locale)); - } - else { - return stringComparerFactory(locale); - } +} +/* @internal */ +export const enum AssertionLevel { + None = 0, + Normal = 1, + Aggressive = 2, + VeryAggressive = 3 +} +/** + * Safer version of `Function` which should not be called. + * Every function should be assignable to this, but this should not be assignable to every function. + */ +/* @internal */ +export type AnyFunction = (...args: never[]) => void; +/* @internal */ +export type AnyConstructor = new (...args: unknown[]) => unknown; +/* @internal */ +export function equateValues(a: T, b: T) { + return a === b; +} +/** + * Compare the equality of two strings using a case-sensitive ordinal comparison. + * + * Case-sensitive comparisons compare both strings one code-point at a time using the integer + * value of each code-point after applying `toUpperCase` to each string. We always map both + * strings to their upper-case form as some unicode characters do not properly round-trip to + * lowercase (such as `ẞ` (German sharp capital s)). + */ +/* @internal */ +export function equateStringsCaseInsensitive(a: string, b: string) { + return a === b + || a !== undefined + && b !== undefined + && a.toUpperCase() === b.toUpperCase(); +} +/** + * Compare the equality of two strings using a case-sensitive ordinal comparison. + * + * Case-sensitive comparisons compare both strings one code-point at a time using the + * integer value of each code-point. + */ +/* @internal */ +export function equateStringsCaseSensitive(a: string, b: string) { + return equateValues(a, b); +} +/* @internal */ +function compareComparableValues(a: string | undefined, b: string | undefined): Comparison; +/* @internal */ +function compareComparableValues(a: number | undefined, b: number | undefined): Comparison; +/* @internal */ +function compareComparableValues(a: string | number | undefined, b: string | number | undefined) { + return a === b ? Comparison.EqualTo : + a === undefined ? Comparison.LessThan : + b === undefined ? Comparison.GreaterThan : + a < b ? Comparison.LessThan : + Comparison.GreaterThan; +} +/** + * Compare two numeric values for their order relative to each other. + * To compare strings, use any of the `compareStrings` functions. + */ +/* @internal */ +export function compareValues(a: number | undefined, b: number | undefined): Comparison { + return compareComparableValues(a, b); +} +/* @internal */ +export function min(a: T, b: T, compare: Comparer): T { + return compare(a, b) === Comparison.LessThan ? a : b; +} +/** + * Compare two strings using a case-insensitive ordinal comparison. + * + * Ordinal comparisons are based on the difference between the unicode code points of both + * strings. Characters with multiple unicode representations are considered unequal. Ordinal + * comparisons provide predictable ordering, but place "a" after "B". + * + * Case-insensitive comparisons compare both strings one code-point at a time using the integer + * value of each code-point after applying `toUpperCase` to each string. We always map both + * strings to their upper-case form as some unicode characters do not properly round-trip to + * lowercase (such as `ẞ` (German sharp capital s)). + */ +/* @internal */ +export function compareStringsCaseInsensitive(a: string, b: string) { + if (a === b) + return Comparison.EqualTo; + if (a === undefined) + return Comparison.LessThan; + if (b === undefined) + return Comparison.GreaterThan; + a = a.toUpperCase(); + b = b.toUpperCase(); + return a < b ? Comparison.LessThan : a > b ? Comparison.GreaterThan : Comparison.EqualTo; +} +/** + * Compare two strings using a case-sensitive ordinal comparison. + * + * Ordinal comparisons are based on the difference between the unicode code points of both + * strings. Characters with multiple unicode representations are considered unequal. Ordinal + * comparisons provide predictable ordering, but place "a" after "B". + * + * Case-sensitive comparisons compare both strings one code-point at a time using the integer + * value of each code-point. + */ +/* @internal */ +export function compareStringsCaseSensitive(a: string | undefined, b: string | undefined): Comparison { + return compareComparableValues(a, b); +} +/* @internal */ +export function getStringComparer(ignoreCase?: boolean) { + return ignoreCase ? compareStringsCaseInsensitive : compareStringsCaseSensitive; +} +/** + * Creates a string comparer for use with string collation in the UI. + */ +/* @internal */ +const createUIStringComparer = (() => { + let defaultComparer: Comparer | undefined; + let enUSComparer: Comparer | undefined; + const stringComparerFactory = getStringComparerFactory(); + return createStringComparer; + function compareWithCallback(a: string | undefined, b: string | undefined, comparer: (a: string, b: string) => number) { + if (a === b) + return Comparison.EqualTo; + if (a === undefined) + return Comparison.LessThan; + if (b === undefined) + return Comparison.GreaterThan; + const value = comparer(a, b); + return value < 0 ? Comparison.LessThan : value > 0 ? Comparison.GreaterThan : Comparison.EqualTo; + } + function createIntlCollatorStringComparer(locale: string | undefined): Comparer { + // Intl.Collator.prototype.compare is bound to the collator. See NOTE in + // http://www.ecma-international.org/ecma-402/2.0/#sec-Intl.Collator.prototype.compare + const comparer = new Intl.Collator(locale, { usage: "sort", sensitivity: "variant" }).compare; + return (a, b) => compareWithCallback(a, b, comparer); + } + function createLocaleCompareStringComparer(locale: string | undefined): Comparer { + // if the locale is not the default locale (`undefined`), use the fallback comparer. + if (locale !== undefined) + return createFallbackStringComparer(); + return (a, b) => compareWithCallback(a, b, compareStrings); + function compareStrings(a: string, b: string) { + return a.localeCompare(b); + } + } + function createFallbackStringComparer(): Comparer { + // An ordinal comparison puts "A" after "b", but for the UI we want "A" before "b". + // We first sort case insensitively. So "Aaa" will come before "baa". + // Then we sort case sensitively, so "aaa" will come before "Aaa". + // + // For case insensitive comparisons we always map both strings to their + // upper-case form as some unicode characters do not properly round-trip to + // lowercase (such as `ẞ` (German sharp capital s)). + return (a, b) => compareWithCallback(a, b, compareDictionaryOrder); + function compareDictionaryOrder(a: string, b: string) { + return compareStrings(a.toUpperCase(), b.toUpperCase()) || compareStrings(a, b); + } + function compareStrings(a: string, b: string) { + return a < b ? Comparison.LessThan : a > b ? Comparison.GreaterThan : Comparison.EqualTo; + } + } + function getStringComparerFactory() { + // If the host supports Intl, we use it for comparisons using the default locale. + if (typeof Intl === "object" && typeof Intl.Collator === "function") { + return createIntlCollatorStringComparer; + } + // If the host does not support Intl, we fall back to localeCompare. + // localeCompare in Node v0.10 is just an ordinal comparison, so don't use it. + if (typeof String.prototype.localeCompare === "function" && + typeof String.prototype.toLocaleUpperCase === "function" && + "a".localeCompare("B") < 0) { + return createLocaleCompareStringComparer; + } + // Otherwise, fall back to ordinal comparison: + return createFallbackStringComparer; + } + function createStringComparer(locale: string | undefined) { + // Hold onto common string comparers. This avoids constantly reallocating comparers during + // tests. + if (locale === undefined) { + return defaultComparer || (defaultComparer = stringComparerFactory(locale)); + } + else if (locale === "en-US") { + return enUSComparer || (enUSComparer = stringComparerFactory(locale)); } - })(); - - let uiComparerCaseSensitive: Comparer | undefined; - let uiLocale: string | undefined; - - export function getUILocale() { - return uiLocale; - } - - export function setUILocale(value: string | undefined) { - if (uiLocale !== value) { - uiLocale = value; - uiComparerCaseSensitive = undefined; + else { + return stringComparerFactory(locale); } } - - /** - * Compare two strings in a using the case-sensitive sort behavior of the UI locale. - * - * Ordering is not predictable between different host locales, but is best for displaying - * ordered data for UI presentation. Characters with multiple unicode representations may - * be considered equal. - * - * Case-sensitive comparisons compare strings that differ in base characters, or - * accents/diacritic marks, or case as unequal. - */ - export function compareStringsCaseSensitiveUI(a: string, b: string) { - const comparer = uiComparerCaseSensitive || (uiComparerCaseSensitive = createUIStringComparer(uiLocale)); - return comparer(a, b); - } - - export function compareProperties(a: T | undefined, b: T | undefined, key: K, comparer: Comparer): Comparison { - return a === b ? Comparison.EqualTo : - a === undefined ? Comparison.LessThan : +})(); +/* @internal */ +let uiComparerCaseSensitive: Comparer | undefined; +/* @internal */ +let uiLocale: string | undefined; +/* @internal */ +export function getUILocale() { + return uiLocale; +} +/* @internal */ +export function setUILocale(value: string | undefined) { + if (uiLocale !== value) { + uiLocale = value; + uiComparerCaseSensitive = undefined; + } +} +/** + * Compare two strings in a using the case-sensitive sort behavior of the UI locale. + * + * Ordering is not predictable between different host locales, but is best for displaying + * ordered data for UI presentation. Characters with multiple unicode representations may + * be considered equal. + * + * Case-sensitive comparisons compare strings that differ in base characters, or + * accents/diacritic marks, or case as unequal. + */ +/* @internal */ +export function compareStringsCaseSensitiveUI(a: string, b: string) { + const comparer = uiComparerCaseSensitive || (uiComparerCaseSensitive = createUIStringComparer(uiLocale)); + return comparer(a, b); +} +/* @internal */ +export function compareProperties(a: T | undefined, b: T | undefined, key: K, comparer: Comparer): Comparison { + return a === b ? Comparison.EqualTo : + a === undefined ? Comparison.LessThan : b === undefined ? Comparison.GreaterThan : - comparer(a[key], b[key]); - } - - /** True is greater than false. */ - export function compareBooleans(a: boolean, b: boolean): Comparison { - return compareValues(a ? 1 : 0, b ? 1 : 0); - } - - /** - * Given a name and a list of names that are *not* equal to the name, return a spelling suggestion if there is one that is close enough. - * Names less than length 3 only check for case-insensitive equality, not Levenshtein distance. - * - * If there is a candidate that's the same except for case, return that. - * If there is a candidate that's within one edit of the name, return that. - * Otherwise, return the candidate with the smallest Levenshtein distance, - * except for candidates: - * * With no name - * * Whose length differs from the target name by more than 0.34 of the length of the name. - * * Whose levenshtein distance is more than 0.4 of the length of the name - * (0.4 allows 1 substitution/transposition for every 5 characters, - * and 1 insertion/deletion at 3 characters) - */ - export function getSpellingSuggestion(name: string, candidates: T[], getName: (candidate: T) => string | undefined): T | undefined { - const maximumLengthDifference = Math.min(2, Math.floor(name.length * 0.34)); - let bestDistance = Math.floor(name.length * 0.4) + 1; // If the best result isn't better than this, don't bother. - let bestCandidate: T | undefined; - let justCheckExactMatches = false; - const nameLowerCase = name.toLowerCase(); - for (const candidate of candidates) { - const candidateName = getName(candidate); - if (candidateName !== undefined && Math.abs(candidateName.length - nameLowerCase.length) <= maximumLengthDifference) { - const candidateNameLowerCase = candidateName.toLowerCase(); - if (candidateNameLowerCase === nameLowerCase) { - if (candidateName === name) { - continue; - } - return candidate; - } - if (justCheckExactMatches) { - continue; - } - if (candidateName.length < 3) { - // Don't bother, user would have noticed a 2-character name having an extra character - continue; - } - // Only care about a result better than the best so far. - const distance = levenshteinWithMax(nameLowerCase, candidateNameLowerCase, bestDistance - 1); - if (distance === undefined) { + comparer(a[key], b[key]); +} +/** True is greater than false. */ +/* @internal */ +export function compareBooleans(a: boolean, b: boolean): Comparison { + return compareValues(a ? 1 : 0, b ? 1 : 0); +} +/** + * Given a name and a list of names that are *not* equal to the name, return a spelling suggestion if there is one that is close enough. + * Names less than length 3 only check for case-insensitive equality, not Levenshtein distance. + * + * If there is a candidate that's the same except for case, return that. + * If there is a candidate that's within one edit of the name, return that. + * Otherwise, return the candidate with the smallest Levenshtein distance, + * except for candidates: + * * With no name + * * Whose length differs from the target name by more than 0.34 of the length of the name. + * * Whose levenshtein distance is more than 0.4 of the length of the name + * (0.4 allows 1 substitution/transposition for every 5 characters, + * and 1 insertion/deletion at 3 characters) + */ +/* @internal */ +export function getSpellingSuggestion(name: string, candidates: T[], getName: (candidate: T) => string | undefined): T | undefined { + const maximumLengthDifference = Math.min(2, Math.floor(name.length * 0.34)); + let bestDistance = Math.floor(name.length * 0.4) + 1; // If the best result isn't better than this, don't bother. + let bestCandidate: T | undefined; + let justCheckExactMatches = false; + const nameLowerCase = name.toLowerCase(); + for (const candidate of candidates) { + const candidateName = getName(candidate); + if (candidateName !== undefined && Math.abs(candidateName.length - nameLowerCase.length) <= maximumLengthDifference) { + const candidateNameLowerCase = candidateName.toLowerCase(); + if (candidateNameLowerCase === nameLowerCase) { + if (candidateName === name) { continue; } - if (distance < 3) { - justCheckExactMatches = true; - bestCandidate = candidate; - } - else { - Debug.assert(distance < bestDistance); // Else `levenshteinWithMax` should return undefined - bestDistance = distance; - bestCandidate = candidate; - } + return candidate; } - } - return bestCandidate; - } - - function levenshteinWithMax(s1: string, s2: string, max: number): number | undefined { - let previous = new Array(s2.length + 1); - let current = new Array(s2.length + 1); - /** Represents any value > max. We don't care about the particular value. */ - const big = max + 1; - - for (let i = 0; i <= s2.length; i++) { - previous[i] = i; - } - - for (let i = 1; i <= s1.length; i++) { - const c1 = s1.charCodeAt(i - 1); - const minJ = i > max ? i - max : 1; - const maxJ = s2.length > max + i ? max + i : s2.length; - current[0] = i; - /** Smallest value of the matrix in the ith column. */ - let colMin = i; - for (let j = 1; j < minJ; j++) { - current[j] = big; + if (justCheckExactMatches) { + continue; + } + if (candidateName.length < 3) { + // Don't bother, user would have noticed a 2-character name having an extra character + continue; } - for (let j = minJ; j <= maxJ; j++) { - const dist = c1 === s2.charCodeAt(j - 1) - ? previous[j - 1] - : Math.min(/*delete*/ previous[j] + 1, /*insert*/ current[j - 1] + 1, /*substitute*/ previous[j - 1] + 2); - current[j] = dist; - colMin = Math.min(colMin, dist); + // Only care about a result better than the best so far. + const distance = levenshteinWithMax(nameLowerCase, candidateNameLowerCase, bestDistance - 1); + if (distance === undefined) { + continue; } - for (let j = maxJ + 1; j <= s2.length; j++) { - current[j] = big; + if (distance < 3) { + justCheckExactMatches = true; + bestCandidate = candidate; } - if (colMin > max) { - // Give up -- everything in this column is > max and it can't get better in future columns. - return undefined; + else { + Debug.assert(distance < bestDistance); // Else `levenshteinWithMax` should return undefined + bestDistance = distance; + bestCandidate = candidate; } - - const temp = previous; - previous = current; - current = temp; } - - const res = previous[s2.length]; - return res > max ? undefined : res; - } - - export function endsWith(str: string, suffix: string): boolean { - const expectedPos = str.length - suffix.length; - return expectedPos >= 0 && str.indexOf(suffix, expectedPos) === expectedPos; } - - export function removeSuffix(str: string, suffix: string): string { - return endsWith(str, suffix) ? str.slice(0, str.length - suffix.length) : str; + return bestCandidate; +} +/* @internal */ +function levenshteinWithMax(s1: string, s2: string, max: number): number | undefined { + let previous = new Array(s2.length + 1); + let current = new Array(s2.length + 1); + /** Represents any value > max. We don't care about the particular value. */ + const big = max + 1; + for (let i = 0; i <= s2.length; i++) { + previous[i] = i; + } + for (let i = 1; i <= s1.length; i++) { + const c1 = s1.charCodeAt(i - 1); + const minJ = i > max ? i - max : 1; + const maxJ = s2.length > max + i ? max + i : s2.length; + current[0] = i; + /** Smallest value of the matrix in the ith column. */ + let colMin = i; + for (let j = 1; j < minJ; j++) { + current[j] = big; + } + for (let j = minJ; j <= maxJ; j++) { + const dist = c1 === s2.charCodeAt(j - 1) + ? previous[j - 1] + : Math.min(/*delete*/ previous[j] + 1, /*insert*/ current[j - 1] + 1, /*substitute*/ previous[j - 1] + 2); + current[j] = dist; + colMin = Math.min(colMin, dist); + } + for (let j = maxJ + 1; j <= s2.length; j++) { + current[j] = big; + } + if (colMin > max) { + // Give up -- everything in this column is > max and it can't get better in future columns. + return undefined; + } + const temp = previous; + previous = current; + current = temp; } - - export function tryRemoveSuffix(str: string, suffix: string): string | undefined { - return endsWith(str, suffix) ? str.slice(0, str.length - suffix.length) : undefined; + const res = previous[s2.length]; + return res > max ? undefined : res; +} +/* @internal */ +export function endsWith(str: string, suffix: string): boolean { + const expectedPos = str.length - suffix.length; + return expectedPos >= 0 && str.indexOf(suffix, expectedPos) === expectedPos; +} +/* @internal */ +export function removeSuffix(str: string, suffix: string): string { + return endsWith(str, suffix) ? str.slice(0, str.length - suffix.length) : str; +} +/* @internal */ +export function tryRemoveSuffix(str: string, suffix: string): string | undefined { + return endsWith(str, suffix) ? str.slice(0, str.length - suffix.length) : undefined; +} +/* @internal */ +export function stringContains(str: string, substring: string): boolean { + return str.indexOf(substring) !== -1; +} +/** + * Takes a string like "jquery-min.4.2.3" and returns "jquery" + */ +/* @internal */ +export function removeMinAndVersionNumbers(fileName: string) { + // Match a "." or "-" followed by a version number or 'min' at the end of the name + const trailingMinOrVersion = /[.-]((min)|(\d+(\.\d+)*))$/; + // The "min" or version may both be present, in either order, so try applying the above twice. + return fileName.replace(trailingMinOrVersion, "").replace(trailingMinOrVersion, ""); +} +/** Remove an item from an array, moving everything to its right one space left. */ +/* @internal */ +export function orderedRemoveItem(array: T[], item: T): boolean { + for (let i = 0; i < array.length; i++) { + if (array[i] === item) { + orderedRemoveItemAt(array, i); + return true; + } } - - export function stringContains(str: string, substring: string): boolean { - return str.indexOf(substring) !== -1; + return false; +} +/** Remove an item by index from an array, moving everything to its right one space left. */ +/* @internal */ +export function orderedRemoveItemAt(array: T[], index: number): void { + // This seems to be faster than either `array.splice(i, 1)` or `array.copyWithin(i, i+ 1)`. + for (let i = index; i < array.length - 1; i++) { + array[i] = array[i + 1]; } - - /** - * Takes a string like "jquery-min.4.2.3" and returns "jquery" - */ - export function removeMinAndVersionNumbers(fileName: string) { - // Match a "." or "-" followed by a version number or 'min' at the end of the name - const trailingMinOrVersion = /[.-]((min)|(\d+(\.\d+)*))$/; - - // The "min" or version may both be present, in either order, so try applying the above twice. - return fileName.replace(trailingMinOrVersion, "").replace(trailingMinOrVersion, ""); - } - - /** Remove an item from an array, moving everything to its right one space left. */ - export function orderedRemoveItem(array: T[], item: T): boolean { - for (let i = 0; i < array.length; i++) { - if (array[i] === item) { - orderedRemoveItemAt(array, i); - return true; - } + array.pop(); +} +/* @internal */ +export function unorderedRemoveItemAt(array: T[], index: number): void { + // Fill in the "hole" left at `index`. + array[index] = array[array.length - 1]; + array.pop(); +} +/** Remove the *first* occurrence of `item` from the array. */ +/* @internal */ +export function unorderedRemoveItem(array: T[], item: T) { + return unorderedRemoveFirstItemWhere(array, element => element === item); +} +/** Remove the *first* element satisfying `predicate`. */ +/* @internal */ +function unorderedRemoveFirstItemWhere(array: T[], predicate: (element: T) => boolean) { + for (let i = 0; i < array.length; i++) { + if (predicate(array[i])) { + unorderedRemoveItemAt(array, i); + return true; } - return false; } - - /** Remove an item by index from an array, moving everything to its right one space left. */ - export function orderedRemoveItemAt(array: T[], index: number): void { - // This seems to be faster than either `array.splice(i, 1)` or `array.copyWithin(i, i+ 1)`. - for (let i = index; i < array.length - 1; i++) { - array[i] = array[i + 1]; - } - array.pop(); - } - - export function unorderedRemoveItemAt(array: T[], index: number): void { - // Fill in the "hole" left at `index`. - array[index] = array[array.length - 1]; - array.pop(); - } - - /** Remove the *first* occurrence of `item` from the array. */ - export function unorderedRemoveItem(array: T[], item: T) { - return unorderedRemoveFirstItemWhere(array, element => element === item); - } - - /** Remove the *first* element satisfying `predicate`. */ - function unorderedRemoveFirstItemWhere(array: T[], predicate: (element: T) => boolean) { - for (let i = 0; i < array.length; i++) { - if (predicate(array[i])) { - unorderedRemoveItemAt(array, i); + return false; +} +/* @internal */ +export type GetCanonicalFileName = (fileName: string) => string; +/* @internal */ +export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean): GetCanonicalFileName { + return useCaseSensitiveFileNames ? identity : toLowerCase; +} +/** Represents a "prefix*suffix" pattern. */ +/* @internal */ +export interface Pattern { + prefix: string; + suffix: string; +} +/* @internal */ +export function patternText({ prefix, suffix }: Pattern): string { + return `${prefix}*${suffix}`; +} +/** + * Given that candidate matches pattern, returns the text matching the '*'. + * E.g.: matchedText(tryParsePattern("foo*baz"), "foobarbaz") === "bar" + */ +/* @internal */ +export function matchedText(pattern: Pattern, candidate: string): string { + Debug.assert(isPatternMatch(pattern, candidate)); + return candidate.substring(pattern.prefix.length, candidate.length - pattern.suffix.length); +} +/** Return the object corresponding to the best pattern to match `candidate`. */ +/* @internal */ +export function findBestPatternMatch(values: readonly T[], getPattern: (value: T) => Pattern, candidate: string): T | undefined { + let matchedValue: T | undefined; + // use length of prefix as betterness criteria + let longestMatchPrefixLength = -1; + for (const v of values) { + const pattern = getPattern(v); + if (isPatternMatch(pattern, candidate) && pattern.prefix.length > longestMatchPrefixLength) { + longestMatchPrefixLength = pattern.prefix.length; + matchedValue = v; + } + } + return matchedValue; +} +/* @internal */ +export function startsWith(str: string, prefix: string): boolean { + return str.lastIndexOf(prefix, 0) === 0; +} +/* @internal */ +export function removePrefix(str: string, prefix: string): string { + return startsWith(str, prefix) ? str.substr(prefix.length) : str; +} +/* @internal */ +export function tryRemovePrefix(str: string, prefix: string, getCanonicalFileName: GetCanonicalFileName = identity): string | undefined { + return startsWith(getCanonicalFileName(str), getCanonicalFileName(prefix)) ? str.substring(prefix.length) : undefined; +} +/* @internal */ +function isPatternMatch({ prefix, suffix }: Pattern, candidate: string) { + return candidate.length >= prefix.length + suffix.length && + startsWith(candidate, prefix) && + endsWith(candidate, suffix); +} +/* @internal */ +export function and(f: (arg: T) => boolean, g: (arg: T) => boolean) { + return (arg: T) => f(arg) && g(arg); +} +/* @internal */ +export function or(...fs: ((arg: T) => boolean)[]): (arg: T) => boolean { + return arg => { + for (const f of fs) { + if (f(arg)) { return true; } } return false; - } - - export type GetCanonicalFileName = (fileName: string) => string; - export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean): GetCanonicalFileName { - return useCaseSensitiveFileNames ? identity : toLowerCase; - } - - /** Represents a "prefix*suffix" pattern. */ - export interface Pattern { - prefix: string; - suffix: string; - } - - export function patternText({ prefix, suffix }: Pattern): string { - return `${prefix}*${suffix}`; - } - - /** - * Given that candidate matches pattern, returns the text matching the '*'. - * E.g.: matchedText(tryParsePattern("foo*baz"), "foobarbaz") === "bar" - */ - export function matchedText(pattern: Pattern, candidate: string): string { - Debug.assert(isPatternMatch(pattern, candidate)); - return candidate.substring(pattern.prefix.length, candidate.length - pattern.suffix.length); - } - - /** Return the object corresponding to the best pattern to match `candidate`. */ - export function findBestPatternMatch(values: readonly T[], getPattern: (value: T) => Pattern, candidate: string): T | undefined { - let matchedValue: T | undefined; - // use length of prefix as betterness criteria - let longestMatchPrefixLength = -1; - - for (const v of values) { - const pattern = getPattern(v); - if (isPatternMatch(pattern, candidate) && pattern.prefix.length > longestMatchPrefixLength) { - longestMatchPrefixLength = pattern.prefix.length; - matchedValue = v; - } + }; +} +/* @internal */ +export function not(fn: (...args: T) => boolean): (...args: T) => boolean { + return (...args) => !fn(...args); +} +/* @internal */ +export function assertType(_: T): void { } +/* @internal */ +export function singleElementArray(t: T | undefined): T[] | undefined { + return t === undefined ? undefined : [t]; +} +/* @internal */ +export function enumerateInsertsAndDeletes(newItems: readonly T[], oldItems: readonly U[], comparer: (a: T, b: U) => Comparison, inserted: (newItem: T) => void, deleted: (oldItem: U) => void, unchanged?: (oldItem: U, newItem: T) => void) { + unchanged = unchanged || noop; + let newIndex = 0; + let oldIndex = 0; + const newLen = newItems.length; + const oldLen = oldItems.length; + while (newIndex < newLen && oldIndex < oldLen) { + const newItem = newItems[newIndex]; + const oldItem = oldItems[oldIndex]; + const compareResult = comparer(newItem, oldItem); + if (compareResult === Comparison.LessThan) { + inserted(newItem); + newIndex++; + } + else if (compareResult === Comparison.GreaterThan) { + deleted(oldItem); + oldIndex++; + } + else { + unchanged(oldItem, newItem); + newIndex++; + oldIndex++; } - - return matchedValue; - } - - export function startsWith(str: string, prefix: string): boolean { - return str.lastIndexOf(prefix, 0) === 0; - } - - export function removePrefix(str: string, prefix: string): string { - return startsWith(str, prefix) ? str.substr(prefix.length) : str; } - - export function tryRemovePrefix(str: string, prefix: string, getCanonicalFileName: GetCanonicalFileName = identity): string | undefined { - return startsWith(getCanonicalFileName(str), getCanonicalFileName(prefix)) ? str.substring(prefix.length) : undefined; + while (newIndex < newLen) { + inserted(newItems[newIndex++]); } - - function isPatternMatch({ prefix, suffix }: Pattern, candidate: string) { - return candidate.length >= prefix.length + suffix.length && - startsWith(candidate, prefix) && - endsWith(candidate, suffix); + while (oldIndex < oldLen) { + deleted(oldItems[oldIndex++]); } - - export function and(f: (arg: T) => boolean, g: (arg: T) => boolean) { - return (arg: T) => f(arg) && g(arg); +} +/* @internal */ +export function fill(length: number, cb: (index: number) => T): T[] { + const result = Array(length); + for (let i = 0; i < length; i++) { + result[i] = cb(i); } - - export function or(...fs: ((arg: T) => boolean)[]): (arg: T) => boolean { - return arg => { - for (const f of fs) { - if (f(arg)) { - return true; - } - } - return false; - }; - } - - export function not(fn: (...args: T) => boolean): (...args: T) => boolean { - return (...args) => !fn(...args); - } - - export function assertType(_: T): void { } - - export function singleElementArray(t: T | undefined): T[] | undefined { - return t === undefined ? undefined : [t]; - } - - export function enumerateInsertsAndDeletes(newItems: readonly T[], oldItems: readonly U[], comparer: (a: T, b: U) => Comparison, inserted: (newItem: T) => void, deleted: (oldItem: U) => void, unchanged?: (oldItem: U, newItem: T) => void) { - unchanged = unchanged || noop; - let newIndex = 0; - let oldIndex = 0; - const newLen = newItems.length; - const oldLen = oldItems.length; - while (newIndex < newLen && oldIndex < oldLen) { - const newItem = newItems[newIndex]; - const oldItem = oldItems[oldIndex]; - const compareResult = comparer(newItem, oldItem); - if (compareResult === Comparison.LessThan) { - inserted(newItem); - newIndex++; - } - else if (compareResult === Comparison.GreaterThan) { - deleted(oldItem); - oldIndex++; - } - else { - unchanged(oldItem, newItem); - newIndex++; - oldIndex++; - } - } - while (newIndex < newLen) { - inserted(newItems[newIndex++]); + return result; +} +/* @internal */ +export function cartesianProduct(arrays: readonly T[][]) { + const result: T[][] = []; + cartesianProductWorker(arrays, result, /*outer*/ undefined, 0); + return result; +} +/* @internal */ +function cartesianProductWorker(arrays: readonly (readonly T[])[], result: (readonly T[])[], outer: readonly T[] | undefined, index: number) { + for (const element of arrays[index]) { + let inner: T[]; + if (outer) { + inner = outer.slice(); + inner.push(element); } - while (oldIndex < oldLen) { - deleted(oldItems[oldIndex++]); + else { + inner = [element]; } - } - - export function fill(length: number, cb: (index: number) => T): T[] { - const result = Array(length); - for (let i = 0; i < length; i++) { - result[i] = cb(i); + if (index === arrays.length - 1) { + result.push(inner); } - return result; - } - - export function cartesianProduct(arrays: readonly T[][]) { - const result: T[][] = []; - cartesianProductWorker(arrays, result, /*outer*/ undefined, 0); - return result; - } - - function cartesianProductWorker(arrays: readonly (readonly T[])[], result: (readonly T[])[], outer: readonly T[] | undefined, index: number) { - for (const element of arrays[index]) { - let inner: T[]; - if (outer) { - inner = outer.slice(); - inner.push(element); - } - else { - inner = [element]; - } - if (index === arrays.length - 1) { - result.push(inner); - } - else { - cartesianProductWorker(arrays, result, inner, index + 1); - } + else { + cartesianProductWorker(arrays, result, inner, index + 1); } } -} \ No newline at end of file +} diff --git a/src/compiler/corePublic.ts b/src/compiler/corePublic.ts index c1c1c97b950aa..3689b94957431 100644 --- a/src/compiler/corePublic.ts +++ b/src/compiler/corePublic.ts @@ -1,96 +1,88 @@ -namespace ts { - // WARNING: The script `configureNightly.ts` uses a regexp to parse out these values. - // If changing the text in this section, be sure to test `configureNightly` too. - export const versionMajorMinor = "3.8"; - /** The version of the TypeScript compiler release */ - export const version = `${versionMajorMinor}.0-dev`; - - /** - * Type of objects whose values are all of the same type. - * The `in` and `for-in` operators can *not* be safely used, - * since `Object.prototype` may be modified by outside code. - */ - export interface MapLike { - [index: string]: T; +import { createMapShim } from "./ts"; +// WARNING: The script `configureNightly.ts` uses a regexp to parse out these values. +// If changing the text in this section, be sure to test `configureNightly` too. +export const versionMajorMinor = "3.8"; +/** The version of the TypeScript compiler release */ +export const version = `${versionMajorMinor}.0-dev`; +/** + * Type of objects whose values are all of the same type. + * The `in` and `for-in` operators can *not* be safely used, + * since `Object.prototype` may be modified by outside code. + */ +export interface MapLike { + [index: string]: T; +} +export interface SortedReadonlyArray extends ReadonlyArray { + " __sortedArrayBrand": any; +} +export interface SortedArray extends Array { + " __sortedArrayBrand": any; +} +/** ES6 Map interface, only read methods included. */ +export interface ReadonlyMap { + get(key: string): T | undefined; + has(key: string): boolean; + forEach(action: (value: T, key: string) => void): void; + readonly size: number; + keys(): Iterator; + values(): Iterator; + entries(): Iterator<[string, T]>; +} +/** ES6 Map interface. */ +export interface Map extends ReadonlyMap { + set(key: string, value: T): this; + delete(key: string): boolean; + clear(): void; +} +/* @internal */ +export interface MapConstructor { + // eslint-disable-next-line @typescript-eslint/prefer-function-type + new (): Map; +} +/** + * Returns the native Map implementation if it is available and compatible (i.e. supports iteration). + */ +/* @internal */ +export function tryGetNativeMap(): MapConstructor | undefined { + // Internet Explorer's Map doesn't support iteration, so don't use it. + // Natives + // NOTE: TS doesn't strictly allow in-line declares, but if we suppress the error, the declaration + // is still used for typechecking _and_ correctly elided, which is out goal, as this prevents us from + // needing to pollute an outer scope with a declaration of `Map` just to satisfy the checks in this function + //@ts-ignore + declare const Map: (new () => Map) | undefined; + // eslint-disable-next-line no-in-operator + return typeof Map !== "undefined" && "entries" in Map.prototype ? Map : undefined; +} +/* @internal */ +export const Map: MapConstructor = tryGetNativeMap() || (() => { + // NOTE: createMapShim will be defined for typescriptServices.js but not for tsc.js, so we must test for it. + if (typeof createMapShim === "function") { + return createMapShim(); } - - export interface SortedReadonlyArray extends ReadonlyArray { - " __sortedArrayBrand": any; - } - - export interface SortedArray extends Array { - " __sortedArrayBrand": any; - } - - /** ES6 Map interface, only read methods included. */ - export interface ReadonlyMap { - get(key: string): T | undefined; - has(key: string): boolean; - forEach(action: (value: T, key: string) => void): void; - readonly size: number; - keys(): Iterator; - values(): Iterator; - entries(): Iterator<[string, T]>; - } - - /** ES6 Map interface. */ - export interface Map extends ReadonlyMap { - set(key: string, value: T): this; - delete(key: string): boolean; - clear(): void; - } - - /* @internal */ - export interface MapConstructor { - // eslint-disable-next-line @typescript-eslint/prefer-function-type - new (): Map; - } - - /** - * Returns the native Map implementation if it is available and compatible (i.e. supports iteration). - */ - /* @internal */ - export function tryGetNativeMap(): MapConstructor | undefined { - // Internet Explorer's Map doesn't support iteration, so don't use it. - // Natives - // NOTE: TS doesn't strictly allow in-line declares, but if we suppress the error, the declaration - // is still used for typechecking _and_ correctly elided, which is out goal, as this prevents us from - // needing to pollute an outer scope with a declaration of `Map` just to satisfy the checks in this function - //@ts-ignore - declare const Map: (new () => Map) | undefined; - // eslint-disable-next-line no-in-operator - return typeof Map !== "undefined" && "entries" in Map.prototype ? Map : undefined; - } - - /* @internal */ - export const Map: MapConstructor = tryGetNativeMap() || (() => { - // NOTE: createMapShim will be defined for typescriptServices.js but not for tsc.js, so we must test for it. - if (typeof createMapShim === "function") { - return createMapShim(); - } - throw new Error("TypeScript requires an environment that provides a compatible native Map implementation."); - })(); - - /** ES6 Iterator type. */ - export interface Iterator { - next(): { value: T, done?: false } | { value: never, done: true }; - } - - /** Array that is only intended to be pushed to, never read. */ - export interface Push { - push(...values: T[]): void; - } - - /* @internal */ - export type EqualityComparer = (a: T, b: T) => boolean; - - /* @internal */ - export type Comparer = (a: T, b: T) => Comparison; - - /* @internal */ - export const enum Comparison { - LessThan = -1, - EqualTo = 0, - GreaterThan = 1 - } -} \ No newline at end of file + throw new Error("TypeScript requires an environment that provides a compatible native Map implementation."); +})(); +/** ES6 Iterator type. */ +export interface Iterator { + next(): { + value: T; + done?: false; + } | { + value: never; + done: true; + }; +} +/** Array that is only intended to be pushed to, never read. */ +export interface Push { + push(...values: T[]): void; +} +/* @internal */ +export type EqualityComparer = (a: T, b: T) => boolean; +/* @internal */ +export type Comparer = (a: T, b: T) => Comparison; +/* @internal */ +export const enum Comparison { + LessThan = -1, + EqualTo = 0, + GreaterThan = 1 +} diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index 9adc40950548c..b78d109c5147b 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -1,324 +1,256 @@ +import { AssertionLevel, AnyFunction, hasProperty, Node, Symbol, unescapeLeadingUnderscores, map, stableSort, compareValues, SyntaxKind, NodeFlags, ModifierFlags, TransformFlags, EmitFlags, SymbolFlags, TypeFlags, ObjectFlags, every, noop, FlowNode, objectAllocator, Type, ObjectType, getModifierFlagsNoCache, isParseTreeNode, getEmitFlags, nodeIsSynthesized, getParseTreeNode, getSourceFileOfNode, getSourceTextOfNodeFromSourceFile, sys, getDirectoryPath, resolvePath, RequireResult } from "./ts"; +import * as ts from "./ts"; /* @internal */ -namespace ts { - export namespace Debug { - /* eslint-disable prefer-const */ - export let currentAssertionLevel = AssertionLevel.None; - export let isDebugging = false; - /* eslint-enable prefer-const */ - - export function shouldAssert(level: AssertionLevel): boolean { - return currentAssertionLevel >= level; - } - - export function assert(expression: boolean, message?: string, verboseDebugInfo?: string | (() => string), stackCrawlMark?: AnyFunction): void { - if (!expression) { - if (verboseDebugInfo) { - message += "\r\nVerbose Debug Information: " + (typeof verboseDebugInfo === "string" ? verboseDebugInfo : verboseDebugInfo()); - } - fail(message ? "False expression: " + message : "False expression.", stackCrawlMark || assert); +export namespace Debug { + /* eslint-disable prefer-const */ + export let currentAssertionLevel = AssertionLevel.None; + export let isDebugging = false; + /* eslint-enable prefer-const */ + export function shouldAssert(level: AssertionLevel): boolean { + return currentAssertionLevel >= level; + } + export function assert(expression: boolean, message?: string, verboseDebugInfo?: string | (() => string), stackCrawlMark?: AnyFunction): void { + if (!expression) { + if (verboseDebugInfo) { + message += "\r\nVerbose Debug Information: " + (typeof verboseDebugInfo === "string" ? verboseDebugInfo : verboseDebugInfo()); } + fail(message ? "False expression: " + message : "False expression.", stackCrawlMark || assert); } - - export function assertEqual(a: T, b: T, msg?: string, msg2?: string): void { - if (a !== b) { - const message = msg ? msg2 ? `${msg} ${msg2}` : msg : ""; - fail(`Expected ${a} === ${b}. ${message}`); - } + } + export function assertEqual(a: T, b: T, msg?: string, msg2?: string): void { + if (a !== b) { + const message = msg ? msg2 ? `${msg} ${msg2}` : msg : ""; + fail(`Expected ${a} === ${b}. ${message}`); } - - export function assertLessThan(a: number, b: number, msg?: string): void { - if (a >= b) { - fail(`Expected ${a} < ${b}. ${msg || ""}`); - } + } + export function assertLessThan(a: number, b: number, msg?: string): void { + if (a >= b) { + fail(`Expected ${a} < ${b}. ${msg || ""}`); } - - export function assertLessThanOrEqual(a: number, b: number): void { - if (a > b) { - fail(`Expected ${a} <= ${b}`); - } + } + export function assertLessThanOrEqual(a: number, b: number): void { + if (a > b) { + fail(`Expected ${a} <= ${b}`); } - - export function assertGreaterThanOrEqual(a: number, b: number): void { - if (a < b) { - fail(`Expected ${a} >= ${b}`); - } + } + export function assertGreaterThanOrEqual(a: number, b: number): void { + if (a < b) { + fail(`Expected ${a} >= ${b}`); } - - export function fail(message?: string, stackCrawlMark?: AnyFunction): never { - debugger; - const e = new Error(message ? `Debug Failure. ${message}` : "Debug Failure."); - if ((Error).captureStackTrace) { - (Error).captureStackTrace(e, stackCrawlMark || fail); - } - throw e; + } + export function fail(message?: string, stackCrawlMark?: AnyFunction): never { + debugger; + const e = new Error(message ? `Debug Failure. ${message}` : "Debug Failure."); + if ((Error).captureStackTrace) { + (Error).captureStackTrace(e, stackCrawlMark || fail); } - - export function assertDefined(value: T | null | undefined, message?: string): T { - // eslint-disable-next-line no-null/no-null - if (value === undefined || value === null) return fail(message); - return value; + throw e; + } + export function assertDefined(value: T | null | undefined, message?: string): T { + // eslint-disable-next-line no-null/no-null + if (value === undefined || value === null) + return fail(message); + return value; + } + export function assertEachDefined(value: A, message?: string): A { + for (const v of value) { + assertDefined(v, message); } - - export function assertEachDefined(value: A, message?: string): A { - for (const v of value) { - assertDefined(v, message); - } - return value; + return value; + } + export function assertNever(member: never, message = "Illegal value:", stackCrawlMark?: AnyFunction): never { + const detail = typeof member === "object" && hasProperty(member, "kind") && hasProperty(member, "pos") && formatSyntaxKind ? "SyntaxKind: " + formatSyntaxKind((member as Node).kind) : JSON.stringify(member); + return fail(`${message} ${detail}`, stackCrawlMark || assertNever); + } + export function getFunctionName(func: AnyFunction) { + if (typeof func !== "function") { + return ""; } - - export function assertNever(member: never, message = "Illegal value:", stackCrawlMark?: AnyFunction): never { - const detail = typeof member === "object" && hasProperty(member, "kind") && hasProperty(member, "pos") && formatSyntaxKind ? "SyntaxKind: " + formatSyntaxKind((member as Node).kind) : JSON.stringify(member); - return fail(`${message} ${detail}`, stackCrawlMark || assertNever); + else if (func.hasOwnProperty("name")) { + return (func).name; } - - export function getFunctionName(func: AnyFunction) { - if (typeof func !== "function") { - return ""; - } - else if (func.hasOwnProperty("name")) { - return (func).name; - } - else { - const text = Function.prototype.toString.call(func); - const match = /^function\s+([\w\$]+)\s*\(/.exec(text); - return match ? match[1] : ""; - } + else { + const text = Function.prototype.toString.call(func); + const match = /^function\s+([\w\$]+)\s*\(/.exec(text); + return match ? match[1] : ""; } - - export function formatSymbol(symbol: Symbol): string { - return `{ name: ${unescapeLeadingUnderscores(symbol.escapedName)}; flags: ${formatSymbolFlags(symbol.flags)}; declarations: ${map(symbol.declarations, node => formatSyntaxKind(node.kind))} }`; + } + export function formatSymbol(symbol: Symbol): string { + return `{ name: ${unescapeLeadingUnderscores(symbol.escapedName)}; flags: ${formatSymbolFlags(symbol.flags)}; declarations: ${map(symbol.declarations, node => formatSyntaxKind(node.kind))} }`; + } + /** + * Formats an enum value as a string for debugging and debug assertions. + */ + export function formatEnum(value = 0, enumObject: any, isFlags?: boolean) { + const members = getEnumMembers(enumObject); + if (value === 0) { + return members.length > 0 && members[0][0] === 0 ? members[0][1] : "0"; } - - /** - * Formats an enum value as a string for debugging and debug assertions. - */ - export function formatEnum(value = 0, enumObject: any, isFlags?: boolean) { - const members = getEnumMembers(enumObject); - if (value === 0) { - return members.length > 0 && members[0][0] === 0 ? members[0][1] : "0"; - } - if (isFlags) { - let result = ""; - let remainingFlags = value; - for (const [enumValue, enumName] of members) { - if (enumValue > value) { - break; - } - if (enumValue !== 0 && enumValue & value) { - result = `${result}${result ? "|" : ""}${enumName}`; - remainingFlags &= ~enumValue; - } + if (isFlags) { + let result = ""; + let remainingFlags = value; + for (const [enumValue, enumName] of members) { + if (enumValue > value) { + break; } - if (remainingFlags === 0) { - return result; + if (enumValue !== 0 && enumValue & value) { + result = `${result}${result ? "|" : ""}${enumName}`; + remainingFlags &= ~enumValue; } } - else { - for (const [enumValue, enumName] of members) { - if (enumValue === value) { - return enumName; - } - } + if (remainingFlags === 0) { + return result; } - return value.toString(); } - - function getEnumMembers(enumObject: any) { - const result: [number, string][] = []; - for (const name in enumObject) { - const value = enumObject[name]; - if (typeof value === "number") { - result.push([value, name]); + else { + for (const [enumValue, enumName] of members) { + if (enumValue === value) { + return enumName; } } - - return stableSort<[number, string]>(result, (x, y) => compareValues(x[0], y[0])); - } - - export function formatSyntaxKind(kind: SyntaxKind | undefined): string { - return formatEnum(kind, (ts).SyntaxKind, /*isFlags*/ false); - } - - export function formatNodeFlags(flags: NodeFlags | undefined): string { - return formatEnum(flags, (ts).NodeFlags, /*isFlags*/ true); - } - - export function formatModifierFlags(flags: ModifierFlags | undefined): string { - return formatEnum(flags, (ts).ModifierFlags, /*isFlags*/ true); } - - export function formatTransformFlags(flags: TransformFlags | undefined): string { - return formatEnum(flags, (ts).TransformFlags, /*isFlags*/ true); - } - - export function formatEmitFlags(flags: EmitFlags | undefined): string { - return formatEnum(flags, (ts).EmitFlags, /*isFlags*/ true); - } - - export function formatSymbolFlags(flags: SymbolFlags | undefined): string { - return formatEnum(flags, (ts).SymbolFlags, /*isFlags*/ true); - } - - export function formatTypeFlags(flags: TypeFlags | undefined): string { - return formatEnum(flags, (ts).TypeFlags, /*isFlags*/ true); - } - - export function formatObjectFlags(flags: ObjectFlags | undefined): string { - return formatEnum(flags, (ts).ObjectFlags, /*isFlags*/ true); - } - - export function failBadSyntaxKind(node: Node, message?: string): never { - return fail( - `${message || "Unexpected node."}\r\nNode ${formatSyntaxKind(node.kind)} was unexpected.`, - failBadSyntaxKind); - } - - export const assertEachNode = shouldAssert(AssertionLevel.Normal) - ? (nodes: Node[], test: (node: Node) => boolean, message?: string): void => assert( - test === undefined || every(nodes, test), - message || "Unexpected node.", - () => `Node array did not pass test '${getFunctionName(test)}'.`, - assertEachNode) - : noop; - - export const assertNode = shouldAssert(AssertionLevel.Normal) - ? (node: Node | undefined, test: ((node: Node | undefined) => boolean) | undefined, message?: string): void => assert( - test === undefined || test(node), - message || "Unexpected node.", - () => `Node ${formatSyntaxKind(node!.kind)} did not pass test '${getFunctionName(test!)}'.`, - assertNode) - : noop; - - export const assertNotNode = shouldAssert(AssertionLevel.Normal) - ? (node: Node | undefined, test: ((node: Node | undefined) => boolean) | undefined, message?: string): void => assert( - test === undefined || !test(node), - message || "Unexpected node.", - () => `Node ${formatSyntaxKind(node!.kind)} should not have passed test '${getFunctionName(test!)}'.`, - assertNode) - : noop; - - export const assertOptionalNode = shouldAssert(AssertionLevel.Normal) - ? (node: Node, test: (node: Node) => boolean, message?: string): void => assert( - test === undefined || node === undefined || test(node), - message || "Unexpected node.", - () => `Node ${formatSyntaxKind(node.kind)} did not pass test '${getFunctionName(test)}'.`, - assertOptionalNode) - : noop; - - export const assertOptionalToken = shouldAssert(AssertionLevel.Normal) - ? (node: Node, kind: SyntaxKind, message?: string): void => assert( - kind === undefined || node === undefined || node.kind === kind, - message || "Unexpected node.", - () => `Node ${formatSyntaxKind(node.kind)} was not a '${formatSyntaxKind(kind)}' token.`, - assertOptionalToken) - : noop; - - export const assertMissingNode = shouldAssert(AssertionLevel.Normal) - ? (node: Node, message?: string): void => assert( - node === undefined, - message || "Unexpected node.", - () => `Node ${formatSyntaxKind(node.kind)} was unexpected'.`, - assertMissingNode) - : noop; - - let isDebugInfoEnabled = false; - - interface ExtendedDebugModule { - init(_ts: typeof ts): void; - formatControlFlowGraph(flowNode: FlowNode): string; - } - - let extendedDebugModule: ExtendedDebugModule | undefined; - - function extendedDebug() { - enableDebugInfo(); - if (!extendedDebugModule) { - throw new Error("Debugging helpers could not be loaded."); + return value.toString(); + } + function getEnumMembers(enumObject: any) { + const result: [number, string][] = []; + for (const name in enumObject) { + const value = enumObject[name]; + if (typeof value === "number") { + result.push([value, name]); } - return extendedDebugModule; } - - export function printControlFlowGraph(flowNode: FlowNode) { - return console.log(formatControlFlowGraph(flowNode)); - } - - export function formatControlFlowGraph(flowNode: FlowNode) { - return extendedDebug().formatControlFlowGraph(flowNode); + return stableSort<[number, string]>(result, (x, y) => compareValues(x[0], y[0])); + } + export function formatSyntaxKind(kind: SyntaxKind | undefined): string { + return formatEnum(kind, (ts).SyntaxKind, /*isFlags*/ false); + } + export function formatNodeFlags(flags: NodeFlags | undefined): string { + return formatEnum(flags, (ts).NodeFlags, /*isFlags*/ true); + } + export function formatModifierFlags(flags: ModifierFlags | undefined): string { + return formatEnum(flags, (ts).ModifierFlags, /*isFlags*/ true); + } + export function formatTransformFlags(flags: TransformFlags | undefined): string { + return formatEnum(flags, (ts).TransformFlags, /*isFlags*/ true); + } + export function formatEmitFlags(flags: EmitFlags | undefined): string { + return formatEnum(flags, (ts).EmitFlags, /*isFlags*/ true); + } + export function formatSymbolFlags(flags: SymbolFlags | undefined): string { + return formatEnum(flags, (ts).SymbolFlags, /*isFlags*/ true); + } + export function formatTypeFlags(flags: TypeFlags | undefined): string { + return formatEnum(flags, (ts).TypeFlags, /*isFlags*/ true); + } + export function formatObjectFlags(flags: ObjectFlags | undefined): string { + return formatEnum(flags, (ts).ObjectFlags, /*isFlags*/ true); + } + export function failBadSyntaxKind(node: Node, message?: string): never { + return fail(`${message || "Unexpected node."}\r\nNode ${formatSyntaxKind(node.kind)} was unexpected.`, failBadSyntaxKind); + } + export const assertEachNode = shouldAssert(AssertionLevel.Normal) + ? (nodes: Node[], test: (node: Node) => boolean, message?: string): void => assert(test === undefined || every(nodes, test), message || "Unexpected node.", () => `Node array did not pass test '${getFunctionName(test)}'.`, assertEachNode) + : noop; + export const assertNode = shouldAssert(AssertionLevel.Normal) + ? (node: Node | undefined, test: ((node: Node | undefined) => boolean) | undefined, message?: string): void => assert(test === undefined || test(node), message || "Unexpected node.", () => `Node ${formatSyntaxKind(node!.kind)} did not pass test '${getFunctionName(test!)}'.`, assertNode) + : noop; + export const assertNotNode = shouldAssert(AssertionLevel.Normal) + ? (node: Node | undefined, test: ((node: Node | undefined) => boolean) | undefined, message?: string): void => assert(test === undefined || !test(node), message || "Unexpected node.", () => `Node ${formatSyntaxKind(node!.kind)} should not have passed test '${getFunctionName(test!)}'.`, assertNode) + : noop; + export const assertOptionalNode = shouldAssert(AssertionLevel.Normal) + ? (node: Node, test: (node: Node) => boolean, message?: string): void => assert(test === undefined || node === undefined || test(node), message || "Unexpected node.", () => `Node ${formatSyntaxKind(node.kind)} did not pass test '${getFunctionName(test)}'.`, assertOptionalNode) + : noop; + export const assertOptionalToken = shouldAssert(AssertionLevel.Normal) + ? (node: Node, kind: SyntaxKind, message?: string): void => assert(kind === undefined || node === undefined || node.kind === kind, message || "Unexpected node.", () => `Node ${formatSyntaxKind(node.kind)} was not a '${formatSyntaxKind(kind)}' token.`, assertOptionalToken) + : noop; + export const assertMissingNode = shouldAssert(AssertionLevel.Normal) + ? (node: Node, message?: string): void => assert(node === undefined, message || "Unexpected node.", () => `Node ${formatSyntaxKind(node.kind)} was unexpected'.`, assertMissingNode) + : noop; + let isDebugInfoEnabled = false; + interface ExtendedDebugModule { + init(_ts: typeof ts): void; + formatControlFlowGraph(flowNode: FlowNode): string; + } + let extendedDebugModule: ExtendedDebugModule | undefined; + function extendedDebug() { + enableDebugInfo(); + if (!extendedDebugModule) { + throw new Error("Debugging helpers could not be loaded."); } - - export function attachFlowNodeDebugInfo(flowNode: FlowNode) { - if (isDebugInfoEnabled) { - if (!("__debugFlowFlags" in flowNode)) { // eslint-disable-line no-in-operator - Object.defineProperties(flowNode, { - __debugFlowFlags: { get(this: FlowNode) { return formatEnum(this.flags, (ts as any).FlowFlags, /*isFlags*/ true); } }, - __debugToString: { value(this: FlowNode) { return formatControlFlowGraph(this); } } - }); - } + return extendedDebugModule; + } + export function printControlFlowGraph(flowNode: FlowNode) { + return console.log(formatControlFlowGraph(flowNode)); + } + export function formatControlFlowGraph(flowNode: FlowNode) { + return extendedDebug().formatControlFlowGraph(flowNode); + } + export function attachFlowNodeDebugInfo(flowNode: FlowNode) { + if (isDebugInfoEnabled) { + if (!("__debugFlowFlags" in flowNode)) { // eslint-disable-line no-in-operator + Object.defineProperties(flowNode, { + __debugFlowFlags: { get(this: FlowNode) { return formatEnum(this.flags, (ts as any).FlowFlags, /*isFlags*/ true); } }, + __debugToString: { value(this: FlowNode) { return formatControlFlowGraph(this); } } + }); } } - - /** - * Injects debug information into frequently used types. - */ - export function enableDebugInfo() { - if (isDebugInfoEnabled) return; - - // Add additional properties in debug mode to assist with debugging. - Object.defineProperties(objectAllocator.getSymbolConstructor().prototype, { - __debugFlags: { get(this: Symbol) { return formatSymbolFlags(this.flags); } } - }); - - Object.defineProperties(objectAllocator.getTypeConstructor().prototype, { - __debugFlags: { get(this: Type) { return formatTypeFlags(this.flags); } }, - __debugObjectFlags: { get(this: Type) { return this.flags & TypeFlags.Object ? formatObjectFlags((this).objectFlags) : ""; } }, - __debugTypeToString: { value(this: Type) { return this.checker.typeToString(this); } }, - }); - - const nodeConstructors = [ - objectAllocator.getNodeConstructor(), - objectAllocator.getIdentifierConstructor(), - objectAllocator.getTokenConstructor(), - objectAllocator.getSourceFileConstructor() - ]; - - for (const ctor of nodeConstructors) { - if (!ctor.prototype.hasOwnProperty("__debugKind")) { - Object.defineProperties(ctor.prototype, { - __debugKind: { get(this: Node) { return formatSyntaxKind(this.kind); } }, - __debugNodeFlags: { get(this: Node) { return formatNodeFlags(this.flags); } }, - __debugModifierFlags: { get(this: Node) { return formatModifierFlags(getModifierFlagsNoCache(this)); } }, - __debugTransformFlags: { get(this: Node) { return formatTransformFlags(this.transformFlags); } }, - __debugIsParseTreeNode: { get(this: Node) { return isParseTreeNode(this); } }, - __debugEmitFlags: { get(this: Node) { return formatEmitFlags(getEmitFlags(this)); } }, - __debugGetText: { - value(this: Node, includeTrivia?: boolean) { - if (nodeIsSynthesized(this)) return ""; - const parseNode = getParseTreeNode(this); - const sourceFile = parseNode && getSourceFileOfNode(parseNode); - return sourceFile ? getSourceTextOfNodeFromSourceFile(sourceFile, parseNode, includeTrivia) : ""; - } + } + /** + * Injects debug information into frequently used types. + */ + export function enableDebugInfo() { + if (isDebugInfoEnabled) + return; + // Add additional properties in debug mode to assist with debugging. + Object.defineProperties(objectAllocator.getSymbolConstructor().prototype, { + __debugFlags: { get(this: Symbol) { return formatSymbolFlags(this.flags); } } + }); + Object.defineProperties(objectAllocator.getTypeConstructor().prototype, { + __debugFlags: { get(this: Type) { return formatTypeFlags(this.flags); } }, + __debugObjectFlags: { get(this: Type) { return this.flags & TypeFlags.Object ? formatObjectFlags((this).objectFlags) : ""; } }, + __debugTypeToString: { value(this: Type) { return this.checker.typeToString(this); } }, + }); + const nodeConstructors = [ + objectAllocator.getNodeConstructor(), + objectAllocator.getIdentifierConstructor(), + objectAllocator.getTokenConstructor(), + objectAllocator.getSourceFileConstructor() + ]; + for (const ctor of nodeConstructors) { + if (!ctor.prototype.hasOwnProperty("__debugKind")) { + Object.defineProperties(ctor.prototype, { + __debugKind: { get(this: Node) { return formatSyntaxKind(this.kind); } }, + __debugNodeFlags: { get(this: Node) { return formatNodeFlags(this.flags); } }, + __debugModifierFlags: { get(this: Node) { return formatModifierFlags(getModifierFlagsNoCache(this)); } }, + __debugTransformFlags: { get(this: Node) { return formatTransformFlags(this.transformFlags); } }, + __debugIsParseTreeNode: { get(this: Node) { return isParseTreeNode(this); } }, + __debugEmitFlags: { get(this: Node) { return formatEmitFlags(getEmitFlags(this)); } }, + __debugGetText: { + value(this: Node, includeTrivia?: boolean) { + if (nodeIsSynthesized(this)) + return ""; + const parseNode = getParseTreeNode(this); + const sourceFile = parseNode && getSourceFileOfNode(parseNode); + return sourceFile ? getSourceTextOfNodeFromSourceFile(sourceFile, parseNode, includeTrivia) : ""; } - }); - } - } - - // attempt to load extended debugging information - try { - if (sys && sys.require) { - const basePath = getDirectoryPath(resolvePath(sys.getExecutingFilePath())); - const result = sys.require(basePath, "./compiler-debug") as RequireResult; - if (!result.error) { - result.module.init(ts); - extendedDebugModule = result.module; } - } + }); } - catch { - // do nothing + } + // attempt to load extended debugging information + try { + if (sys && sys.require) { + const basePath = getDirectoryPath(resolvePath(sys.getExecutingFilePath())); + const result = (sys.require(basePath, "./compiler-debug") as RequireResult); + if (!result.error) { + result.module.init(ts); + extendedDebugModule = result.module; + } } - - isDebugInfoEnabled = true; } - + catch { + // do nothing + } + isDebugInfoEnabled = true; } } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 703bc0365be14..049f680f1c20e 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1,5181 +1,4555 @@ -namespace ts { - const brackets = createBracketsMap(); - const syntheticParent: TextRange = { pos: -1, end: -1 }; - - /*@internal*/ - export function isBuildInfoFile(file: string) { - return fileExtensionIs(file, Extension.TsBuildInfo); - } - - /*@internal*/ - /** - * Iterates over the source files that are expected to have an emit output. - * - * @param host An EmitHost. - * @param action The action to execute. - * @param sourceFilesOrTargetSourceFile - * If an array, the full list of source files to emit. - * Else, calls `getSourceFilesToEmit` with the (optional) target source file to determine the list of source files to emit. - */ - export function forEachEmittedFile( - host: EmitHost, action: (emitFileNames: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle | undefined) => T, - sourceFilesOrTargetSourceFile?: readonly SourceFile[] | SourceFile, - forceDtsEmit = false, - onlyBuildInfo?: boolean, - includeBuildInfo?: boolean) { - const sourceFiles = isArray(sourceFilesOrTargetSourceFile) ? sourceFilesOrTargetSourceFile : getSourceFilesToEmit(host, sourceFilesOrTargetSourceFile, forceDtsEmit); - const options = host.getCompilerOptions(); - if (options.outFile || options.out) { - const prepends = host.getPrependNodes(); - if (sourceFiles.length || prepends.length) { - const bundle = createBundle(sourceFiles, prepends); - const result = action(getOutputPathsFor(bundle, host, forceDtsEmit), bundle); +import { TextRange, fileExtensionIs, Extension, EmitHost, EmitFileNames, SourceFile, Bundle, isArray, getSourceFilesToEmit, createBundle, CompilerOptions, isIncrementalCompilation, removeFileExtension, resolvePath, getRelativePathFromDirectory, combinePaths, getBaseFileName, getEmitDeclarations, getAreDeclarationMapsEnabled, SyntaxKind, getOwnEmitOutputFilePath, isJsonSourceFile, comparePaths, Comparison, getDeclarationEmitOutputFilePath, JsxEmit, isSourceFileJS, LanguageVariant, ParsedCommandLine, getDirectoryPath, Debug, changeExtension, emptyArray, normalizePath, contains, EmitResolver, EmitTransformers, EmitResult, SourceMapEmitResult, createDiagnosticCollection, getNewLineCharacter, createTextWriter, BundleBuildInfo, ExportedModulesFromDeclarationEmit, isBundle, getNormalizedAbsolutePath, ensurePathIsNonModuleName, writeFile, transformNodes, PrinterOptions, isSourceFile, length, Node, isExportAssignment, Identifier, isExportSpecifier, forEachChild, Printer, SourceMapGenerator, createSourceMapGenerator, normalizeSlashes, ensureTrailingDirectorySeparator, getSourceFilePathInNewDir, getRootLength, base64encode, sys, getRelativePathToDirectoryOrUrl, BuildInfo, notImplemented, LateBoundDeclaration, OutputFile, ModuleResolutionHost, createNode, createNodeArray, forEach, PrologueDirective, StringLiteral, ProjectReference, CustomTransformers, createInputFiles, createPrependNodes, memoize, returnUndefined, returnFalse, createMultiMap, getTransformers, PrintHandlers, noEmitNotification, noEmitSubstitution, getEmitModuleKind, createMap, EmitTextWriter, BundleFileInfo, BundleFileTextLikeKind, BundleFileSectionKind, SourceMapSource, EmitHint, isIdentifier, isExpression, UnparsedSource, ListFormat, NodeArray, TypeNode, lastOrUndefined, isDeclaration, isVariableStatement, isInternalDeclaration, isBundleFileTextLike, BundleFileTextLike, getTrailingSemicolonDeferringWriter, getLineStarts, Expression, isInJsonFile, cast, isTypeParameterDeclaration, isEmptyStatement, isKeyword, LiteralExpression, UnparsedNode, UnparsedTextLike, UnparsedSyntheticReference, QualifiedName, ComputedPropertyName, TypeParameterDeclaration, ParameterDeclaration, Decorator, PropertySignature, PropertyDeclaration, MethodSignature, MethodDeclaration, ConstructorDeclaration, AccessorDeclaration, CallSignatureDeclaration, ConstructSignatureDeclaration, IndexSignatureDeclaration, TypePredicateNode, TypeReferenceNode, FunctionTypeNode, JSDocFunctionType, ConstructorTypeNode, TypeQueryNode, TypeLiteralNode, ArrayTypeNode, TupleTypeNode, OptionalTypeNode, UnionTypeNode, IntersectionTypeNode, ConditionalTypeNode, InferTypeNode, ParenthesizedTypeNode, ExpressionWithTypeArguments, TypeOperatorNode, IndexedAccessTypeNode, MappedTypeNode, LiteralTypeNode, ImportTypeNode, JSDocNullableType, JSDocNonNullableType, JSDocOptionalType, RestTypeNode, JSDocVariadicType, ObjectBindingPattern, ArrayBindingPattern, BindingElement, TemplateSpan, Block, VariableStatement, ExpressionStatement, IfStatement, DoStatement, WhileStatement, ForStatement, ForInStatement, ForOfStatement, ContinueStatement, BreakStatement, ReturnStatement, WithStatement, SwitchStatement, LabeledStatement, ThrowStatement, TryStatement, DebuggerStatement, VariableDeclaration, VariableDeclarationList, FunctionDeclaration, ClassDeclaration, InterfaceDeclaration, TypeAliasDeclaration, EnumDeclaration, ModuleDeclaration, ModuleBlock, CaseBlock, NamespaceExportDeclaration, ImportEqualsDeclaration, ImportDeclaration, ImportClause, NamespaceImport, NamedImports, ImportSpecifier, ExportAssignment, ExportDeclaration, NamedExports, ExportSpecifier, ExternalModuleReference, JsxText, JsxOpeningElement, JsxClosingElement, JsxAttribute, JsxAttributes, JsxSpreadAttribute, JsxExpression, CaseClause, DefaultClause, HeritageClause, CatchClause, PropertyAssignment, ShorthandPropertyAssignment, SpreadAssignment, EnumMember, JSDocPropertyLikeTag, JSDocTypeTag, JSDocAugmentsTag, JSDocTemplateTag, JSDocTypedefTag, JSDocCallbackTag, JSDocSignature, JSDocTypeLiteral, JSDocTag, JSDoc, isToken, NumericLiteral, BigIntLiteral, ArrayLiteralExpression, ObjectLiteralExpression, PropertyAccessExpression, ElementAccessExpression, CallExpression, NewExpression, TaggedTemplateExpression, TypeAssertion, ParenthesizedExpression, FunctionExpression, ArrowFunction, DeleteExpression, TypeOfExpression, VoidExpression, AwaitExpression, PrefixUnaryExpression, PostfixUnaryExpression, BinaryExpression, ConditionalExpression, TemplateExpression, YieldExpression, SpreadElement, ClassExpression, AsExpression, NonNullExpression, MetaProperty, JsxElement, JsxSelfClosingElement, JsxFragment, PartiallyEmittedExpression, CommaListExpression, ModuleKind, getExternalHelpersModuleName, isUnparsedSource, hasRecordedExternalHelpers, getEmitHelpers, stableSort, compareEmitHelpers, LiteralLikeNode, isTemplateLiteralKind, UnparsedPrepend, clone, EntityName, getEmitFlags, EmitFlags, ScriptTarget, getDotOrQuestionDotToken, skipPartiallyEmittedExpressions, isNumericLiteral, stringContains, tokenToString, isAccessExpression, getConstantValue, BlockLike, nodeIsSynthesized, isBlock, getParseTreeNode, skipTrivia, positionsAreOnSameLine, isLet, isVarConst, FunctionLikeDeclaration, SignatureDeclaration, rangeIsOnSingleLine, Statement, NodeFlags, ModuleReference, NamedImportsOrExports, ImportOrExportSpecifier, JsxOpeningFragment, isJsxOpeningElement, JsxClosingFragment, isJsxClosingElement, JsxTagNameExpression, rangeStartPositionsAreOnSameLine, getCommentRange, JSDocThisTag, JSDocEnumTag, JSDocReturnTag, JSDocTypeExpression, isPrologueDirective, FileReference, findIndex, UnparsedPrologue, SourceFilePrologueInfo, SourceFilePrologueDirective, getShebang, Modifier, isFunctionLike, singleOrUndefined, isArrowFunction, some, Symbol, guessIndentation, positionIsSynthesized, rangeEndIsOnSameLineAsRangeStart, getStartsOnNewLine, rangeEndPositionsAreOnSameLine, isGeneratedIdentifier, getSourceFileOfNode, getOriginalNode, idText, isLiteralExpression, getSourceTextOfNodeFromSourceFile, escapeString, escapeNonAsciiString, getLiteralText, ForInOrOfStatement, CaseOrDefaultClause, NamedDeclaration, BindingPattern, DeclarationName, isBindingPattern, GeneratedIdentifier, GeneratedIdentifierFlags, getNodeId, isNodeDescendantOf, escapeLeadingUnderscores, SymbolFlags, CharacterCodes, getExternalModuleName, isStringLiteral, makeIdentifierFromModuleName, getSyntheticLeadingComments, getSyntheticTrailingComments, SynthesizedComment, computeLineStarts, writeCommentRange, isJSDocLikeText, isPinnedComment, emitNewLineBeforeLeadingCommentOfPosition, forEachLeadingCommentRange, forEachTrailingCommentRange, last, emitDetachedComments, isRecognizedTripleSlashComment, tryParseRawSourceMap, isUnparsedPrepend, isUnparsedNode, getSourceMapRange, getLineAndCharacterOfPosition } from "./ts"; +import { createTimer, createTimerIf } from "./ts.performance"; +import * as ts from "./ts"; +const brackets = createBracketsMap(); +const syntheticParent: TextRange = { pos: -1, end: -1 }; +/*@internal*/ +export function isBuildInfoFile(file: string) { + return fileExtensionIs(file, Extension.TsBuildInfo); +} +/*@internal*/ +/** + * Iterates over the source files that are expected to have an emit output. + * + * @param host An EmitHost. + * @param action The action to execute. + * @param sourceFilesOrTargetSourceFile + * If an array, the full list of source files to emit. + * Else, calls `getSourceFilesToEmit` with the (optional) target source file to determine the list of source files to emit. + */ +export function forEachEmittedFile(host: EmitHost, action: (emitFileNames: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle | undefined) => T, sourceFilesOrTargetSourceFile?: readonly SourceFile[] | SourceFile, forceDtsEmit = false, onlyBuildInfo?: boolean, includeBuildInfo?: boolean) { + const sourceFiles = isArray(sourceFilesOrTargetSourceFile) ? sourceFilesOrTargetSourceFile : getSourceFilesToEmit(host, sourceFilesOrTargetSourceFile, forceDtsEmit); + const options = host.getCompilerOptions(); + if (options.outFile || options.out) { + const prepends = host.getPrependNodes(); + if (sourceFiles.length || prepends.length) { + const bundle = createBundle(sourceFiles, prepends); + const result = action(getOutputPathsFor(bundle, host, forceDtsEmit), bundle); + if (result) { + return result; + } + } + } + else { + if (!onlyBuildInfo) { + for (const sourceFile of sourceFiles) { + const result = action(getOutputPathsFor(sourceFile, host, forceDtsEmit), sourceFile); if (result) { return result; } } } - else { - if (!onlyBuildInfo) { - for (const sourceFile of sourceFiles) { - const result = action(getOutputPathsFor(sourceFile, host, forceDtsEmit), sourceFile); - if (result) { - return result; - } - } - } - if (includeBuildInfo) { - const buildInfoPath = getTsBuildInfoEmitOutputFilePath(host.getCompilerOptions()); - if (buildInfoPath) return action({ buildInfoPath }, /*sourceFileOrBundle*/ undefined); - } + if (includeBuildInfo) { + const buildInfoPath = getTsBuildInfoEmitOutputFilePath(host.getCompilerOptions()); + if (buildInfoPath) + return action({ buildInfoPath }, /*sourceFileOrBundle*/ undefined); } } - - export function getTsBuildInfoEmitOutputFilePath(options: CompilerOptions) { - const configFile = options.configFilePath; - if (!isIncrementalCompilation(options)) return undefined; - if (options.tsBuildInfoFile) return options.tsBuildInfoFile; - const outPath = options.outFile || options.out; - let buildInfoExtensionLess: string; - if (outPath) { - buildInfoExtensionLess = removeFileExtension(outPath); - } - else { - if (!configFile) return undefined; - const configFileExtensionLess = removeFileExtension(configFile); - buildInfoExtensionLess = options.outDir ? - options.rootDir ? - resolvePath(options.outDir, getRelativePathFromDirectory(options.rootDir, configFileExtensionLess, /*ignoreCase*/ true)) : - combinePaths(options.outDir, getBaseFileName(configFileExtensionLess)) : - configFileExtensionLess; - } - return buildInfoExtensionLess + Extension.TsBuildInfo; - } - - /*@internal*/ - export function getOutputPathsForBundle(options: CompilerOptions, forceDtsPaths: boolean): EmitFileNames { - const outPath = options.outFile || options.out!; - const jsFilePath = options.emitDeclarationOnly ? undefined : outPath; - const sourceMapFilePath = jsFilePath && getSourceMapFilePath(jsFilePath, options); - const declarationFilePath = (forceDtsPaths || getEmitDeclarations(options)) ? removeFileExtension(outPath) + Extension.Dts : undefined; +} +export function getTsBuildInfoEmitOutputFilePath(options: CompilerOptions) { + const configFile = options.configFilePath; + if (!isIncrementalCompilation(options)) + return undefined; + if (options.tsBuildInfoFile) + return options.tsBuildInfoFile; + const outPath = options.outFile || options.out; + let buildInfoExtensionLess: string; + if (outPath) { + buildInfoExtensionLess = removeFileExtension(outPath); + } + else { + if (!configFile) + return undefined; + const configFileExtensionLess = removeFileExtension(configFile); + buildInfoExtensionLess = options.outDir ? + options.rootDir ? + resolvePath(options.outDir, getRelativePathFromDirectory(options.rootDir, configFileExtensionLess, /*ignoreCase*/ true)) : + combinePaths(options.outDir, getBaseFileName(configFileExtensionLess)) : + configFileExtensionLess; + } + return buildInfoExtensionLess + Extension.TsBuildInfo; +} +/*@internal*/ +export function getOutputPathsForBundle(options: CompilerOptions, forceDtsPaths: boolean): EmitFileNames { + const outPath = options.outFile || options.out!; + const jsFilePath = options.emitDeclarationOnly ? undefined : outPath; + const sourceMapFilePath = jsFilePath && getSourceMapFilePath(jsFilePath, options); + const declarationFilePath = (forceDtsPaths || getEmitDeclarations(options)) ? removeFileExtension(outPath) + Extension.Dts : undefined; + const declarationMapPath = declarationFilePath && getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined; + const buildInfoPath = getTsBuildInfoEmitOutputFilePath(options); + return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath }; +} +/*@internal*/ +export function getOutputPathsFor(sourceFile: SourceFile | Bundle, host: EmitHost, forceDtsPaths: boolean): EmitFileNames { + const options = host.getCompilerOptions(); + if (sourceFile.kind === SyntaxKind.Bundle) { + return getOutputPathsForBundle(options, forceDtsPaths); + } + else { + const ownOutputFilePath = getOwnEmitOutputFilePath(sourceFile.fileName, host, getOutputExtension(sourceFile, options)); + // If json file emits to the same location skip writing it, if emitDeclarationOnly skip writing it + const isJsonEmittedToSameLocation = isJsonSourceFile(sourceFile) && + comparePaths(sourceFile.fileName, ownOutputFilePath, host.getCurrentDirectory(), !host.useCaseSensitiveFileNames()) === Comparison.EqualTo; + const jsFilePath = options.emitDeclarationOnly || isJsonEmittedToSameLocation ? undefined : ownOutputFilePath; + const sourceMapFilePath = !jsFilePath || isJsonSourceFile(sourceFile) ? undefined : getSourceMapFilePath(jsFilePath, options); + const declarationFilePath = (forceDtsPaths || getEmitDeclarations(options)) ? getDeclarationEmitOutputFilePath(sourceFile.fileName, host) : undefined; const declarationMapPath = declarationFilePath && getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined; - const buildInfoPath = getTsBuildInfoEmitOutputFilePath(options); - return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath }; + return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath: undefined }; } - - /*@internal*/ - export function getOutputPathsFor(sourceFile: SourceFile | Bundle, host: EmitHost, forceDtsPaths: boolean): EmitFileNames { - const options = host.getCompilerOptions(); - if (sourceFile.kind === SyntaxKind.Bundle) { - return getOutputPathsForBundle(options, forceDtsPaths); - } - else { - const ownOutputFilePath = getOwnEmitOutputFilePath(sourceFile.fileName, host, getOutputExtension(sourceFile, options)); - // If json file emits to the same location skip writing it, if emitDeclarationOnly skip writing it - const isJsonEmittedToSameLocation = isJsonSourceFile(sourceFile) && - comparePaths(sourceFile.fileName, ownOutputFilePath, host.getCurrentDirectory(), !host.useCaseSensitiveFileNames()) === Comparison.EqualTo; - const jsFilePath = options.emitDeclarationOnly || isJsonEmittedToSameLocation ? undefined : ownOutputFilePath; - const sourceMapFilePath = !jsFilePath || isJsonSourceFile(sourceFile) ? undefined : getSourceMapFilePath(jsFilePath, options); - const declarationFilePath = (forceDtsPaths || getEmitDeclarations(options)) ? getDeclarationEmitOutputFilePath(sourceFile.fileName, host) : undefined; - const declarationMapPath = declarationFilePath && getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined; - return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath: undefined }; - } - } - - function getSourceMapFilePath(jsFilePath: string, options: CompilerOptions) { - return (options.sourceMap && !options.inlineSourceMap) ? jsFilePath + ".map" : undefined; - } - - // JavaScript files are always LanguageVariant.JSX, as JSX syntax is allowed in .js files also. - // So for JavaScript files, '.jsx' is only emitted if the input was '.jsx', and JsxEmit.Preserve. - // For TypeScript, the only time to emit with a '.jsx' extension, is on JSX input, and JsxEmit.Preserve - /* @internal */ - export function getOutputExtension(sourceFile: SourceFile, options: CompilerOptions): Extension { - if (isJsonSourceFile(sourceFile)) { - return Extension.Json; - } - - if (options.jsx === JsxEmit.Preserve) { - if (isSourceFileJS(sourceFile)) { - if (fileExtensionIs(sourceFile.fileName, Extension.Jsx)) { - return Extension.Jsx; - } - } - else if (sourceFile.languageVariant === LanguageVariant.JSX) { - // TypeScript source file preserving JSX syntax +} +function getSourceMapFilePath(jsFilePath: string, options: CompilerOptions) { + return (options.sourceMap && !options.inlineSourceMap) ? jsFilePath + ".map" : undefined; +} +// JavaScript files are always LanguageVariant.JSX, as JSX syntax is allowed in .js files also. +// So for JavaScript files, '.jsx' is only emitted if the input was '.jsx', and JsxEmit.Preserve. +// For TypeScript, the only time to emit with a '.jsx' extension, is on JSX input, and JsxEmit.Preserve +/* @internal */ +export function getOutputExtension(sourceFile: SourceFile, options: CompilerOptions): Extension { + if (isJsonSourceFile(sourceFile)) { + return Extension.Json; + } + if (options.jsx === JsxEmit.Preserve) { + if (isSourceFileJS(sourceFile)) { + if (fileExtensionIs(sourceFile.fileName, Extension.Jsx)) { return Extension.Jsx; } } - return Extension.Js; - } - - function rootDirOfOptions(configFile: ParsedCommandLine) { - return configFile.options.rootDir || getDirectoryPath(Debug.assertDefined(configFile.options.configFilePath)); - } - - function getOutputPathWithoutChangingExt(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, outputDir: string | undefined) { - return outputDir ? - resolvePath( - outputDir, - getRelativePathFromDirectory(rootDirOfOptions(configFile), inputFileName, ignoreCase) - ) : - inputFileName; - } - - /* @internal */ - export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean) { - Debug.assert(!fileExtensionIs(inputFileName, Extension.Dts)); - return changeExtension( - getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.declarationDir || configFile.options.outDir), - Extension.Dts - ); - } - - function getOutputJSFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean) { - if (configFile.options.emitDeclarationOnly) return undefined; - const isJsonFile = fileExtensionIs(inputFileName, Extension.Json); - const outputFileName = changeExtension( - getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.outDir), - isJsonFile ? - Extension.Json : - fileExtensionIs(inputFileName, Extension.Tsx) && configFile.options.jsx === JsxEmit.Preserve ? - Extension.Jsx : - Extension.Js - ); - return !isJsonFile || comparePaths(inputFileName, outputFileName, Debug.assertDefined(configFile.options.configFilePath), ignoreCase) !== Comparison.EqualTo ? - outputFileName : - undefined; - } - - function createAddOutput() { - let outputs: string[] | undefined; - return { addOutput, getOutputs }; - function addOutput(path: string | undefined) { - if (path) { - (outputs || (outputs = [])).push(path); - } + else if (sourceFile.languageVariant === LanguageVariant.JSX) { + // TypeScript source file preserving JSX syntax + return Extension.Jsx; } - function getOutputs(): readonly string[] { - return outputs || emptyArray; + } + return Extension.Js; +} +function rootDirOfOptions(configFile: ParsedCommandLine) { + return configFile.options.rootDir || getDirectoryPath(Debug.assertDefined(configFile.options.configFilePath)); +} +function getOutputPathWithoutChangingExt(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, outputDir: string | undefined) { + return outputDir ? + resolvePath(outputDir, getRelativePathFromDirectory(rootDirOfOptions(configFile), inputFileName, ignoreCase)) : + inputFileName; +} +/* @internal */ +export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean) { + Debug.assert(!fileExtensionIs(inputFileName, Extension.Dts)); + return changeExtension(getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.declarationDir || configFile.options.outDir), Extension.Dts); +} +function getOutputJSFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean) { + if (configFile.options.emitDeclarationOnly) + return undefined; + const isJsonFile = fileExtensionIs(inputFileName, Extension.Json); + const outputFileName = changeExtension(getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.outDir), isJsonFile ? + Extension.Json : + fileExtensionIs(inputFileName, Extension.Tsx) && configFile.options.jsx === JsxEmit.Preserve ? + Extension.Jsx : + Extension.Js); + return !isJsonFile || comparePaths(inputFileName, outputFileName, Debug.assertDefined(configFile.options.configFilePath), ignoreCase) !== Comparison.EqualTo ? + outputFileName : + undefined; +} +function createAddOutput() { + let outputs: string[] | undefined; + return { addOutput, getOutputs }; + function addOutput(path: string | undefined) { + if (path) { + (outputs || (outputs = [])).push(path); } } - - function getSingleOutputFileNames(configFile: ParsedCommandLine, addOutput: ReturnType["addOutput"]) { - const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(configFile.options, /*forceDtsPaths*/ false); - addOutput(jsFilePath); - addOutput(sourceMapFilePath); - addOutput(declarationFilePath); - addOutput(declarationMapPath); - addOutput(buildInfoPath); + function getOutputs(): readonly string[] { + return outputs || emptyArray; } - - function getOwnOutputFileNames(configFile: ParsedCommandLine, inputFileName: string, ignoreCase: boolean, addOutput: ReturnType["addOutput"]) { - if (fileExtensionIs(inputFileName, Extension.Dts)) return; - const js = getOutputJSFileName(inputFileName, configFile, ignoreCase); - addOutput(js); - if (fileExtensionIs(inputFileName, Extension.Json)) return; - if (js && configFile.options.sourceMap) { - addOutput(`${js}.map`); - } - if (getEmitDeclarations(configFile.options)) { - const dts = getOutputDeclarationFileName(inputFileName, configFile, ignoreCase); - addOutput(dts); - if (configFile.options.declarationMap) { - addOutput(`${dts}.map`); - } - } +} +function getSingleOutputFileNames(configFile: ParsedCommandLine, addOutput: ReturnType["addOutput"]) { + const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(configFile.options, /*forceDtsPaths*/ false); + addOutput(jsFilePath); + addOutput(sourceMapFilePath); + addOutput(declarationFilePath); + addOutput(declarationMapPath); + addOutput(buildInfoPath); +} +function getOwnOutputFileNames(configFile: ParsedCommandLine, inputFileName: string, ignoreCase: boolean, addOutput: ReturnType["addOutput"]) { + if (fileExtensionIs(inputFileName, Extension.Dts)) + return; + const js = getOutputJSFileName(inputFileName, configFile, ignoreCase); + addOutput(js); + if (fileExtensionIs(inputFileName, Extension.Json)) + return; + if (js && configFile.options.sourceMap) { + addOutput(`${js}.map`); } - - /*@internal*/ - export function getAllProjectOutputs(configFile: ParsedCommandLine, ignoreCase: boolean): readonly string[] { - const { addOutput, getOutputs } = createAddOutput(); - if (configFile.options.outFile || configFile.options.out) { - getSingleOutputFileNames(configFile, addOutput); - } - else { - for (const inputFileName of configFile.fileNames) { - getOwnOutputFileNames(configFile, inputFileName, ignoreCase, addOutput); - } - addOutput(getTsBuildInfoEmitOutputFilePath(configFile.options)); + if (getEmitDeclarations(configFile.options)) { + const dts = getOutputDeclarationFileName(inputFileName, configFile, ignoreCase); + addOutput(dts); + if (configFile.options.declarationMap) { + addOutput(`${dts}.map`); } - return getOutputs(); } - - export function getOutputFileNames(commandLine: ParsedCommandLine, inputFileName: string, ignoreCase: boolean): readonly string[] { - inputFileName = normalizePath(inputFileName); - Debug.assert(contains(commandLine.fileNames, inputFileName), `Expected fileName to be present in command line`); - const { addOutput, getOutputs } = createAddOutput(); - if (commandLine.options.outFile || commandLine.options.out) { - getSingleOutputFileNames(commandLine, addOutput); - } - else { - getOwnOutputFileNames(commandLine, inputFileName, ignoreCase, addOutput); - } - return getOutputs(); +} +/*@internal*/ +export function getAllProjectOutputs(configFile: ParsedCommandLine, ignoreCase: boolean): readonly string[] { + const { addOutput, getOutputs } = createAddOutput(); + if (configFile.options.outFile || configFile.options.out) { + getSingleOutputFileNames(configFile, addOutput); } - - /*@internal*/ - export function getFirstProjectOutput(configFile: ParsedCommandLine, ignoreCase: boolean): string { - if (configFile.options.outFile || configFile.options.out) { - const { jsFilePath } = getOutputPathsForBundle(configFile.options, /*forceDtsPaths*/ false); - return Debug.assertDefined(jsFilePath, `project ${configFile.options.configFilePath} expected to have at least one output`); - } - + else { for (const inputFileName of configFile.fileNames) { - if (fileExtensionIs(inputFileName, Extension.Dts)) continue; - const jsFilePath = getOutputJSFileName(inputFileName, configFile, ignoreCase); - if (jsFilePath) return jsFilePath; - if (fileExtensionIs(inputFileName, Extension.Json)) continue; - if (getEmitDeclarations(configFile.options)) { - return getOutputDeclarationFileName(inputFileName, configFile, ignoreCase); - } + getOwnOutputFileNames(configFile, inputFileName, ignoreCase, addOutput); } - const buildInfoPath = getTsBuildInfoEmitOutputFilePath(configFile.options); - if (buildInfoPath) return buildInfoPath; - return Debug.fail(`project ${configFile.options.configFilePath} expected to have at least one output`); - } - - /*@internal*/ - // targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature - export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile | undefined, { scriptTransformers, declarationTransformers }: EmitTransformers, emitOnlyDtsFiles?: boolean, onlyBuildInfo?: boolean, forceDtsEmit?: boolean): EmitResult { - const compilerOptions = host.getCompilerOptions(); - const sourceMapDataList: SourceMapEmitResult[] | undefined = (compilerOptions.sourceMap || compilerOptions.inlineSourceMap || getAreDeclarationMapsEnabled(compilerOptions)) ? [] : undefined; - const emittedFilesList: string[] | undefined = compilerOptions.listEmittedFiles ? [] : undefined; - const emitterDiagnostics = createDiagnosticCollection(); - const newLine = getNewLineCharacter(compilerOptions, () => host.getNewLine()); - const writer = createTextWriter(newLine); - const { enter, exit } = performance.createTimer("printTime", "beforePrint", "afterPrint"); - let bundleBuildInfo: BundleBuildInfo | undefined; - let emitSkipped = false; - let exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined; - - // Emit each output file - enter(); - forEachEmittedFile( - host, - emitSourceFileOrBundle, - getSourceFilesToEmit(host, targetSourceFile, forceDtsEmit), - forceDtsEmit, - onlyBuildInfo, - !targetSourceFile - ); - exit(); - - - return { - emitSkipped, - diagnostics: emitterDiagnostics.getDiagnostics(), - emittedFiles: emittedFilesList, - sourceMaps: sourceMapDataList, - exportedModulesFromDeclarationEmit - }; - - function emitSourceFileOrBundle({ jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath }: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle | undefined) { - let buildInfoDirectory: string | undefined; - if (buildInfoPath && sourceFileOrBundle && isBundle(sourceFileOrBundle)) { - buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory())); - bundleBuildInfo = { - commonSourceDirectory: relativeToBuildInfo(host.getCommonSourceDirectory()), - sourceFiles: sourceFileOrBundle.sourceFiles.map(file => relativeToBuildInfo(getNormalizedAbsolutePath(file.fileName, host.getCurrentDirectory()))) - }; - } - emitJsFileOrBundle(sourceFileOrBundle, jsFilePath, sourceMapFilePath, relativeToBuildInfo); - emitDeclarationFileOrBundle(sourceFileOrBundle, declarationFilePath, declarationMapPath, relativeToBuildInfo); - emitBuildInfo(bundleBuildInfo, buildInfoPath); - - if (!emitSkipped && emittedFilesList) { - if (!emitOnlyDtsFiles) { - if (jsFilePath) { - emittedFilesList.push(jsFilePath); - } - if (sourceMapFilePath) { - emittedFilesList.push(sourceMapFilePath); - } - if (buildInfoPath) { - emittedFilesList.push(buildInfoPath); - } - } - if (declarationFilePath) { - emittedFilesList.push(declarationFilePath); - } - if (declarationMapPath) { - emittedFilesList.push(declarationMapPath); - } - } - - function relativeToBuildInfo(path: string) { - return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory!, path, host.getCanonicalFileName)); - } + addOutput(getTsBuildInfoEmitOutputFilePath(configFile.options)); + } + return getOutputs(); +} +export function getOutputFileNames(commandLine: ParsedCommandLine, inputFileName: string, ignoreCase: boolean): readonly string[] { + inputFileName = normalizePath(inputFileName); + Debug.assert(contains(commandLine.fileNames, inputFileName), `Expected fileName to be present in command line`); + const { addOutput, getOutputs } = createAddOutput(); + if (commandLine.options.outFile || commandLine.options.out) { + getSingleOutputFileNames(commandLine, addOutput); + } + else { + getOwnOutputFileNames(commandLine, inputFileName, ignoreCase, addOutput); + } + return getOutputs(); +} +/*@internal*/ +export function getFirstProjectOutput(configFile: ParsedCommandLine, ignoreCase: boolean): string { + if (configFile.options.outFile || configFile.options.out) { + const { jsFilePath } = getOutputPathsForBundle(configFile.options, /*forceDtsPaths*/ false); + return Debug.assertDefined(jsFilePath, `project ${configFile.options.configFilePath} expected to have at least one output`); + } + for (const inputFileName of configFile.fileNames) { + if (fileExtensionIs(inputFileName, Extension.Dts)) + continue; + const jsFilePath = getOutputJSFileName(inputFileName, configFile, ignoreCase); + if (jsFilePath) + return jsFilePath; + if (fileExtensionIs(inputFileName, Extension.Json)) + continue; + if (getEmitDeclarations(configFile.options)) { + return getOutputDeclarationFileName(inputFileName, configFile, ignoreCase); } - - function emitBuildInfo(bundle: BundleBuildInfo | undefined, buildInfoPath: string | undefined) { - // Write build information if applicable - if (!buildInfoPath || targetSourceFile || emitSkipped) return; - const program = host.getProgramBuildInfo(); - if (host.isEmitBlocked(buildInfoPath) || compilerOptions.noEmit) { - emitSkipped = true; - return; - } - const version = ts.version; // Extracted into a const so the form is stable between namespace and module - writeFile(host, emitterDiagnostics, buildInfoPath, getBuildInfoText({ bundle, program, version }), /*writeByteOrderMark*/ false); - } - - function emitJsFileOrBundle( - sourceFileOrBundle: SourceFile | Bundle | undefined, - jsFilePath: string | undefined, - sourceMapFilePath: string | undefined, - relativeToBuildInfo: (path: string) => string) { - if (!sourceFileOrBundle || emitOnlyDtsFiles || !jsFilePath) { - return; - } - - // Make sure not to write js file and source map file if any of them cannot be written - if ((jsFilePath && host.isEmitBlocked(jsFilePath)) || compilerOptions.noEmit) { - emitSkipped = true; - return; - } - // Transform the source files - const transform = transformNodes(resolver, host, compilerOptions, [sourceFileOrBundle], scriptTransformers, /*allowDtsFiles*/ false); - - const printerOptions: PrinterOptions = { - removeComments: compilerOptions.removeComments, - newLine: compilerOptions.newLine, - noEmitHelpers: compilerOptions.noEmitHelpers, - module: compilerOptions.module, - target: compilerOptions.target, - sourceMap: compilerOptions.sourceMap, - inlineSourceMap: compilerOptions.inlineSourceMap, - inlineSources: compilerOptions.inlineSources, - extendedDiagnostics: compilerOptions.extendedDiagnostics, - writeBundleFileInfo: !!bundleBuildInfo, - relativeToBuildInfo - }; - - // Create a printer to print the nodes - const printer = createPrinter(printerOptions, { - // resolver hooks - hasGlobalName: resolver.hasGlobalName, - - // transform hooks - onEmitNode: transform.emitNodeWithNotification, - substituteNode: transform.substituteNode, - }); - - Debug.assert(transform.transformed.length === 1, "Should only see one output from the transform"); - printSourceFileOrBundle(jsFilePath, sourceMapFilePath, transform.transformed[0], printer, compilerOptions); - - // Clean up emit nodes on parse tree - transform.dispose(); - if (bundleBuildInfo) bundleBuildInfo.js = printer.bundleFileInfo; - } - - function emitDeclarationFileOrBundle( - sourceFileOrBundle: SourceFile | Bundle | undefined, - declarationFilePath: string | undefined, - declarationMapPath: string | undefined, - relativeToBuildInfo: (path: string) => string) { - if (!sourceFileOrBundle) return; - if (!declarationFilePath) { - if (emitOnlyDtsFiles || compilerOptions.emitDeclarationOnly) emitSkipped = true; - return; - } - const sourceFiles = isSourceFile(sourceFileOrBundle) ? [sourceFileOrBundle] : sourceFileOrBundle.sourceFiles; - // Setup and perform the transformation to retrieve declarations from the input files - const inputListOrBundle = (compilerOptions.outFile || compilerOptions.out) ? [createBundle(sourceFiles, !isSourceFile(sourceFileOrBundle) ? sourceFileOrBundle.prepends : undefined)] : sourceFiles; - if (emitOnlyDtsFiles && !getEmitDeclarations(compilerOptions)) { - // Checker wont collect the linked aliases since thats only done when declaration is enabled. - // Do that here when emitting only dts files - sourceFiles.forEach(collectLinkedAliases); - } - const declarationTransform = transformNodes(resolver, host, compilerOptions, inputListOrBundle, declarationTransformers, /*allowDtsFiles*/ false); - if (length(declarationTransform.diagnostics)) { - for (const diagnostic of declarationTransform.diagnostics!) { - emitterDiagnostics.add(diagnostic); - } - } - - const printerOptions: PrinterOptions = { - removeComments: compilerOptions.removeComments, - newLine: compilerOptions.newLine, - noEmitHelpers: true, - module: compilerOptions.module, - target: compilerOptions.target, - sourceMap: compilerOptions.sourceMap, - inlineSourceMap: compilerOptions.inlineSourceMap, - extendedDiagnostics: compilerOptions.extendedDiagnostics, - onlyPrintJsDocStyle: true, - writeBundleFileInfo: !!bundleBuildInfo, - recordInternalSection: !!bundleBuildInfo, - relativeToBuildInfo + } + const buildInfoPath = getTsBuildInfoEmitOutputFilePath(configFile.options); + if (buildInfoPath) + return buildInfoPath; + return Debug.fail(`project ${configFile.options.configFilePath} expected to have at least one output`); +} +/*@internal*/ +// targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature +export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile | undefined, { scriptTransformers, declarationTransformers }: EmitTransformers, emitOnlyDtsFiles?: boolean, onlyBuildInfo?: boolean, forceDtsEmit?: boolean): EmitResult { + const compilerOptions = host.getCompilerOptions(); + const sourceMapDataList: SourceMapEmitResult[] | undefined = (compilerOptions.sourceMap || compilerOptions.inlineSourceMap || getAreDeclarationMapsEnabled(compilerOptions)) ? [] : undefined; + const emittedFilesList: string[] | undefined = compilerOptions.listEmittedFiles ? [] : undefined; + const emitterDiagnostics = createDiagnosticCollection(); + const newLine = getNewLineCharacter(compilerOptions, () => host.getNewLine()); + const writer = createTextWriter(newLine); + const { enter, exit } = createTimer("printTime", "beforePrint", "afterPrint"); + let bundleBuildInfo: BundleBuildInfo | undefined; + let emitSkipped = false; + let exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined; + // Emit each output file + enter(); + forEachEmittedFile(host, emitSourceFileOrBundle, getSourceFilesToEmit(host, targetSourceFile, forceDtsEmit), forceDtsEmit, onlyBuildInfo, !targetSourceFile); + exit(); + return { + emitSkipped, + diagnostics: emitterDiagnostics.getDiagnostics(), + emittedFiles: emittedFilesList, + sourceMaps: sourceMapDataList, + exportedModulesFromDeclarationEmit + }; + function emitSourceFileOrBundle({ jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath }: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle | undefined) { + let buildInfoDirectory: string | undefined; + if (buildInfoPath && sourceFileOrBundle && isBundle(sourceFileOrBundle)) { + buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory())); + bundleBuildInfo = { + commonSourceDirectory: relativeToBuildInfo(host.getCommonSourceDirectory()), + sourceFiles: sourceFileOrBundle.sourceFiles.map(file => relativeToBuildInfo(getNormalizedAbsolutePath(file.fileName, host.getCurrentDirectory()))) }; - - const declarationPrinter = createPrinter(printerOptions, { - // resolver hooks - hasGlobalName: resolver.hasGlobalName, - - // transform hooks - onEmitNode: declarationTransform.emitNodeWithNotification, - substituteNode: declarationTransform.substituteNode, - }); - const declBlocked = (!!declarationTransform.diagnostics && !!declarationTransform.diagnostics.length) || !!host.isEmitBlocked(declarationFilePath) || !!compilerOptions.noEmit; - emitSkipped = emitSkipped || declBlocked; - if (!declBlocked || forceDtsEmit) { - Debug.assert(declarationTransform.transformed.length === 1, "Should only see one output from the decl transform"); - printSourceFileOrBundle( - declarationFilePath, - declarationMapPath, - declarationTransform.transformed[0], - declarationPrinter, - { - sourceMap: compilerOptions.declarationMap, - sourceRoot: compilerOptions.sourceRoot, - mapRoot: compilerOptions.mapRoot, - extendedDiagnostics: compilerOptions.extendedDiagnostics, - // Explicitly do not passthru either `inline` option - } - ); - if (forceDtsEmit && declarationTransform.transformed[0].kind === SyntaxKind.SourceFile) { - const sourceFile = declarationTransform.transformed[0]; - exportedModulesFromDeclarationEmit = sourceFile.exportedModulesFromDeclarationEmit; - } - } - declarationTransform.dispose(); - if (bundleBuildInfo) bundleBuildInfo.dts = declarationPrinter.bundleFileInfo; - } - - function collectLinkedAliases(node: Node) { - if (isExportAssignment(node)) { - if (node.expression.kind === SyntaxKind.Identifier) { - resolver.collectLinkedAliases(node.expression as Identifier, /*setVisibility*/ true); - } - return; - } - else if (isExportSpecifier(node)) { - resolver.collectLinkedAliases(node.propertyName || node.name, /*setVisibility*/ true); - return; - } - forEachChild(node, collectLinkedAliases); - } - - function printSourceFileOrBundle(jsFilePath: string, sourceMapFilePath: string | undefined, sourceFileOrBundle: SourceFile | Bundle, printer: Printer, mapOptions: SourceMapOptions) { - const bundle = sourceFileOrBundle.kind === SyntaxKind.Bundle ? sourceFileOrBundle : undefined; - const sourceFile = sourceFileOrBundle.kind === SyntaxKind.SourceFile ? sourceFileOrBundle : undefined; - const sourceFiles = bundle ? bundle.sourceFiles : [sourceFile!]; - - let sourceMapGenerator: SourceMapGenerator | undefined; - if (shouldEmitSourceMaps(mapOptions, sourceFileOrBundle)) { - sourceMapGenerator = createSourceMapGenerator( - host, - getBaseFileName(normalizeSlashes(jsFilePath)), - getSourceRoot(mapOptions), - getSourceMapDirectory(mapOptions, jsFilePath, sourceFile), - mapOptions); - } - - if (bundle) { - printer.writeBundle(bundle, writer, sourceMapGenerator); - } - else { - printer.writeFile(sourceFile!, writer, sourceMapGenerator); - } - - if (sourceMapGenerator) { - if (sourceMapDataList) { - sourceMapDataList.push({ - inputSourceFileNames: sourceMapGenerator.getSources(), - sourceMap: sourceMapGenerator.toJSON() - }); - } - - const sourceMappingURL = getSourceMappingURL( - mapOptions, - sourceMapGenerator, - jsFilePath, - sourceMapFilePath, - sourceFile); - - if (sourceMappingURL) { - if (!writer.isAtStartOfLine()) writer.rawWrite(newLine); - writer.writeComment(`//# ${"sourceMappingURL"}=${sourceMappingURL}`); // Tools can sometimes see this line as a source mapping url comment + } + emitJsFileOrBundle(sourceFileOrBundle, jsFilePath, sourceMapFilePath, relativeToBuildInfo); + emitDeclarationFileOrBundle(sourceFileOrBundle, declarationFilePath, declarationMapPath, relativeToBuildInfo); + emitBuildInfo(bundleBuildInfo, buildInfoPath); + if (!emitSkipped && emittedFilesList) { + if (!emitOnlyDtsFiles) { + if (jsFilePath) { + emittedFilesList.push(jsFilePath); } - - // Write the source map if (sourceMapFilePath) { - const sourceMap = sourceMapGenerator.toString(); - writeFile(host, emitterDiagnostics, sourceMapFilePath, sourceMap, /*writeByteOrderMark*/ false, sourceFiles); - } - } - else { - writer.writeLine(); - } - - // Write the output file - writeFile(host, emitterDiagnostics, jsFilePath, writer.getText(), !!compilerOptions.emitBOM, sourceFiles); - - // Reset state - writer.clear(); - } - - interface SourceMapOptions { - sourceMap?: boolean; - inlineSourceMap?: boolean; - inlineSources?: boolean; - sourceRoot?: string; - mapRoot?: string; - extendedDiagnostics?: boolean; - } - - function shouldEmitSourceMaps(mapOptions: SourceMapOptions, sourceFileOrBundle: SourceFile | Bundle) { - return (mapOptions.sourceMap || mapOptions.inlineSourceMap) - && (sourceFileOrBundle.kind !== SyntaxKind.SourceFile || !fileExtensionIs(sourceFileOrBundle.fileName, Extension.Json)); - } - - function getSourceRoot(mapOptions: SourceMapOptions) { - // Normalize source root and make sure it has trailing "/" so that it can be used to combine paths with the - // relative paths of the sources list in the sourcemap - const sourceRoot = normalizeSlashes(mapOptions.sourceRoot || ""); - return sourceRoot ? ensureTrailingDirectorySeparator(sourceRoot) : sourceRoot; - } - - function getSourceMapDirectory(mapOptions: SourceMapOptions, filePath: string, sourceFile: SourceFile | undefined) { - if (mapOptions.sourceRoot) return host.getCommonSourceDirectory(); - if (mapOptions.mapRoot) { - let sourceMapDir = normalizeSlashes(mapOptions.mapRoot); - if (sourceFile) { - // For modules or multiple emit files the mapRoot will have directory structure like the sources - // So if src\a.ts and src\lib\b.ts are compiled together user would be moving the maps into mapRoot\a.js.map and mapRoot\lib\b.js.map - sourceMapDir = getDirectoryPath(getSourceFilePathInNewDir(sourceFile.fileName, host, sourceMapDir)); + emittedFilesList.push(sourceMapFilePath); } - if (getRootLength(sourceMapDir) === 0) { - // The relative paths are relative to the common directory - sourceMapDir = combinePaths(host.getCommonSourceDirectory(), sourceMapDir); + if (buildInfoPath) { + emittedFilesList.push(buildInfoPath); } - return sourceMapDir; } - return getDirectoryPath(normalizePath(filePath)); - } - - function getSourceMappingURL(mapOptions: SourceMapOptions, sourceMapGenerator: SourceMapGenerator, filePath: string, sourceMapFilePath: string | undefined, sourceFile: SourceFile | undefined) { - if (mapOptions.inlineSourceMap) { - // Encode the sourceMap into the sourceMap url - const sourceMapText = sourceMapGenerator.toString(); - const base64SourceMapText = base64encode(sys, sourceMapText); - return `data:application/json;base64,${base64SourceMapText}`; + if (declarationFilePath) { + emittedFilesList.push(declarationFilePath); } - - const sourceMapFile = getBaseFileName(normalizeSlashes(Debug.assertDefined(sourceMapFilePath))); - if (mapOptions.mapRoot) { - let sourceMapDir = normalizeSlashes(mapOptions.mapRoot); - if (sourceFile) { - // For modules or multiple emit files the mapRoot will have directory structure like the sources - // So if src\a.ts and src\lib\b.ts are compiled together user would be moving the maps into mapRoot\a.js.map and mapRoot\lib\b.js.map - sourceMapDir = getDirectoryPath(getSourceFilePathInNewDir(sourceFile.fileName, host, sourceMapDir)); - } - if (getRootLength(sourceMapDir) === 0) { - // The relative paths are relative to the common directory - sourceMapDir = combinePaths(host.getCommonSourceDirectory(), sourceMapDir); - return getRelativePathToDirectoryOrUrl( - getDirectoryPath(normalizePath(filePath)), // get the relative sourceMapDir path based on jsFilePath - combinePaths(sourceMapDir, sourceMapFile), // this is where user expects to see sourceMap - host.getCurrentDirectory(), - host.getCanonicalFileName, - /*isAbsolutePathAnUrl*/ true); - } - else { - return combinePaths(sourceMapDir, sourceMapFile); - } + if (declarationMapPath) { + emittedFilesList.push(declarationMapPath); } - return sourceMapFile; - } - } - - /*@internal*/ - export function getBuildInfoText(buildInfo: BuildInfo) { - return JSON.stringify(buildInfo, undefined, 2); - } - - /*@internal*/ - export function getBuildInfo(buildInfoText: string) { - return JSON.parse(buildInfoText) as BuildInfo; - } - - /*@internal*/ - export const notImplementedResolver: EmitResolver = { - hasGlobalName: notImplemented, - getReferencedExportContainer: notImplemented, - getReferencedImportDeclaration: notImplemented, - getReferencedDeclarationWithCollidingName: notImplemented, - isDeclarationWithCollidingName: notImplemented, - isValueAliasDeclaration: notImplemented, - isReferencedAliasDeclaration: notImplemented, - isTopLevelValueImportEqualsWithEntityName: notImplemented, - getNodeCheckFlags: notImplemented, - isDeclarationVisible: notImplemented, - isLateBound: (_node): _node is LateBoundDeclaration => false, - collectLinkedAliases: notImplemented, - isImplementationOfOverload: notImplemented, - isRequiredInitializedParameter: notImplemented, - isOptionalUninitializedParameterProperty: notImplemented, - isExpandoFunctionDeclaration: notImplemented, - getPropertiesOfContainerFunction: notImplemented, - createTypeOfDeclaration: notImplemented, - createReturnTypeOfSignatureDeclaration: notImplemented, - createTypeOfExpression: notImplemented, - createLiteralConstValue: notImplemented, - isSymbolAccessible: notImplemented, - isEntityNameVisible: notImplemented, - // Returns the constant value this property access resolves to: notImplemented, or 'undefined' for a non-constant - getConstantValue: notImplemented, - getReferencedValueDeclaration: notImplemented, - getTypeReferenceSerializationKind: notImplemented, - isOptionalParameter: notImplemented, - moduleExportsSomeValue: notImplemented, - isArgumentsLocalBinding: notImplemented, - getExternalModuleFileFromDeclaration: notImplemented, - getTypeReferenceDirectivesForEntityName: notImplemented, - getTypeReferenceDirectivesForSymbol: notImplemented, - isLiteralConstDeclaration: notImplemented, - getJsxFactoryEntity: notImplemented, - getAllAccessorDeclarations: notImplemented, - getSymbolOfExternalModuleSpecifier: notImplemented, - isBindingCapturedByNode: notImplemented, - getDeclarationStatementsForSourceFile: notImplemented, - }; - - /*@internal*/ - /** File that isnt present resulting in error or output files */ - export type EmitUsingBuildInfoResult = string | readonly OutputFile[]; - - /*@internal*/ - export interface EmitUsingBuildInfoHost extends ModuleResolutionHost { - getCurrentDirectory(): string; - getCanonicalFileName(fileName: string): string; - useCaseSensitiveFileNames(): boolean; - getNewLine(): string; - } - - function createSourceFilesFromBundleBuildInfo(bundle: BundleBuildInfo, buildInfoDirectory: string, host: EmitUsingBuildInfoHost): readonly SourceFile[] { - const sourceFiles = bundle.sourceFiles.map(fileName => { - const sourceFile = createNode(SyntaxKind.SourceFile, 0, 0) as SourceFile; - sourceFile.fileName = getRelativePathFromDirectory( - host.getCurrentDirectory(), - getNormalizedAbsolutePath(fileName, buildInfoDirectory), - !host.useCaseSensitiveFileNames() - ); - sourceFile.text = ""; - sourceFile.statements = createNodeArray(); - return sourceFile; - }); - const jsBundle = Debug.assertDefined(bundle.js); - forEach(jsBundle.sources && jsBundle.sources.prologues, prologueInfo => { - const sourceFile = sourceFiles[prologueInfo.file]; - sourceFile.text = prologueInfo.text; - sourceFile.end = prologueInfo.text.length; - sourceFile.statements = createNodeArray(prologueInfo.directives.map(directive => { - const statement = createNode(SyntaxKind.ExpressionStatement, directive.pos, directive.end) as PrologueDirective; - statement.expression = createNode(SyntaxKind.StringLiteral, directive.expression.pos, directive.expression.end) as StringLiteral; - statement.expression.text = directive.expression.text; - return statement; - })); - }); - return sourceFiles; - } - - /*@internal*/ - export function emitUsingBuildInfo( - config: ParsedCommandLine, - host: EmitUsingBuildInfoHost, - getCommandLine: (ref: ProjectReference) => ParsedCommandLine | undefined, - customTransformers?: CustomTransformers - ): EmitUsingBuildInfoResult { - const { buildInfoPath, jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath } = getOutputPathsForBundle(config.options, /*forceDtsPaths*/ false); - const buildInfoText = host.readFile(Debug.assertDefined(buildInfoPath)); - if (!buildInfoText) return buildInfoPath!; - const jsFileText = host.readFile(Debug.assertDefined(jsFilePath)); - if (!jsFileText) return jsFilePath!; - const sourceMapText = sourceMapFilePath && host.readFile(sourceMapFilePath); - // error if no source map or for now if inline sourcemap - if ((sourceMapFilePath && !sourceMapText) || config.options.inlineSourceMap) return sourceMapFilePath || "inline sourcemap decoding"; - // read declaration text - const declarationText = declarationFilePath && host.readFile(declarationFilePath); - if (declarationFilePath && !declarationText) return declarationFilePath; - const declarationMapText = declarationMapPath && host.readFile(declarationMapPath); - // error if no source map or for now if inline sourcemap - if ((declarationMapPath && !declarationMapText) || config.options.inlineSourceMap) return declarationMapPath || "inline sourcemap decoding"; - - const buildInfo = getBuildInfo(buildInfoText); - if (!buildInfo.bundle || !buildInfo.bundle.js || (declarationText && !buildInfo.bundle.dts)) return buildInfoPath!; - const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath!, host.getCurrentDirectory())); - const ownPrependInput = createInputFiles( - jsFileText, - declarationText!, - sourceMapFilePath, - sourceMapText, - declarationMapPath, - declarationMapText, - jsFilePath, - declarationFilePath, - buildInfoPath, - buildInfo, - /*onlyOwnText*/ true - ); - const outputFiles: OutputFile[] = []; - const prependNodes = createPrependNodes(config.projectReferences, getCommandLine, f => host.readFile(f)); - const sourceFilesForJsEmit = createSourceFilesFromBundleBuildInfo(buildInfo.bundle, buildInfoDirectory, host); - const emitHost: EmitHost = { - getPrependNodes: memoize(() => [...prependNodes, ownPrependInput]), - getCanonicalFileName: host.getCanonicalFileName, - getCommonSourceDirectory: () => getNormalizedAbsolutePath(buildInfo.bundle!.commonSourceDirectory, buildInfoDirectory), - getCompilerOptions: () => config.options, - getCurrentDirectory: () => host.getCurrentDirectory(), - getNewLine: () => host.getNewLine(), - getSourceFile: returnUndefined, - getSourceFileByPath: returnUndefined, - getSourceFiles: () => sourceFilesForJsEmit, - getLibFileFromReference: notImplemented, - isSourceFileFromExternalLibrary: returnFalse, - getResolvedProjectReferenceToRedirect: returnUndefined, - isSourceOfProjectReferenceRedirect: returnFalse, - writeFile: (name, text, writeByteOrderMark) => { - switch (name) { - case jsFilePath: - if (jsFileText === text) return; - break; - case sourceMapFilePath: - if (sourceMapText === text) return; - break; - case buildInfoPath: - const newBuildInfo = getBuildInfo(text); - newBuildInfo.program = buildInfo.program; - // Update sourceFileInfo - const { js, dts, sourceFiles } = buildInfo.bundle!; - newBuildInfo.bundle!.js!.sources = js!.sources; - if (dts) { - newBuildInfo.bundle!.dts!.sources = dts.sources; - } - newBuildInfo.bundle!.sourceFiles = sourceFiles; - outputFiles.push({ name, text: getBuildInfoText(newBuildInfo), writeByteOrderMark }); - return; - case declarationFilePath: - if (declarationText === text) return; - break; - case declarationMapPath: - if (declarationMapText === text) return; - break; - default: - Debug.fail(`Unexpected path: ${name}`); - } - outputFiles.push({ name, text, writeByteOrderMark }); - }, - isEmitBlocked: returnFalse, - readFile: f => host.readFile(f), - fileExists: f => host.fileExists(f), - directoryExists: host.directoryExists && (f => host.directoryExists!(f)), - useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), - getProgramBuildInfo: returnUndefined, - getSourceFileFromReference: returnUndefined, - redirectTargetsMap: createMultiMap() + } + function relativeToBuildInfo(path: string) { + return ensurePathIsNonModuleName(getRelativePathFromDirectory((buildInfoDirectory!), path, host.getCanonicalFileName)); + } + } + function emitBuildInfo(bundle: BundleBuildInfo | undefined, buildInfoPath: string | undefined) { + // Write build information if applicable + if (!buildInfoPath || targetSourceFile || emitSkipped) + return; + const program = host.getProgramBuildInfo(); + if (host.isEmitBlocked(buildInfoPath) || compilerOptions.noEmit) { + emitSkipped = true; + return; + } + const version = ts.version; // Extracted into a const so the form is stable between namespace and module + writeFile(host, emitterDiagnostics, buildInfoPath, getBuildInfoText({ bundle, program, version }), /*writeByteOrderMark*/ false); + } + function emitJsFileOrBundle(sourceFileOrBundle: SourceFile | Bundle | undefined, jsFilePath: string | undefined, sourceMapFilePath: string | undefined, relativeToBuildInfo: (path: string) => string) { + if (!sourceFileOrBundle || emitOnlyDtsFiles || !jsFilePath) { + return; + } + // Make sure not to write js file and source map file if any of them cannot be written + if ((jsFilePath && host.isEmitBlocked(jsFilePath)) || compilerOptions.noEmit) { + emitSkipped = true; + return; + } + // Transform the source files + const transform = transformNodes(resolver, host, compilerOptions, [sourceFileOrBundle], scriptTransformers, /*allowDtsFiles*/ false); + const printerOptions: PrinterOptions = { + removeComments: compilerOptions.removeComments, + newLine: compilerOptions.newLine, + noEmitHelpers: compilerOptions.noEmitHelpers, + module: compilerOptions.module, + target: compilerOptions.target, + sourceMap: compilerOptions.sourceMap, + inlineSourceMap: compilerOptions.inlineSourceMap, + inlineSources: compilerOptions.inlineSources, + extendedDiagnostics: compilerOptions.extendedDiagnostics, + writeBundleFileInfo: !!bundleBuildInfo, + relativeToBuildInfo }; - emitFiles( - notImplementedResolver, - emitHost, - /*targetSourceFile*/ undefined, - getTransformers(config.options, customTransformers) - ); - return outputFiles; - } - - const enum PipelinePhase { - Notification, - Substitution, - Comments, - SourceMaps, - Emit - } - - export function createPrinter(printerOptions: PrinterOptions = {}, handlers: PrintHandlers = {}): Printer { - const { - hasGlobalName, - onEmitNode = noEmitNotification, - substituteNode = noEmitSubstitution, - onBeforeEmitNodeArray, - onAfterEmitNodeArray, - onBeforeEmitToken, - onAfterEmitToken - } = handlers; - - const extendedDiagnostics = !!printerOptions.extendedDiagnostics; - const newLine = getNewLineCharacter(printerOptions); - const moduleKind = getEmitModuleKind(printerOptions); - const bundledHelpers = createMap(); - - let currentSourceFile: SourceFile | undefined; - let nodeIdToGeneratedName: string[]; // Map of generated names for specific nodes. - let autoGeneratedIdToGeneratedName: string[]; // Map of generated names for temp and loop variables. - let generatedNames: Map; // Set of names generated by the NameGenerator. - let tempFlagsStack: TempFlags[]; // Stack of enclosing name generation scopes. - let tempFlags: TempFlags; // TempFlags for the current name generation scope. - let reservedNamesStack: Map[]; // Stack of TempFlags reserved in enclosing name generation scopes. - let reservedNames: Map; // TempFlags to reserve in nested name generation scopes. - - let writer: EmitTextWriter; - let ownWriter: EmitTextWriter; // Reusable `EmitTextWriter` for basic printing. - let write = writeBase; - let isOwnFileEmit: boolean; - const bundleFileInfo = printerOptions.writeBundleFileInfo ? { sections: [] } as BundleFileInfo : undefined; - const relativeToBuildInfo = bundleFileInfo ? Debug.assertDefined(printerOptions.relativeToBuildInfo) : undefined; - const recordInternalSection = printerOptions.recordInternalSection; - let sourceFileTextPos = 0; - let sourceFileTextKind: BundleFileTextLikeKind = BundleFileSectionKind.Text; - - // Source Maps - let sourceMapsDisabled = true; - let sourceMapGenerator: SourceMapGenerator | undefined; - let sourceMapSource: SourceMapSource; - let sourceMapSourceIndex = -1; - - // Comments - let containerPos = -1; - let containerEnd = -1; - let declarationListContainerEnd = -1; - let currentLineMap: readonly number[] | undefined; - let detachedCommentsInfo: { nodePos: number, detachedCommentEndPos: number}[] | undefined; - let hasWrittenComment = false; - let commentsDisabled = !!printerOptions.removeComments; - let lastNode: Node | undefined; - let lastSubstitution: Node | undefined; - const { enter: enterComment, exit: exitComment } = performance.createTimerIf(extendedDiagnostics, "commentTime", "beforeComment", "afterComment"); - - reset(); - return { - // public API - printNode, - printList, - printFile, - printBundle, - - // internal API - writeNode, - writeList, - writeFile, - writeBundle, - bundleFileInfo + // Create a printer to print the nodes + const printer = createPrinter(printerOptions, { + // resolver hooks + hasGlobalName: resolver.hasGlobalName, + // transform hooks + onEmitNode: transform.emitNodeWithNotification, + substituteNode: transform.substituteNode, + }); + Debug.assert(transform.transformed.length === 1, "Should only see one output from the transform"); + printSourceFileOrBundle(jsFilePath, sourceMapFilePath, transform.transformed[0], printer, compilerOptions); + // Clean up emit nodes on parse tree + transform.dispose(); + if (bundleBuildInfo) + bundleBuildInfo.js = printer.bundleFileInfo; + } + function emitDeclarationFileOrBundle(sourceFileOrBundle: SourceFile | Bundle | undefined, declarationFilePath: string | undefined, declarationMapPath: string | undefined, relativeToBuildInfo: (path: string) => string) { + if (!sourceFileOrBundle) + return; + if (!declarationFilePath) { + if (emitOnlyDtsFiles || compilerOptions.emitDeclarationOnly) + emitSkipped = true; + return; + } + const sourceFiles = isSourceFile(sourceFileOrBundle) ? [sourceFileOrBundle] : sourceFileOrBundle.sourceFiles; + // Setup and perform the transformation to retrieve declarations from the input files + const inputListOrBundle = (compilerOptions.outFile || compilerOptions.out) ? [createBundle(sourceFiles, !isSourceFile(sourceFileOrBundle) ? sourceFileOrBundle.prepends : undefined)] : sourceFiles; + if (emitOnlyDtsFiles && !getEmitDeclarations(compilerOptions)) { + // Checker wont collect the linked aliases since thats only done when declaration is enabled. + // Do that here when emitting only dts files + sourceFiles.forEach(collectLinkedAliases); + } + const declarationTransform = transformNodes(resolver, host, compilerOptions, inputListOrBundle, declarationTransformers, /*allowDtsFiles*/ false); + if (length(declarationTransform.diagnostics)) { + for (const diagnostic of declarationTransform.diagnostics!) { + emitterDiagnostics.add(diagnostic); + } + } + const printerOptions: PrinterOptions = { + removeComments: compilerOptions.removeComments, + newLine: compilerOptions.newLine, + noEmitHelpers: true, + module: compilerOptions.module, + target: compilerOptions.target, + sourceMap: compilerOptions.sourceMap, + inlineSourceMap: compilerOptions.inlineSourceMap, + extendedDiagnostics: compilerOptions.extendedDiagnostics, + onlyPrintJsDocStyle: true, + writeBundleFileInfo: !!bundleBuildInfo, + recordInternalSection: !!bundleBuildInfo, + relativeToBuildInfo }; - - function printNode(hint: EmitHint, node: Node, sourceFile: SourceFile): string { - switch (hint) { - case EmitHint.SourceFile: - Debug.assert(isSourceFile(node), "Expected a SourceFile node."); - break; - case EmitHint.IdentifierName: - Debug.assert(isIdentifier(node), "Expected an Identifier node."); - break; - case EmitHint.Expression: - Debug.assert(isExpression(node), "Expected an Expression node."); - break; - } - switch (node.kind) { - case SyntaxKind.SourceFile: return printFile(node); - case SyntaxKind.Bundle: return printBundle(node); - case SyntaxKind.UnparsedSource: return printUnparsedSource(node); - } - writeNode(hint, node, sourceFile, beginPrint()); - return endPrint(); - } - - function printList(format: ListFormat, nodes: NodeArray, sourceFile: SourceFile) { - writeList(format, nodes, sourceFile, beginPrint()); - return endPrint(); - } - - function printBundle(bundle: Bundle): string { - writeBundle(bundle, beginPrint(), /*sourceMapEmitter*/ undefined); - return endPrint(); - } - - function printFile(sourceFile: SourceFile): string { - writeFile(sourceFile, beginPrint(), /*sourceMapEmitter*/ undefined); - return endPrint(); - } - - function printUnparsedSource(unparsed: UnparsedSource): string { - writeUnparsedSource(unparsed, beginPrint()); - return endPrint(); - } - - /** - * If `sourceFile` is `undefined`, `node` must be a synthesized `TypeNode`. - */ - function writeNode(hint: EmitHint, node: TypeNode, sourceFile: undefined, output: EmitTextWriter): void; - function writeNode(hint: EmitHint, node: Node, sourceFile: SourceFile, output: EmitTextWriter): void; - function writeNode(hint: EmitHint, node: Node, sourceFile: SourceFile | undefined, output: EmitTextWriter) { - const previousWriter = writer; - setWriter(output, /*_sourceMapGenerator*/ undefined); - print(hint, node, sourceFile); - reset(); - writer = previousWriter; - } - - function writeList(format: ListFormat, nodes: NodeArray, sourceFile: SourceFile | undefined, output: EmitTextWriter) { - const previousWriter = writer; - setWriter(output, /*_sourceMapGenerator*/ undefined); - if (sourceFile) { - setSourceFile(sourceFile); - } - emitList(syntheticParent, nodes, format); - reset(); - writer = previousWriter; - } - - function getTextPosWithWriteLine() { - return writer.getTextPosWithWriteLine ? writer.getTextPosWithWriteLine() : writer.getTextPos(); - } - - function updateOrPushBundleFileTextLike(pos: number, end: number, kind: BundleFileTextLikeKind) { - const last = lastOrUndefined(bundleFileInfo!.sections); - if (last && last.kind === kind) { - last.end = end; - } - else { - bundleFileInfo!.sections.push({ pos, end, kind }); + const declarationPrinter = createPrinter(printerOptions, { + // resolver hooks + hasGlobalName: resolver.hasGlobalName, + // transform hooks + onEmitNode: declarationTransform.emitNodeWithNotification, + substituteNode: declarationTransform.substituteNode, + }); + const declBlocked = (!!declarationTransform.diagnostics && !!declarationTransform.diagnostics.length) || !!host.isEmitBlocked(declarationFilePath) || !!compilerOptions.noEmit; + emitSkipped = emitSkipped || declBlocked; + if (!declBlocked || forceDtsEmit) { + Debug.assert(declarationTransform.transformed.length === 1, "Should only see one output from the decl transform"); + printSourceFileOrBundle(declarationFilePath, declarationMapPath, declarationTransform.transformed[0], declarationPrinter, { + sourceMap: compilerOptions.declarationMap, + sourceRoot: compilerOptions.sourceRoot, + mapRoot: compilerOptions.mapRoot, + extendedDiagnostics: compilerOptions.extendedDiagnostics, + }); + if (forceDtsEmit && declarationTransform.transformed[0].kind === SyntaxKind.SourceFile) { + const sourceFile = declarationTransform.transformed[0]; + exportedModulesFromDeclarationEmit = sourceFile.exportedModulesFromDeclarationEmit; } } - - function recordBundleFileInternalSectionStart(node: Node) { - if (recordInternalSection && - bundleFileInfo && - currentSourceFile && - (isDeclaration(node) || isVariableStatement(node)) && - isInternalDeclaration(node, currentSourceFile) && - sourceFileTextKind !== BundleFileSectionKind.Internal) { - const prevSourceFileTextKind = sourceFileTextKind; - recordBundleFileTextLikeSection(writer.getTextPos()); - sourceFileTextPos = getTextPosWithWriteLine(); - sourceFileTextKind = BundleFileSectionKind.Internal; - return prevSourceFileTextKind; + declarationTransform.dispose(); + if (bundleBuildInfo) + bundleBuildInfo.dts = declarationPrinter.bundleFileInfo; + } + function collectLinkedAliases(node: Node) { + if (isExportAssignment(node)) { + if (node.expression.kind === SyntaxKind.Identifier) { + resolver.collectLinkedAliases((node.expression as Identifier), /*setVisibility*/ true); } - return undefined; + return; } - - function recordBundleFileInternalSectionEnd(prevSourceFileTextKind: ReturnType) { - if (prevSourceFileTextKind) { - recordBundleFileTextLikeSection(writer.getTextPos()); - sourceFileTextPos = getTextPosWithWriteLine(); - sourceFileTextKind = prevSourceFileTextKind; - } + else if (isExportSpecifier(node)) { + resolver.collectLinkedAliases(node.propertyName || node.name, /*setVisibility*/ true); + return; } - - function recordBundleFileTextLikeSection(end: number) { - if (sourceFileTextPos < end) { - updateOrPushBundleFileTextLike(sourceFileTextPos, end, sourceFileTextKind); - return true; - } - return false; + forEachChild(node, collectLinkedAliases); + } + function printSourceFileOrBundle(jsFilePath: string, sourceMapFilePath: string | undefined, sourceFileOrBundle: SourceFile | Bundle, printer: Printer, mapOptions: SourceMapOptions) { + const bundle = sourceFileOrBundle.kind === SyntaxKind.Bundle ? sourceFileOrBundle : undefined; + const sourceFile = sourceFileOrBundle.kind === SyntaxKind.SourceFile ? sourceFileOrBundle : undefined; + const sourceFiles = bundle ? bundle.sourceFiles : [sourceFile!]; + let sourceMapGenerator: SourceMapGenerator | undefined; + if (shouldEmitSourceMaps(mapOptions, sourceFileOrBundle)) { + sourceMapGenerator = createSourceMapGenerator(host, getBaseFileName(normalizeSlashes(jsFilePath)), getSourceRoot(mapOptions), getSourceMapDirectory(mapOptions, jsFilePath, sourceFile), mapOptions); } - - function writeBundle(bundle: Bundle, output: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined) { - isOwnFileEmit = false; - const previousWriter = writer; - setWriter(output, sourceMapGenerator); - emitShebangIfNeeded(bundle); - emitPrologueDirectivesIfNeeded(bundle); - emitHelpers(bundle); - emitSyntheticTripleSlashReferencesIfNeeded(bundle); - - for (const prepend of bundle.prepends) { - writeLine(); - const pos = writer.getTextPos(); - const savedSections = bundleFileInfo && bundleFileInfo.sections; - if (savedSections) bundleFileInfo!.sections = []; - print(EmitHint.Unspecified, prepend, /*sourceFile*/ undefined); - if (bundleFileInfo) { - const newSections = bundleFileInfo.sections; - bundleFileInfo.sections = savedSections!; - if (prepend.oldFileOfCurrentEmit) bundleFileInfo.sections.push(...newSections); - else { - newSections.forEach(section => Debug.assert(isBundleFileTextLike(section))); - bundleFileInfo.sections.push({ - pos, - end: writer.getTextPos(), - kind: BundleFileSectionKind.Prepend, - data: relativeToBuildInfo!((prepend as UnparsedSource).fileName), - texts: newSections as BundleFileTextLike[] - }); - } - } + if (bundle) { + printer.writeBundle(bundle, writer, sourceMapGenerator); + } + else { + printer.writeFile(sourceFile!, writer, sourceMapGenerator); + } + if (sourceMapGenerator) { + if (sourceMapDataList) { + sourceMapDataList.push({ + inputSourceFileNames: sourceMapGenerator.getSources(), + sourceMap: sourceMapGenerator.toJSON() + }); } - - sourceFileTextPos = getTextPosWithWriteLine(); - for (const sourceFile of bundle.sourceFiles) { - print(EmitHint.SourceFile, sourceFile, sourceFile); + const sourceMappingURL = getSourceMappingURL(mapOptions, sourceMapGenerator, jsFilePath, sourceMapFilePath, sourceFile); + if (sourceMappingURL) { + if (!writer.isAtStartOfLine()) + writer.rawWrite(newLine); + writer.writeComment(`//# ${"sourceMappingURL"}=${sourceMappingURL}`); // Tools can sometimes see this line as a source mapping url comment } - if (bundleFileInfo && bundle.sourceFiles.length) { - const end = writer.getTextPos(); - if (recordBundleFileTextLikeSection(end)) { - // Store prologues - const prologues = getPrologueDirectivesFromBundledSourceFiles(bundle); - if (prologues) { - if (!bundleFileInfo.sources) bundleFileInfo.sources = {}; - bundleFileInfo.sources.prologues = prologues; - } - - // Store helpes - const helpers = getHelpersFromBundledSourceFiles(bundle); - if (helpers) { - if (!bundleFileInfo.sources) bundleFileInfo.sources = {}; - bundleFileInfo.sources.helpers = helpers; - } - } + // Write the source map + if (sourceMapFilePath) { + const sourceMap = sourceMapGenerator.toString(); + writeFile(host, emitterDiagnostics, sourceMapFilePath, sourceMap, /*writeByteOrderMark*/ false, sourceFiles); } - - reset(); - writer = previousWriter; - } - - function writeUnparsedSource(unparsed: UnparsedSource, output: EmitTextWriter) { - const previousWriter = writer; - setWriter(output, /*_sourceMapGenerator*/ undefined); - print(EmitHint.Unspecified, unparsed, /*sourceFile*/ undefined); - reset(); - writer = previousWriter; - } - - function writeFile(sourceFile: SourceFile, output: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined) { - isOwnFileEmit = true; - const previousWriter = writer; - setWriter(output, sourceMapGenerator); - emitShebangIfNeeded(sourceFile); - emitPrologueDirectivesIfNeeded(sourceFile); - print(EmitHint.SourceFile, sourceFile, sourceFile); - reset(); - writer = previousWriter; } - - function beginPrint() { - return ownWriter || (ownWriter = createTextWriter(newLine)); - } - - function endPrint() { - const text = ownWriter.getText(); - ownWriter.clear(); - return text; + else { + writer.writeLine(); } - - function print(hint: EmitHint, node: Node, sourceFile: SourceFile | undefined) { + // Write the output file + writeFile(host, emitterDiagnostics, jsFilePath, writer.getText(), !!compilerOptions.emitBOM, sourceFiles); + // Reset state + writer.clear(); + } + interface SourceMapOptions { + sourceMap?: boolean; + inlineSourceMap?: boolean; + inlineSources?: boolean; + sourceRoot?: string; + mapRoot?: string; + extendedDiagnostics?: boolean; + } + function shouldEmitSourceMaps(mapOptions: SourceMapOptions, sourceFileOrBundle: SourceFile | Bundle) { + return (mapOptions.sourceMap || mapOptions.inlineSourceMap) + && (sourceFileOrBundle.kind !== SyntaxKind.SourceFile || !fileExtensionIs(sourceFileOrBundle.fileName, Extension.Json)); + } + function getSourceRoot(mapOptions: SourceMapOptions) { + // Normalize source root and make sure it has trailing "/" so that it can be used to combine paths with the + // relative paths of the sources list in the sourcemap + const sourceRoot = normalizeSlashes(mapOptions.sourceRoot || ""); + return sourceRoot ? ensureTrailingDirectorySeparator(sourceRoot) : sourceRoot; + } + function getSourceMapDirectory(mapOptions: SourceMapOptions, filePath: string, sourceFile: SourceFile | undefined) { + if (mapOptions.sourceRoot) + return host.getCommonSourceDirectory(); + if (mapOptions.mapRoot) { + let sourceMapDir = normalizeSlashes(mapOptions.mapRoot); if (sourceFile) { - setSourceFile(sourceFile); + // For modules or multiple emit files the mapRoot will have directory structure like the sources + // So if src\a.ts and src\lib\b.ts are compiled together user would be moving the maps into mapRoot\a.js.map and mapRoot\lib\b.js.map + sourceMapDir = getDirectoryPath(getSourceFilePathInNewDir(sourceFile.fileName, host, sourceMapDir)); } - - pipelineEmit(hint, node); - } - - function setSourceFile(sourceFile: SourceFile | undefined) { - currentSourceFile = sourceFile; - currentLineMap = undefined; - detachedCommentsInfo = undefined; - if (sourceFile) { - setSourceMapSource(sourceFile); + if (getRootLength(sourceMapDir) === 0) { + // The relative paths are relative to the common directory + sourceMapDir = combinePaths(host.getCommonSourceDirectory(), sourceMapDir); } + return sourceMapDir; } - - function setWriter(_writer: EmitTextWriter | undefined, _sourceMapGenerator: SourceMapGenerator | undefined) { - if (_writer && printerOptions.omitTrailingSemicolon) { - _writer = getTrailingSemicolonDeferringWriter(_writer); + return getDirectoryPath(normalizePath(filePath)); + } + function getSourceMappingURL(mapOptions: SourceMapOptions, sourceMapGenerator: SourceMapGenerator, filePath: string, sourceMapFilePath: string | undefined, sourceFile: SourceFile | undefined) { + if (mapOptions.inlineSourceMap) { + // Encode the sourceMap into the sourceMap url + const sourceMapText = sourceMapGenerator.toString(); + const base64SourceMapText = base64encode(sys, sourceMapText); + return `data:application/json;base64,${base64SourceMapText}`; + } + const sourceMapFile = getBaseFileName(normalizeSlashes(Debug.assertDefined(sourceMapFilePath))); + if (mapOptions.mapRoot) { + let sourceMapDir = normalizeSlashes(mapOptions.mapRoot); + if (sourceFile) { + // For modules or multiple emit files the mapRoot will have directory structure like the sources + // So if src\a.ts and src\lib\b.ts are compiled together user would be moving the maps into mapRoot\a.js.map and mapRoot\lib\b.js.map + sourceMapDir = getDirectoryPath(getSourceFilePathInNewDir(sourceFile.fileName, host, sourceMapDir)); + } + if (getRootLength(sourceMapDir) === 0) { + // The relative paths are relative to the common directory + sourceMapDir = combinePaths(host.getCommonSourceDirectory(), sourceMapDir); + return getRelativePathToDirectoryOrUrl(getDirectoryPath(normalizePath(filePath)), // get the relative sourceMapDir path based on jsFilePath + combinePaths(sourceMapDir, sourceMapFile), // this is where user expects to see sourceMap + host.getCurrentDirectory(), host.getCanonicalFileName, + /*isAbsolutePathAnUrl*/ true); } - - writer = _writer!; // TODO: GH#18217 - sourceMapGenerator = _sourceMapGenerator; - sourceMapsDisabled = !writer || !sourceMapGenerator; - } - - function reset() { - nodeIdToGeneratedName = []; - autoGeneratedIdToGeneratedName = []; - generatedNames = createMap(); - tempFlagsStack = []; - tempFlags = TempFlags.Auto; - reservedNamesStack = []; - currentSourceFile = undefined!; - currentLineMap = undefined!; - detachedCommentsInfo = undefined; - lastNode = undefined; - lastSubstitution = undefined; - setWriter(/*output*/ undefined, /*_sourceMapGenerator*/ undefined); - } - - function getCurrentLineMap() { - return currentLineMap || (currentLineMap = getLineStarts(currentSourceFile!)); - } - - function emit(node: Node): Node; - function emit(node: Node | undefined): Node | undefined; - function emit(node: Node | undefined) { - if (node === undefined) return; - - const prevSourceFileTextKind = recordBundleFileInternalSectionStart(node); - const substitute = pipelineEmit(EmitHint.Unspecified, node); - recordBundleFileInternalSectionEnd(prevSourceFileTextKind); - return substitute; - } - - function emitIdentifierName(node: Identifier): Node; - function emitIdentifierName(node: Identifier | undefined): Node | undefined; - function emitIdentifierName(node: Identifier | undefined): Node | undefined { - if (node === undefined) return; - return pipelineEmit(EmitHint.IdentifierName, node); - } - - function emitExpression(node: Expression): Node; - function emitExpression(node: Expression | undefined): Node | undefined; - function emitExpression(node: Expression | undefined): Node | undefined { - if (node === undefined) return; - return pipelineEmit(EmitHint.Expression, node); - } - - function pipelineEmit(emitHint: EmitHint, node: Node) { - const savedLastNode = lastNode; - const savedLastSubstitution = lastSubstitution; - lastNode = node; - lastSubstitution = undefined; - - const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node); - pipelinePhase(emitHint, node); - - Debug.assert(lastNode === node); - - const substitute = lastSubstitution; - lastNode = savedLastNode; - lastSubstitution = savedLastSubstitution; - - return substitute || node; - } - - function getPipelinePhase(phase: PipelinePhase, node: Node) { - switch (phase) { - case PipelinePhase.Notification: - if (onEmitNode !== noEmitNotification) { - return pipelineEmitWithNotification; - } - // falls through - - case PipelinePhase.Substitution: - if (substituteNode !== noEmitSubstitution) { - return pipelineEmitWithSubstitution; - } - // falls through - - case PipelinePhase.Comments: - if (!commentsDisabled && node.kind !== SyntaxKind.SourceFile) { - return pipelineEmitWithComments; - } - // falls through - - case PipelinePhase.SourceMaps: - if (!sourceMapsDisabled && node.kind !== SyntaxKind.SourceFile && !isInJsonFile(node)) { - return pipelineEmitWithSourceMap; - } - // falls through - - case PipelinePhase.Emit: - return pipelineEmitWithHint; - - default: - return Debug.assertNever(phase); + else { + return combinePaths(sourceMapDir, sourceMapFile); } } - - function getNextPipelinePhase(currentPhase: PipelinePhase, node: Node) { - return getPipelinePhase(currentPhase + 1, node); - } - - function pipelineEmitWithNotification(hint: EmitHint, node: Node) { - Debug.assert(lastNode === node); - const pipelinePhase = getNextPipelinePhase(PipelinePhase.Notification, node); - onEmitNode(hint, node, pipelinePhase); - Debug.assert(lastNode === node); - } - - function pipelineEmitWithHint(hint: EmitHint, node: Node): void { - Debug.assert(lastNode === node || lastSubstitution === node); - if (hint === EmitHint.SourceFile) return emitSourceFile(cast(node, isSourceFile)); - if (hint === EmitHint.IdentifierName) return emitIdentifier(cast(node, isIdentifier)); - if (hint === EmitHint.MappedTypeParameter) return emitMappedTypeParameter(cast(node, isTypeParameterDeclaration)); - if (hint === EmitHint.EmbeddedStatement) { - Debug.assertNode(node, isEmptyStatement); - return emitEmptyStatement(/*isEmbeddedStatement*/ true); - } - if (hint === EmitHint.Unspecified) { - if (isKeyword(node.kind)) return writeTokenNode(node, writeKeyword); - - switch (node.kind) { - // Pseudo-literals - case SyntaxKind.TemplateHead: - case SyntaxKind.TemplateMiddle: - case SyntaxKind.TemplateTail: - return emitLiteral(node); - - case SyntaxKind.UnparsedSource: - case SyntaxKind.UnparsedPrepend: - return emitUnparsedSourceOrPrepend(node); - - case SyntaxKind.UnparsedPrologue: - return writeUnparsedNode(node); - - case SyntaxKind.UnparsedText: - case SyntaxKind.UnparsedInternalText: - return emitUnparsedTextLike(node); - - case SyntaxKind.UnparsedSyntheticReference: - return emitUnparsedSyntheticReference(node); - - - // Identifiers - case SyntaxKind.Identifier: - return emitIdentifier(node); - - // Parse tree nodes - // Names - case SyntaxKind.QualifiedName: - return emitQualifiedName(node); - case SyntaxKind.ComputedPropertyName: - return emitComputedPropertyName(node); - - // Signature elements - case SyntaxKind.TypeParameter: - return emitTypeParameter(node); - case SyntaxKind.Parameter: - return emitParameter(node); - case SyntaxKind.Decorator: - return emitDecorator(node); - - // Type members - case SyntaxKind.PropertySignature: - return emitPropertySignature(node); - case SyntaxKind.PropertyDeclaration: - return emitPropertyDeclaration(node); - case SyntaxKind.MethodSignature: - return emitMethodSignature(node); - case SyntaxKind.MethodDeclaration: - return emitMethodDeclaration(node); - case SyntaxKind.Constructor: - return emitConstructor(node); - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return emitAccessorDeclaration(node); - case SyntaxKind.CallSignature: - return emitCallSignature(node); - case SyntaxKind.ConstructSignature: - return emitConstructSignature(node); - case SyntaxKind.IndexSignature: - return emitIndexSignature(node); - - // Types - case SyntaxKind.TypePredicate: - return emitTypePredicate(node); - case SyntaxKind.TypeReference: - return emitTypeReference(node); - case SyntaxKind.FunctionType: - return emitFunctionType(node); - case SyntaxKind.JSDocFunctionType: - return emitJSDocFunctionType(node as JSDocFunctionType); - case SyntaxKind.ConstructorType: - return emitConstructorType(node); - case SyntaxKind.TypeQuery: - return emitTypeQuery(node); - case SyntaxKind.TypeLiteral: - return emitTypeLiteral(node); - case SyntaxKind.ArrayType: - return emitArrayType(node); - case SyntaxKind.TupleType: - return emitTupleType(node); - case SyntaxKind.OptionalType: - return emitOptionalType(node); - case SyntaxKind.UnionType: - return emitUnionType(node); - case SyntaxKind.IntersectionType: - return emitIntersectionType(node); - case SyntaxKind.ConditionalType: - return emitConditionalType(node); - case SyntaxKind.InferType: - return emitInferType(node); - case SyntaxKind.ParenthesizedType: - return emitParenthesizedType(node); - case SyntaxKind.ExpressionWithTypeArguments: - return emitExpressionWithTypeArguments(node); - case SyntaxKind.ThisType: - return emitThisType(); - case SyntaxKind.TypeOperator: - return emitTypeOperator(node); - case SyntaxKind.IndexedAccessType: - return emitIndexedAccessType(node); - case SyntaxKind.MappedType: - return emitMappedType(node); - case SyntaxKind.LiteralType: - return emitLiteralType(node); - case SyntaxKind.ImportType: - return emitImportTypeNode(node); - case SyntaxKind.JSDocAllType: - writePunctuation("*"); - return; - case SyntaxKind.JSDocUnknownType: - writePunctuation("?"); + return sourceMapFile; + } +} +/*@internal*/ +export function getBuildInfoText(buildInfo: BuildInfo) { + return JSON.stringify(buildInfo, undefined, 2); +} +/*@internal*/ +export function getBuildInfo(buildInfoText: string) { + return JSON.parse(buildInfoText) as BuildInfo; +} +/*@internal*/ +export const notImplementedResolver: EmitResolver = { + hasGlobalName: notImplemented, + getReferencedExportContainer: notImplemented, + getReferencedImportDeclaration: notImplemented, + getReferencedDeclarationWithCollidingName: notImplemented, + isDeclarationWithCollidingName: notImplemented, + isValueAliasDeclaration: notImplemented, + isReferencedAliasDeclaration: notImplemented, + isTopLevelValueImportEqualsWithEntityName: notImplemented, + getNodeCheckFlags: notImplemented, + isDeclarationVisible: notImplemented, + isLateBound: (_node): _node is LateBoundDeclaration => false, + collectLinkedAliases: notImplemented, + isImplementationOfOverload: notImplemented, + isRequiredInitializedParameter: notImplemented, + isOptionalUninitializedParameterProperty: notImplemented, + isExpandoFunctionDeclaration: notImplemented, + getPropertiesOfContainerFunction: notImplemented, + createTypeOfDeclaration: notImplemented, + createReturnTypeOfSignatureDeclaration: notImplemented, + createTypeOfExpression: notImplemented, + createLiteralConstValue: notImplemented, + isSymbolAccessible: notImplemented, + isEntityNameVisible: notImplemented, + // Returns the constant value this property access resolves to: notImplemented, or 'undefined' for a non-constant + getConstantValue: notImplemented, + getReferencedValueDeclaration: notImplemented, + getTypeReferenceSerializationKind: notImplemented, + isOptionalParameter: notImplemented, + moduleExportsSomeValue: notImplemented, + isArgumentsLocalBinding: notImplemented, + getExternalModuleFileFromDeclaration: notImplemented, + getTypeReferenceDirectivesForEntityName: notImplemented, + getTypeReferenceDirectivesForSymbol: notImplemented, + isLiteralConstDeclaration: notImplemented, + getJsxFactoryEntity: notImplemented, + getAllAccessorDeclarations: notImplemented, + getSymbolOfExternalModuleSpecifier: notImplemented, + isBindingCapturedByNode: notImplemented, + getDeclarationStatementsForSourceFile: notImplemented, +}; +/*@internal*/ +/** File that isnt present resulting in error or output files */ +export type EmitUsingBuildInfoResult = string | readonly OutputFile[]; +/*@internal*/ +export interface EmitUsingBuildInfoHost extends ModuleResolutionHost { + getCurrentDirectory(): string; + getCanonicalFileName(fileName: string): string; + useCaseSensitiveFileNames(): boolean; + getNewLine(): string; +} +function createSourceFilesFromBundleBuildInfo(bundle: BundleBuildInfo, buildInfoDirectory: string, host: EmitUsingBuildInfoHost): readonly SourceFile[] { + const sourceFiles = bundle.sourceFiles.map(fileName => { + const sourceFile = (createNode(SyntaxKind.SourceFile, 0, 0) as SourceFile); + sourceFile.fileName = getRelativePathFromDirectory(host.getCurrentDirectory(), getNormalizedAbsolutePath(fileName, buildInfoDirectory), !host.useCaseSensitiveFileNames()); + sourceFile.text = ""; + sourceFile.statements = createNodeArray(); + return sourceFile; + }); + const jsBundle = Debug.assertDefined(bundle.js); + forEach(jsBundle.sources && jsBundle.sources.prologues, prologueInfo => { + const sourceFile = sourceFiles[prologueInfo.file]; + sourceFile.text = prologueInfo.text; + sourceFile.end = prologueInfo.text.length; + sourceFile.statements = createNodeArray(prologueInfo.directives.map(directive => { + const statement = (createNode(SyntaxKind.ExpressionStatement, directive.pos, directive.end) as PrologueDirective); + statement.expression = (createNode(SyntaxKind.StringLiteral, directive.expression.pos, directive.expression.end) as StringLiteral); + statement.expression.text = directive.expression.text; + return statement; + })); + }); + return sourceFiles; +} +/*@internal*/ +export function emitUsingBuildInfo(config: ParsedCommandLine, host: EmitUsingBuildInfoHost, getCommandLine: (ref: ProjectReference) => ParsedCommandLine | undefined, customTransformers?: CustomTransformers): EmitUsingBuildInfoResult { + const { buildInfoPath, jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath } = getOutputPathsForBundle(config.options, /*forceDtsPaths*/ false); + const buildInfoText = host.readFile(Debug.assertDefined(buildInfoPath)); + if (!buildInfoText) + return buildInfoPath!; + const jsFileText = host.readFile(Debug.assertDefined(jsFilePath)); + if (!jsFileText) + return jsFilePath!; + const sourceMapText = sourceMapFilePath && host.readFile(sourceMapFilePath); + // error if no source map or for now if inline sourcemap + if ((sourceMapFilePath && !sourceMapText) || config.options.inlineSourceMap) + return sourceMapFilePath || "inline sourcemap decoding"; + // read declaration text + const declarationText = declarationFilePath && host.readFile(declarationFilePath); + if (declarationFilePath && !declarationText) + return declarationFilePath; + const declarationMapText = declarationMapPath && host.readFile(declarationMapPath); + // error if no source map or for now if inline sourcemap + if ((declarationMapPath && !declarationMapText) || config.options.inlineSourceMap) + return declarationMapPath || "inline sourcemap decoding"; + const buildInfo = getBuildInfo(buildInfoText); + if (!buildInfo.bundle || !buildInfo.bundle.js || (declarationText && !buildInfo.bundle.dts)) + return buildInfoPath!; + const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath((buildInfoPath!), host.getCurrentDirectory())); + const ownPrependInput = createInputFiles(jsFileText, (declarationText!), sourceMapFilePath, sourceMapText, declarationMapPath, declarationMapText, jsFilePath, declarationFilePath, buildInfoPath, buildInfo, + /*onlyOwnText*/ true); + const outputFiles: OutputFile[] = []; + const prependNodes = createPrependNodes(config.projectReferences, getCommandLine, f => host.readFile(f)); + const sourceFilesForJsEmit = createSourceFilesFromBundleBuildInfo(buildInfo.bundle, buildInfoDirectory, host); + const emitHost: EmitHost = { + getPrependNodes: memoize(() => [...prependNodes, ownPrependInput]), + getCanonicalFileName: host.getCanonicalFileName, + getCommonSourceDirectory: () => getNormalizedAbsolutePath(buildInfo.bundle!.commonSourceDirectory, buildInfoDirectory), + getCompilerOptions: () => config.options, + getCurrentDirectory: () => host.getCurrentDirectory(), + getNewLine: () => host.getNewLine(), + getSourceFile: returnUndefined, + getSourceFileByPath: returnUndefined, + getSourceFiles: () => sourceFilesForJsEmit, + getLibFileFromReference: notImplemented, + isSourceFileFromExternalLibrary: returnFalse, + getResolvedProjectReferenceToRedirect: returnUndefined, + isSourceOfProjectReferenceRedirect: returnFalse, + writeFile: (name, text, writeByteOrderMark) => { + switch (name) { + case jsFilePath: + if (jsFileText === text) return; - case SyntaxKind.JSDocNullableType: - return emitJSDocNullableType(node as JSDocNullableType); - case SyntaxKind.JSDocNonNullableType: - return emitJSDocNonNullableType(node as JSDocNonNullableType); - case SyntaxKind.JSDocOptionalType: - return emitJSDocOptionalType(node as JSDocOptionalType); - case SyntaxKind.RestType: - case SyntaxKind.JSDocVariadicType: - return emitRestOrJSDocVariadicType(node as RestTypeNode | JSDocVariadicType); - - // Binding patterns - case SyntaxKind.ObjectBindingPattern: - return emitObjectBindingPattern(node); - case SyntaxKind.ArrayBindingPattern: - return emitArrayBindingPattern(node); - case SyntaxKind.BindingElement: - return emitBindingElement(node); - - // Misc - case SyntaxKind.TemplateSpan: - return emitTemplateSpan(node); - case SyntaxKind.SemicolonClassElement: - return emitSemicolonClassElement(); - - // Statements - case SyntaxKind.Block: - return emitBlock(node); - case SyntaxKind.VariableStatement: - return emitVariableStatement(node); - case SyntaxKind.EmptyStatement: - return emitEmptyStatement(/*isEmbeddedStatement*/ false); - case SyntaxKind.ExpressionStatement: - return emitExpressionStatement(node); - case SyntaxKind.IfStatement: - return emitIfStatement(node); - case SyntaxKind.DoStatement: - return emitDoStatement(node); - case SyntaxKind.WhileStatement: - return emitWhileStatement(node); - case SyntaxKind.ForStatement: - return emitForStatement(node); - case SyntaxKind.ForInStatement: - return emitForInStatement(node); - case SyntaxKind.ForOfStatement: - return emitForOfStatement(node); - case SyntaxKind.ContinueStatement: - return emitContinueStatement(node); - case SyntaxKind.BreakStatement: - return emitBreakStatement(node); - case SyntaxKind.ReturnStatement: - return emitReturnStatement(node); - case SyntaxKind.WithStatement: - return emitWithStatement(node); - case SyntaxKind.SwitchStatement: - return emitSwitchStatement(node); - case SyntaxKind.LabeledStatement: - return emitLabeledStatement(node); - case SyntaxKind.ThrowStatement: - return emitThrowStatement(node); - case SyntaxKind.TryStatement: - return emitTryStatement(node); - case SyntaxKind.DebuggerStatement: - return emitDebuggerStatement(node); - - // Declarations - case SyntaxKind.VariableDeclaration: - return emitVariableDeclaration(node); - case SyntaxKind.VariableDeclarationList: - return emitVariableDeclarationList(node); - case SyntaxKind.FunctionDeclaration: - return emitFunctionDeclaration(node); - case SyntaxKind.ClassDeclaration: - return emitClassDeclaration(node); - case SyntaxKind.InterfaceDeclaration: - return emitInterfaceDeclaration(node); - case SyntaxKind.TypeAliasDeclaration: - return emitTypeAliasDeclaration(node); - case SyntaxKind.EnumDeclaration: - return emitEnumDeclaration(node); - case SyntaxKind.ModuleDeclaration: - return emitModuleDeclaration(node); - case SyntaxKind.ModuleBlock: - return emitModuleBlock(node); - case SyntaxKind.CaseBlock: - return emitCaseBlock(node); - case SyntaxKind.NamespaceExportDeclaration: - return emitNamespaceExportDeclaration(node); - case SyntaxKind.ImportEqualsDeclaration: - return emitImportEqualsDeclaration(node); - case SyntaxKind.ImportDeclaration: - return emitImportDeclaration(node); - case SyntaxKind.ImportClause: - return emitImportClause(node); - case SyntaxKind.NamespaceImport: - return emitNamespaceImport(node); - case SyntaxKind.NamedImports: - return emitNamedImports(node); - case SyntaxKind.ImportSpecifier: - return emitImportSpecifier(node); - case SyntaxKind.ExportAssignment: - return emitExportAssignment(node); - case SyntaxKind.ExportDeclaration: - return emitExportDeclaration(node); - case SyntaxKind.NamedExports: - return emitNamedExports(node); - case SyntaxKind.ExportSpecifier: - return emitExportSpecifier(node); - case SyntaxKind.MissingDeclaration: + break; + case sourceMapFilePath: + if (sourceMapText === text) return; - - // Module references - case SyntaxKind.ExternalModuleReference: - return emitExternalModuleReference(node); - - // JSX (non-expression) - case SyntaxKind.JsxText: - return emitJsxText(node); - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxOpeningFragment: - return emitJsxOpeningElementOrFragment(node); - case SyntaxKind.JsxClosingElement: - case SyntaxKind.JsxClosingFragment: - return emitJsxClosingElementOrFragment(node); - case SyntaxKind.JsxAttribute: - return emitJsxAttribute(node); - case SyntaxKind.JsxAttributes: - return emitJsxAttributes(node); - case SyntaxKind.JsxSpreadAttribute: - return emitJsxSpreadAttribute(node); - case SyntaxKind.JsxExpression: - return emitJsxExpression(node); - - // Clauses - case SyntaxKind.CaseClause: - return emitCaseClause(node); - case SyntaxKind.DefaultClause: - return emitDefaultClause(node); - case SyntaxKind.HeritageClause: - return emitHeritageClause(node); - case SyntaxKind.CatchClause: - return emitCatchClause(node); - - // Property assignments - case SyntaxKind.PropertyAssignment: - return emitPropertyAssignment(node); - case SyntaxKind.ShorthandPropertyAssignment: - return emitShorthandPropertyAssignment(node); - case SyntaxKind.SpreadAssignment: - return emitSpreadAssignment(node as SpreadAssignment); - - // Enum - case SyntaxKind.EnumMember: - return emitEnumMember(node); - - // JSDoc nodes (only used in codefixes currently) - case SyntaxKind.JSDocParameterTag: - case SyntaxKind.JSDocPropertyTag: - return emitJSDocPropertyLikeTag(node as JSDocPropertyLikeTag); - case SyntaxKind.JSDocReturnTag: - case SyntaxKind.JSDocTypeTag: - case SyntaxKind.JSDocThisTag: - case SyntaxKind.JSDocEnumTag: - return emitJSDocSimpleTypedTag(node as JSDocTypeTag); - case SyntaxKind.JSDocAugmentsTag: - return emitJSDocAugmentsTag(node as JSDocAugmentsTag); - case SyntaxKind.JSDocTemplateTag: - return emitJSDocTemplateTag(node as JSDocTemplateTag); - case SyntaxKind.JSDocTypedefTag: - return emitJSDocTypedefTag(node as JSDocTypedefTag); - case SyntaxKind.JSDocCallbackTag: - return emitJSDocCallbackTag(node as JSDocCallbackTag); - case SyntaxKind.JSDocSignature: - return emitJSDocSignature(node as JSDocSignature); - case SyntaxKind.JSDocTypeLiteral: - return emitJSDocTypeLiteral(node as JSDocTypeLiteral); - case SyntaxKind.JSDocClassTag: - case SyntaxKind.JSDocTag: - return emitJSDocSimpleTag(node as JSDocTag); - - case SyntaxKind.JSDocComment: - return emitJSDoc(node as JSDoc); - - // Transformation nodes (ignored) - } - - if (isExpression(node)) { - hint = EmitHint.Expression; - if (substituteNode !== noEmitSubstitution) { - lastSubstitution = node = substituteNode(hint, node); + break; + case buildInfoPath: + const newBuildInfo = getBuildInfo(text); + newBuildInfo.program = buildInfo.program; + // Update sourceFileInfo + const { js, dts, sourceFiles } = buildInfo.bundle!; + newBuildInfo.bundle!.js!.sources = js!.sources; + if (dts) { + newBuildInfo.bundle!.dts!.sources = dts.sources; } - } - else if (isToken(node)) { - return writeTokenNode(node, writePunctuation); - } - } - if (hint === EmitHint.Expression) { - switch (node.kind) { - // Literals - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - return emitNumericOrBigIntLiteral(node); - - case SyntaxKind.StringLiteral: - case SyntaxKind.RegularExpressionLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - return emitLiteral(node); - - // Identifiers - case SyntaxKind.Identifier: - return emitIdentifier(node); - - // Reserved words - case SyntaxKind.FalseKeyword: - case SyntaxKind.NullKeyword: - case SyntaxKind.SuperKeyword: - case SyntaxKind.TrueKeyword: - case SyntaxKind.ThisKeyword: - case SyntaxKind.ImportKeyword: - writeTokenNode(node, writeKeyword); + newBuildInfo.bundle!.sourceFiles = sourceFiles; + outputFiles.push({ name, text: getBuildInfoText(newBuildInfo), writeByteOrderMark }); + return; + case declarationFilePath: + if (declarationText === text) return; - - // Expressions - case SyntaxKind.ArrayLiteralExpression: - return emitArrayLiteralExpression(node); - case SyntaxKind.ObjectLiteralExpression: - return emitObjectLiteralExpression(node); - case SyntaxKind.PropertyAccessExpression: - return emitPropertyAccessExpression(node); - case SyntaxKind.ElementAccessExpression: - return emitElementAccessExpression(node); - case SyntaxKind.CallExpression: - return emitCallExpression(node); - case SyntaxKind.NewExpression: - return emitNewExpression(node); - case SyntaxKind.TaggedTemplateExpression: - return emitTaggedTemplateExpression(node); - case SyntaxKind.TypeAssertionExpression: - return emitTypeAssertionExpression(node); - case SyntaxKind.ParenthesizedExpression: - return emitParenthesizedExpression(node); - case SyntaxKind.FunctionExpression: - return emitFunctionExpression(node); - case SyntaxKind.ArrowFunction: - return emitArrowFunction(node); - case SyntaxKind.DeleteExpression: - return emitDeleteExpression(node); - case SyntaxKind.TypeOfExpression: - return emitTypeOfExpression(node); - case SyntaxKind.VoidExpression: - return emitVoidExpression(node); - case SyntaxKind.AwaitExpression: - return emitAwaitExpression(node); - case SyntaxKind.PrefixUnaryExpression: - return emitPrefixUnaryExpression(node); - case SyntaxKind.PostfixUnaryExpression: - return emitPostfixUnaryExpression(node); - case SyntaxKind.BinaryExpression: - return emitBinaryExpression(node); - case SyntaxKind.ConditionalExpression: - return emitConditionalExpression(node); - case SyntaxKind.TemplateExpression: - return emitTemplateExpression(node); - case SyntaxKind.YieldExpression: - return emitYieldExpression(node); - case SyntaxKind.SpreadElement: - return emitSpreadExpression(node); - case SyntaxKind.ClassExpression: - return emitClassExpression(node); - case SyntaxKind.OmittedExpression: + break; + case declarationMapPath: + if (declarationMapText === text) return; - case SyntaxKind.AsExpression: - return emitAsExpression(node); - case SyntaxKind.NonNullExpression: - return emitNonNullExpression(node); - case SyntaxKind.MetaProperty: - return emitMetaProperty(node); - - // JSX - case SyntaxKind.JsxElement: - return emitJsxElement(node); - case SyntaxKind.JsxSelfClosingElement: - return emitJsxSelfClosingElement(node); - case SyntaxKind.JsxFragment: - return emitJsxFragment(node); - - // Transformation nodes - case SyntaxKind.PartiallyEmittedExpression: - return emitPartiallyEmittedExpression(node); - - case SyntaxKind.CommaListExpression: - return emitCommaList(node); + break; + default: + Debug.fail(`Unexpected path: ${name}`); + } + outputFiles.push({ name, text, writeByteOrderMark }); + }, + isEmitBlocked: returnFalse, + readFile: f => host.readFile(f), + fileExists: f => host.fileExists(f), + directoryExists: host.directoryExists && (f => host.directoryExists!(f)), + useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), + getProgramBuildInfo: returnUndefined, + getSourceFileFromReference: returnUndefined, + redirectTargetsMap: createMultiMap() + }; + emitFiles(notImplementedResolver, emitHost, + /*targetSourceFile*/ undefined, getTransformers(config.options, customTransformers)); + return outputFiles; +} +const enum PipelinePhase { + Notification, + Substitution, + Comments, + SourceMaps, + Emit +} +export function createPrinter(printerOptions: PrinterOptions = {}, handlers: PrintHandlers = {}): Printer { + const { hasGlobalName, onEmitNode = noEmitNotification, substituteNode = noEmitSubstitution, onBeforeEmitNodeArray, onAfterEmitNodeArray, onBeforeEmitToken, onAfterEmitToken } = handlers; + const extendedDiagnostics = !!printerOptions.extendedDiagnostics; + const newLine = getNewLineCharacter(printerOptions); + const moduleKind = getEmitModuleKind(printerOptions); + const bundledHelpers = createMap(); + let currentSourceFile: SourceFile | undefined; + let nodeIdToGeneratedName: string[]; // Map of generated names for specific nodes. + let autoGeneratedIdToGeneratedName: string[]; // Map of generated names for temp and loop variables. + let generatedNames: ts.Map; // Set of names generated by the NameGenerator. + let tempFlagsStack: TempFlags[]; // Stack of enclosing name generation scopes. + let tempFlags: TempFlags; // TempFlags for the current name generation scope. + let reservedNamesStack: ts.Map[]; // Stack of TempFlags reserved in enclosing name generation scopes. + let reservedNames: ts.Map; // TempFlags to reserve in nested name generation scopes. + let writer: EmitTextWriter; + let ownWriter: EmitTextWriter; // Reusable `EmitTextWriter` for basic printing. + let write = writeBase; + let isOwnFileEmit: boolean; + const bundleFileInfo = printerOptions.writeBundleFileInfo ? { sections: [] } as BundleFileInfo : undefined; + const relativeToBuildInfo = bundleFileInfo ? Debug.assertDefined(printerOptions.relativeToBuildInfo) : undefined; + const recordInternalSection = printerOptions.recordInternalSection; + let sourceFileTextPos = 0; + let sourceFileTextKind: BundleFileTextLikeKind = BundleFileSectionKind.Text; + // Source Maps + let sourceMapsDisabled = true; + let sourceMapGenerator: SourceMapGenerator | undefined; + let sourceMapSource: SourceMapSource; + let sourceMapSourceIndex = -1; + // Comments + let containerPos = -1; + let containerEnd = -1; + let declarationListContainerEnd = -1; + let currentLineMap: readonly number[] | undefined; + let detachedCommentsInfo: { + nodePos: number; + detachedCommentEndPos: number; + }[] | undefined; + let hasWrittenComment = false; + let commentsDisabled = !!printerOptions.removeComments; + let lastNode: Node | undefined; + let lastSubstitution: Node | undefined; + const { enter: enterComment, exit: exitComment } = createTimerIf(extendedDiagnostics, "commentTime", "beforeComment", "afterComment"); + reset(); + return { + // public API + printNode, + printList, + printFile, + printBundle, + // internal API + writeNode, + writeList, + writeFile, + writeBundle, + bundleFileInfo + }; + function printNode(hint: EmitHint, node: Node, sourceFile: SourceFile): string { + switch (hint) { + case EmitHint.SourceFile: + Debug.assert(isSourceFile(node), "Expected a SourceFile node."); + break; + case EmitHint.IdentifierName: + Debug.assert(isIdentifier(node), "Expected an Identifier node."); + break; + case EmitHint.Expression: + Debug.assert(isExpression(node), "Expected an Expression node."); + break; + } + switch (node.kind) { + case SyntaxKind.SourceFile: return printFile((node)); + case SyntaxKind.Bundle: return printBundle((node)); + case SyntaxKind.UnparsedSource: return printUnparsedSource((node)); + } + writeNode(hint, node, sourceFile, beginPrint()); + return endPrint(); + } + function printList(format: ListFormat, nodes: NodeArray, sourceFile: SourceFile) { + writeList(format, nodes, sourceFile, beginPrint()); + return endPrint(); + } + function printBundle(bundle: Bundle): string { + writeBundle(bundle, beginPrint(), /*sourceMapEmitter*/ undefined); + return endPrint(); + } + function printFile(sourceFile: SourceFile): string { + writeFile(sourceFile, beginPrint(), /*sourceMapEmitter*/ undefined); + return endPrint(); + } + function printUnparsedSource(unparsed: UnparsedSource): string { + writeUnparsedSource(unparsed, beginPrint()); + return endPrint(); + } + /** + * If `sourceFile` is `undefined`, `node` must be a synthesized `TypeNode`. + */ + function writeNode(hint: EmitHint, node: TypeNode, sourceFile: undefined, output: EmitTextWriter): void; + function writeNode(hint: EmitHint, node: Node, sourceFile: SourceFile, output: EmitTextWriter): void; + function writeNode(hint: EmitHint, node: Node, sourceFile: SourceFile | undefined, output: EmitTextWriter) { + const previousWriter = writer; + setWriter(output, /*_sourceMapGenerator*/ undefined); + print(hint, node, sourceFile); + reset(); + writer = previousWriter; + } + function writeList(format: ListFormat, nodes: NodeArray, sourceFile: SourceFile | undefined, output: EmitTextWriter) { + const previousWriter = writer; + setWriter(output, /*_sourceMapGenerator*/ undefined); + if (sourceFile) { + setSourceFile(sourceFile); + } + emitList(syntheticParent, nodes, format); + reset(); + writer = previousWriter; + } + function getTextPosWithWriteLine() { + return writer.getTextPosWithWriteLine ? writer.getTextPosWithWriteLine() : writer.getTextPos(); + } + function updateOrPushBundleFileTextLike(pos: number, end: number, kind: BundleFileTextLikeKind) { + const last = lastOrUndefined(bundleFileInfo!.sections); + if (last && last.kind === kind) { + last.end = end; + } + else { + bundleFileInfo!.sections.push({ pos, end, kind }); + } + } + function recordBundleFileInternalSectionStart(node: Node) { + if (recordInternalSection && + bundleFileInfo && + currentSourceFile && + (isDeclaration(node) || isVariableStatement(node)) && + isInternalDeclaration(node, currentSourceFile) && + sourceFileTextKind !== BundleFileSectionKind.Internal) { + const prevSourceFileTextKind = sourceFileTextKind; + recordBundleFileTextLikeSection(writer.getTextPos()); + sourceFileTextPos = getTextPosWithWriteLine(); + sourceFileTextKind = BundleFileSectionKind.Internal; + return prevSourceFileTextKind; + } + return undefined; + } + function recordBundleFileInternalSectionEnd(prevSourceFileTextKind: ReturnType) { + if (prevSourceFileTextKind) { + recordBundleFileTextLikeSection(writer.getTextPos()); + sourceFileTextPos = getTextPosWithWriteLine(); + sourceFileTextKind = prevSourceFileTextKind; + } + } + function recordBundleFileTextLikeSection(end: number) { + if (sourceFileTextPos < end) { + updateOrPushBundleFileTextLike(sourceFileTextPos, end, sourceFileTextKind); + return true; + } + return false; + } + function writeBundle(bundle: Bundle, output: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined) { + isOwnFileEmit = false; + const previousWriter = writer; + setWriter(output, sourceMapGenerator); + emitShebangIfNeeded(bundle); + emitPrologueDirectivesIfNeeded(bundle); + emitHelpers(bundle); + emitSyntheticTripleSlashReferencesIfNeeded(bundle); + for (const prepend of bundle.prepends) { + writeLine(); + const pos = writer.getTextPos(); + const savedSections = bundleFileInfo && bundleFileInfo.sections; + if (savedSections) + bundleFileInfo!.sections = []; + print(EmitHint.Unspecified, prepend, /*sourceFile*/ undefined); + if (bundleFileInfo) { + const newSections = bundleFileInfo.sections; + bundleFileInfo.sections = savedSections!; + if (prepend.oldFileOfCurrentEmit) + bundleFileInfo.sections.push(...newSections); + else { + newSections.forEach(section => Debug.assert(isBundleFileTextLike(section))); + bundleFileInfo.sections.push({ + pos, + end: writer.getTextPos(), + kind: BundleFileSectionKind.Prepend, + data: relativeToBuildInfo!((prepend as UnparsedSource).fileName), + texts: (newSections as BundleFileTextLike[]) + }); } } } - - function emitMappedTypeParameter(node: TypeParameterDeclaration): void { - emit(node.name); - writeSpace(); - writeKeyword("in"); - writeSpace(); - emit(node.constraint); + sourceFileTextPos = getTextPosWithWriteLine(); + for (const sourceFile of bundle.sourceFiles) { + print(EmitHint.SourceFile, sourceFile, sourceFile); } - - function pipelineEmitWithSubstitution(hint: EmitHint, node: Node) { - Debug.assert(lastNode === node || lastSubstitution === node); - const pipelinePhase = getNextPipelinePhase(PipelinePhase.Substitution, node); - lastSubstitution = substituteNode(hint, node); - pipelinePhase(hint, lastSubstitution); - Debug.assert(lastNode === node || lastSubstitution === node); - } - - function getHelpersFromBundledSourceFiles(bundle: Bundle): string[] | undefined { - let result: string[] | undefined; - if (moduleKind === ModuleKind.None || printerOptions.noEmitHelpers) { - return undefined; - } - const bundledHelpers = createMap(); - for (const sourceFile of bundle.sourceFiles) { - const shouldSkip = getExternalHelpersModuleName(sourceFile) !== undefined; - const helpers = getSortedEmitHelpers(sourceFile); - if (!helpers) continue; - for (const helper of helpers) { - if (!helper.scoped && !shouldSkip && !bundledHelpers.get(helper.name)) { - bundledHelpers.set(helper.name, true); - (result || (result = [])).push(helper.name); - } + if (bundleFileInfo && bundle.sourceFiles.length) { + const end = writer.getTextPos(); + if (recordBundleFileTextLikeSection(end)) { + // Store prologues + const prologues = getPrologueDirectivesFromBundledSourceFiles(bundle); + if (prologues) { + if (!bundleFileInfo.sources) + bundleFileInfo.sources = {}; + bundleFileInfo.sources.prologues = prologues; } - } - - return result; - } - - function emitHelpers(node: Node) { - let helpersEmitted = false; - const bundle = node.kind === SyntaxKind.Bundle ? node : undefined; - if (bundle && moduleKind === ModuleKind.None) { - return; - } - const numPrepends = bundle ? bundle.prepends.length : 0; - const numNodes = bundle ? bundle.sourceFiles.length + numPrepends : 1; - for (let i = 0; i < numNodes; i++) { - const currentNode = bundle ? i < numPrepends ? bundle.prepends[i] : bundle.sourceFiles[i - numPrepends] : node; - const sourceFile = isSourceFile(currentNode) ? currentNode : isUnparsedSource(currentNode) ? undefined : currentSourceFile!; - const shouldSkip = printerOptions.noEmitHelpers || (!!sourceFile && hasRecordedExternalHelpers(sourceFile)); - const shouldBundle = (isSourceFile(currentNode) || isUnparsedSource(currentNode)) && !isOwnFileEmit; - const helpers = isUnparsedSource(currentNode) ? currentNode.helpers : getSortedEmitHelpers(currentNode); + // Store helpes + const helpers = getHelpersFromBundledSourceFiles(bundle); if (helpers) { - for (const helper of helpers) { - if (!helper.scoped) { - // Skip the helper if it can be skipped and the noEmitHelpers compiler - // option is set, or if it can be imported and the importHelpers compiler - // option is set. - if (shouldSkip) continue; - - // Skip the helper if it can be bundled but hasn't already been emitted and we - // are emitting a bundled module. - if (shouldBundle) { - if (bundledHelpers.get(helper.name)) { - continue; - } - - bundledHelpers.set(helper.name, true); - } - } - else if (bundle) { - // Skip the helper if it is scoped and we are emitting bundled helpers - continue; - } - const pos = getTextPosWithWriteLine(); - if (typeof helper.text === "string") { - writeLines(helper.text); - } - else { - writeLines(helper.text(makeFileLevelOptimisticUniqueName)); - } - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.EmitHelpers, data: helper.name }); - helpersEmitted = true; - } + if (!bundleFileInfo.sources) + bundleFileInfo.sources = {}; + bundleFileInfo.sources.helpers = helpers; } } - - return helpersEmitted; - } - - function getSortedEmitHelpers(node: Node) { - const helpers = getEmitHelpers(node); - return helpers && stableSort(helpers, compareEmitHelpers); } - - // - // Literals/Pseudo-literals - // - - // SyntaxKind.NumericLiteral - // SyntaxKind.BigIntLiteral - function emitNumericOrBigIntLiteral(node: NumericLiteral | BigIntLiteral) { - emitLiteral(node); - } - - // SyntaxKind.StringLiteral - // SyntaxKind.RegularExpressionLiteral - // SyntaxKind.NoSubstitutionTemplateLiteral - // SyntaxKind.TemplateHead - // SyntaxKind.TemplateMiddle - // SyntaxKind.TemplateTail - function emitLiteral(node: LiteralLikeNode) { - const text = getLiteralTextOfNode(node, printerOptions.neverAsciiEscape); - if ((printerOptions.sourceMap || printerOptions.inlineSourceMap) - && (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))) { - writeLiteral(text); - } - else { - // Quick info expects all literals to be called with writeStringLiteral, as there's no specific type for numberLiterals - writeStringLiteral(text); - } - } - - // SyntaxKind.UnparsedSource - // SyntaxKind.UnparsedPrepend - function emitUnparsedSourceOrPrepend(unparsed: UnparsedSource | UnparsedPrepend) { - for (const text of unparsed.texts) { - writeLine(); - emit(text); - } - } - - // SyntaxKind.UnparsedPrologue - // SyntaxKind.UnparsedText - // SyntaxKind.UnparsedInternal - // SyntaxKind.UnparsedSyntheticReference - function writeUnparsedNode(unparsed: UnparsedNode) { - writer.rawWrite(unparsed.parent.text.substring(unparsed.pos, unparsed.end)); - } - - // SyntaxKind.UnparsedText - // SyntaxKind.UnparsedInternal - function emitUnparsedTextLike(unparsed: UnparsedTextLike) { - const pos = getTextPosWithWriteLine(); - writeUnparsedNode(unparsed); - if (bundleFileInfo) { - updateOrPushBundleFileTextLike( - pos, - writer.getTextPos(), - unparsed.kind === SyntaxKind.UnparsedText ? - BundleFileSectionKind.Text : - BundleFileSectionKind.Internal - ); - } + reset(); + writer = previousWriter; + } + function writeUnparsedSource(unparsed: UnparsedSource, output: EmitTextWriter) { + const previousWriter = writer; + setWriter(output, /*_sourceMapGenerator*/ undefined); + print(EmitHint.Unspecified, unparsed, /*sourceFile*/ undefined); + reset(); + writer = previousWriter; + } + function writeFile(sourceFile: SourceFile, output: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined) { + isOwnFileEmit = true; + const previousWriter = writer; + setWriter(output, sourceMapGenerator); + emitShebangIfNeeded(sourceFile); + emitPrologueDirectivesIfNeeded(sourceFile); + print(EmitHint.SourceFile, sourceFile, sourceFile); + reset(); + writer = previousWriter; + } + function beginPrint() { + return ownWriter || (ownWriter = createTextWriter(newLine)); + } + function endPrint() { + const text = ownWriter.getText(); + ownWriter.clear(); + return text; + } + function print(hint: EmitHint, node: Node, sourceFile: SourceFile | undefined) { + if (sourceFile) { + setSourceFile(sourceFile); } - - // SyntaxKind.UnparsedSyntheticReference - function emitUnparsedSyntheticReference(unparsed: UnparsedSyntheticReference) { - const pos = getTextPosWithWriteLine(); - writeUnparsedNode(unparsed); - if (bundleFileInfo) { - const section = clone(unparsed.section); - section.pos = pos; - section.end = writer.getTextPos(); - bundleFileInfo.sections.push(section); - } + pipelineEmit(hint, node); + } + function setSourceFile(sourceFile: SourceFile | undefined) { + currentSourceFile = sourceFile; + currentLineMap = undefined; + detachedCommentsInfo = undefined; + if (sourceFile) { + setSourceMapSource(sourceFile); } - - // - // Identifiers - // - - function emitIdentifier(node: Identifier) { - const writeText = node.symbol ? writeSymbol : write; - writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol); - emitList(node, node.typeArguments, ListFormat.TypeParameters); // Call emitList directly since it could be an array of TypeParameterDeclarations _or_ type arguments + } + function setWriter(_writer: EmitTextWriter | undefined, _sourceMapGenerator: SourceMapGenerator | undefined) { + if (_writer && printerOptions.omitTrailingSemicolon) { + _writer = getTrailingSemicolonDeferringWriter(_writer); } - - // - // Names - // - - function emitQualifiedName(node: QualifiedName) { - emitEntityName(node.left); - writePunctuation("."); - emit(node.right); + writer = _writer!; // TODO: GH#18217 + sourceMapGenerator = _sourceMapGenerator; + sourceMapsDisabled = !writer || !sourceMapGenerator; + } + function reset() { + nodeIdToGeneratedName = []; + autoGeneratedIdToGeneratedName = []; + generatedNames = createMap(); + tempFlagsStack = []; + tempFlags = TempFlags.Auto; + reservedNamesStack = []; + currentSourceFile = undefined!; + currentLineMap = undefined!; + detachedCommentsInfo = undefined; + lastNode = undefined; + lastSubstitution = undefined; + setWriter(/*output*/ undefined, /*_sourceMapGenerator*/ undefined); + } + function getCurrentLineMap() { + return currentLineMap || (currentLineMap = getLineStarts((currentSourceFile!))); + } + function emit(node: Node): Node; + function emit(node: Node | undefined): Node | undefined; + function emit(node: Node | undefined) { + if (node === undefined) + return; + const prevSourceFileTextKind = recordBundleFileInternalSectionStart(node); + const substitute = pipelineEmit(EmitHint.Unspecified, node); + recordBundleFileInternalSectionEnd(prevSourceFileTextKind); + return substitute; + } + function emitIdentifierName(node: Identifier): Node; + function emitIdentifierName(node: Identifier | undefined): Node | undefined; + function emitIdentifierName(node: Identifier | undefined): Node | undefined { + if (node === undefined) + return; + return pipelineEmit(EmitHint.IdentifierName, node); + } + function emitExpression(node: Expression): Node; + function emitExpression(node: Expression | undefined): Node | undefined; + function emitExpression(node: Expression | undefined): Node | undefined { + if (node === undefined) + return; + return pipelineEmit(EmitHint.Expression, node); + } + function pipelineEmit(emitHint: EmitHint, node: Node) { + const savedLastNode = lastNode; + const savedLastSubstitution = lastSubstitution; + lastNode = node; + lastSubstitution = undefined; + const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node); + pipelinePhase(emitHint, node); + Debug.assert(lastNode === node); + const substitute = lastSubstitution; + lastNode = savedLastNode; + lastSubstitution = savedLastSubstitution; + return substitute || node; + } + function getPipelinePhase(phase: PipelinePhase, node: Node) { + switch (phase) { + case PipelinePhase.Notification: + if (onEmitNode !== noEmitNotification) { + return pipelineEmitWithNotification; + } + // falls through + case PipelinePhase.Substitution: + if (substituteNode !== noEmitSubstitution) { + return pipelineEmitWithSubstitution; + } + // falls through + case PipelinePhase.Comments: + if (!commentsDisabled && node.kind !== SyntaxKind.SourceFile) { + return pipelineEmitWithComments; + } + // falls through + case PipelinePhase.SourceMaps: + if (!sourceMapsDisabled && node.kind !== SyntaxKind.SourceFile && !isInJsonFile(node)) { + return pipelineEmitWithSourceMap; + } + // falls through + case PipelinePhase.Emit: + return pipelineEmitWithHint; + default: + return Debug.assertNever(phase); } - - function emitEntityName(node: EntityName) { - if (node.kind === SyntaxKind.Identifier) { - emitExpression(node); + } + function getNextPipelinePhase(currentPhase: PipelinePhase, node: Node) { + return getPipelinePhase(currentPhase + 1, node); + } + function pipelineEmitWithNotification(hint: EmitHint, node: Node) { + Debug.assert(lastNode === node); + const pipelinePhase = getNextPipelinePhase(PipelinePhase.Notification, node); + onEmitNode(hint, node, pipelinePhase); + Debug.assert(lastNode === node); + } + function pipelineEmitWithHint(hint: EmitHint, node: Node): void { + Debug.assert(lastNode === node || lastSubstitution === node); + if (hint === EmitHint.SourceFile) + return emitSourceFile(cast(node, isSourceFile)); + if (hint === EmitHint.IdentifierName) + return emitIdentifier(cast(node, isIdentifier)); + if (hint === EmitHint.MappedTypeParameter) + return emitMappedTypeParameter(cast(node, isTypeParameterDeclaration)); + if (hint === EmitHint.EmbeddedStatement) { + Debug.assertNode(node, isEmptyStatement); + return emitEmptyStatement(/*isEmbeddedStatement*/ true); + } + if (hint === EmitHint.Unspecified) { + if (isKeyword(node.kind)) + return writeTokenNode(node, writeKeyword); + switch (node.kind) { + // Pseudo-literals + case SyntaxKind.TemplateHead: + case SyntaxKind.TemplateMiddle: + case SyntaxKind.TemplateTail: + return emitLiteral((node)); + case SyntaxKind.UnparsedSource: + case SyntaxKind.UnparsedPrepend: + return emitUnparsedSourceOrPrepend((node)); + case SyntaxKind.UnparsedPrologue: + return writeUnparsedNode((node)); + case SyntaxKind.UnparsedText: + case SyntaxKind.UnparsedInternalText: + return emitUnparsedTextLike((node)); + case SyntaxKind.UnparsedSyntheticReference: + return emitUnparsedSyntheticReference((node)); + // Identifiers + case SyntaxKind.Identifier: + return emitIdentifier((node)); + // Parse tree nodes + // Names + case SyntaxKind.QualifiedName: + return emitQualifiedName((node)); + case SyntaxKind.ComputedPropertyName: + return emitComputedPropertyName((node)); + // Signature elements + case SyntaxKind.TypeParameter: + return emitTypeParameter((node)); + case SyntaxKind.Parameter: + return emitParameter((node)); + case SyntaxKind.Decorator: + return emitDecorator((node)); + // Type members + case SyntaxKind.PropertySignature: + return emitPropertySignature((node)); + case SyntaxKind.PropertyDeclaration: + return emitPropertyDeclaration((node)); + case SyntaxKind.MethodSignature: + return emitMethodSignature((node)); + case SyntaxKind.MethodDeclaration: + return emitMethodDeclaration((node)); + case SyntaxKind.Constructor: + return emitConstructor((node)); + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return emitAccessorDeclaration((node)); + case SyntaxKind.CallSignature: + return emitCallSignature((node)); + case SyntaxKind.ConstructSignature: + return emitConstructSignature((node)); + case SyntaxKind.IndexSignature: + return emitIndexSignature((node)); + // Types + case SyntaxKind.TypePredicate: + return emitTypePredicate((node)); + case SyntaxKind.TypeReference: + return emitTypeReference((node)); + case SyntaxKind.FunctionType: + return emitFunctionType((node)); + case SyntaxKind.JSDocFunctionType: + return emitJSDocFunctionType((node as JSDocFunctionType)); + case SyntaxKind.ConstructorType: + return emitConstructorType((node)); + case SyntaxKind.TypeQuery: + return emitTypeQuery((node)); + case SyntaxKind.TypeLiteral: + return emitTypeLiteral((node)); + case SyntaxKind.ArrayType: + return emitArrayType((node)); + case SyntaxKind.TupleType: + return emitTupleType((node)); + case SyntaxKind.OptionalType: + return emitOptionalType((node)); + case SyntaxKind.UnionType: + return emitUnionType((node)); + case SyntaxKind.IntersectionType: + return emitIntersectionType((node)); + case SyntaxKind.ConditionalType: + return emitConditionalType((node)); + case SyntaxKind.InferType: + return emitInferType((node)); + case SyntaxKind.ParenthesizedType: + return emitParenthesizedType((node)); + case SyntaxKind.ExpressionWithTypeArguments: + return emitExpressionWithTypeArguments((node)); + case SyntaxKind.ThisType: + return emitThisType(); + case SyntaxKind.TypeOperator: + return emitTypeOperator((node)); + case SyntaxKind.IndexedAccessType: + return emitIndexedAccessType((node)); + case SyntaxKind.MappedType: + return emitMappedType((node)); + case SyntaxKind.LiteralType: + return emitLiteralType((node)); + case SyntaxKind.ImportType: + return emitImportTypeNode((node)); + case SyntaxKind.JSDocAllType: + writePunctuation("*"); + return; + case SyntaxKind.JSDocUnknownType: + writePunctuation("?"); + return; + case SyntaxKind.JSDocNullableType: + return emitJSDocNullableType((node as JSDocNullableType)); + case SyntaxKind.JSDocNonNullableType: + return emitJSDocNonNullableType((node as JSDocNonNullableType)); + case SyntaxKind.JSDocOptionalType: + return emitJSDocOptionalType((node as JSDocOptionalType)); + case SyntaxKind.RestType: + case SyntaxKind.JSDocVariadicType: + return emitRestOrJSDocVariadicType((node as RestTypeNode | JSDocVariadicType)); + // Binding patterns + case SyntaxKind.ObjectBindingPattern: + return emitObjectBindingPattern((node)); + case SyntaxKind.ArrayBindingPattern: + return emitArrayBindingPattern((node)); + case SyntaxKind.BindingElement: + return emitBindingElement((node)); + // Misc + case SyntaxKind.TemplateSpan: + return emitTemplateSpan((node)); + case SyntaxKind.SemicolonClassElement: + return emitSemicolonClassElement(); + // Statements + case SyntaxKind.Block: + return emitBlock((node)); + case SyntaxKind.VariableStatement: + return emitVariableStatement((node)); + case SyntaxKind.EmptyStatement: + return emitEmptyStatement(/*isEmbeddedStatement*/ false); + case SyntaxKind.ExpressionStatement: + return emitExpressionStatement((node)); + case SyntaxKind.IfStatement: + return emitIfStatement((node)); + case SyntaxKind.DoStatement: + return emitDoStatement((node)); + case SyntaxKind.WhileStatement: + return emitWhileStatement((node)); + case SyntaxKind.ForStatement: + return emitForStatement((node)); + case SyntaxKind.ForInStatement: + return emitForInStatement((node)); + case SyntaxKind.ForOfStatement: + return emitForOfStatement((node)); + case SyntaxKind.ContinueStatement: + return emitContinueStatement((node)); + case SyntaxKind.BreakStatement: + return emitBreakStatement((node)); + case SyntaxKind.ReturnStatement: + return emitReturnStatement((node)); + case SyntaxKind.WithStatement: + return emitWithStatement((node)); + case SyntaxKind.SwitchStatement: + return emitSwitchStatement((node)); + case SyntaxKind.LabeledStatement: + return emitLabeledStatement((node)); + case SyntaxKind.ThrowStatement: + return emitThrowStatement((node)); + case SyntaxKind.TryStatement: + return emitTryStatement((node)); + case SyntaxKind.DebuggerStatement: + return emitDebuggerStatement((node)); + // Declarations + case SyntaxKind.VariableDeclaration: + return emitVariableDeclaration((node)); + case SyntaxKind.VariableDeclarationList: + return emitVariableDeclarationList((node)); + case SyntaxKind.FunctionDeclaration: + return emitFunctionDeclaration((node)); + case SyntaxKind.ClassDeclaration: + return emitClassDeclaration((node)); + case SyntaxKind.InterfaceDeclaration: + return emitInterfaceDeclaration((node)); + case SyntaxKind.TypeAliasDeclaration: + return emitTypeAliasDeclaration((node)); + case SyntaxKind.EnumDeclaration: + return emitEnumDeclaration((node)); + case SyntaxKind.ModuleDeclaration: + return emitModuleDeclaration((node)); + case SyntaxKind.ModuleBlock: + return emitModuleBlock((node)); + case SyntaxKind.CaseBlock: + return emitCaseBlock((node)); + case SyntaxKind.NamespaceExportDeclaration: + return emitNamespaceExportDeclaration((node)); + case SyntaxKind.ImportEqualsDeclaration: + return emitImportEqualsDeclaration((node)); + case SyntaxKind.ImportDeclaration: + return emitImportDeclaration((node)); + case SyntaxKind.ImportClause: + return emitImportClause((node)); + case SyntaxKind.NamespaceImport: + return emitNamespaceImport((node)); + case SyntaxKind.NamedImports: + return emitNamedImports((node)); + case SyntaxKind.ImportSpecifier: + return emitImportSpecifier((node)); + case SyntaxKind.ExportAssignment: + return emitExportAssignment((node)); + case SyntaxKind.ExportDeclaration: + return emitExportDeclaration((node)); + case SyntaxKind.NamedExports: + return emitNamedExports((node)); + case SyntaxKind.ExportSpecifier: + return emitExportSpecifier((node)); + case SyntaxKind.MissingDeclaration: + return; + // Module references + case SyntaxKind.ExternalModuleReference: + return emitExternalModuleReference((node)); + // JSX (non-expression) + case SyntaxKind.JsxText: + return emitJsxText((node)); + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxOpeningFragment: + return emitJsxOpeningElementOrFragment((node)); + case SyntaxKind.JsxClosingElement: + case SyntaxKind.JsxClosingFragment: + return emitJsxClosingElementOrFragment((node)); + case SyntaxKind.JsxAttribute: + return emitJsxAttribute((node)); + case SyntaxKind.JsxAttributes: + return emitJsxAttributes((node)); + case SyntaxKind.JsxSpreadAttribute: + return emitJsxSpreadAttribute((node)); + case SyntaxKind.JsxExpression: + return emitJsxExpression((node)); + // Clauses + case SyntaxKind.CaseClause: + return emitCaseClause((node)); + case SyntaxKind.DefaultClause: + return emitDefaultClause((node)); + case SyntaxKind.HeritageClause: + return emitHeritageClause((node)); + case SyntaxKind.CatchClause: + return emitCatchClause((node)); + // Property assignments + case SyntaxKind.PropertyAssignment: + return emitPropertyAssignment((node)); + case SyntaxKind.ShorthandPropertyAssignment: + return emitShorthandPropertyAssignment((node)); + case SyntaxKind.SpreadAssignment: + return emitSpreadAssignment((node as SpreadAssignment)); + // Enum + case SyntaxKind.EnumMember: + return emitEnumMember((node)); + // JSDoc nodes (only used in codefixes currently) + case SyntaxKind.JSDocParameterTag: + case SyntaxKind.JSDocPropertyTag: + return emitJSDocPropertyLikeTag((node as JSDocPropertyLikeTag)); + case SyntaxKind.JSDocReturnTag: + case SyntaxKind.JSDocTypeTag: + case SyntaxKind.JSDocThisTag: + case SyntaxKind.JSDocEnumTag: + return emitJSDocSimpleTypedTag((node as JSDocTypeTag)); + case SyntaxKind.JSDocAugmentsTag: + return emitJSDocAugmentsTag((node as JSDocAugmentsTag)); + case SyntaxKind.JSDocTemplateTag: + return emitJSDocTemplateTag((node as JSDocTemplateTag)); + case SyntaxKind.JSDocTypedefTag: + return emitJSDocTypedefTag((node as JSDocTypedefTag)); + case SyntaxKind.JSDocCallbackTag: + return emitJSDocCallbackTag((node as JSDocCallbackTag)); + case SyntaxKind.JSDocSignature: + return emitJSDocSignature((node as JSDocSignature)); + case SyntaxKind.JSDocTypeLiteral: + return emitJSDocTypeLiteral((node as JSDocTypeLiteral)); + case SyntaxKind.JSDocClassTag: + case SyntaxKind.JSDocTag: + return emitJSDocSimpleTag((node as JSDocTag)); + case SyntaxKind.JSDocComment: + return emitJSDoc((node as JSDoc)); + // Transformation nodes (ignored) + } + if (isExpression(node)) { + hint = EmitHint.Expression; + if (substituteNode !== noEmitSubstitution) { + lastSubstitution = node = substituteNode(hint, node); + } } - else { - emit(node); + else if (isToken(node)) { + return writeTokenNode(node, writePunctuation); } } - - function emitComputedPropertyName(node: ComputedPropertyName) { - writePunctuation("["); - emitExpression(node.expression); - writePunctuation("]"); - } - - // - // Signature elements - // - - function emitTypeParameter(node: TypeParameterDeclaration) { - emit(node.name); - if (node.constraint) { - writeSpace(); - writeKeyword("extends"); - writeSpace(); - emit(node.constraint); - } - if (node.default) { - writeSpace(); - writeOperator("="); - writeSpace(); - emit(node.default); + if (hint === EmitHint.Expression) { + switch (node.kind) { + // Literals + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + return emitNumericOrBigIntLiteral((node)); + case SyntaxKind.StringLiteral: + case SyntaxKind.RegularExpressionLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + return emitLiteral((node)); + // Identifiers + case SyntaxKind.Identifier: + return emitIdentifier((node)); + // Reserved words + case SyntaxKind.FalseKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.SuperKeyword: + case SyntaxKind.TrueKeyword: + case SyntaxKind.ThisKeyword: + case SyntaxKind.ImportKeyword: + writeTokenNode(node, writeKeyword); + return; + // Expressions + case SyntaxKind.ArrayLiteralExpression: + return emitArrayLiteralExpression((node)); + case SyntaxKind.ObjectLiteralExpression: + return emitObjectLiteralExpression((node)); + case SyntaxKind.PropertyAccessExpression: + return emitPropertyAccessExpression((node)); + case SyntaxKind.ElementAccessExpression: + return emitElementAccessExpression((node)); + case SyntaxKind.CallExpression: + return emitCallExpression((node)); + case SyntaxKind.NewExpression: + return emitNewExpression((node)); + case SyntaxKind.TaggedTemplateExpression: + return emitTaggedTemplateExpression((node)); + case SyntaxKind.TypeAssertionExpression: + return emitTypeAssertionExpression((node)); + case SyntaxKind.ParenthesizedExpression: + return emitParenthesizedExpression((node)); + case SyntaxKind.FunctionExpression: + return emitFunctionExpression((node)); + case SyntaxKind.ArrowFunction: + return emitArrowFunction((node)); + case SyntaxKind.DeleteExpression: + return emitDeleteExpression((node)); + case SyntaxKind.TypeOfExpression: + return emitTypeOfExpression((node)); + case SyntaxKind.VoidExpression: + return emitVoidExpression((node)); + case SyntaxKind.AwaitExpression: + return emitAwaitExpression((node)); + case SyntaxKind.PrefixUnaryExpression: + return emitPrefixUnaryExpression((node)); + case SyntaxKind.PostfixUnaryExpression: + return emitPostfixUnaryExpression((node)); + case SyntaxKind.BinaryExpression: + return emitBinaryExpression((node)); + case SyntaxKind.ConditionalExpression: + return emitConditionalExpression((node)); + case SyntaxKind.TemplateExpression: + return emitTemplateExpression((node)); + case SyntaxKind.YieldExpression: + return emitYieldExpression((node)); + case SyntaxKind.SpreadElement: + return emitSpreadExpression((node)); + case SyntaxKind.ClassExpression: + return emitClassExpression((node)); + case SyntaxKind.OmittedExpression: + return; + case SyntaxKind.AsExpression: + return emitAsExpression((node)); + case SyntaxKind.NonNullExpression: + return emitNonNullExpression((node)); + case SyntaxKind.MetaProperty: + return emitMetaProperty((node)); + // JSX + case SyntaxKind.JsxElement: + return emitJsxElement((node)); + case SyntaxKind.JsxSelfClosingElement: + return emitJsxSelfClosingElement((node)); + case SyntaxKind.JsxFragment: + return emitJsxFragment((node)); + // Transformation nodes + case SyntaxKind.PartiallyEmittedExpression: + return emitPartiallyEmittedExpression((node)); + case SyntaxKind.CommaListExpression: + return emitCommaList((node)); } } - - function emitParameter(node: ParameterDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emit(node.dotDotDotToken); - emitNodeWithWriter(node.name, writeParameter); - emit(node.questionToken); - if (node.parent && node.parent.kind === SyntaxKind.JSDocFunctionType && !node.name) { - emit(node.type); - } - else { - emitTypeAnnotation(node.type); + } + function emitMappedTypeParameter(node: TypeParameterDeclaration): void { + emit(node.name); + writeSpace(); + writeKeyword("in"); + writeSpace(); + emit(node.constraint); + } + function pipelineEmitWithSubstitution(hint: EmitHint, node: Node) { + Debug.assert(lastNode === node || lastSubstitution === node); + const pipelinePhase = getNextPipelinePhase(PipelinePhase.Substitution, node); + lastSubstitution = substituteNode(hint, node); + pipelinePhase(hint, lastSubstitution); + Debug.assert(lastNode === node || lastSubstitution === node); + } + function getHelpersFromBundledSourceFiles(bundle: Bundle): string[] | undefined { + let result: string[] | undefined; + if (moduleKind === ModuleKind.None || printerOptions.noEmitHelpers) { + return undefined; + } + const bundledHelpers = createMap(); + for (const sourceFile of bundle.sourceFiles) { + const shouldSkip = getExternalHelpersModuleName(sourceFile) !== undefined; + const helpers = getSortedEmitHelpers(sourceFile); + if (!helpers) + continue; + for (const helper of helpers) { + if (!helper.scoped && !shouldSkip && !bundledHelpers.get(helper.name)) { + bundledHelpers.set(helper.name, true); + (result || (result = [])).push(helper.name); + } } - // The comment position has to fallback to any present node within the parameterdeclaration because as it turns out, the parser can make parameter declarations with _just_ an initializer. - emitInitializer(node.initializer, node.type ? node.type.end : node.questionToken ? node.questionToken.end : node.name ? node.name.end : node.modifiers ? node.modifiers.end : node.decorators ? node.decorators.end : node.pos, node); } - - function emitDecorator(decorator: Decorator) { - writePunctuation("@"); - emitExpression(decorator.expression); + return result; + } + function emitHelpers(node: Node) { + let helpersEmitted = false; + const bundle = node.kind === SyntaxKind.Bundle ? node : undefined; + if (bundle && moduleKind === ModuleKind.None) { + return; + } + const numPrepends = bundle ? bundle.prepends.length : 0; + const numNodes = bundle ? bundle.sourceFiles.length + numPrepends : 1; + for (let i = 0; i < numNodes; i++) { + const currentNode = bundle ? i < numPrepends ? bundle.prepends[i] : bundle.sourceFiles[i - numPrepends] : node; + const sourceFile = isSourceFile(currentNode) ? currentNode : isUnparsedSource(currentNode) ? undefined : currentSourceFile!; + const shouldSkip = printerOptions.noEmitHelpers || (!!sourceFile && hasRecordedExternalHelpers(sourceFile)); + const shouldBundle = (isSourceFile(currentNode) || isUnparsedSource(currentNode)) && !isOwnFileEmit; + const helpers = isUnparsedSource(currentNode) ? currentNode.helpers : getSortedEmitHelpers(currentNode); + if (helpers) { + for (const helper of helpers) { + if (!helper.scoped) { + // Skip the helper if it can be skipped and the noEmitHelpers compiler + // option is set, or if it can be imported and the importHelpers compiler + // option is set. + if (shouldSkip) + continue; + // Skip the helper if it can be bundled but hasn't already been emitted and we + // are emitting a bundled module. + if (shouldBundle) { + if (bundledHelpers.get(helper.name)) { + continue; + } + bundledHelpers.set(helper.name, true); + } + } + else if (bundle) { + // Skip the helper if it is scoped and we are emitting bundled helpers + continue; + } + const pos = getTextPosWithWriteLine(); + if (typeof helper.text === "string") { + writeLines(helper.text); + } + else { + writeLines(helper.text(makeFileLevelOptimisticUniqueName)); + } + if (bundleFileInfo) + bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.EmitHelpers, data: helper.name }); + helpersEmitted = true; + } + } } - - // - // Type members - // - - function emitPropertySignature(node: PropertySignature) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emitNodeWithWriter(node.name, writeProperty); - emit(node.questionToken); - emitTypeAnnotation(node.type); - writeTrailingSemicolon(); + return helpersEmitted; + } + function getSortedEmitHelpers(node: Node) { + const helpers = getEmitHelpers(node); + return helpers && stableSort(helpers, compareEmitHelpers); + } + // + // Literals/Pseudo-literals + // + // SyntaxKind.NumericLiteral + // SyntaxKind.BigIntLiteral + function emitNumericOrBigIntLiteral(node: NumericLiteral | BigIntLiteral) { + emitLiteral(node); + } + // SyntaxKind.StringLiteral + // SyntaxKind.RegularExpressionLiteral + // SyntaxKind.NoSubstitutionTemplateLiteral + // SyntaxKind.TemplateHead + // SyntaxKind.TemplateMiddle + // SyntaxKind.TemplateTail + function emitLiteral(node: LiteralLikeNode) { + const text = getLiteralTextOfNode(node, printerOptions.neverAsciiEscape); + if ((printerOptions.sourceMap || printerOptions.inlineSourceMap) + && (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))) { + writeLiteral(text); } - - function emitPropertyDeclaration(node: PropertyDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emit(node.name); - emit(node.questionToken); - emit(node.exclamationToken); - emitTypeAnnotation(node.type); - emitInitializer(node.initializer, node.type ? node.type.end : node.questionToken ? node.questionToken.end : node.name.end, node); - writeTrailingSemicolon(); + else { + // Quick info expects all literals to be called with writeStringLiteral, as there's no specific type for numberLiterals + writeStringLiteral(text); } - - function emitMethodSignature(node: MethodSignature) { - pushNameGenerationScope(node); - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emit(node.name); - emit(node.questionToken); - emitTypeParameters(node, node.typeParameters); - emitParameters(node, node.parameters); - emitTypeAnnotation(node.type); - writeTrailingSemicolon(); - popNameGenerationScope(node); - } - - function emitMethodDeclaration(node: MethodDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emit(node.asteriskToken); - emit(node.name); - emit(node.questionToken); - emitSignatureAndBody(node, emitSignatureHead); - } - - function emitConstructor(node: ConstructorDeclaration) { - emitModifiers(node, node.modifiers); - writeKeyword("constructor"); - emitSignatureAndBody(node, emitSignatureHead); - } - - function emitAccessorDeclaration(node: AccessorDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - writeKeyword(node.kind === SyntaxKind.GetAccessor ? "get" : "set"); - writeSpace(); - emit(node.name); - emitSignatureAndBody(node, emitSignatureHead); - } - - function emitCallSignature(node: CallSignatureDeclaration) { - pushNameGenerationScope(node); - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emitTypeParameters(node, node.typeParameters); - emitParameters(node, node.parameters); - emitTypeAnnotation(node.type); - writeTrailingSemicolon(); - popNameGenerationScope(node); - } - - function emitConstructSignature(node: ConstructSignatureDeclaration) { - pushNameGenerationScope(node); - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - writeKeyword("new"); - writeSpace(); - emitTypeParameters(node, node.typeParameters); - emitParameters(node, node.parameters); - emitTypeAnnotation(node.type); - writeTrailingSemicolon(); - popNameGenerationScope(node); + } + // SyntaxKind.UnparsedSource + // SyntaxKind.UnparsedPrepend + function emitUnparsedSourceOrPrepend(unparsed: UnparsedSource | UnparsedPrepend) { + for (const text of unparsed.texts) { + writeLine(); + emit(text); } - - function emitIndexSignature(node: IndexSignatureDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emitParametersForIndexSignature(node, node.parameters); - emitTypeAnnotation(node.type); - writeTrailingSemicolon(); + } + // SyntaxKind.UnparsedPrologue + // SyntaxKind.UnparsedText + // SyntaxKind.UnparsedInternal + // SyntaxKind.UnparsedSyntheticReference + function writeUnparsedNode(unparsed: UnparsedNode) { + writer.rawWrite(unparsed.parent.text.substring(unparsed.pos, unparsed.end)); + } + // SyntaxKind.UnparsedText + // SyntaxKind.UnparsedInternal + function emitUnparsedTextLike(unparsed: UnparsedTextLike) { + const pos = getTextPosWithWriteLine(); + writeUnparsedNode(unparsed); + if (bundleFileInfo) { + updateOrPushBundleFileTextLike(pos, writer.getTextPos(), unparsed.kind === SyntaxKind.UnparsedText ? + BundleFileSectionKind.Text : + BundleFileSectionKind.Internal); } - - function emitSemicolonClassElement() { - writeTrailingSemicolon(); + } + // SyntaxKind.UnparsedSyntheticReference + function emitUnparsedSyntheticReference(unparsed: UnparsedSyntheticReference) { + const pos = getTextPosWithWriteLine(); + writeUnparsedNode(unparsed); + if (bundleFileInfo) { + const section = clone(unparsed.section); + section.pos = pos; + section.end = writer.getTextPos(); + bundleFileInfo.sections.push(section); } - - // - // Types - // - - function emitTypePredicate(node: TypePredicateNode) { - if (node.assertsModifier) { - emit(node.assertsModifier); - writeSpace(); - } - emit(node.parameterName); - if (node.type) { - writeSpace(); - writeKeyword("is"); - writeSpace(); - emit(node.type); - } + } + // + // Identifiers + // + function emitIdentifier(node: Identifier) { + const writeText = node.symbol ? writeSymbol : write; + writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol); + emitList(node, node.typeArguments, ListFormat.TypeParameters); // Call emitList directly since it could be an array of TypeParameterDeclarations _or_ type arguments + } + // + // Names + // + function emitQualifiedName(node: QualifiedName) { + emitEntityName(node.left); + writePunctuation("."); + emit(node.right); + } + function emitEntityName(node: EntityName) { + if (node.kind === SyntaxKind.Identifier) { + emitExpression(node); } - - function emitTypeReference(node: TypeReferenceNode) { - emit(node.typeName); - emitTypeArguments(node, node.typeArguments); + else { + emit(node); } - - function emitFunctionType(node: FunctionTypeNode) { - pushNameGenerationScope(node); - emitTypeParameters(node, node.typeParameters); - emitParametersForArrow(node, node.parameters); + } + function emitComputedPropertyName(node: ComputedPropertyName) { + writePunctuation("["); + emitExpression(node.expression); + writePunctuation("]"); + } + // + // Signature elements + // + function emitTypeParameter(node: TypeParameterDeclaration) { + emit(node.name); + if (node.constraint) { writeSpace(); - writePunctuation("=>"); + writeKeyword("extends"); writeSpace(); - emit(node.type); - popNameGenerationScope(node); - } - - function emitJSDocFunctionType(node: JSDocFunctionType) { - writeKeyword("function"); - emitParameters(node, node.parameters); - writePunctuation(":"); - emit(node.type); - } - - - function emitJSDocNullableType(node: JSDocNullableType) { - writePunctuation("?"); - emit(node.type); - } - - function emitJSDocNonNullableType(node: JSDocNonNullableType) { - writePunctuation("!"); - emit(node.type); - } - - function emitJSDocOptionalType(node: JSDocOptionalType) { - emit(node.type); - writePunctuation("="); + emit(node.constraint); } - - function emitConstructorType(node: ConstructorTypeNode) { - pushNameGenerationScope(node); - writeKeyword("new"); + if (node.default) { writeSpace(); - emitTypeParameters(node, node.typeParameters); - emitParameters(node, node.parameters); + writeOperator("="); writeSpace(); - writePunctuation("=>"); - writeSpace(); - emit(node.type); - popNameGenerationScope(node); + emit(node.default); } - - function emitTypeQuery(node: TypeQueryNode) { - writeKeyword("typeof"); - writeSpace(); - emit(node.exprName); - } - - function emitTypeLiteral(node: TypeLiteralNode) { - writePunctuation("{"); - const flags = getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineTypeLiteralMembers : ListFormat.MultiLineTypeLiteralMembers; - emitList(node, node.members, flags | ListFormat.NoSpaceIfEmpty); - writePunctuation("}"); - } - - function emitArrayType(node: ArrayTypeNode) { - emit(node.elementType); - writePunctuation("["); - writePunctuation("]"); - } - - function emitRestOrJSDocVariadicType(node: RestTypeNode | JSDocVariadicType) { - writePunctuation("..."); - emit(node.type); - } - - function emitTupleType(node: TupleTypeNode) { - writePunctuation("["); - emitList(node, node.elementTypes, ListFormat.TupleTypeElements); - writePunctuation("]"); - } - - function emitOptionalType(node: OptionalTypeNode) { + } + function emitParameter(node: ParameterDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emit(node.dotDotDotToken); + emitNodeWithWriter(node.name, writeParameter); + emit(node.questionToken); + if (node.parent && node.parent.kind === SyntaxKind.JSDocFunctionType && !node.name) { emit(node.type); - writePunctuation("?"); - } - - function emitUnionType(node: UnionTypeNode) { - emitList(node, node.types, ListFormat.UnionTypeConstituents); } - - function emitIntersectionType(node: IntersectionTypeNode) { - emitList(node, node.types, ListFormat.IntersectionTypeConstituents); + else { + emitTypeAnnotation(node.type); } - - function emitConditionalType(node: ConditionalTypeNode) { - emit(node.checkType); - writeSpace(); - writeKeyword("extends"); - writeSpace(); - emit(node.extendsType); - writeSpace(); - writePunctuation("?"); - writeSpace(); - emit(node.trueType); - writeSpace(); - writePunctuation(":"); + // The comment position has to fallback to any present node within the parameterdeclaration because as it turns out, the parser can make parameter declarations with _just_ an initializer. + emitInitializer(node.initializer, node.type ? node.type.end : node.questionToken ? node.questionToken.end : node.name ? node.name.end : node.modifiers ? node.modifiers.end : node.decorators ? node.decorators.end : node.pos, node); + } + function emitDecorator(decorator: Decorator) { + writePunctuation("@"); + emitExpression(decorator.expression); + } + // + // Type members + // + function emitPropertySignature(node: PropertySignature) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emitNodeWithWriter(node.name, writeProperty); + emit(node.questionToken); + emitTypeAnnotation(node.type); + writeTrailingSemicolon(); + } + function emitPropertyDeclaration(node: PropertyDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emit(node.name); + emit(node.questionToken); + emit(node.exclamationToken); + emitTypeAnnotation(node.type); + emitInitializer(node.initializer, node.type ? node.type.end : node.questionToken ? node.questionToken.end : node.name.end, node); + writeTrailingSemicolon(); + } + function emitMethodSignature(node: MethodSignature) { + pushNameGenerationScope(node); + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emit(node.name); + emit(node.questionToken); + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + emitTypeAnnotation(node.type); + writeTrailingSemicolon(); + popNameGenerationScope(node); + } + function emitMethodDeclaration(node: MethodDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emit(node.asteriskToken); + emit(node.name); + emit(node.questionToken); + emitSignatureAndBody(node, emitSignatureHead); + } + function emitConstructor(node: ConstructorDeclaration) { + emitModifiers(node, node.modifiers); + writeKeyword("constructor"); + emitSignatureAndBody(node, emitSignatureHead); + } + function emitAccessorDeclaration(node: AccessorDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeKeyword(node.kind === SyntaxKind.GetAccessor ? "get" : "set"); + writeSpace(); + emit(node.name); + emitSignatureAndBody(node, emitSignatureHead); + } + function emitCallSignature(node: CallSignatureDeclaration) { + pushNameGenerationScope(node); + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + emitTypeAnnotation(node.type); + writeTrailingSemicolon(); + popNameGenerationScope(node); + } + function emitConstructSignature(node: ConstructSignatureDeclaration) { + pushNameGenerationScope(node); + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeKeyword("new"); + writeSpace(); + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + emitTypeAnnotation(node.type); + writeTrailingSemicolon(); + popNameGenerationScope(node); + } + function emitIndexSignature(node: IndexSignatureDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emitParametersForIndexSignature(node, node.parameters); + emitTypeAnnotation(node.type); + writeTrailingSemicolon(); + } + function emitSemicolonClassElement() { + writeTrailingSemicolon(); + } + // + // Types + // + function emitTypePredicate(node: TypePredicateNode) { + if (node.assertsModifier) { + emit(node.assertsModifier); writeSpace(); - emit(node.falseType); } - - function emitInferType(node: InferTypeNode) { - writeKeyword("infer"); + emit(node.parameterName); + if (node.type) { writeSpace(); - emit(node.typeParameter); - } - - function emitParenthesizedType(node: ParenthesizedTypeNode) { - writePunctuation("("); - emit(node.type); - writePunctuation(")"); - } - - function emitThisType() { - writeKeyword("this"); - } - - function emitTypeOperator(node: TypeOperatorNode) { - writeTokenText(node.operator, writeKeyword); + writeKeyword("is"); writeSpace(); emit(node.type); } - - function emitIndexedAccessType(node: IndexedAccessTypeNode) { - emit(node.objectType); - writePunctuation("["); - emit(node.indexType); - writePunctuation("]"); - } - - function emitMappedType(node: MappedTypeNode) { - const emitFlags = getEmitFlags(node); - writePunctuation("{"); - if (emitFlags & EmitFlags.SingleLine) { - writeSpace(); - } - else { - writeLine(); - increaseIndent(); - } - if (node.readonlyToken) { - emit(node.readonlyToken); - if (node.readonlyToken.kind !== SyntaxKind.ReadonlyKeyword) { - writeKeyword("readonly"); - } - writeSpace(); - } - writePunctuation("["); - - pipelineEmit(EmitHint.MappedTypeParameter, node.typeParameter); - - writePunctuation("]"); - if (node.questionToken) { - emit(node.questionToken); - if (node.questionToken.kind !== SyntaxKind.QuestionToken) { - writePunctuation("?"); - } - } - writePunctuation(":"); + } + function emitTypeReference(node: TypeReferenceNode) { + emit(node.typeName); + emitTypeArguments(node, node.typeArguments); + } + function emitFunctionType(node: FunctionTypeNode) { + pushNameGenerationScope(node); + emitTypeParameters(node, node.typeParameters); + emitParametersForArrow(node, node.parameters); + writeSpace(); + writePunctuation("=>"); + writeSpace(); + emit(node.type); + popNameGenerationScope(node); + } + function emitJSDocFunctionType(node: JSDocFunctionType) { + writeKeyword("function"); + emitParameters(node, node.parameters); + writePunctuation(":"); + emit(node.type); + } + function emitJSDocNullableType(node: JSDocNullableType) { + writePunctuation("?"); + emit(node.type); + } + function emitJSDocNonNullableType(node: JSDocNonNullableType) { + writePunctuation("!"); + emit(node.type); + } + function emitJSDocOptionalType(node: JSDocOptionalType) { + emit(node.type); + writePunctuation("="); + } + function emitConstructorType(node: ConstructorTypeNode) { + pushNameGenerationScope(node); + writeKeyword("new"); + writeSpace(); + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + writeSpace(); + writePunctuation("=>"); + writeSpace(); + emit(node.type); + popNameGenerationScope(node); + } + function emitTypeQuery(node: TypeQueryNode) { + writeKeyword("typeof"); + writeSpace(); + emit(node.exprName); + } + function emitTypeLiteral(node: TypeLiteralNode) { + writePunctuation("{"); + const flags = getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineTypeLiteralMembers : ListFormat.MultiLineTypeLiteralMembers; + emitList(node, node.members, flags | ListFormat.NoSpaceIfEmpty); + writePunctuation("}"); + } + function emitArrayType(node: ArrayTypeNode) { + emit(node.elementType); + writePunctuation("["); + writePunctuation("]"); + } + function emitRestOrJSDocVariadicType(node: RestTypeNode | JSDocVariadicType) { + writePunctuation("..."); + emit(node.type); + } + function emitTupleType(node: TupleTypeNode) { + writePunctuation("["); + emitList(node, node.elementTypes, ListFormat.TupleTypeElements); + writePunctuation("]"); + } + function emitOptionalType(node: OptionalTypeNode) { + emit(node.type); + writePunctuation("?"); + } + function emitUnionType(node: UnionTypeNode) { + emitList(node, node.types, ListFormat.UnionTypeConstituents); + } + function emitIntersectionType(node: IntersectionTypeNode) { + emitList(node, node.types, ListFormat.IntersectionTypeConstituents); + } + function emitConditionalType(node: ConditionalTypeNode) { + emit(node.checkType); + writeSpace(); + writeKeyword("extends"); + writeSpace(); + emit(node.extendsType); + writeSpace(); + writePunctuation("?"); + writeSpace(); + emit(node.trueType); + writeSpace(); + writePunctuation(":"); + writeSpace(); + emit(node.falseType); + } + function emitInferType(node: InferTypeNode) { + writeKeyword("infer"); + writeSpace(); + emit(node.typeParameter); + } + function emitParenthesizedType(node: ParenthesizedTypeNode) { + writePunctuation("("); + emit(node.type); + writePunctuation(")"); + } + function emitThisType() { + writeKeyword("this"); + } + function emitTypeOperator(node: TypeOperatorNode) { + writeTokenText(node.operator, writeKeyword); + writeSpace(); + emit(node.type); + } + function emitIndexedAccessType(node: IndexedAccessTypeNode) { + emit(node.objectType); + writePunctuation("["); + emit(node.indexType); + writePunctuation("]"); + } + function emitMappedType(node: MappedTypeNode) { + const emitFlags = getEmitFlags(node); + writePunctuation("{"); + if (emitFlags & EmitFlags.SingleLine) { writeSpace(); - emit(node.type); - writeTrailingSemicolon(); - if (emitFlags & EmitFlags.SingleLine) { - writeSpace(); - } - else { - writeLine(); - decreaseIndent(); - } - writePunctuation("}"); - } - - function emitLiteralType(node: LiteralTypeNode) { - emitExpression(node.literal); - } - - function emitImportTypeNode(node: ImportTypeNode) { - if (node.isTypeOf) { - writeKeyword("typeof"); - writeSpace(); - } - writeKeyword("import"); - writePunctuation("("); - emit(node.argument); - writePunctuation(")"); - if (node.qualifier) { - writePunctuation("."); - emit(node.qualifier); - } - emitTypeArguments(node, node.typeArguments); - } - - // - // Binding patterns - // - - function emitObjectBindingPattern(node: ObjectBindingPattern) { - writePunctuation("{"); - emitList(node, node.elements, ListFormat.ObjectBindingPatternElements); - writePunctuation("}"); - } - - function emitArrayBindingPattern(node: ArrayBindingPattern) { - writePunctuation("["); - emitList(node, node.elements, ListFormat.ArrayBindingPatternElements); - writePunctuation("]"); - } - - function emitBindingElement(node: BindingElement) { - emit(node.dotDotDotToken); - if (node.propertyName) { - emit(node.propertyName); - writePunctuation(":"); - writeSpace(); - } - emit(node.name); - emitInitializer(node.initializer, node.name.end, node); } - - // - // Expressions - // - - function emitArrayLiteralExpression(node: ArrayLiteralExpression) { - const elements = node.elements; - const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; - emitExpressionList(node, elements, ListFormat.ArrayLiteralExpressionElements | preferNewLine); - } - - function emitObjectLiteralExpression(node: ObjectLiteralExpression) { - forEach(node.properties, generateMemberNames); - - const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; - if (indentedFlag) { - increaseIndent(); - } - - const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; - const allowTrailingComma = currentSourceFile!.languageVersion >= ScriptTarget.ES5 && !isJsonSourceFile(currentSourceFile!) ? ListFormat.AllowTrailingComma : ListFormat.None; - emitList(node, node.properties, ListFormat.ObjectLiteralExpressionProperties | allowTrailingComma | preferNewLine); - - if (indentedFlag) { - decreaseIndent(); - } + else { + writeLine(); + increaseIndent(); } - - function emitPropertyAccessExpression(node: PropertyAccessExpression) { - const expression = cast(emitExpression(node.expression), isExpression); - const token = getDotOrQuestionDotToken(node); - const indentBeforeDot = needsIndentation(node, node.expression, token); - const indentAfterDot = needsIndentation(node, token, node.name); - - increaseIndentIf(indentBeforeDot, /*writeSpaceIfNotIndenting*/ false); - - const shouldEmitDotDot = - token.kind !== SyntaxKind.QuestionDotToken && - mayNeedDotDotForPropertyAccess(expression) && - !writer.hasTrailingComment() && - !writer.hasTrailingWhitespace(); - - if (shouldEmitDotDot) { - writePunctuation("."); + if (node.readonlyToken) { + emit(node.readonlyToken); + if (node.readonlyToken.kind !== SyntaxKind.ReadonlyKeyword) { + writeKeyword("readonly"); } - - emitTokenWithComment(token.kind, node.expression.end, writePunctuation, node); - increaseIndentIf(indentAfterDot, /*writeSpaceIfNotIndenting*/ false); - emit(node.name); - decreaseIndentIf(indentBeforeDot, indentAfterDot); - } - - // 1..toString is a valid property access, emit a dot after the literal - // Also emit a dot if expression is a integer const enum value - it will appear in generated code as numeric literal - function mayNeedDotDotForPropertyAccess(expression: Expression) { - expression = skipPartiallyEmittedExpressions(expression); - if (isNumericLiteral(expression)) { - // check if numeric literal is a decimal literal that was originally written with a dot - const text = getLiteralTextOfNode(expression, /*neverAsciiEscape*/ true); - // If he number will be printed verbatim and it doesn't already contain a dot, add one - // if the expression doesn't have any comments that will be emitted. - return !expression.numericLiteralFlags && !stringContains(text, tokenToString(SyntaxKind.DotToken)!); - } - else if (isAccessExpression(expression)) { - // check if constant enum value is integer - const constantValue = getConstantValue(expression); - // isFinite handles cases when constantValue is undefined - return typeof constantValue === "number" && isFinite(constantValue) - && Math.floor(constantValue) === constantValue; - } - } - - function emitElementAccessExpression(node: ElementAccessExpression) { - emitExpression(node.expression); - emit(node.questionDotToken); - emitTokenWithComment(SyntaxKind.OpenBracketToken, node.expression.end, writePunctuation, node); - emitExpression(node.argumentExpression); - emitTokenWithComment(SyntaxKind.CloseBracketToken, node.argumentExpression.end, writePunctuation, node); - } - - function emitCallExpression(node: CallExpression) { - emitExpression(node.expression); - emit(node.questionDotToken); - emitTypeArguments(node, node.typeArguments); - emitExpressionList(node, node.arguments, ListFormat.CallExpressionArguments); - } - - function emitNewExpression(node: NewExpression) { - emitTokenWithComment(SyntaxKind.NewKeyword, node.pos, writeKeyword, node); writeSpace(); - emitExpression(node.expression); - emitTypeArguments(node, node.typeArguments); - emitExpressionList(node, node.arguments, ListFormat.NewExpressionArguments); } - - function emitTaggedTemplateExpression(node: TaggedTemplateExpression) { - emitExpression(node.tag); - emitTypeArguments(node, node.typeArguments); - writeSpace(); - emitExpression(node.template); - } - - function emitTypeAssertionExpression(node: TypeAssertion) { - writePunctuation("<"); - emit(node.type); - writePunctuation(">"); - emitExpression(node.expression); + writePunctuation("["); + pipelineEmit(EmitHint.MappedTypeParameter, node.typeParameter); + writePunctuation("]"); + if (node.questionToken) { + emit(node.questionToken); + if (node.questionToken.kind !== SyntaxKind.QuestionToken) { + writePunctuation("?"); + } } - - function emitParenthesizedExpression(node: ParenthesizedExpression) { - const openParenPos = emitTokenWithComment(SyntaxKind.OpenParenToken, node.pos, writePunctuation, node); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression ? node.expression.end : openParenPos, writePunctuation, node); - } - - function emitFunctionExpression(node: FunctionExpression) { - generateNameIfNeeded(node.name); - emitFunctionDeclarationOrExpression(node); - } - - function emitArrowFunction(node: ArrowFunction) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emitSignatureAndBody(node, emitArrowFunctionHead); - } - - function emitArrowFunctionHead(node: ArrowFunction) { - emitTypeParameters(node, node.typeParameters); - emitParametersForArrow(node, node.parameters); - emitTypeAnnotation(node.type); + writePunctuation(":"); + writeSpace(); + emit(node.type); + writeTrailingSemicolon(); + if (emitFlags & EmitFlags.SingleLine) { writeSpace(); - emit(node.equalsGreaterThanToken); } - - function emitDeleteExpression(node: DeleteExpression) { - emitTokenWithComment(SyntaxKind.DeleteKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitExpression(node.expression); + else { + writeLine(); + decreaseIndent(); } - - function emitTypeOfExpression(node: TypeOfExpression) { - emitTokenWithComment(SyntaxKind.TypeOfKeyword, node.pos, writeKeyword, node); + writePunctuation("}"); + } + function emitLiteralType(node: LiteralTypeNode) { + emitExpression(node.literal); + } + function emitImportTypeNode(node: ImportTypeNode) { + if (node.isTypeOf) { + writeKeyword("typeof"); writeSpace(); - emitExpression(node.expression); } - - function emitVoidExpression(node: VoidExpression) { - emitTokenWithComment(SyntaxKind.VoidKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitExpression(node.expression); + writeKeyword("import"); + writePunctuation("("); + emit(node.argument); + writePunctuation(")"); + if (node.qualifier) { + writePunctuation("."); + emit(node.qualifier); } - - function emitAwaitExpression(node: AwaitExpression) { - emitTokenWithComment(SyntaxKind.AwaitKeyword, node.pos, writeKeyword, node); + emitTypeArguments(node, node.typeArguments); + } + // + // Binding patterns + // + function emitObjectBindingPattern(node: ObjectBindingPattern) { + writePunctuation("{"); + emitList(node, node.elements, ListFormat.ObjectBindingPatternElements); + writePunctuation("}"); + } + function emitArrayBindingPattern(node: ArrayBindingPattern) { + writePunctuation("["); + emitList(node, node.elements, ListFormat.ArrayBindingPatternElements); + writePunctuation("]"); + } + function emitBindingElement(node: BindingElement) { + emit(node.dotDotDotToken); + if (node.propertyName) { + emit(node.propertyName); + writePunctuation(":"); writeSpace(); - emitExpression(node.expression); - } - - function emitPrefixUnaryExpression(node: PrefixUnaryExpression) { - writeTokenText(node.operator, writeOperator); - if (shouldEmitWhitespaceBeforeOperand(node)) { - writeSpace(); - } - emitExpression(node.operand); - } - - function shouldEmitWhitespaceBeforeOperand(node: PrefixUnaryExpression) { - // In some cases, we need to emit a space between the operator and the operand. One obvious case - // is when the operator is an identifier, like delete or typeof. We also need to do this for plus - // and minus expressions in certain cases. Specifically, consider the following two cases (parens - // are just for clarity of exposition, and not part of the source code): - // - // (+(+1)) - // (+(++1)) - // - // We need to emit a space in both cases. In the first case, the absence of a space will make - // the resulting expression a prefix increment operation. And in the second, it will make the resulting - // expression a prefix increment whose operand is a plus expression - (++(+x)) - // The same is true of minus of course. - const operand = node.operand; - return operand.kind === SyntaxKind.PrefixUnaryExpression - && ((node.operator === SyntaxKind.PlusToken && ((operand).operator === SyntaxKind.PlusToken || (operand).operator === SyntaxKind.PlusPlusToken)) - || (node.operator === SyntaxKind.MinusToken && ((operand).operator === SyntaxKind.MinusToken || (operand).operator === SyntaxKind.MinusMinusToken))); - } - - function emitPostfixUnaryExpression(node: PostfixUnaryExpression) { - emitExpression(node.operand); - writeTokenText(node.operator, writeOperator); - } - - function emitBinaryExpression(node: BinaryExpression) { - const isCommaOperator = node.operatorToken.kind !== SyntaxKind.CommaToken; - const indentBeforeOperator = needsIndentation(node, node.left, node.operatorToken); - const indentAfterOperator = needsIndentation(node, node.operatorToken, node.right); - - emitExpression(node.left); - increaseIndentIf(indentBeforeOperator, isCommaOperator); - emitLeadingCommentsOfPosition(node.operatorToken.pos); - writeTokenNode(node.operatorToken, node.operatorToken.kind === SyntaxKind.InKeyword ? writeKeyword : writeOperator); - emitTrailingCommentsOfPosition(node.operatorToken.end, /*prefixSpace*/ true); // Binary operators should have a space before the comment starts - increaseIndentIf(indentAfterOperator, /*writeSpaceIfNotIndenting*/ true); - emitExpression(node.right); - decreaseIndentIf(indentBeforeOperator, indentAfterOperator); - } - - function emitConditionalExpression(node: ConditionalExpression) { - const indentBeforeQuestion = needsIndentation(node, node.condition, node.questionToken); - const indentAfterQuestion = needsIndentation(node, node.questionToken, node.whenTrue); - const indentBeforeColon = needsIndentation(node, node.whenTrue, node.colonToken); - const indentAfterColon = needsIndentation(node, node.colonToken, node.whenFalse); - - emitExpression(node.condition); - increaseIndentIf(indentBeforeQuestion, /*writeSpaceIfNotIndenting*/ true); - emit(node.questionToken); - increaseIndentIf(indentAfterQuestion, /*writeSpaceIfNotIndenting*/ true); - emitExpression(node.whenTrue); - decreaseIndentIf(indentBeforeQuestion, indentAfterQuestion); - - increaseIndentIf(indentBeforeColon, /*writeSpaceIfNotIndenting*/ true); - emit(node.colonToken); - increaseIndentIf(indentAfterColon, /*writeSpaceIfNotIndenting*/ true); - emitExpression(node.whenFalse); - decreaseIndentIf(indentBeforeColon, indentAfterColon); - } - - function emitTemplateExpression(node: TemplateExpression) { - emit(node.head); - emitList(node, node.templateSpans, ListFormat.TemplateExpressionSpans); - } - - function emitYieldExpression(node: YieldExpression) { - emitTokenWithComment(SyntaxKind.YieldKeyword, node.pos, writeKeyword, node); - emit(node.asteriskToken); - emitExpressionWithLeadingSpace(node.expression); - } - - function emitSpreadExpression(node: SpreadElement) { - emitTokenWithComment(SyntaxKind.DotDotDotToken, node.pos, writePunctuation, node); - emitExpression(node.expression); - } - - function emitClassExpression(node: ClassExpression) { - generateNameIfNeeded(node.name); - emitClassDeclarationOrExpression(node); - } - - function emitExpressionWithTypeArguments(node: ExpressionWithTypeArguments) { - emitExpression(node.expression); - emitTypeArguments(node, node.typeArguments); } - - function emitAsExpression(node: AsExpression) { - emitExpression(node.expression); - if (node.type) { - writeSpace(); - writeKeyword("as"); - writeSpace(); - emit(node.type); - } + emit(node.name); + emitInitializer(node.initializer, node.name.end, node); + } + // + // Expressions + // + function emitArrayLiteralExpression(node: ArrayLiteralExpression) { + const elements = node.elements; + const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; + emitExpressionList(node, elements, ListFormat.ArrayLiteralExpressionElements | preferNewLine); + } + function emitObjectLiteralExpression(node: ObjectLiteralExpression) { + forEach(node.properties, generateMemberNames); + const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; + if (indentedFlag) { + increaseIndent(); } - - function emitNonNullExpression(node: NonNullExpression) { - emitExpression(node.expression); - writeOperator("!"); + const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; + const allowTrailingComma = currentSourceFile!.languageVersion >= ScriptTarget.ES5 && !isJsonSourceFile((currentSourceFile!)) ? ListFormat.AllowTrailingComma : ListFormat.None; + emitList(node, node.properties, ListFormat.ObjectLiteralExpressionProperties | allowTrailingComma | preferNewLine); + if (indentedFlag) { + decreaseIndent(); } - - function emitMetaProperty(node: MetaProperty) { - writeToken(node.keywordToken, node.pos, writePunctuation); + } + function emitPropertyAccessExpression(node: PropertyAccessExpression) { + const expression = cast(emitExpression(node.expression), isExpression); + const token = getDotOrQuestionDotToken(node); + const indentBeforeDot = needsIndentation(node, node.expression, token); + const indentAfterDot = needsIndentation(node, token, node.name); + increaseIndentIf(indentBeforeDot, /*writeSpaceIfNotIndenting*/ false); + const shouldEmitDotDot = token.kind !== SyntaxKind.QuestionDotToken && + mayNeedDotDotForPropertyAccess(expression) && + !writer.hasTrailingComment() && + !writer.hasTrailingWhitespace(); + if (shouldEmitDotDot) { writePunctuation("."); - emit(node.name); - } - - // - // Misc - // - - function emitTemplateSpan(node: TemplateSpan) { - emitExpression(node.expression); - emit(node.literal); } - - // - // Statements - // - - function emitBlock(node: Block) { - emitBlockStatements(node, /*forceSingleLine*/ !node.multiLine && isEmptyBlock(node)); - } - - function emitBlockStatements(node: BlockLike, forceSingleLine: boolean) { - emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, /*contextNode*/ node); - const format = forceSingleLine || getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineBlockStatements : ListFormat.MultiLineBlockStatements; - emitList(node, node.statements, format); - emitTokenWithComment(SyntaxKind.CloseBraceToken, node.statements.end, writePunctuation, /*contextNode*/ node, /*indentLeading*/ !!(format & ListFormat.MultiLine)); - } - - function emitVariableStatement(node: VariableStatement) { - emitModifiers(node, node.modifiers); - emit(node.declarationList); - writeTrailingSemicolon(); - } - - function emitEmptyStatement(isEmbeddedStatement: boolean) { - // While most trailing semicolons are possibly insignificant, an embedded "empty" - // statement is significant and cannot be elided by a trailing-semicolon-omitting writer. - if (isEmbeddedStatement) { - writePunctuation(";"); - } - else { - writeTrailingSemicolon(); - } - } - - - function emitExpressionStatement(node: ExpressionStatement) { - emitExpression(node.expression); - // Emit semicolon in non json files - // or if json file that created synthesized expression(eg.define expression statement when --out and amd code generation) - if (!isJsonSourceFile(currentSourceFile!) || nodeIsSynthesized(node.expression)) { - writeTrailingSemicolon(); - } - } - - function emitIfStatement(node: IfStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.IfKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - emitEmbeddedStatement(node, node.thenStatement); - if (node.elseStatement) { - writeLineOrSpace(node); - emitTokenWithComment(SyntaxKind.ElseKeyword, node.thenStatement.end, writeKeyword, node); - if (node.elseStatement.kind === SyntaxKind.IfStatement) { - writeSpace(); - emit(node.elseStatement); - } - else { - emitEmbeddedStatement(node, node.elseStatement); - } - } - } - - function emitWhileClause(node: WhileStatement | DoStatement, startPos: number) { - const openParenPos = emitTokenWithComment(SyntaxKind.WhileKeyword, startPos, writeKeyword, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - } - - function emitDoStatement(node: DoStatement) { - emitTokenWithComment(SyntaxKind.DoKeyword, node.pos, writeKeyword, node); - emitEmbeddedStatement(node, node.statement); - if (isBlock(node.statement)) { - writeSpace(); - } - else { - writeLineOrSpace(node); - } - - emitWhileClause(node, node.statement.end); - writeTrailingSemicolon(); - } - - function emitWhileStatement(node: WhileStatement) { - emitWhileClause(node, node.pos); - emitEmbeddedStatement(node, node.statement); + emitTokenWithComment(token.kind, node.expression.end, writePunctuation, node); + increaseIndentIf(indentAfterDot, /*writeSpaceIfNotIndenting*/ false); + emit(node.name); + decreaseIndentIf(indentBeforeDot, indentAfterDot); + } + // 1..toString is a valid property access, emit a dot after the literal + // Also emit a dot if expression is a integer const enum value - it will appear in generated code as numeric literal + function mayNeedDotDotForPropertyAccess(expression: Expression) { + expression = skipPartiallyEmittedExpressions(expression); + if (isNumericLiteral(expression)) { + // check if numeric literal is a decimal literal that was originally written with a dot + const text = getLiteralTextOfNode((expression), /*neverAsciiEscape*/ true); + // If he number will be printed verbatim and it doesn't already contain a dot, add one + // if the expression doesn't have any comments that will be emitted. + return !expression.numericLiteralFlags && !stringContains(text, (tokenToString(SyntaxKind.DotToken)!)); + } + else if (isAccessExpression(expression)) { + // check if constant enum value is integer + const constantValue = getConstantValue(expression); + // isFinite handles cases when constantValue is undefined + return typeof constantValue === "number" && isFinite(constantValue) + && Math.floor(constantValue) === constantValue; } - - function emitForStatement(node: ForStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); - writeSpace(); - let pos = emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, /*contextNode*/ node); - emitForBinding(node.initializer); - pos = emitTokenWithComment(SyntaxKind.SemicolonToken, node.initializer ? node.initializer.end : pos, writePunctuation, node); - emitExpressionWithLeadingSpace(node.condition); - pos = emitTokenWithComment(SyntaxKind.SemicolonToken, node.condition ? node.condition.end : pos, writePunctuation, node); - emitExpressionWithLeadingSpace(node.incrementor); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.incrementor ? node.incrementor.end : pos, writePunctuation, node); - emitEmbeddedStatement(node, node.statement); - } - - function emitForInStatement(node: ForInStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emitForBinding(node.initializer); - writeSpace(); - emitTokenWithComment(SyntaxKind.InKeyword, node.initializer.end, writeKeyword, node); + } + function emitElementAccessExpression(node: ElementAccessExpression) { + emitExpression(node.expression); + emit(node.questionDotToken); + emitTokenWithComment(SyntaxKind.OpenBracketToken, node.expression.end, writePunctuation, node); + emitExpression(node.argumentExpression); + emitTokenWithComment(SyntaxKind.CloseBracketToken, node.argumentExpression.end, writePunctuation, node); + } + function emitCallExpression(node: CallExpression) { + emitExpression(node.expression); + emit(node.questionDotToken); + emitTypeArguments(node, node.typeArguments); + emitExpressionList(node, node.arguments, ListFormat.CallExpressionArguments); + } + function emitNewExpression(node: NewExpression) { + emitTokenWithComment(SyntaxKind.NewKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression); + emitTypeArguments(node, node.typeArguments); + emitExpressionList(node, node.arguments, ListFormat.NewExpressionArguments); + } + function emitTaggedTemplateExpression(node: TaggedTemplateExpression) { + emitExpression(node.tag); + emitTypeArguments(node, node.typeArguments); + writeSpace(); + emitExpression(node.template); + } + function emitTypeAssertionExpression(node: TypeAssertion) { + writePunctuation("<"); + emit(node.type); + writePunctuation(">"); + emitExpression(node.expression); + } + function emitParenthesizedExpression(node: ParenthesizedExpression) { + const openParenPos = emitTokenWithComment(SyntaxKind.OpenParenToken, node.pos, writePunctuation, node); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression ? node.expression.end : openParenPos, writePunctuation, node); + } + function emitFunctionExpression(node: FunctionExpression) { + generateNameIfNeeded(node.name); + emitFunctionDeclarationOrExpression(node); + } + function emitArrowFunction(node: ArrowFunction) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emitSignatureAndBody(node, emitArrowFunctionHead); + } + function emitArrowFunctionHead(node: ArrowFunction) { + emitTypeParameters(node, node.typeParameters); + emitParametersForArrow(node, node.parameters); + emitTypeAnnotation(node.type); + writeSpace(); + emit(node.equalsGreaterThanToken); + } + function emitDeleteExpression(node: DeleteExpression) { + emitTokenWithComment(SyntaxKind.DeleteKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression); + } + function emitTypeOfExpression(node: TypeOfExpression) { + emitTokenWithComment(SyntaxKind.TypeOfKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression); + } + function emitVoidExpression(node: VoidExpression) { + emitTokenWithComment(SyntaxKind.VoidKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression); + } + function emitAwaitExpression(node: AwaitExpression) { + emitTokenWithComment(SyntaxKind.AwaitKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression); + } + function emitPrefixUnaryExpression(node: PrefixUnaryExpression) { + writeTokenText(node.operator, writeOperator); + if (shouldEmitWhitespaceBeforeOperand(node)) { writeSpace(); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - emitEmbeddedStatement(node, node.statement); } - - function emitForOfStatement(node: ForOfStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitWithTrailingSpace(node.awaitModifier); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emitForBinding(node.initializer); + emitExpression(node.operand); + } + function shouldEmitWhitespaceBeforeOperand(node: PrefixUnaryExpression) { + // In some cases, we need to emit a space between the operator and the operand. One obvious case + // is when the operator is an identifier, like delete or typeof. We also need to do this for plus + // and minus expressions in certain cases. Specifically, consider the following two cases (parens + // are just for clarity of exposition, and not part of the source code): + // + // (+(+1)) + // (+(++1)) + // + // We need to emit a space in both cases. In the first case, the absence of a space will make + // the resulting expression a prefix increment operation. And in the second, it will make the resulting + // expression a prefix increment whose operand is a plus expression - (++(+x)) + // The same is true of minus of course. + const operand = node.operand; + return operand.kind === SyntaxKind.PrefixUnaryExpression + && ((node.operator === SyntaxKind.PlusToken && ((operand).operator === SyntaxKind.PlusToken || (operand).operator === SyntaxKind.PlusPlusToken)) + || (node.operator === SyntaxKind.MinusToken && ((operand).operator === SyntaxKind.MinusToken || (operand).operator === SyntaxKind.MinusMinusToken))); + } + function emitPostfixUnaryExpression(node: PostfixUnaryExpression) { + emitExpression(node.operand); + writeTokenText(node.operator, writeOperator); + } + function emitBinaryExpression(node: BinaryExpression) { + const isCommaOperator = node.operatorToken.kind !== SyntaxKind.CommaToken; + const indentBeforeOperator = needsIndentation(node, node.left, node.operatorToken); + const indentAfterOperator = needsIndentation(node, node.operatorToken, node.right); + emitExpression(node.left); + increaseIndentIf(indentBeforeOperator, isCommaOperator); + emitLeadingCommentsOfPosition(node.operatorToken.pos); + writeTokenNode(node.operatorToken, node.operatorToken.kind === SyntaxKind.InKeyword ? writeKeyword : writeOperator); + emitTrailingCommentsOfPosition(node.operatorToken.end, /*prefixSpace*/ true); // Binary operators should have a space before the comment starts + increaseIndentIf(indentAfterOperator, /*writeSpaceIfNotIndenting*/ true); + emitExpression(node.right); + decreaseIndentIf(indentBeforeOperator, indentAfterOperator); + } + function emitConditionalExpression(node: ConditionalExpression) { + const indentBeforeQuestion = needsIndentation(node, node.condition, node.questionToken); + const indentAfterQuestion = needsIndentation(node, node.questionToken, node.whenTrue); + const indentBeforeColon = needsIndentation(node, node.whenTrue, node.colonToken); + const indentAfterColon = needsIndentation(node, node.colonToken, node.whenFalse); + emitExpression(node.condition); + increaseIndentIf(indentBeforeQuestion, /*writeSpaceIfNotIndenting*/ true); + emit(node.questionToken); + increaseIndentIf(indentAfterQuestion, /*writeSpaceIfNotIndenting*/ true); + emitExpression(node.whenTrue); + decreaseIndentIf(indentBeforeQuestion, indentAfterQuestion); + increaseIndentIf(indentBeforeColon, /*writeSpaceIfNotIndenting*/ true); + emit(node.colonToken); + increaseIndentIf(indentAfterColon, /*writeSpaceIfNotIndenting*/ true); + emitExpression(node.whenFalse); + decreaseIndentIf(indentBeforeColon, indentAfterColon); + } + function emitTemplateExpression(node: TemplateExpression) { + emit(node.head); + emitList(node, node.templateSpans, ListFormat.TemplateExpressionSpans); + } + function emitYieldExpression(node: YieldExpression) { + emitTokenWithComment(SyntaxKind.YieldKeyword, node.pos, writeKeyword, node); + emit(node.asteriskToken); + emitExpressionWithLeadingSpace(node.expression); + } + function emitSpreadExpression(node: SpreadElement) { + emitTokenWithComment(SyntaxKind.DotDotDotToken, node.pos, writePunctuation, node); + emitExpression(node.expression); + } + function emitClassExpression(node: ClassExpression) { + generateNameIfNeeded(node.name); + emitClassDeclarationOrExpression(node); + } + function emitExpressionWithTypeArguments(node: ExpressionWithTypeArguments) { + emitExpression(node.expression); + emitTypeArguments(node, node.typeArguments); + } + function emitAsExpression(node: AsExpression) { + emitExpression(node.expression); + if (node.type) { writeSpace(); - emitTokenWithComment(SyntaxKind.OfKeyword, node.initializer.end, writeKeyword, node); + writeKeyword("as"); writeSpace(); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - emitEmbeddedStatement(node, node.statement); + emit(node.type); } - - function emitForBinding(node: VariableDeclarationList | Expression | undefined) { - if (node !== undefined) { - if (node.kind === SyntaxKind.VariableDeclarationList) { - emit(node); - } - else { - emitExpression(node); - } - } + } + function emitNonNullExpression(node: NonNullExpression) { + emitExpression(node.expression); + writeOperator("!"); + } + function emitMetaProperty(node: MetaProperty) { + writeToken(node.keywordToken, node.pos, writePunctuation); + writePunctuation("."); + emit(node.name); + } + // + // Misc + // + function emitTemplateSpan(node: TemplateSpan) { + emitExpression(node.expression); + emit(node.literal); + } + // + // Statements + // + function emitBlock(node: Block) { + emitBlockStatements(node, /*forceSingleLine*/ !node.multiLine && isEmptyBlock(node)); + } + function emitBlockStatements(node: BlockLike, forceSingleLine: boolean) { + emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, /*contextNode*/ node); + const format = forceSingleLine || getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineBlockStatements : ListFormat.MultiLineBlockStatements; + emitList(node, node.statements, format); + emitTokenWithComment(SyntaxKind.CloseBraceToken, node.statements.end, writePunctuation, /*contextNode*/ node, /*indentLeading*/ !!(format & ListFormat.MultiLine)); + } + function emitVariableStatement(node: VariableStatement) { + emitModifiers(node, node.modifiers); + emit(node.declarationList); + writeTrailingSemicolon(); + } + function emitEmptyStatement(isEmbeddedStatement: boolean) { + // While most trailing semicolons are possibly insignificant, an embedded "empty" + // statement is significant and cannot be elided by a trailing-semicolon-omitting writer. + if (isEmbeddedStatement) { + writePunctuation(";"); } - - function emitContinueStatement(node: ContinueStatement) { - emitTokenWithComment(SyntaxKind.ContinueKeyword, node.pos, writeKeyword, node); - emitWithLeadingSpace(node.label); + else { writeTrailingSemicolon(); } - - function emitBreakStatement(node: BreakStatement) { - emitTokenWithComment(SyntaxKind.BreakKeyword, node.pos, writeKeyword, node); - emitWithLeadingSpace(node.label); + } + function emitExpressionStatement(node: ExpressionStatement) { + emitExpression(node.expression); + // Emit semicolon in non json files + // or if json file that created synthesized expression(eg.define expression statement when --out and amd code generation) + if (!isJsonSourceFile((currentSourceFile!)) || nodeIsSynthesized(node.expression)) { writeTrailingSemicolon(); } - - function emitTokenWithComment(token: SyntaxKind, pos: number, writer: (s: string) => void, contextNode: Node, indentLeading?: boolean) { - const node = getParseTreeNode(contextNode); - const isSimilarNode = node && node.kind === contextNode.kind; - const startPos = pos; - if (isSimilarNode) { - pos = skipTrivia(currentSourceFile!.text, pos); - } - if (emitLeadingCommentsOfPosition && isSimilarNode && contextNode.pos !== startPos) { - const needsIndent = indentLeading && !positionsAreOnSameLine(startPos, pos, currentSourceFile!); - if (needsIndent) { - increaseIndent(); - } - emitLeadingCommentsOfPosition(startPos); - if (needsIndent) { - decreaseIndent(); - } + } + function emitIfStatement(node: IfStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.IfKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + emitEmbeddedStatement(node, node.thenStatement); + if (node.elseStatement) { + writeLineOrSpace(node); + emitTokenWithComment(SyntaxKind.ElseKeyword, node.thenStatement.end, writeKeyword, node); + if (node.elseStatement.kind === SyntaxKind.IfStatement) { + writeSpace(); + emit(node.elseStatement); } - pos = writeTokenText(token, writer, pos); - if (emitTrailingCommentsOfPosition && isSimilarNode && contextNode.end !== pos) { - emitTrailingCommentsOfPosition(pos, /*prefixSpace*/ true); + else { + emitEmbeddedStatement(node, node.elseStatement); } - return pos; - } - - function emitReturnStatement(node: ReturnStatement) { - emitTokenWithComment(SyntaxKind.ReturnKeyword, node.pos, writeKeyword, /*contextNode*/ node); - emitExpressionWithLeadingSpace(node.expression); - writeTrailingSemicolon(); - } - - function emitWithStatement(node: WithStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.WithKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - emitEmbeddedStatement(node, node.statement); - } - - function emitSwitchStatement(node: SwitchStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.SwitchKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - writeSpace(); - emit(node.caseBlock); } - - function emitLabeledStatement(node: LabeledStatement) { - emit(node.label); - emitTokenWithComment(SyntaxKind.ColonToken, node.label.end, writePunctuation, node); + } + function emitWhileClause(node: WhileStatement | DoStatement, startPos: number) { + const openParenPos = emitTokenWithComment(SyntaxKind.WhileKeyword, startPos, writeKeyword, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + } + function emitDoStatement(node: DoStatement) { + emitTokenWithComment(SyntaxKind.DoKeyword, node.pos, writeKeyword, node); + emitEmbeddedStatement(node, node.statement); + if (isBlock(node.statement)) { writeSpace(); - emit(node.statement); } - - function emitThrowStatement(node: ThrowStatement) { - emitTokenWithComment(SyntaxKind.ThrowKeyword, node.pos, writeKeyword, node); - emitExpressionWithLeadingSpace(node.expression); - writeTrailingSemicolon(); + else { + writeLineOrSpace(node); } - - function emitTryStatement(node: TryStatement) { - emitTokenWithComment(SyntaxKind.TryKeyword, node.pos, writeKeyword, node); - writeSpace(); - emit(node.tryBlock); - if (node.catchClause) { - writeLineOrSpace(node); - emit(node.catchClause); + emitWhileClause(node, node.statement.end); + writeTrailingSemicolon(); + } + function emitWhileStatement(node: WhileStatement) { + emitWhileClause(node, node.pos); + emitEmbeddedStatement(node, node.statement); + } + function emitForStatement(node: ForStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); + writeSpace(); + let pos = emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, /*contextNode*/ node); + emitForBinding(node.initializer); + pos = emitTokenWithComment(SyntaxKind.SemicolonToken, node.initializer ? node.initializer.end : pos, writePunctuation, node); + emitExpressionWithLeadingSpace(node.condition); + pos = emitTokenWithComment(SyntaxKind.SemicolonToken, node.condition ? node.condition.end : pos, writePunctuation, node); + emitExpressionWithLeadingSpace(node.incrementor); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.incrementor ? node.incrementor.end : pos, writePunctuation, node); + emitEmbeddedStatement(node, node.statement); + } + function emitForInStatement(node: ForInStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitForBinding(node.initializer); + writeSpace(); + emitTokenWithComment(SyntaxKind.InKeyword, node.initializer.end, writeKeyword, node); + writeSpace(); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + emitEmbeddedStatement(node, node.statement); + } + function emitForOfStatement(node: ForOfStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitWithTrailingSpace(node.awaitModifier); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitForBinding(node.initializer); + writeSpace(); + emitTokenWithComment(SyntaxKind.OfKeyword, node.initializer.end, writeKeyword, node); + writeSpace(); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + emitEmbeddedStatement(node, node.statement); + } + function emitForBinding(node: VariableDeclarationList | Expression | undefined) { + if (node !== undefined) { + if (node.kind === SyntaxKind.VariableDeclarationList) { + emit(node); } - if (node.finallyBlock) { - writeLineOrSpace(node); - emitTokenWithComment(SyntaxKind.FinallyKeyword, (node.catchClause || node.tryBlock).end, writeKeyword, node); - writeSpace(); - emit(node.finallyBlock); + else { + emitExpression(node); } } - - function emitDebuggerStatement(node: DebuggerStatement) { - writeToken(SyntaxKind.DebuggerKeyword, node.pos, writeKeyword); - writeTrailingSemicolon(); + } + function emitContinueStatement(node: ContinueStatement) { + emitTokenWithComment(SyntaxKind.ContinueKeyword, node.pos, writeKeyword, node); + emitWithLeadingSpace(node.label); + writeTrailingSemicolon(); + } + function emitBreakStatement(node: BreakStatement) { + emitTokenWithComment(SyntaxKind.BreakKeyword, node.pos, writeKeyword, node); + emitWithLeadingSpace(node.label); + writeTrailingSemicolon(); + } + function emitTokenWithComment(token: SyntaxKind, pos: number, writer: (s: string) => void, contextNode: Node, indentLeading?: boolean) { + const node = getParseTreeNode(contextNode); + const isSimilarNode = node && node.kind === contextNode.kind; + const startPos = pos; + if (isSimilarNode) { + pos = skipTrivia(currentSourceFile!.text, pos); + } + if (emitLeadingCommentsOfPosition && isSimilarNode && contextNode.pos !== startPos) { + const needsIndent = indentLeading && !positionsAreOnSameLine(startPos, pos, (currentSourceFile!)); + if (needsIndent) { + increaseIndent(); + } + emitLeadingCommentsOfPosition(startPos); + if (needsIndent) { + decreaseIndent(); + } } - - // - // Declarations - // - - function emitVariableDeclaration(node: VariableDeclaration) { - emit(node.name); - emit(node.exclamationToken); - emitTypeAnnotation(node.type); - emitInitializer(node.initializer, node.type ? node.type.end : node.name.end, node); + pos = writeTokenText(token, writer, pos); + if (emitTrailingCommentsOfPosition && isSimilarNode && contextNode.end !== pos) { + emitTrailingCommentsOfPosition(pos, /*prefixSpace*/ true); } - - function emitVariableDeclarationList(node: VariableDeclarationList) { - writeKeyword(isLet(node) ? "let" : isVarConst(node) ? "const" : "var"); - writeSpace(); - emitList(node, node.declarations, ListFormat.VariableDeclarationList); - } - - function emitFunctionDeclaration(node: FunctionDeclaration) { - emitFunctionDeclarationOrExpression(node); - } - - function emitFunctionDeclarationOrExpression(node: FunctionDeclaration | FunctionExpression) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - writeKeyword("function"); - emit(node.asteriskToken); + return pos; + } + function emitReturnStatement(node: ReturnStatement) { + emitTokenWithComment(SyntaxKind.ReturnKeyword, node.pos, writeKeyword, /*contextNode*/ node); + emitExpressionWithLeadingSpace(node.expression); + writeTrailingSemicolon(); + } + function emitWithStatement(node: WithStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.WithKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + emitEmbeddedStatement(node, node.statement); + } + function emitSwitchStatement(node: SwitchStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.SwitchKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + writeSpace(); + emit(node.caseBlock); + } + function emitLabeledStatement(node: LabeledStatement) { + emit(node.label); + emitTokenWithComment(SyntaxKind.ColonToken, node.label.end, writePunctuation, node); + writeSpace(); + emit(node.statement); + } + function emitThrowStatement(node: ThrowStatement) { + emitTokenWithComment(SyntaxKind.ThrowKeyword, node.pos, writeKeyword, node); + emitExpressionWithLeadingSpace(node.expression); + writeTrailingSemicolon(); + } + function emitTryStatement(node: TryStatement) { + emitTokenWithComment(SyntaxKind.TryKeyword, node.pos, writeKeyword, node); + writeSpace(); + emit(node.tryBlock); + if (node.catchClause) { + writeLineOrSpace(node); + emit(node.catchClause); + } + if (node.finallyBlock) { + writeLineOrSpace(node); + emitTokenWithComment(SyntaxKind.FinallyKeyword, (node.catchClause || node.tryBlock).end, writeKeyword, node); writeSpace(); - emitIdentifierName(node.name); - emitSignatureAndBody(node, emitSignatureHead); - } - - function emitBlockCallback(_hint: EmitHint, body: Node): void { - emitBlockFunctionBody(body); - } - - function emitSignatureAndBody(node: FunctionLikeDeclaration, emitSignatureHead: (node: SignatureDeclaration) => void) { - const body = node.body; - if (body) { - if (isBlock(body)) { - const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; - if (indentedFlag) { - increaseIndent(); - } - - pushNameGenerationScope(node); - forEach(node.parameters, generateNames); - generateNames(node.body); - - emitSignatureHead(node); - if (onEmitNode) { - onEmitNode(EmitHint.Unspecified, body, emitBlockCallback); - } - else { - emitBlockFunctionBody(body); - } - popNameGenerationScope(node); - - if (indentedFlag) { - decreaseIndent(); - } + emit(node.finallyBlock); + } + } + function emitDebuggerStatement(node: DebuggerStatement) { + writeToken(SyntaxKind.DebuggerKeyword, node.pos, writeKeyword); + writeTrailingSemicolon(); + } + // + // Declarations + // + function emitVariableDeclaration(node: VariableDeclaration) { + emit(node.name); + emit(node.exclamationToken); + emitTypeAnnotation(node.type); + emitInitializer(node.initializer, node.type ? node.type.end : node.name.end, node); + } + function emitVariableDeclarationList(node: VariableDeclarationList) { + writeKeyword(isLet(node) ? "let" : isVarConst(node) ? "const" : "var"); + writeSpace(); + emitList(node, node.declarations, ListFormat.VariableDeclarationList); + } + function emitFunctionDeclaration(node: FunctionDeclaration) { + emitFunctionDeclarationOrExpression(node); + } + function emitFunctionDeclarationOrExpression(node: FunctionDeclaration | FunctionExpression) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeKeyword("function"); + emit(node.asteriskToken); + writeSpace(); + emitIdentifierName(node.name); + emitSignatureAndBody(node, emitSignatureHead); + } + function emitBlockCallback(_hint: EmitHint, body: Node): void { + emitBlockFunctionBody((body)); + } + function emitSignatureAndBody(node: FunctionLikeDeclaration, emitSignatureHead: (node: SignatureDeclaration) => void) { + const body = node.body; + if (body) { + if (isBlock(body)) { + const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; + if (indentedFlag) { + increaseIndent(); + } + pushNameGenerationScope(node); + forEach(node.parameters, generateNames); + generateNames(node.body); + emitSignatureHead(node); + if (onEmitNode) { + onEmitNode(EmitHint.Unspecified, body, emitBlockCallback); } else { - emitSignatureHead(node); - writeSpace(); - emitExpression(body); + emitBlockFunctionBody(body); + } + popNameGenerationScope(node); + if (indentedFlag) { + decreaseIndent(); } } else { emitSignatureHead(node); - writeTrailingSemicolon(); + writeSpace(); + emitExpression(body); } - } - - function emitSignatureHead(node: FunctionDeclaration | FunctionExpression | MethodDeclaration | AccessorDeclaration | ConstructorDeclaration) { - emitTypeParameters(node, node.typeParameters); - emitParameters(node, node.parameters); - emitTypeAnnotation(node.type); + else { + emitSignatureHead(node); + writeTrailingSemicolon(); } - - function shouldEmitBlockFunctionBodyOnSingleLine(body: Block) { - // We must emit a function body as a single-line body in the following case: - // * The body has NodeEmitFlags.SingleLine specified. - - // We must emit a function body as a multi-line body in the following cases: - // * The body is explicitly marked as multi-line. - // * A non-synthesized body's start and end position are on different lines. - // * Any statement in the body starts on a new line. - - if (getEmitFlags(body) & EmitFlags.SingleLine) { - return true; - } - - if (body.multiLine) { - return false; - } - - if (!nodeIsSynthesized(body) && !rangeIsOnSingleLine(body, currentSourceFile!)) { - return false; - } - - if (shouldWriteLeadingLineTerminator(body, body.statements, ListFormat.PreserveLines) - || shouldWriteClosingLineTerminator(body, body.statements, ListFormat.PreserveLines)) { + } + function emitSignatureHead(node: FunctionDeclaration | FunctionExpression | MethodDeclaration | AccessorDeclaration | ConstructorDeclaration) { + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + emitTypeAnnotation(node.type); + } + function shouldEmitBlockFunctionBodyOnSingleLine(body: Block) { + // We must emit a function body as a single-line body in the following case: + // * The body has NodeEmitFlags.SingleLine specified. + // We must emit a function body as a multi-line body in the following cases: + // * The body is explicitly marked as multi-line. + // * A non-synthesized body's start and end position are on different lines. + // * Any statement in the body starts on a new line. + if (getEmitFlags(body) & EmitFlags.SingleLine) { + return true; + } + if (body.multiLine) { + return false; + } + if (!nodeIsSynthesized(body) && !rangeIsOnSingleLine(body, (currentSourceFile!))) { + return false; + } + if (shouldWriteLeadingLineTerminator(body, body.statements, ListFormat.PreserveLines) + || shouldWriteClosingLineTerminator(body, body.statements, ListFormat.PreserveLines)) { + return false; + } + let previousStatement: Statement | undefined; + for (const statement of body.statements) { + if (shouldWriteSeparatingLineTerminator(previousStatement, statement, ListFormat.PreserveLines)) { return false; } - - let previousStatement: Statement | undefined; - for (const statement of body.statements) { - if (shouldWriteSeparatingLineTerminator(previousStatement, statement, ListFormat.PreserveLines)) { - return false; - } - - previousStatement = statement; - } - - return true; + previousStatement = statement; + } + return true; + } + function emitBlockFunctionBody(body: Block) { + writeSpace(); + writePunctuation("{"); + increaseIndent(); + const emitBlockFunctionBody = shouldEmitBlockFunctionBodyOnSingleLine(body) + ? emitBlockFunctionBodyOnSingleLine + : emitBlockFunctionBodyWorker; + if (emitBodyWithDetachedComments) { + emitBodyWithDetachedComments(body, body.statements, emitBlockFunctionBody); + } + else { + emitBlockFunctionBody(body); + } + decreaseIndent(); + writeToken(SyntaxKind.CloseBraceToken, body.statements.end, writePunctuation, body); + } + function emitBlockFunctionBodyOnSingleLine(body: Block) { + emitBlockFunctionBodyWorker(body, /*emitBlockFunctionBodyOnSingleLine*/ true); + } + function emitBlockFunctionBodyWorker(body: Block, emitBlockFunctionBodyOnSingleLine?: boolean) { + // Emit all the prologue directives (like "use strict"). + const statementOffset = emitPrologueDirectives(body.statements); + const pos = writer.getTextPos(); + emitHelpers(body); + if (statementOffset === 0 && pos === writer.getTextPos() && emitBlockFunctionBodyOnSingleLine) { + decreaseIndent(); + emitList(body, body.statements, ListFormat.SingleLineFunctionBodyStatements); + increaseIndent(); + } + else { + emitList(body, body.statements, ListFormat.MultiLineFunctionBodyStatements, statementOffset); } - - function emitBlockFunctionBody(body: Block) { + } + function emitClassDeclaration(node: ClassDeclaration) { + emitClassDeclarationOrExpression(node); + } + function emitClassDeclarationOrExpression(node: ClassDeclaration | ClassExpression) { + forEach(node.members, generateMemberNames); + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeKeyword("class"); + if (node.name) { writeSpace(); - writePunctuation("{"); + emitIdentifierName(node.name); + } + const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; + if (indentedFlag) { increaseIndent(); - - const emitBlockFunctionBody = shouldEmitBlockFunctionBodyOnSingleLine(body) - ? emitBlockFunctionBodyOnSingleLine - : emitBlockFunctionBodyWorker; - - if (emitBodyWithDetachedComments) { - emitBodyWithDetachedComments(body, body.statements, emitBlockFunctionBody); - } - else { - emitBlockFunctionBody(body); - } - + } + emitTypeParameters(node, node.typeParameters); + emitList(node, node.heritageClauses, ListFormat.ClassHeritageClauses); + writeSpace(); + writePunctuation("{"); + emitList(node, node.members, ListFormat.ClassMembers); + writePunctuation("}"); + if (indentedFlag) { decreaseIndent(); - writeToken(SyntaxKind.CloseBraceToken, body.statements.end, writePunctuation, body); } - - function emitBlockFunctionBodyOnSingleLine(body: Block) { - emitBlockFunctionBodyWorker(body, /*emitBlockFunctionBodyOnSingleLine*/ true); + } + function emitInterfaceDeclaration(node: InterfaceDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeKeyword("interface"); + writeSpace(); + emit(node.name); + emitTypeParameters(node, node.typeParameters); + emitList(node, node.heritageClauses, ListFormat.HeritageClauses); + writeSpace(); + writePunctuation("{"); + emitList(node, node.members, ListFormat.InterfaceMembers); + writePunctuation("}"); + } + function emitTypeAliasDeclaration(node: TypeAliasDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeKeyword("type"); + writeSpace(); + emit(node.name); + emitTypeParameters(node, node.typeParameters); + writeSpace(); + writePunctuation("="); + writeSpace(); + emit(node.type); + writeTrailingSemicolon(); + } + function emitEnumDeclaration(node: EnumDeclaration) { + emitModifiers(node, node.modifiers); + writeKeyword("enum"); + writeSpace(); + emit(node.name); + writeSpace(); + writePunctuation("{"); + emitList(node, node.members, ListFormat.EnumMembers); + writePunctuation("}"); + } + function emitModuleDeclaration(node: ModuleDeclaration) { + emitModifiers(node, node.modifiers); + if (~node.flags & NodeFlags.GlobalAugmentation) { + writeKeyword(node.flags & NodeFlags.Namespace ? "namespace" : "module"); + writeSpace(); } - - function emitBlockFunctionBodyWorker(body: Block, emitBlockFunctionBodyOnSingleLine?: boolean) { - // Emit all the prologue directives (like "use strict"). - const statementOffset = emitPrologueDirectives(body.statements); - const pos = writer.getTextPos(); - emitHelpers(body); - if (statementOffset === 0 && pos === writer.getTextPos() && emitBlockFunctionBodyOnSingleLine) { - decreaseIndent(); - emitList(body, body.statements, ListFormat.SingleLineFunctionBodyStatements); - increaseIndent(); - } - else { - emitList(body, body.statements, ListFormat.MultiLineFunctionBodyStatements, statementOffset); - } + emit(node.name); + let body = node.body; + if (!body) + return writeTrailingSemicolon(); + while (body.kind === SyntaxKind.ModuleDeclaration) { + writePunctuation("."); + emit((body).name); + body = ((body).body!); } - - function emitClassDeclaration(node: ClassDeclaration) { - emitClassDeclarationOrExpression(node); - } - - function emitClassDeclarationOrExpression(node: ClassDeclaration | ClassExpression) { - forEach(node.members, generateMemberNames); - - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - writeKeyword("class"); - if (node.name) { - writeSpace(); - emitIdentifierName(node.name); - } - - const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; - if (indentedFlag) { - increaseIndent(); - } - - emitTypeParameters(node, node.typeParameters); - emitList(node, node.heritageClauses, ListFormat.ClassHeritageClauses); - - writeSpace(); - writePunctuation("{"); - emitList(node, node.members, ListFormat.ClassMembers); - writePunctuation("}"); - - if (indentedFlag) { - decreaseIndent(); - } + writeSpace(); + emit(body); + } + function emitModuleBlock(node: ModuleBlock) { + pushNameGenerationScope(node); + forEach(node.statements, generateNames); + emitBlockStatements(node, /*forceSingleLine*/ isEmptyBlock(node)); + popNameGenerationScope(node); + } + function emitCaseBlock(node: CaseBlock) { + emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, node); + emitList(node, node.clauses, ListFormat.CaseBlockClauses); + emitTokenWithComment(SyntaxKind.CloseBraceToken, node.clauses.end, writePunctuation, node, /*indentLeading*/ true); + } + function emitImportEqualsDeclaration(node: ImportEqualsDeclaration) { + emitModifiers(node, node.modifiers); + emitTokenWithComment(SyntaxKind.ImportKeyword, node.modifiers ? node.modifiers.end : node.pos, writeKeyword, node); + writeSpace(); + emit(node.name); + writeSpace(); + emitTokenWithComment(SyntaxKind.EqualsToken, node.name.end, writePunctuation, node); + writeSpace(); + emitModuleReference(node.moduleReference); + writeTrailingSemicolon(); + } + function emitModuleReference(node: ModuleReference) { + if (node.kind === SyntaxKind.Identifier) { + emitExpression(node); } - - function emitInterfaceDeclaration(node: InterfaceDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - writeKeyword("interface"); - writeSpace(); - emit(node.name); - emitTypeParameters(node, node.typeParameters); - emitList(node, node.heritageClauses, ListFormat.HeritageClauses); - writeSpace(); - writePunctuation("{"); - emitList(node, node.members, ListFormat.InterfaceMembers); - writePunctuation("}"); + else { + emit(node); } - - function emitTypeAliasDeclaration(node: TypeAliasDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - writeKeyword("type"); - writeSpace(); - emit(node.name); - emitTypeParameters(node, node.typeParameters); + } + function emitImportDeclaration(node: ImportDeclaration) { + emitModifiers(node, node.modifiers); + emitTokenWithComment(SyntaxKind.ImportKeyword, node.modifiers ? node.modifiers.end : node.pos, writeKeyword, node); + writeSpace(); + if (node.importClause) { + emit(node.importClause); writeSpace(); - writePunctuation("="); + emitTokenWithComment(SyntaxKind.FromKeyword, node.importClause.end, writeKeyword, node); writeSpace(); - emit(node.type); - writeTrailingSemicolon(); } - - function emitEnumDeclaration(node: EnumDeclaration) { - emitModifiers(node, node.modifiers); - writeKeyword("enum"); - writeSpace(); - emit(node.name); - + emitExpression(node.moduleSpecifier); + writeTrailingSemicolon(); + } + function emitImportClause(node: ImportClause) { + emit(node.name); + if (node.name && node.namedBindings) { + emitTokenWithComment(SyntaxKind.CommaToken, node.name.end, writePunctuation, node); writeSpace(); - writePunctuation("{"); - emitList(node, node.members, ListFormat.EnumMembers); - writePunctuation("}"); } - - function emitModuleDeclaration(node: ModuleDeclaration) { - emitModifiers(node, node.modifiers); - if (~node.flags & NodeFlags.GlobalAugmentation) { - writeKeyword(node.flags & NodeFlags.Namespace ? "namespace" : "module"); - writeSpace(); - } - emit(node.name); - - let body = node.body; - if (!body) return writeTrailingSemicolon(); - while (body.kind === SyntaxKind.ModuleDeclaration) { - writePunctuation("."); - emit((body).name); - body = (body).body!; - } - - writeSpace(); - emit(body); - } - - function emitModuleBlock(node: ModuleBlock) { - pushNameGenerationScope(node); - forEach(node.statements, generateNames); - emitBlockStatements(node, /*forceSingleLine*/ isEmptyBlock(node)); - popNameGenerationScope(node); - } - - function emitCaseBlock(node: CaseBlock) { - emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, node); - emitList(node, node.clauses, ListFormat.CaseBlockClauses); - emitTokenWithComment(SyntaxKind.CloseBraceToken, node.clauses.end, writePunctuation, node, /*indentLeading*/ true); - } - - function emitImportEqualsDeclaration(node: ImportEqualsDeclaration) { - emitModifiers(node, node.modifiers); - emitTokenWithComment(SyntaxKind.ImportKeyword, node.modifiers ? node.modifiers.end : node.pos, writeKeyword, node); - writeSpace(); - emit(node.name); - writeSpace(); - emitTokenWithComment(SyntaxKind.EqualsToken, node.name.end, writePunctuation, node); - writeSpace(); - emitModuleReference(node.moduleReference); - writeTrailingSemicolon(); + emit(node.namedBindings); + } + function emitNamespaceImport(node: NamespaceImport) { + const asPos = emitTokenWithComment(SyntaxKind.AsteriskToken, node.pos, writePunctuation, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.AsKeyword, asPos, writeKeyword, node); + writeSpace(); + emit(node.name); + } + function emitNamedImports(node: NamedImports) { + emitNamedImportsOrExports(node); + } + function emitImportSpecifier(node: ImportSpecifier) { + emitImportOrExportSpecifier(node); + } + function emitExportAssignment(node: ExportAssignment) { + const nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); + writeSpace(); + if (node.isExportEquals) { + emitTokenWithComment(SyntaxKind.EqualsToken, nextPos, writeOperator, node); } - - function emitModuleReference(node: ModuleReference) { - if (node.kind === SyntaxKind.Identifier) { - emitExpression(node); - } - else { - emit(node); - } + else { + emitTokenWithComment(SyntaxKind.DefaultKeyword, nextPos, writeKeyword, node); } - - function emitImportDeclaration(node: ImportDeclaration) { - emitModifiers(node, node.modifiers); - emitTokenWithComment(SyntaxKind.ImportKeyword, node.modifiers ? node.modifiers.end : node.pos, writeKeyword, node); - writeSpace(); - if (node.importClause) { - emit(node.importClause); - writeSpace(); - emitTokenWithComment(SyntaxKind.FromKeyword, node.importClause.end, writeKeyword, node); - writeSpace(); - } - emitExpression(node.moduleSpecifier); - writeTrailingSemicolon(); + writeSpace(); + emitExpression(node.expression); + writeTrailingSemicolon(); + } + function emitExportDeclaration(node: ExportDeclaration) { + let nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); + writeSpace(); + if (node.exportClause) { + emit(node.exportClause); } - - function emitImportClause(node: ImportClause) { - emit(node.name); - if (node.name && node.namedBindings) { - emitTokenWithComment(SyntaxKind.CommaToken, node.name.end, writePunctuation, node); - writeSpace(); - } - emit(node.namedBindings); + else { + nextPos = emitTokenWithComment(SyntaxKind.AsteriskToken, nextPos, writePunctuation, node); } - - function emitNamespaceImport(node: NamespaceImport) { - const asPos = emitTokenWithComment(SyntaxKind.AsteriskToken, node.pos, writePunctuation, node); + if (node.moduleSpecifier) { writeSpace(); - emitTokenWithComment(SyntaxKind.AsKeyword, asPos, writeKeyword, node); + const fromPos = node.exportClause ? node.exportClause.end : nextPos; + emitTokenWithComment(SyntaxKind.FromKeyword, fromPos, writeKeyword, node); writeSpace(); - emit(node.name); - } - - function emitNamedImports(node: NamedImports) { - emitNamedImportsOrExports(node); - } - - function emitImportSpecifier(node: ImportSpecifier) { - emitImportOrExportSpecifier(node); + emitExpression(node.moduleSpecifier); } - - function emitExportAssignment(node: ExportAssignment) { - const nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); + writeTrailingSemicolon(); + } + function emitNamespaceExportDeclaration(node: NamespaceExportDeclaration) { + let nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); + writeSpace(); + nextPos = emitTokenWithComment(SyntaxKind.AsKeyword, nextPos, writeKeyword, node); + writeSpace(); + nextPos = emitTokenWithComment(SyntaxKind.NamespaceKeyword, nextPos, writeKeyword, node); + writeSpace(); + emit(node.name); + writeTrailingSemicolon(); + } + function emitNamedExports(node: NamedExports) { + emitNamedImportsOrExports(node); + } + function emitExportSpecifier(node: ExportSpecifier) { + emitImportOrExportSpecifier(node); + } + function emitNamedImportsOrExports(node: NamedImportsOrExports) { + writePunctuation("{"); + emitList(node, node.elements, ListFormat.NamedImportsOrExportsElements); + writePunctuation("}"); + } + function emitImportOrExportSpecifier(node: ImportOrExportSpecifier) { + if (node.propertyName) { + emit(node.propertyName); writeSpace(); - if (node.isExportEquals) { - emitTokenWithComment(SyntaxKind.EqualsToken, nextPos, writeOperator, node); - } - else { - emitTokenWithComment(SyntaxKind.DefaultKeyword, nextPos, writeKeyword, node); - } + emitTokenWithComment(SyntaxKind.AsKeyword, node.propertyName.end, writeKeyword, node); writeSpace(); - emitExpression(node.expression); - writeTrailingSemicolon(); } - - function emitExportDeclaration(node: ExportDeclaration) { - let nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); - writeSpace(); - if (node.exportClause) { - emit(node.exportClause); - } - else { - nextPos = emitTokenWithComment(SyntaxKind.AsteriskToken, nextPos, writePunctuation, node); - } - if (node.moduleSpecifier) { - writeSpace(); - const fromPos = node.exportClause ? node.exportClause.end : nextPos; - emitTokenWithComment(SyntaxKind.FromKeyword, fromPos, writeKeyword, node); + emit(node.name); + } + // + // Module references + // + function emitExternalModuleReference(node: ExternalModuleReference) { + writeKeyword("require"); + writePunctuation("("); + emitExpression(node.expression); + writePunctuation(")"); + } + // + // JSX + // + function emitJsxElement(node: JsxElement) { + emit(node.openingElement); + emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren); + emit(node.closingElement); + } + function emitJsxSelfClosingElement(node: JsxSelfClosingElement) { + writePunctuation("<"); + emitJsxTagName(node.tagName); + emitTypeArguments(node, node.typeArguments); + writeSpace(); + emit(node.attributes); + writePunctuation("/>"); + } + function emitJsxFragment(node: JsxFragment) { + emit(node.openingFragment); + emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren); + emit(node.closingFragment); + } + function emitJsxOpeningElementOrFragment(node: JsxOpeningElement | JsxOpeningFragment) { + writePunctuation("<"); + if (isJsxOpeningElement(node)) { + emitJsxTagName(node.tagName); + emitTypeArguments(node, node.typeArguments); + if (node.attributes.properties && node.attributes.properties.length > 0) { writeSpace(); - emitExpression(node.moduleSpecifier); } - writeTrailingSemicolon(); - } - - function emitNamespaceExportDeclaration(node: NamespaceExportDeclaration) { - let nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); - writeSpace(); - nextPos = emitTokenWithComment(SyntaxKind.AsKeyword, nextPos, writeKeyword, node); - writeSpace(); - nextPos = emitTokenWithComment(SyntaxKind.NamespaceKeyword, nextPos, writeKeyword, node); - writeSpace(); - emit(node.name); - writeTrailingSemicolon(); - } - - function emitNamedExports(node: NamedExports) { - emitNamedImportsOrExports(node); + emit(node.attributes); } - - function emitExportSpecifier(node: ExportSpecifier) { - emitImportOrExportSpecifier(node); + writePunctuation(">"); + } + function emitJsxText(node: JsxText) { + writer.writeLiteral(node.text); + } + function emitJsxClosingElementOrFragment(node: JsxClosingElement | JsxClosingFragment) { + writePunctuation(""); + } + function emitJsxAttributes(node: JsxAttributes) { + emitList(node, node.properties, ListFormat.JsxElementAttributes); + } + function emitJsxAttribute(node: JsxAttribute) { + emit(node.name); + emitNodeWithPrefix("=", writePunctuation, node.initializer!, emit); // TODO: GH#18217 + } + function emitJsxSpreadAttribute(node: JsxSpreadAttribute) { + writePunctuation("{..."); + emitExpression(node.expression); + writePunctuation("}"); + } + function emitJsxExpression(node: JsxExpression) { + if (node.expression) { writePunctuation("{"); - emitList(node, node.elements, ListFormat.NamedImportsOrExportsElements); + emit(node.dotDotDotToken); + emitExpression(node.expression); writePunctuation("}"); } - - function emitImportOrExportSpecifier(node: ImportOrExportSpecifier) { - if (node.propertyName) { - emit(node.propertyName); - writeSpace(); - emitTokenWithComment(SyntaxKind.AsKeyword, node.propertyName.end, writeKeyword, node); - writeSpace(); - } - - emit(node.name); + } + function emitJsxTagName(node: JsxTagNameExpression) { + if (node.kind === SyntaxKind.Identifier) { + emitExpression(node); } - - // - // Module references - // - - function emitExternalModuleReference(node: ExternalModuleReference) { - writeKeyword("require"); - writePunctuation("("); - emitExpression(node.expression); - writePunctuation(")"); + else { + emit(node); } - - // - // JSX - // - - function emitJsxElement(node: JsxElement) { - emit(node.openingElement); - emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren); - emit(node.closingElement); - } - - function emitJsxSelfClosingElement(node: JsxSelfClosingElement) { - writePunctuation("<"); - emitJsxTagName(node.tagName); - emitTypeArguments(node, node.typeArguments); + } + // + // Clauses + // + function emitCaseClause(node: CaseClause) { + emitTokenWithComment(SyntaxKind.CaseKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression); + emitCaseOrDefaultClauseRest(node, node.statements, node.expression.end); + } + function emitDefaultClause(node: DefaultClause) { + const pos = emitTokenWithComment(SyntaxKind.DefaultKeyword, node.pos, writeKeyword, node); + emitCaseOrDefaultClauseRest(node, node.statements, pos); + } + function emitCaseOrDefaultClauseRest(parentNode: Node, statements: NodeArray, colonPos: number) { + const emitAsSingleStatement = statements.length === 1 && + ( + // treat synthesized nodes as located on the same line for emit purposes + nodeIsSynthesized(parentNode) || + nodeIsSynthesized(statements[0]) || + rangeStartPositionsAreOnSameLine(parentNode, statements[0], (currentSourceFile!))); + let format = ListFormat.CaseOrDefaultClauseStatements; + if (emitAsSingleStatement) { + writeToken(SyntaxKind.ColonToken, colonPos, writePunctuation, parentNode); writeSpace(); - emit(node.attributes); - writePunctuation("/>"); - } - - function emitJsxFragment(node: JsxFragment) { - emit(node.openingFragment); - emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren); - emit(node.closingFragment); - } - - function emitJsxOpeningElementOrFragment(node: JsxOpeningElement | JsxOpeningFragment) { - writePunctuation("<"); - - if (isJsxOpeningElement(node)) { - emitJsxTagName(node.tagName); - emitTypeArguments(node, node.typeArguments); - if (node.attributes.properties && node.attributes.properties.length > 0) { - writeSpace(); - } - emit(node.attributes); - } - - writePunctuation(">"); - } - - function emitJsxText(node: JsxText) { - writer.writeLiteral(node.text); - } - - function emitJsxClosingElementOrFragment(node: JsxClosingElement | JsxClosingFragment) { - writePunctuation(""); + format &= ~(ListFormat.MultiLine | ListFormat.Indented); + } + else { + emitTokenWithComment(SyntaxKind.ColonToken, colonPos, writePunctuation, parentNode); } - - function emitJsxAttributes(node: JsxAttributes) { - emitList(node, node.properties, ListFormat.JsxElementAttributes); + emitList(parentNode, statements, format); + } + function emitHeritageClause(node: HeritageClause) { + writeSpace(); + writeTokenText(node.token, writeKeyword); + writeSpace(); + emitList(node, node.types, ListFormat.HeritageClauseTypes); + } + function emitCatchClause(node: CatchClause) { + const openParenPos = emitTokenWithComment(SyntaxKind.CatchKeyword, node.pos, writeKeyword, node); + writeSpace(); + if (node.variableDeclaration) { + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emit(node.variableDeclaration); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.variableDeclaration.end, writePunctuation, node); + writeSpace(); } - - function emitJsxAttribute(node: JsxAttribute) { - emit(node.name); - emitNodeWithPrefix("=", writePunctuation, node.initializer!, emit); // TODO: GH#18217 + emit(node.block); + } + // + // Property assignments + // + function emitPropertyAssignment(node: PropertyAssignment) { + emit(node.name); + writePunctuation(":"); + writeSpace(); + // This is to ensure that we emit comment in the following case: + // For example: + // obj = { + // id: /*comment1*/ ()=>void + // } + // "comment1" is not considered to be leading comment for node.initializer + // but rather a trailing comment on the previous node. + const initializer = node.initializer; + if (emitTrailingCommentsOfPosition && (getEmitFlags(initializer) & EmitFlags.NoLeadingComments) === 0) { + const commentRange = getCommentRange(initializer); + emitTrailingCommentsOfPosition(commentRange.pos); + } + emitExpression(initializer); + } + function emitShorthandPropertyAssignment(node: ShorthandPropertyAssignment) { + emit(node.name); + if (node.objectAssignmentInitializer) { + writeSpace(); + writePunctuation("="); + writeSpace(); + emitExpression(node.objectAssignmentInitializer); } - - function emitJsxSpreadAttribute(node: JsxSpreadAttribute) { - writePunctuation("{..."); + } + function emitSpreadAssignment(node: SpreadAssignment) { + if (node.expression) { + emitTokenWithComment(SyntaxKind.DotDotDotToken, node.pos, writePunctuation, node); emitExpression(node.expression); - writePunctuation("}"); } - - function emitJsxExpression(node: JsxExpression) { - if (node.expression) { - writePunctuation("{"); - emit(node.dotDotDotToken); - emitExpression(node.expression); - writePunctuation("}"); + } + // + // Enum + // + function emitEnumMember(node: EnumMember) { + emit(node.name); + emitInitializer(node.initializer, node.name.end, node); + } + // + // JSDoc + // + function emitJSDoc(node: JSDoc) { + write("/**"); + if (node.comment) { + const lines = node.comment.split(/\r\n?|\n/g); + for (const line of lines) { + writeLine(); + writeSpace(); + writePunctuation("*"); + writeSpace(); + write(line); } } - - function emitJsxTagName(node: JsxTagNameExpression) { - if (node.kind === SyntaxKind.Identifier) { - emitExpression(node); + if (node.tags) { + if (node.tags.length === 1 && node.tags[0].kind === SyntaxKind.JSDocTypeTag && !node.comment) { + writeSpace(); + emit(node.tags[0]); } else { - emit(node); + emitList(node, node.tags, ListFormat.JSDocComment); } } - - // - // Clauses - // - - function emitCaseClause(node: CaseClause) { - emitTokenWithComment(SyntaxKind.CaseKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitExpression(node.expression); - - emitCaseOrDefaultClauseRest(node, node.statements, node.expression.end); - } - - function emitDefaultClause(node: DefaultClause) { - const pos = emitTokenWithComment(SyntaxKind.DefaultKeyword, node.pos, writeKeyword, node); - emitCaseOrDefaultClauseRest(node, node.statements, pos); - } - - function emitCaseOrDefaultClauseRest(parentNode: Node, statements: NodeArray, colonPos: number) { - const emitAsSingleStatement = - statements.length === 1 && - ( - // treat synthesized nodes as located on the same line for emit purposes - nodeIsSynthesized(parentNode) || - nodeIsSynthesized(statements[0]) || - rangeStartPositionsAreOnSameLine(parentNode, statements[0], currentSourceFile!) - ); - - let format = ListFormat.CaseOrDefaultClauseStatements; - if (emitAsSingleStatement) { - writeToken(SyntaxKind.ColonToken, colonPos, writePunctuation, parentNode); - writeSpace(); - format &= ~(ListFormat.MultiLine | ListFormat.Indented); + writeSpace(); + write("*/"); + } + function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag) { + emitJSDocTagName(tag.tagName); + emitJSDocTypeExpression(tag.typeExpression); + emitJSDocComment(tag.comment); + } + function emitJSDocAugmentsTag(tag: JSDocAugmentsTag) { + emitJSDocTagName(tag.tagName); + writeSpace(); + writePunctuation("{"); + emit(tag.class); + writePunctuation("}"); + emitJSDocComment(tag.comment); + } + function emitJSDocTemplateTag(tag: JSDocTemplateTag) { + emitJSDocTagName(tag.tagName); + emitJSDocTypeExpression(tag.constraint); + writeSpace(); + emitList(tag, tag.typeParameters, ListFormat.CommaListElements); + emitJSDocComment(tag.comment); + } + function emitJSDocTypedefTag(tag: JSDocTypedefTag) { + emitJSDocTagName(tag.tagName); + if (tag.typeExpression) { + if (tag.typeExpression.kind === SyntaxKind.JSDocTypeExpression) { + emitJSDocTypeExpression(tag.typeExpression); } else { - emitTokenWithComment(SyntaxKind.ColonToken, colonPos, writePunctuation, parentNode); + writeSpace(); + writePunctuation("{"); + write("Object"); + if (tag.typeExpression.isArrayType) { + writePunctuation("["); + writePunctuation("]"); + } + writePunctuation("}"); } - emitList(parentNode, statements, format); } - - function emitHeritageClause(node: HeritageClause) { - writeSpace(); - writeTokenText(node.token, writeKeyword); + if (tag.fullName) { writeSpace(); - emitList(node, node.types, ListFormat.HeritageClauseTypes); + emit(tag.fullName); } - - function emitCatchClause(node: CatchClause) { - const openParenPos = emitTokenWithComment(SyntaxKind.CatchKeyword, node.pos, writeKeyword, node); - writeSpace(); - if (node.variableDeclaration) { - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emit(node.variableDeclaration); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.variableDeclaration.end, writePunctuation, node); - writeSpace(); - } - emit(node.block); + emitJSDocComment(tag.comment); + if (tag.typeExpression && tag.typeExpression.kind === SyntaxKind.JSDocTypeLiteral) { + emitJSDocTypeLiteral(tag.typeExpression); } - - // - // Property assignments - // - - function emitPropertyAssignment(node: PropertyAssignment) { - emit(node.name); - writePunctuation(":"); + } + function emitJSDocCallbackTag(tag: JSDocCallbackTag) { + emitJSDocTagName(tag.tagName); + if (tag.name) { writeSpace(); - // This is to ensure that we emit comment in the following case: - // For example: - // obj = { - // id: /*comment1*/ ()=>void - // } - // "comment1" is not considered to be leading comment for node.initializer - // but rather a trailing comment on the previous node. - const initializer = node.initializer; - if (emitTrailingCommentsOfPosition && (getEmitFlags(initializer) & EmitFlags.NoLeadingComments) === 0) { - const commentRange = getCommentRange(initializer); - emitTrailingCommentsOfPosition(commentRange.pos); - } - emitExpression(initializer); - } - - function emitShorthandPropertyAssignment(node: ShorthandPropertyAssignment) { - emit(node.name); - if (node.objectAssignmentInitializer) { - writeSpace(); - writePunctuation("="); - writeSpace(); - emitExpression(node.objectAssignmentInitializer); - } + emit(tag.name); } - - function emitSpreadAssignment(node: SpreadAssignment) { - if (node.expression) { - emitTokenWithComment(SyntaxKind.DotDotDotToken, node.pos, writePunctuation, node); - emitExpression(node.expression); - } + emitJSDocComment(tag.comment); + emitJSDocSignature(tag.typeExpression); + } + function emitJSDocSimpleTag(tag: JSDocTag) { + emitJSDocTagName(tag.tagName); + emitJSDocComment(tag.comment); + } + function emitJSDocTypeLiteral(lit: JSDocTypeLiteral) { + emitList(lit, createNodeArray(lit.jsDocPropertyTags), ListFormat.JSDocComment); + } + function emitJSDocSignature(sig: JSDocSignature) { + if (sig.typeParameters) { + emitList(sig, createNodeArray(sig.typeParameters), ListFormat.JSDocComment); } - - // - // Enum - // - - function emitEnumMember(node: EnumMember) { - emit(node.name); - emitInitializer(node.initializer, node.name.end, node); + if (sig.parameters) { + emitList(sig, createNodeArray(sig.parameters), ListFormat.JSDocComment); } - - // - // JSDoc - // - function emitJSDoc(node: JSDoc) { - write("/**"); - if (node.comment) { - const lines = node.comment.split(/\r\n?|\n/g); - for (const line of lines) { - writeLine(); - writeSpace(); - writePunctuation("*"); - writeSpace(); - write(line); - } - } - if (node.tags) { - if (node.tags.length === 1 && node.tags[0].kind === SyntaxKind.JSDocTypeTag && !node.comment) { - writeSpace(); - emit(node.tags[0]); - } - else { - emitList(node, node.tags, ListFormat.JSDocComment); - } - } + if (sig.type) { + writeLine(); + writeSpace(); + writePunctuation("*"); writeSpace(); - write("*/"); + emit(sig.type); } - - function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag) { - emitJSDocTagName(tag.tagName); - emitJSDocTypeExpression(tag.typeExpression); - emitJSDocComment(tag.comment); + } + function emitJSDocPropertyLikeTag(param: JSDocPropertyLikeTag) { + emitJSDocTagName(param.tagName); + emitJSDocTypeExpression(param.typeExpression); + writeSpace(); + if (param.isBracketed) { + writePunctuation("["); + } + emit(param.name); + if (param.isBracketed) { + writePunctuation("]"); + } + emitJSDocComment(param.comment); + } + function emitJSDocTagName(tagName: Identifier) { + writePunctuation("@"); + emit(tagName); + } + function emitJSDocComment(comment: string | undefined) { + if (comment) { + writeSpace(); + write(comment); } - - function emitJSDocAugmentsTag(tag: JSDocAugmentsTag) { - emitJSDocTagName(tag.tagName); + } + function emitJSDocTypeExpression(typeExpression: JSDocTypeExpression | undefined) { + if (typeExpression) { writeSpace(); writePunctuation("{"); - emit(tag.class); + emit(typeExpression.type); writePunctuation("}"); - emitJSDocComment(tag.comment); } - - function emitJSDocTemplateTag(tag: JSDocTemplateTag) { - emitJSDocTagName(tag.tagName); - emitJSDocTypeExpression(tag.constraint); - writeSpace(); - emitList(tag, tag.typeParameters, ListFormat.CommaListElements); - emitJSDocComment(tag.comment); - } - - function emitJSDocTypedefTag(tag: JSDocTypedefTag) { - emitJSDocTagName(tag.tagName); - if (tag.typeExpression) { - if (tag.typeExpression.kind === SyntaxKind.JSDocTypeExpression) { - emitJSDocTypeExpression(tag.typeExpression); - } - else { - writeSpace(); - writePunctuation("{"); - write("Object"); - if (tag.typeExpression.isArrayType) { - writePunctuation("["); - writePunctuation("]"); - } - writePunctuation("}"); - } - } - if (tag.fullName) { - writeSpace(); - emit(tag.fullName); - } - emitJSDocComment(tag.comment); - if (tag.typeExpression && tag.typeExpression.kind === SyntaxKind.JSDocTypeLiteral) { - emitJSDocTypeLiteral(tag.typeExpression); + } + // + // Top-level nodes + // + function emitSourceFile(node: SourceFile) { + writeLine(); + const statements = node.statements; + if (emitBodyWithDetachedComments) { + // Emit detached comment if there are no prologue directives or if the first node is synthesized. + // The synthesized node will have no leading comment so some comments may be missed. + const shouldEmitDetachedComment = statements.length === 0 || + !isPrologueDirective(statements[0]) || + nodeIsSynthesized(statements[0]); + if (shouldEmitDetachedComment) { + emitBodyWithDetachedComments(node, statements, emitSourceFileWorker); + return; } } - - function emitJSDocCallbackTag(tag: JSDocCallbackTag) { - emitJSDocTagName(tag.tagName); - if (tag.name) { - writeSpace(); - emit(tag.name); - } - emitJSDocComment(tag.comment); - emitJSDocSignature(tag.typeExpression); - } - - function emitJSDocSimpleTag(tag: JSDocTag) { - emitJSDocTagName(tag.tagName); - emitJSDocComment(tag.comment); - } - - function emitJSDocTypeLiteral(lit: JSDocTypeLiteral) { - emitList(lit, createNodeArray(lit.jsDocPropertyTags), ListFormat.JSDocComment); - } - - function emitJSDocSignature(sig: JSDocSignature) { - if (sig.typeParameters) { - emitList(sig, createNodeArray(sig.typeParameters), ListFormat.JSDocComment); - } - if (sig.parameters) { - emitList(sig, createNodeArray(sig.parameters), ListFormat.JSDocComment); - } - if (sig.type) { - writeLine(); - writeSpace(); - writePunctuation("*"); - writeSpace(); - emit(sig.type); + emitSourceFileWorker(node); + } + function emitSyntheticTripleSlashReferencesIfNeeded(node: Bundle) { + emitTripleSlashDirectives(!!node.hasNoDefaultLib, node.syntheticFileReferences || [], node.syntheticTypeReferences || [], node.syntheticLibReferences || []); + for (const prepend of node.prepends) { + if (isUnparsedSource(prepend) && prepend.syntheticReferences) { + for (const ref of prepend.syntheticReferences) { + emit(ref); + writeLine(); + } } } - - function emitJSDocPropertyLikeTag(param: JSDocPropertyLikeTag) { - emitJSDocTagName(param.tagName); - emitJSDocTypeExpression(param.typeExpression); - writeSpace(); - if (param.isBracketed) { - writePunctuation("["); - } - emit(param.name); - if (param.isBracketed) { - writePunctuation("]"); - } - emitJSDocComment(param.comment); + } + function emitTripleSlashDirectivesIfNeeded(node: SourceFile) { + if (node.isDeclarationFile) + emitTripleSlashDirectives(node.hasNoDefaultLib, node.referencedFiles, node.typeReferenceDirectives, node.libReferenceDirectives); + } + function emitTripleSlashDirectives(hasNoDefaultLib: boolean, files: readonly FileReference[], types: readonly FileReference[], libs: readonly FileReference[]) { + if (hasNoDefaultLib) { + const pos = writer.getTextPos(); + writeComment(`/// `); + if (bundleFileInfo) + bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.NoDefaultLib }); + writeLine(); } - - function emitJSDocTagName(tagName: Identifier) { - writePunctuation("@"); - emit(tagName); + if (currentSourceFile && currentSourceFile.moduleName) { + writeComment(`/// `); + writeLine(); } - - function emitJSDocComment(comment: string | undefined) { - if (comment) { - writeSpace(); - write(comment); + if (currentSourceFile && currentSourceFile.amdDependencies) { + for (const dep of currentSourceFile.amdDependencies) { + if (dep.name) { + writeComment(`/// `); + } + else { + writeComment(`/// `); + } + writeLine(); } } - - function emitJSDocTypeExpression(typeExpression: JSDocTypeExpression | undefined) { - if (typeExpression) { - writeSpace(); - writePunctuation("{"); - emit(typeExpression.type); - writePunctuation("}"); - } + for (const directive of files) { + const pos = writer.getTextPos(); + writeComment(`/// `); + if (bundleFileInfo) + bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Reference, data: directive.fileName }); + writeLine(); } - - // - // Top-level nodes - // - - function emitSourceFile(node: SourceFile) { + for (const directive of types) { + const pos = writer.getTextPos(); + writeComment(`/// `); + if (bundleFileInfo) + bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Type, data: directive.fileName }); writeLine(); - const statements = node.statements; - if (emitBodyWithDetachedComments) { - // Emit detached comment if there are no prologue directives or if the first node is synthesized. - // The synthesized node will have no leading comment so some comments may be missed. - const shouldEmitDetachedComment = statements.length === 0 || - !isPrologueDirective(statements[0]) || - nodeIsSynthesized(statements[0]); - if (shouldEmitDetachedComment) { - emitBodyWithDetachedComments(node, statements, emitSourceFileWorker); - return; - } - } - emitSourceFileWorker(node); - } - - function emitSyntheticTripleSlashReferencesIfNeeded(node: Bundle) { - emitTripleSlashDirectives(!!node.hasNoDefaultLib, node.syntheticFileReferences || [], node.syntheticTypeReferences || [], node.syntheticLibReferences || []); - for (const prepend of node.prepends) { - if (isUnparsedSource(prepend) && prepend.syntheticReferences) { - for (const ref of prepend.syntheticReferences) { - emit(ref); - writeLine(); - } - } - } } - - function emitTripleSlashDirectivesIfNeeded(node: SourceFile) { - if (node.isDeclarationFile) emitTripleSlashDirectives(node.hasNoDefaultLib, node.referencedFiles, node.typeReferenceDirectives, node.libReferenceDirectives); + for (const directive of libs) { + const pos = writer.getTextPos(); + writeComment(`/// `); + if (bundleFileInfo) + bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Lib, data: directive.fileName }); + writeLine(); } - - function emitTripleSlashDirectives(hasNoDefaultLib: boolean, files: readonly FileReference[], types: readonly FileReference[], libs: readonly FileReference[]) { - if (hasNoDefaultLib) { - const pos = writer.getTextPos(); - writeComment(`/// `); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.NoDefaultLib }); - writeLine(); - } - if (currentSourceFile && currentSourceFile.moduleName) { - writeComment(`/// `); - writeLine(); - } - if (currentSourceFile && currentSourceFile.amdDependencies) { - for (const dep of currentSourceFile.amdDependencies) { - if (dep.name) { - writeComment(`/// `); - } - else { - writeComment(`/// `); + } + function emitSourceFileWorker(node: SourceFile) { + const statements = node.statements; + pushNameGenerationScope(node); + forEach(node.statements, generateNames); + emitHelpers(node); + const index = findIndex(statements, statement => !isPrologueDirective(statement)); + emitTripleSlashDirectivesIfNeeded(node); + emitList(node, statements, ListFormat.MultiLine, index === -1 ? statements.length : index); + popNameGenerationScope(node); + } + // Transformation nodes + function emitPartiallyEmittedExpression(node: PartiallyEmittedExpression) { + emitExpression(node.expression); + } + function emitCommaList(node: CommaListExpression) { + emitExpressionList(node, node.elements, ListFormat.CommaListElements); + } + /** + * Emits any prologue directives at the start of a Statement list, returning the + * number of prologue directives written to the output. + */ + function emitPrologueDirectives(statements: readonly Node[], sourceFile?: SourceFile, seenPrologueDirectives?: ts.Map, recordBundleFileSection?: true): number { + let needsToSetSourceFile = !!sourceFile; + for (let i = 0; i < statements.length; i++) { + const statement = statements[i]; + if (isPrologueDirective(statement)) { + const shouldEmitPrologueDirective = seenPrologueDirectives ? !seenPrologueDirectives.has(statement.expression.text) : true; + if (shouldEmitPrologueDirective) { + if (needsToSetSourceFile) { + needsToSetSourceFile = false; + setSourceFile(sourceFile); } writeLine(); + const pos = writer.getTextPos(); + emit(statement); + if (recordBundleFileSection && bundleFileInfo) + bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Prologue, data: statement.expression.text }); + if (seenPrologueDirectives) { + seenPrologueDirectives.set(statement.expression.text, true); + } } } - for (const directive of files) { - const pos = writer.getTextPos(); - writeComment(`/// `); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Reference, data: directive.fileName }); - writeLine(); + else { + // return index of the first non prologue directive + return i; } - for (const directive of types) { - const pos = writer.getTextPos(); - writeComment(`/// `); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Type, data: directive.fileName }); + } + return statements.length; + } + function emitUnparsedPrologues(prologues: readonly UnparsedPrologue[], seenPrologueDirectives: ts.Map) { + for (const prologue of prologues) { + if (!seenPrologueDirectives.has(prologue.data)) { writeLine(); - } - for (const directive of libs) { const pos = writer.getTextPos(); - writeComment(`/// `); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Lib, data: directive.fileName }); - writeLine(); + emit(prologue); + if (bundleFileInfo) + bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Prologue, data: prologue.data }); + if (seenPrologueDirectives) { + seenPrologueDirectives.set(prologue.data, true); + } } } - - function emitSourceFileWorker(node: SourceFile) { - const statements = node.statements; - pushNameGenerationScope(node); - forEach(node.statements, generateNames); - emitHelpers(node); - const index = findIndex(statements, statement => !isPrologueDirective(statement)); - emitTripleSlashDirectivesIfNeeded(node); - emitList(node, statements, ListFormat.MultiLine, index === -1 ? statements.length : index); - popNameGenerationScope(node); - } - - // Transformation nodes - - function emitPartiallyEmittedExpression(node: PartiallyEmittedExpression) { - emitExpression(node.expression); + } + function emitPrologueDirectivesIfNeeded(sourceFileOrBundle: Bundle | SourceFile) { + if (isSourceFile(sourceFileOrBundle)) { + emitPrologueDirectives(sourceFileOrBundle.statements, sourceFileOrBundle); } - - function emitCommaList(node: CommaListExpression) { - emitExpressionList(node, node.elements, ListFormat.CommaListElements); - } - - /** - * Emits any prologue directives at the start of a Statement list, returning the - * number of prologue directives written to the output. - */ - function emitPrologueDirectives(statements: readonly Node[], sourceFile?: SourceFile, seenPrologueDirectives?: Map, recordBundleFileSection?: true): number { - let needsToSetSourceFile = !!sourceFile; - for (let i = 0; i < statements.length; i++) { - const statement = statements[i]; - if (isPrologueDirective(statement)) { - const shouldEmitPrologueDirective = seenPrologueDirectives ? !seenPrologueDirectives.has(statement.expression.text) : true; - if (shouldEmitPrologueDirective) { - if (needsToSetSourceFile) { - needsToSetSourceFile = false; - setSourceFile(sourceFile); - } - writeLine(); - const pos = writer.getTextPos(); - emit(statement); - if (recordBundleFileSection && bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Prologue, data: statement.expression.text }); - if (seenPrologueDirectives) { - seenPrologueDirectives.set(statement.expression.text, true); - } - } - } - else { - // return index of the first non prologue directive - return i; - } + else { + const seenPrologueDirectives = createMap(); + for (const prepend of sourceFileOrBundle.prepends) { + emitUnparsedPrologues((prepend as UnparsedSource).prologues, seenPrologueDirectives); + } + for (const sourceFile of sourceFileOrBundle.sourceFiles) { + emitPrologueDirectives(sourceFile.statements, sourceFile, seenPrologueDirectives, /*recordBundleFileSection*/ true); } - - return statements.length; + setSourceFile(undefined); } - - function emitUnparsedPrologues(prologues: readonly UnparsedPrologue[], seenPrologueDirectives: Map) { - for (const prologue of prologues) { - if (!seenPrologueDirectives.has(prologue.data)) { - writeLine(); - const pos = writer.getTextPos(); - emit(prologue); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Prologue, data: prologue.data }); - if (seenPrologueDirectives) { - seenPrologueDirectives.set(prologue.data, true); + } + function getPrologueDirectivesFromBundledSourceFiles(bundle: Bundle): SourceFilePrologueInfo[] | undefined { + const seenPrologueDirectives = createMap(); + let prologues: SourceFilePrologueInfo[] | undefined; + for (let index = 0; index < bundle.sourceFiles.length; index++) { + const sourceFile = bundle.sourceFiles[index]; + let directives: SourceFilePrologueDirective[] | undefined; + let end = 0; + for (const statement of sourceFile.statements) { + if (!isPrologueDirective(statement)) + break; + if (seenPrologueDirectives.has(statement.expression.text)) + continue; + seenPrologueDirectives.set(statement.expression.text, true); + (directives || (directives = [])).push({ + pos: statement.pos, + end: statement.end, + expression: { + pos: statement.expression.pos, + end: statement.expression.end, + text: statement.expression.text } - } + }); + end = end < statement.end ? statement.end : end; } + if (directives) + (prologues || (prologues = [])).push({ file: index, text: sourceFile.text.substring(0, end), directives }); } - - function emitPrologueDirectivesIfNeeded(sourceFileOrBundle: Bundle | SourceFile) { - if (isSourceFile(sourceFileOrBundle)) { - emitPrologueDirectives(sourceFileOrBundle.statements, sourceFileOrBundle); - } - else { - const seenPrologueDirectives = createMap(); - for (const prepend of sourceFileOrBundle.prepends) { - emitUnparsedPrologues((prepend as UnparsedSource).prologues, seenPrologueDirectives); - } - for (const sourceFile of sourceFileOrBundle.sourceFiles) { - emitPrologueDirectives(sourceFile.statements, sourceFile, seenPrologueDirectives, /*recordBundleFileSection*/ true); - } - setSourceFile(undefined); + return prologues; + } + function emitShebangIfNeeded(sourceFileOrBundle: Bundle | SourceFile | UnparsedSource) { + if (isSourceFile(sourceFileOrBundle) || isUnparsedSource(sourceFileOrBundle)) { + const shebang = getShebang(sourceFileOrBundle.text); + if (shebang) { + writeComment(shebang); + writeLine(); + return true; } } - - function getPrologueDirectivesFromBundledSourceFiles(bundle: Bundle): SourceFilePrologueInfo[] | undefined { - const seenPrologueDirectives = createMap(); - let prologues: SourceFilePrologueInfo[] | undefined; - for (let index = 0; index < bundle.sourceFiles.length; index++) { - const sourceFile = bundle.sourceFiles[index]; - let directives: SourceFilePrologueDirective[] | undefined; - let end = 0; - for (const statement of sourceFile.statements) { - if (!isPrologueDirective(statement)) break; - if (seenPrologueDirectives.has(statement.expression.text)) continue; - seenPrologueDirectives.set(statement.expression.text, true); - (directives || (directives = [])).push({ - pos: statement.pos, - end: statement.end, - expression: { - pos: statement.expression.pos, - end: statement.expression.end, - text: statement.expression.text - } - }); - end = end < statement.end ? statement.end : end; - } - if (directives) (prologues || (prologues = [])).push({ file: index, text: sourceFile.text.substring(0, end), directives }); - } - return prologues; - } - - function emitShebangIfNeeded(sourceFileOrBundle: Bundle | SourceFile | UnparsedSource) { - if (isSourceFile(sourceFileOrBundle) || isUnparsedSource(sourceFileOrBundle)) { - const shebang = getShebang(sourceFileOrBundle.text); - if (shebang) { - writeComment(shebang); - writeLine(); + else { + for (const prepend of sourceFileOrBundle.prepends) { + Debug.assertNode(prepend, isUnparsedSource); + if (emitShebangIfNeeded((prepend as UnparsedSource))) { return true; } } - else { - for (const prepend of sourceFileOrBundle.prepends) { - Debug.assertNode(prepend, isUnparsedSource); - if (emitShebangIfNeeded(prepend as UnparsedSource)) { - return true; - } - } - for (const sourceFile of sourceFileOrBundle.sourceFiles) { - // Emit only the first encountered shebang - if (emitShebangIfNeeded(sourceFile)) { - return true; - } + for (const sourceFile of sourceFileOrBundle.sourceFiles) { + // Emit only the first encountered shebang + if (emitShebangIfNeeded(sourceFile)) { + return true; } } } - - // - // Helpers - // - - function emitNodeWithWriter(node: Node | undefined, writer: typeof write) { - if (!node) return; - const savedWrite = write; - write = writer; - emit(node); - write = savedWrite; + } + // + // Helpers + // + function emitNodeWithWriter(node: Node | undefined, writer: typeof write) { + if (!node) + return; + const savedWrite = write; + write = writer; + emit(node); + write = savedWrite; + } + function emitModifiers(node: Node, modifiers: NodeArray | undefined) { + if (modifiers && modifiers.length) { + emitList(node, modifiers, ListFormat.Modifiers); + writeSpace(); } - - function emitModifiers(node: Node, modifiers: NodeArray | undefined) { - if (modifiers && modifiers.length) { - emitList(node, modifiers, ListFormat.Modifiers); - writeSpace(); - } + } + function emitTypeAnnotation(node: TypeNode | undefined) { + if (node) { + writePunctuation(":"); + writeSpace(); + emit(node); } - - function emitTypeAnnotation(node: TypeNode | undefined) { - if (node) { - writePunctuation(":"); - writeSpace(); - emit(node); - } + } + function emitInitializer(node: Expression | undefined, equalCommentStartPos: number, container: Node) { + if (node) { + writeSpace(); + emitTokenWithComment(SyntaxKind.EqualsToken, equalCommentStartPos, writeOperator, container); + writeSpace(); + emitExpression(node); } - - function emitInitializer(node: Expression | undefined, equalCommentStartPos: number, container: Node) { - if (node) { - writeSpace(); - emitTokenWithComment(SyntaxKind.EqualsToken, equalCommentStartPos, writeOperator, container); - writeSpace(); - emitExpression(node); - } + } + function emitNodeWithPrefix(prefix: string, prefixWriter: (s: string) => void, node: Node, emit: (node: Node) => void) { + if (node) { + prefixWriter(prefix); + emit(node); } - - function emitNodeWithPrefix(prefix: string, prefixWriter: (s: string) => void, node: Node, emit: (node: Node) => void) { - if (node) { - prefixWriter(prefix); - emit(node); - } + } + function emitWithLeadingSpace(node: Node | undefined) { + if (node) { + writeSpace(); + emit(node); } - - function emitWithLeadingSpace(node: Node | undefined) { - if (node) { - writeSpace(); - emit(node); - } + } + function emitExpressionWithLeadingSpace(node: Expression | undefined) { + if (node) { + writeSpace(); + emitExpression(node); } - - function emitExpressionWithLeadingSpace(node: Expression | undefined) { - if (node) { - writeSpace(); - emitExpression(node); - } + } + function emitWithTrailingSpace(node: Node | undefined) { + if (node) { + emit(node); + writeSpace(); } - - function emitWithTrailingSpace(node: Node | undefined) { - if (node) { - emit(node); - writeSpace(); - } + } + function emitEmbeddedStatement(parent: Node, node: Statement) { + if (isBlock(node) || getEmitFlags(parent) & EmitFlags.SingleLine) { + writeSpace(); + emit(node); } - - function emitEmbeddedStatement(parent: Node, node: Statement) { - if (isBlock(node) || getEmitFlags(parent) & EmitFlags.SingleLine) { - writeSpace(); - emit(node); + else { + writeLine(); + increaseIndent(); + if (isEmptyStatement(node)) { + pipelineEmit(EmitHint.EmbeddedStatement, node); } else { - writeLine(); - increaseIndent(); - if (isEmptyStatement(node)) { - pipelineEmit(EmitHint.EmbeddedStatement, node); - } - else { - emit(node); - } - decreaseIndent(); + emit(node); } + decreaseIndent(); + } + } + function emitDecorators(parentNode: Node, decorators: NodeArray | undefined) { + emitList(parentNode, decorators, ListFormat.Decorators); + } + function emitTypeArguments(parentNode: Node, typeArguments: NodeArray | undefined) { + emitList(parentNode, typeArguments, ListFormat.TypeArguments); + } + function emitTypeParameters(parentNode: SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration | ClassDeclaration | ClassExpression, typeParameters: NodeArray | undefined) { + if (isFunctionLike(parentNode) && parentNode.typeArguments) { // Quick info uses type arguments in place of type parameters on instantiated signatures + return emitTypeArguments(parentNode, parentNode.typeArguments); + } + emitList(parentNode, typeParameters, ListFormat.TypeParameters); + } + function emitParameters(parentNode: Node, parameters: NodeArray) { + emitList(parentNode, parameters, ListFormat.Parameters); + } + function canEmitSimpleArrowHead(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray) { + const parameter = singleOrUndefined(parameters); + return parameter + && parameter.pos === parentNode.pos // may not have parsed tokens between parent and parameter + && isArrowFunction(parentNode) // only arrow functions may have simple arrow head + && !parentNode.type // arrow function may not have return type annotation + && !some(parentNode.decorators) // parent may not have decorators + && !some(parentNode.modifiers) // parent may not have modifiers + && !some(parentNode.typeParameters) // parent may not have type parameters + && !some(parameter.decorators) // parameter may not have decorators + && !some(parameter.modifiers) // parameter may not have modifiers + && !parameter.dotDotDotToken // parameter may not be rest + && !parameter.questionToken // parameter may not be optional + && !parameter.type // parameter may not have a type annotation + && !parameter.initializer // parameter may not have an initializer + && isIdentifier(parameter.name); // parameter name must be identifier + } + function emitParametersForArrow(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray) { + if (canEmitSimpleArrowHead(parentNode, parameters)) { + emitList(parentNode, parameters, ListFormat.Parameters & ~ListFormat.Parenthesis); + } + else { + emitParameters(parentNode, parameters); } - - function emitDecorators(parentNode: Node, decorators: NodeArray | undefined) { - emitList(parentNode, decorators, ListFormat.Decorators); + } + function emitParametersForIndexSignature(parentNode: Node, parameters: NodeArray) { + emitList(parentNode, parameters, ListFormat.IndexSignatureParameters); + } + function emitList(parentNode: TextRange, children: NodeArray | undefined, format: ListFormat, start?: number, count?: number) { + emitNodeList(emit, parentNode, children, format, start, count); + } + function emitExpressionList(parentNode: TextRange, children: NodeArray | undefined, format: ListFormat, start?: number, count?: number) { + emitNodeList((emitExpression as (node: Node) => void), parentNode, children, format, start, count); // TODO: GH#18217 + } + function writeDelimiter(format: ListFormat) { + switch (format & ListFormat.DelimitersMask) { + case ListFormat.None: + break; + case ListFormat.CommaDelimited: + writePunctuation(","); + break; + case ListFormat.BarDelimited: + writeSpace(); + writePunctuation("|"); + break; + case ListFormat.AsteriskDelimited: + writeSpace(); + writePunctuation("*"); + writeSpace(); + break; + case ListFormat.AmpersandDelimited: + writeSpace(); + writePunctuation("&"); + break; } - - function emitTypeArguments(parentNode: Node, typeArguments: NodeArray | undefined) { - emitList(parentNode, typeArguments, ListFormat.TypeArguments); + } + function emitNodeList(emit: (node: Node) => void, parentNode: TextRange, children: NodeArray | undefined, format: ListFormat, start = 0, count = children ? children.length - start : 0) { + const isUndefined = children === undefined; + if (isUndefined && format & ListFormat.OptionalIfUndefined) { + return; } - - function emitTypeParameters(parentNode: SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration | ClassDeclaration | ClassExpression, typeParameters: NodeArray | undefined) { - if (isFunctionLike(parentNode) && parentNode.typeArguments) { // Quick info uses type arguments in place of type parameters on instantiated signatures - return emitTypeArguments(parentNode, parentNode.typeArguments); - } - emitList(parentNode, typeParameters, ListFormat.TypeParameters); - } - - function emitParameters(parentNode: Node, parameters: NodeArray) { - emitList(parentNode, parameters, ListFormat.Parameters); - } - - function canEmitSimpleArrowHead(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray) { - const parameter = singleOrUndefined(parameters); - return parameter - && parameter.pos === parentNode.pos // may not have parsed tokens between parent and parameter - && isArrowFunction(parentNode) // only arrow functions may have simple arrow head - && !parentNode.type // arrow function may not have return type annotation - && !some(parentNode.decorators) // parent may not have decorators - && !some(parentNode.modifiers) // parent may not have modifiers - && !some(parentNode.typeParameters) // parent may not have type parameters - && !some(parameter.decorators) // parameter may not have decorators - && !some(parameter.modifiers) // parameter may not have modifiers - && !parameter.dotDotDotToken // parameter may not be rest - && !parameter.questionToken // parameter may not be optional - && !parameter.type // parameter may not have a type annotation - && !parameter.initializer // parameter may not have an initializer - && isIdentifier(parameter.name); // parameter name must be identifier - } - - function emitParametersForArrow(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray) { - if (canEmitSimpleArrowHead(parentNode, parameters)) { - emitList(parentNode, parameters, ListFormat.Parameters & ~ListFormat.Parenthesis); + const isEmpty = children === undefined || start >= children.length || count === 0; + if (isEmpty && format & ListFormat.OptionalIfEmpty) { + if (onBeforeEmitNodeArray) { + onBeforeEmitNodeArray(children); } - else { - emitParameters(parentNode, parameters); + if (onAfterEmitNodeArray) { + onAfterEmitNodeArray(children); } + return; } - - function emitParametersForIndexSignature(parentNode: Node, parameters: NodeArray) { - emitList(parentNode, parameters, ListFormat.IndexSignatureParameters); - } - - function emitList(parentNode: TextRange, children: NodeArray | undefined, format: ListFormat, start?: number, count?: number) { - emitNodeList(emit, parentNode, children, format, start, count); - } - - function emitExpressionList(parentNode: TextRange, children: NodeArray | undefined, format: ListFormat, start?: number, count?: number) { - emitNodeList(emitExpression as (node: Node) => void, parentNode, children, format, start, count); // TODO: GH#18217 - } - - function writeDelimiter(format: ListFormat) { - switch (format & ListFormat.DelimitersMask) { - case ListFormat.None: - break; - case ListFormat.CommaDelimited: - writePunctuation(","); - break; - case ListFormat.BarDelimited: - writeSpace(); - writePunctuation("|"); - break; - case ListFormat.AsteriskDelimited: - writeSpace(); - writePunctuation("*"); - writeSpace(); - break; - case ListFormat.AmpersandDelimited: - writeSpace(); - writePunctuation("&"); - break; + if (format & ListFormat.BracketsMask) { + writePunctuation(getOpeningBracket(format)); + if (isEmpty && !isUndefined) { + // TODO: GH#18217 + emitTrailingCommentsOfPosition(children!.pos, /*prefixSpace*/ true); // Emit comments within empty bracketed lists } } - - function emitNodeList(emit: (node: Node) => void, parentNode: TextRange, children: NodeArray | undefined, format: ListFormat, start = 0, count = children ? children.length - start : 0) { - const isUndefined = children === undefined; - if (isUndefined && format & ListFormat.OptionalIfUndefined) { - return; + if (onBeforeEmitNodeArray) { + onBeforeEmitNodeArray(children); + } + if (isEmpty) { + // Write a line terminator if the parent node was multi-line + if (format & ListFormat.MultiLine) { + writeLine(); } - - const isEmpty = children === undefined || start >= children.length || count === 0; - if (isEmpty && format & ListFormat.OptionalIfEmpty) { - if (onBeforeEmitNodeArray) { - onBeforeEmitNodeArray(children); - } - if (onAfterEmitNodeArray) { - onAfterEmitNodeArray(children); - } - return; + else if (format & ListFormat.SpaceBetweenBraces && !(format & ListFormat.NoSpaceIfEmpty)) { + writeSpace(); } - - if (format & ListFormat.BracketsMask) { - writePunctuation(getOpeningBracket(format)); - if (isEmpty && !isUndefined) { - // TODO: GH#18217 - emitTrailingCommentsOfPosition(children!.pos, /*prefixSpace*/ true); // Emit comments within empty bracketed lists - } + } + else { + // Write the opening line terminator or leading whitespace. + const mayEmitInterveningComments = (format & ListFormat.NoInterveningComments) === 0; + let shouldEmitInterveningComments = mayEmitInterveningComments; + if (shouldWriteLeadingLineTerminator(parentNode, children!, format)) { // TODO: GH#18217 + writeLine(); + shouldEmitInterveningComments = false; } - - if (onBeforeEmitNodeArray) { - onBeforeEmitNodeArray(children); + else if (format & ListFormat.SpaceBetweenBraces) { + writeSpace(); } - - if (isEmpty) { - // Write a line terminator if the parent node was multi-line - if (format & ListFormat.MultiLine) { - writeLine(); - } - else if (format & ListFormat.SpaceBetweenBraces && !(format & ListFormat.NoSpaceIfEmpty)) { - writeSpace(); - } + // Increase the indent, if requested. + if (format & ListFormat.Indented) { + increaseIndent(); } - else { - // Write the opening line terminator or leading whitespace. - const mayEmitInterveningComments = (format & ListFormat.NoInterveningComments) === 0; - let shouldEmitInterveningComments = mayEmitInterveningComments; - if (shouldWriteLeadingLineTerminator(parentNode, children!, format)) { // TODO: GH#18217 + // Emit each child. + let previousSibling: Node | undefined; + let previousSourceFileTextKind: ReturnType; + let shouldDecreaseIndentAfterEmit = false; + for (let i = 0; i < count; i++) { + const child = children![start + i]; + // Write the delimiter if this is not the first node. + if (format & ListFormat.AsteriskDelimited) { + // always write JSDoc in the format "\n *" writeLine(); - shouldEmitInterveningComments = false; - } - else if (format & ListFormat.SpaceBetweenBraces) { - writeSpace(); - } - - // Increase the indent, if requested. - if (format & ListFormat.Indented) { - increaseIndent(); + writeDelimiter(format); } - - // Emit each child. - let previousSibling: Node | undefined; - let previousSourceFileTextKind: ReturnType; - let shouldDecreaseIndentAfterEmit = false; - for (let i = 0; i < count; i++) { - const child = children![start + i]; - - // Write the delimiter if this is not the first node. - if (format & ListFormat.AsteriskDelimited) { - // always write JSDoc in the format "\n *" - writeLine(); - writeDelimiter(format); - } - else if (previousSibling) { - // i.e - // function commentedParameters( - // /* Parameter a */ - // a - // /* End of parameter a */ -> this comment isn't considered to be trailing comment of parameter "a" due to newline - // , - if (format & ListFormat.DelimitersMask && previousSibling.end !== parentNode.end) { - emitLeadingCommentsOfPosition(previousSibling.end); - } - writeDelimiter(format); - recordBundleFileInternalSectionEnd(previousSourceFileTextKind); - - // Write either a line terminator or whitespace to separate the elements. - if (shouldWriteSeparatingLineTerminator(previousSibling, child, format)) { - // If a synthesized node in a single-line list starts on a new - // line, we should increase the indent. - if ((format & (ListFormat.LinesMask | ListFormat.Indented)) === ListFormat.SingleLine) { - increaseIndent(); - shouldDecreaseIndentAfterEmit = true; - } - - writeLine(); - shouldEmitInterveningComments = false; - } - else if (previousSibling && format & ListFormat.SpaceBetweenSiblings) { - writeSpace(); - } + else if (previousSibling) { + // i.e + // function commentedParameters( + // /* Parameter a */ + // a + // /* End of parameter a */ -> this comment isn't considered to be trailing comment of parameter "a" due to newline + // , + if (format & ListFormat.DelimitersMask && previousSibling.end !== parentNode.end) { + emitLeadingCommentsOfPosition(previousSibling.end); } - - // Emit this child. - previousSourceFileTextKind = recordBundleFileInternalSectionStart(child); - if (shouldEmitInterveningComments) { - if (emitTrailingCommentsOfPosition) { - const commentRange = getCommentRange(child); - emitTrailingCommentsOfPosition(commentRange.pos); + writeDelimiter(format); + recordBundleFileInternalSectionEnd(previousSourceFileTextKind); + // Write either a line terminator or whitespace to separate the elements. + if (shouldWriteSeparatingLineTerminator(previousSibling, child, format)) { + // If a synthesized node in a single-line list starts on a new + // line, we should increase the indent. + if ((format & (ListFormat.LinesMask | ListFormat.Indented)) === ListFormat.SingleLine) { + increaseIndent(); + shouldDecreaseIndentAfterEmit = true; } + writeLine(); + shouldEmitInterveningComments = false; } - else { - shouldEmitInterveningComments = mayEmitInterveningComments; - } - - emit(child); - - if (shouldDecreaseIndentAfterEmit) { - decreaseIndent(); - shouldDecreaseIndentAfterEmit = false; + else if (previousSibling && format & ListFormat.SpaceBetweenSiblings) { + writeSpace(); } - - previousSibling = child; } - - // Write a trailing comma, if requested. - const hasTrailingComma = (format & ListFormat.AllowTrailingComma) && children!.hasTrailingComma; - if (format & ListFormat.CommaDelimited && hasTrailingComma) { - writePunctuation(","); + // Emit this child. + previousSourceFileTextKind = recordBundleFileInternalSectionStart(child); + if (shouldEmitInterveningComments) { + if (emitTrailingCommentsOfPosition) { + const commentRange = getCommentRange(child); + emitTrailingCommentsOfPosition(commentRange.pos); + } } - - - // Emit any trailing comment of the last element in the list - // i.e - // var array = [... - // 2 - // /* end of element 2 */ - // ]; - if (previousSibling && format & ListFormat.DelimitersMask && previousSibling.end !== parentNode.end && !(getEmitFlags(previousSibling) & EmitFlags.NoTrailingComments)) { - emitLeadingCommentsOfPosition(previousSibling.end); + else { + shouldEmitInterveningComments = mayEmitInterveningComments; } - - // Decrease the indent, if requested. - if (format & ListFormat.Indented) { + emit(child); + if (shouldDecreaseIndentAfterEmit) { decreaseIndent(); + shouldDecreaseIndentAfterEmit = false; } - - recordBundleFileInternalSectionEnd(previousSourceFileTextKind); - - // Write the closing line terminator or closing whitespace. - if (shouldWriteClosingLineTerminator(parentNode, children!, format)) { - writeLine(); - } - else if (format & ListFormat.SpaceBetweenBraces) { - writeSpace(); - } + previousSibling = child; + } + // Write a trailing comma, if requested. + const hasTrailingComma = (format & ListFormat.AllowTrailingComma) && children!.hasTrailingComma; + if (format & ListFormat.CommaDelimited && hasTrailingComma) { + writePunctuation(","); + } + // Emit any trailing comment of the last element in the list + // i.e + // var array = [... + // 2 + // /* end of element 2 */ + // ]; + if (previousSibling && format & ListFormat.DelimitersMask && previousSibling.end !== parentNode.end && !(getEmitFlags(previousSibling) & EmitFlags.NoTrailingComments)) { + emitLeadingCommentsOfPosition(previousSibling.end); + } + // Decrease the indent, if requested. + if (format & ListFormat.Indented) { + decreaseIndent(); } - - if (onAfterEmitNodeArray) { - onAfterEmitNodeArray(children); + recordBundleFileInternalSectionEnd(previousSourceFileTextKind); + // Write the closing line terminator or closing whitespace. + if (shouldWriteClosingLineTerminator(parentNode, children!, format)) { + writeLine(); } - - if (format & ListFormat.BracketsMask) { - if (isEmpty && !isUndefined) { - // TODO: GH#18217 - emitLeadingCommentsOfPosition(children!.end); // Emit leading comments within empty lists - } - writePunctuation(getClosingBracket(format)); + else if (format & ListFormat.SpaceBetweenBraces) { + writeSpace(); } } - - // Writers - - function writeLiteral(s: string) { - writer.writeLiteral(s); - } - - function writeStringLiteral(s: string) { - writer.writeStringLiteral(s); - } - - function writeBase(s: string) { - writer.write(s); - } - - function writeSymbol(s: string, sym: Symbol) { - writer.writeSymbol(s, sym); - } - - function writePunctuation(s: string) { - writer.writePunctuation(s); + if (onAfterEmitNodeArray) { + onAfterEmitNodeArray(children); } - - function writeTrailingSemicolon() { - writer.writeTrailingSemicolon(";"); + if (format & ListFormat.BracketsMask) { + if (isEmpty && !isUndefined) { + // TODO: GH#18217 + emitLeadingCommentsOfPosition(children!.end); // Emit leading comments within empty lists + } + writePunctuation(getClosingBracket(format)); } - - function writeKeyword(s: string) { - writer.writeKeyword(s); + } + // Writers + function writeLiteral(s: string) { + writer.writeLiteral(s); + } + function writeStringLiteral(s: string) { + writer.writeStringLiteral(s); + } + function writeBase(s: string) { + writer.write(s); + } + function writeSymbol(s: string, sym: Symbol) { + writer.writeSymbol(s, sym); + } + function writePunctuation(s: string) { + writer.writePunctuation(s); + } + function writeTrailingSemicolon() { + writer.writeTrailingSemicolon(";"); + } + function writeKeyword(s: string) { + writer.writeKeyword(s); + } + function writeOperator(s: string) { + writer.writeOperator(s); + } + function writeParameter(s: string) { + writer.writeParameter(s); + } + function writeComment(s: string) { + writer.writeComment(s); + } + function writeSpace() { + writer.writeSpace(" "); + } + function writeProperty(s: string) { + writer.writeProperty(s); + } + function writeLine() { + writer.writeLine(); + } + function increaseIndent() { + writer.increaseIndent(); + } + function decreaseIndent() { + writer.decreaseIndent(); + } + function writeToken(token: SyntaxKind, pos: number, writer: (s: string) => void, contextNode?: Node) { + return !sourceMapsDisabled + ? emitTokenWithSourceMap(contextNode, token, writer, pos, writeTokenText) + : writeTokenText(token, writer, pos); + } + function writeTokenNode(node: Node, writer: (s: string) => void) { + if (onBeforeEmitToken) { + onBeforeEmitToken(node); } - - function writeOperator(s: string) { - writer.writeOperator(s); + writer((tokenToString(node.kind)!)); + if (onAfterEmitToken) { + onAfterEmitToken(node); } - - function writeParameter(s: string) { - writer.writeParameter(s); + } + function writeTokenText(token: SyntaxKind, writer: (s: string) => void): void; + function writeTokenText(token: SyntaxKind, writer: (s: string) => void, pos: number): number; + function writeTokenText(token: SyntaxKind, writer: (s: string) => void, pos?: number): number { + const tokenString = (tokenToString(token)!); + writer(tokenString); + return pos! < 0 ? pos! : pos! + tokenString.length; + } + function writeLineOrSpace(node: Node) { + if (getEmitFlags(node) & EmitFlags.SingleLine) { + writeSpace(); } - - function writeComment(s: string) { - writer.writeComment(s); + else { + writeLine(); } - - function writeSpace() { - writer.writeSpace(" "); + } + function writeLines(text: string): void { + const lines = text.split(/\r\n?|\n/g); + const indentation = guessIndentation(lines); + for (const lineText of lines) { + const line = indentation ? lineText.slice(indentation) : lineText; + if (line.length) { + writeLine(); + write(line); + } } - - function writeProperty(s: string) { - writer.writeProperty(s); + } + function increaseIndentIf(value: boolean, writeSpaceIfNotIndenting: boolean) { + if (value) { + increaseIndent(); + writeLine(); } - - function writeLine() { - writer.writeLine(); + else if (writeSpaceIfNotIndenting) { + writeSpace(); } - - function increaseIndent() { - writer.increaseIndent(); + } + // Helper function to decrease the indent if we previously indented. Allows multiple + // previous indent values to be considered at a time. This also allows caller to just + // call this once, passing in all their appropriate indent values, instead of needing + // to call this helper function multiple times. + function decreaseIndentIf(value1: boolean, value2: boolean) { + if (value1) { + decreaseIndent(); } - - function decreaseIndent() { - writer.decreaseIndent(); + if (value2) { + decreaseIndent(); } - - function writeToken(token: SyntaxKind, pos: number, writer: (s: string) => void, contextNode?: Node) { - return !sourceMapsDisabled - ? emitTokenWithSourceMap(contextNode, token, writer, pos, writeTokenText) - : writeTokenText(token, writer, pos); + } + function shouldWriteLeadingLineTerminator(parentNode: TextRange, children: NodeArray, format: ListFormat) { + if (format & ListFormat.MultiLine) { + return true; } - - function writeTokenNode(node: Node, writer: (s: string) => void) { - if (onBeforeEmitToken) { - onBeforeEmitToken(node); + if (format & ListFormat.PreserveLines) { + if (format & ListFormat.PreferNewLine) { + return true; } - writer(tokenToString(node.kind)!); - if (onAfterEmitToken) { - onAfterEmitToken(node); + const firstChild = children[0]; + if (firstChild === undefined) { + return !rangeIsOnSingleLine(parentNode, (currentSourceFile!)); } - } - - function writeTokenText(token: SyntaxKind, writer: (s: string) => void): void; - function writeTokenText(token: SyntaxKind, writer: (s: string) => void, pos: number): number; - function writeTokenText(token: SyntaxKind, writer: (s: string) => void, pos?: number): number { - const tokenString = tokenToString(token)!; - writer(tokenString); - return pos! < 0 ? pos! : pos! + tokenString.length; - } - - function writeLineOrSpace(node: Node) { - if (getEmitFlags(node) & EmitFlags.SingleLine) { - writeSpace(); + else if (positionIsSynthesized(parentNode.pos) || nodeIsSynthesized(firstChild)) { + return synthesizedNodeStartsOnNewLine(firstChild, format); } else { - writeLine(); - } - } - - function writeLines(text: string): void { - const lines = text.split(/\r\n?|\n/g); - const indentation = guessIndentation(lines); - for (const lineText of lines) { - const line = indentation ? lineText.slice(indentation) : lineText; - if (line.length) { - writeLine(); - write(line); - } + return !rangeStartPositionsAreOnSameLine(parentNode, firstChild, (currentSourceFile!)); } } - - function increaseIndentIf(value: boolean, writeSpaceIfNotIndenting: boolean) { - if (value) { - increaseIndent(); - writeLine(); - } - else if (writeSpaceIfNotIndenting) { - writeSpace(); - } + else { + return false; } - - // Helper function to decrease the indent if we previously indented. Allows multiple - // previous indent values to be considered at a time. This also allows caller to just - // call this once, passing in all their appropriate indent values, instead of needing - // to call this helper function multiple times. - function decreaseIndentIf(value1: boolean, value2: boolean) { - if (value1) { - decreaseIndent(); - } - if (value2) { - decreaseIndent(); - } + } + function shouldWriteSeparatingLineTerminator(previousNode: Node | undefined, nextNode: Node, format: ListFormat) { + if (format & ListFormat.MultiLine) { + return true; } - - function shouldWriteLeadingLineTerminator(parentNode: TextRange, children: NodeArray, format: ListFormat) { - if (format & ListFormat.MultiLine) { - return true; + else if (format & ListFormat.PreserveLines) { + if (previousNode === undefined || nextNode === undefined) { + return false; } - - if (format & ListFormat.PreserveLines) { - if (format & ListFormat.PreferNewLine) { - return true; - } - - const firstChild = children[0]; - if (firstChild === undefined) { - return !rangeIsOnSingleLine(parentNode, currentSourceFile!); - } - else if (positionIsSynthesized(parentNode.pos) || nodeIsSynthesized(firstChild)) { - return synthesizedNodeStartsOnNewLine(firstChild, format); - } - else { - return !rangeStartPositionsAreOnSameLine(parentNode, firstChild, currentSourceFile!); - } + else if (nodeIsSynthesized(previousNode) || nodeIsSynthesized(nextNode)) { + return synthesizedNodeStartsOnNewLine(previousNode, format) || synthesizedNodeStartsOnNewLine(nextNode, format); } else { - return false; + return !rangeEndIsOnSameLineAsRangeStart(previousNode, nextNode, (currentSourceFile!)); } } - - function shouldWriteSeparatingLineTerminator(previousNode: Node | undefined, nextNode: Node, format: ListFormat) { - if (format & ListFormat.MultiLine) { + else { + return getStartsOnNewLine(nextNode); + } + } + function shouldWriteClosingLineTerminator(parentNode: TextRange, children: NodeArray, format: ListFormat) { + if (format & ListFormat.MultiLine) { + return (format & ListFormat.NoTrailingNewLine) === 0; + } + else if (format & ListFormat.PreserveLines) { + if (format & ListFormat.PreferNewLine) { return true; } - else if (format & ListFormat.PreserveLines) { - if (previousNode === undefined || nextNode === undefined) { - return false; - } - else if (nodeIsSynthesized(previousNode) || nodeIsSynthesized(nextNode)) { - return synthesizedNodeStartsOnNewLine(previousNode, format) || synthesizedNodeStartsOnNewLine(nextNode, format); - } - else { - return !rangeEndIsOnSameLineAsRangeStart(previousNode, nextNode, currentSourceFile!); - } - } - else { - return getStartsOnNewLine(nextNode); - } - } - - function shouldWriteClosingLineTerminator(parentNode: TextRange, children: NodeArray, format: ListFormat) { - if (format & ListFormat.MultiLine) { - return (format & ListFormat.NoTrailingNewLine) === 0; + const lastChild = lastOrUndefined(children); + if (lastChild === undefined) { + return !rangeIsOnSingleLine(parentNode, (currentSourceFile!)); } - else if (format & ListFormat.PreserveLines) { - if (format & ListFormat.PreferNewLine) { - return true; - } - - const lastChild = lastOrUndefined(children); - if (lastChild === undefined) { - return !rangeIsOnSingleLine(parentNode, currentSourceFile!); - } - else if (positionIsSynthesized(parentNode.pos) || nodeIsSynthesized(lastChild)) { - return synthesizedNodeStartsOnNewLine(lastChild, format); - } - else { - return !rangeEndPositionsAreOnSameLine(parentNode, lastChild, currentSourceFile!); - } + else if (positionIsSynthesized(parentNode.pos) || nodeIsSynthesized(lastChild)) { + return synthesizedNodeStartsOnNewLine(lastChild, format); } else { - return false; + return !rangeEndPositionsAreOnSameLine(parentNode, lastChild, (currentSourceFile!)); } } - - function synthesizedNodeStartsOnNewLine(node: Node, format: ListFormat) { - if (nodeIsSynthesized(node)) { - const startsOnNewLine = getStartsOnNewLine(node); - if (startsOnNewLine === undefined) { - return (format & ListFormat.PreferNewLine) !== 0; - } - - return startsOnNewLine; - } - - return (format & ListFormat.PreferNewLine) !== 0; + else { + return false; } - - function needsIndentation(parent: Node, node1: Node, node2: Node): boolean { - if (getEmitFlags(parent) & EmitFlags.NoIndentation) { - return false; - } - - parent = skipSynthesizedParentheses(parent); - node1 = skipSynthesizedParentheses(node1); - node2 = skipSynthesizedParentheses(node2); - - // Always use a newline for synthesized code if the synthesizer desires it. - if (getStartsOnNewLine(node2)) { - return true; - } - - return !nodeIsSynthesized(parent) - && !nodeIsSynthesized(node1) - && !nodeIsSynthesized(node2) - && !rangeEndIsOnSameLineAsRangeStart(node1, node2, currentSourceFile!); - } - - function isEmptyBlock(block: BlockLike) { - return block.statements.length === 0 - && rangeEndIsOnSameLineAsRangeStart(block, block, currentSourceFile!); - } - - function skipSynthesizedParentheses(node: Node) { - while (node.kind === SyntaxKind.ParenthesizedExpression && nodeIsSynthesized(node)) { - node = (node).expression; + } + function synthesizedNodeStartsOnNewLine(node: Node, format: ListFormat) { + if (nodeIsSynthesized(node)) { + const startsOnNewLine = getStartsOnNewLine(node); + if (startsOnNewLine === undefined) { + return (format & ListFormat.PreferNewLine) !== 0; } - - return node; + return startsOnNewLine; } - - function getTextOfNode(node: Node, includeTrivia?: boolean): string { - if (isGeneratedIdentifier(node)) { - return generateName(node); - } - else if (isIdentifier(node) && (nodeIsSynthesized(node) || !node.parent || !currentSourceFile || (node.parent && currentSourceFile && getSourceFileOfNode(node) !== getOriginalNode(currentSourceFile)))) { - return idText(node); - } - else if (node.kind === SyntaxKind.StringLiteral && (node).textSourceNode) { - return getTextOfNode((node).textSourceNode!, includeTrivia); - } - else if (isLiteralExpression(node) && (nodeIsSynthesized(node) || !node.parent)) { - return node.text; - } - - return getSourceTextOfNodeFromSourceFile(currentSourceFile!, node, includeTrivia); - } - - function getLiteralTextOfNode(node: LiteralLikeNode, neverAsciiEscape: boolean | undefined): string { - if (node.kind === SyntaxKind.StringLiteral && (node).textSourceNode) { - const textSourceNode = (node).textSourceNode!; - if (isIdentifier(textSourceNode)) { - return neverAsciiEscape || (getEmitFlags(node) & EmitFlags.NoAsciiEscaping) ? - `"${escapeString(getTextOfNode(textSourceNode))}"` : - `"${escapeNonAsciiString(getTextOfNode(textSourceNode))}"`; - } - else { - return getLiteralTextOfNode(textSourceNode, neverAsciiEscape); - } - } - - return getLiteralText(node, currentSourceFile!, neverAsciiEscape); - } - - /** - * Push a new name generation scope. - */ - function pushNameGenerationScope(node: Node | undefined) { - if (node && getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { - return; - } - tempFlagsStack.push(tempFlags); - tempFlags = 0; - reservedNamesStack.push(reservedNames); - } - - /** - * Pop the current name generation scope. - */ - function popNameGenerationScope(node: Node | undefined) { - if (node && getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { - return; - } - tempFlags = tempFlagsStack.pop()!; - reservedNames = reservedNamesStack.pop()!; + return (format & ListFormat.PreferNewLine) !== 0; + } + function needsIndentation(parent: Node, node1: Node, node2: Node): boolean { + if (getEmitFlags(parent) & EmitFlags.NoIndentation) { + return false; } - - function reserveNameInNestedScopes(name: string) { - if (!reservedNames || reservedNames === lastOrUndefined(reservedNamesStack)) { - reservedNames = createMap(); - } - reservedNames.set(name, true); + parent = skipSynthesizedParentheses(parent); + node1 = skipSynthesizedParentheses(node1); + node2 = skipSynthesizedParentheses(node2); + // Always use a newline for synthesized code if the synthesizer desires it. + if (getStartsOnNewLine(node2)) { + return true; } - - function generateNames(node: Node | undefined) { - if (!node) return; - switch (node.kind) { - case SyntaxKind.Block: - forEach((node).statements, generateNames); - break; - case SyntaxKind.LabeledStatement: - case SyntaxKind.WithStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - generateNames((node).statement); - break; - case SyntaxKind.IfStatement: - generateNames((node).thenStatement); - generateNames((node).elseStatement); - break; - case SyntaxKind.ForStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.ForInStatement: - generateNames((node).initializer); - generateNames((node).statement); - break; - case SyntaxKind.SwitchStatement: - generateNames((node).caseBlock); - break; - case SyntaxKind.CaseBlock: - forEach((node).clauses, generateNames); - break; - case SyntaxKind.CaseClause: - case SyntaxKind.DefaultClause: - forEach((node).statements, generateNames); - break; - case SyntaxKind.TryStatement: - generateNames((node).tryBlock); - generateNames((node).catchClause); - generateNames((node).finallyBlock); - break; - case SyntaxKind.CatchClause: - generateNames((node).variableDeclaration); - generateNames((node).block); - break; - case SyntaxKind.VariableStatement: - generateNames((node).declarationList); - break; - case SyntaxKind.VariableDeclarationList: - forEach((node).declarations, generateNames); - break; - case SyntaxKind.VariableDeclaration: - case SyntaxKind.Parameter: - case SyntaxKind.BindingElement: - case SyntaxKind.ClassDeclaration: - generateNameIfNeeded((node).name); - break; - case SyntaxKind.FunctionDeclaration: - generateNameIfNeeded((node).name); - if (getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { - forEach((node).parameters, generateNames); - generateNames((node).body); - } - break; - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ArrayBindingPattern: - forEach((node).elements, generateNames); - break; - case SyntaxKind.ImportDeclaration: - generateNames((node).importClause); - break; - case SyntaxKind.ImportClause: - generateNameIfNeeded((node).name); - generateNames((node).namedBindings); - break; - case SyntaxKind.NamespaceImport: - generateNameIfNeeded((node).name); - break; - case SyntaxKind.NamedImports: - forEach((node).elements, generateNames); - break; - case SyntaxKind.ImportSpecifier: - generateNameIfNeeded((node).propertyName || (node).name); - break; - } + return !nodeIsSynthesized(parent) + && !nodeIsSynthesized(node1) + && !nodeIsSynthesized(node2) + && !rangeEndIsOnSameLineAsRangeStart(node1, node2, (currentSourceFile!)); + } + function isEmptyBlock(block: BlockLike) { + return block.statements.length === 0 + && rangeEndIsOnSameLineAsRangeStart(block, block, (currentSourceFile!)); + } + function skipSynthesizedParentheses(node: Node) { + while (node.kind === SyntaxKind.ParenthesizedExpression && nodeIsSynthesized(node)) { + node = (node).expression; } - - function generateMemberNames(node: Node | undefined) { - if (!node) return; - switch (node.kind) { - case SyntaxKind.PropertyAssignment: - case SyntaxKind.ShorthandPropertyAssignment: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - generateNameIfNeeded((node).name); - break; + return node; + } + function getTextOfNode(node: Node, includeTrivia?: boolean): string { + if (isGeneratedIdentifier(node)) { + return generateName(node); + } + else if (isIdentifier(node) && (nodeIsSynthesized(node) || !node.parent || !currentSourceFile || (node.parent && currentSourceFile && getSourceFileOfNode(node) !== getOriginalNode(currentSourceFile)))) { + return idText(node); + } + else if (node.kind === SyntaxKind.StringLiteral && (node).textSourceNode) { + return getTextOfNode(((node).textSourceNode!), includeTrivia); + } + else if (isLiteralExpression(node) && (nodeIsSynthesized(node) || !node.parent)) { + return node.text; + } + return getSourceTextOfNodeFromSourceFile((currentSourceFile!), node, includeTrivia); + } + function getLiteralTextOfNode(node: LiteralLikeNode, neverAsciiEscape: boolean | undefined): string { + if (node.kind === SyntaxKind.StringLiteral && (node).textSourceNode) { + const textSourceNode = ((node).textSourceNode!); + if (isIdentifier(textSourceNode)) { + return neverAsciiEscape || (getEmitFlags(node) & EmitFlags.NoAsciiEscaping) ? + `"${escapeString(getTextOfNode(textSourceNode))}"` : + `"${escapeNonAsciiString(getTextOfNode(textSourceNode))}"`; + } + else { + return getLiteralTextOfNode(textSourceNode, neverAsciiEscape); } } - - function generateNameIfNeeded(name: DeclarationName | undefined) { - if (name) { - if (isGeneratedIdentifier(name)) { - generateName(name); - } - else if (isBindingPattern(name)) { - generateNames(name); + return getLiteralText(node, (currentSourceFile!), neverAsciiEscape); + } + /** + * Push a new name generation scope. + */ + function pushNameGenerationScope(node: Node | undefined) { + if (node && getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { + return; + } + tempFlagsStack.push(tempFlags); + tempFlags = 0; + reservedNamesStack.push(reservedNames); + } + /** + * Pop the current name generation scope. + */ + function popNameGenerationScope(node: Node | undefined) { + if (node && getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { + return; + } + tempFlags = tempFlagsStack.pop()!; + reservedNames = reservedNamesStack.pop()!; + } + function reserveNameInNestedScopes(name: string) { + if (!reservedNames || reservedNames === lastOrUndefined(reservedNamesStack)) { + reservedNames = createMap(); + } + reservedNames.set(name, true); + } + function generateNames(node: Node | undefined) { + if (!node) + return; + switch (node.kind) { + case SyntaxKind.Block: + forEach((node).statements, generateNames); + break; + case SyntaxKind.LabeledStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + generateNames((node).statement); + break; + case SyntaxKind.IfStatement: + generateNames((node).thenStatement); + generateNames((node).elseStatement); + break; + case SyntaxKind.ForStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.ForInStatement: + generateNames((node).initializer); + generateNames((node).statement); + break; + case SyntaxKind.SwitchStatement: + generateNames((node).caseBlock); + break; + case SyntaxKind.CaseBlock: + forEach((node).clauses, generateNames); + break; + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + forEach((node).statements, generateNames); + break; + case SyntaxKind.TryStatement: + generateNames((node).tryBlock); + generateNames((node).catchClause); + generateNames((node).finallyBlock); + break; + case SyntaxKind.CatchClause: + generateNames((node).variableDeclaration); + generateNames((node).block); + break; + case SyntaxKind.VariableStatement: + generateNames((node).declarationList); + break; + case SyntaxKind.VariableDeclarationList: + forEach((node).declarations, generateNames); + break; + case SyntaxKind.VariableDeclaration: + case SyntaxKind.Parameter: + case SyntaxKind.BindingElement: + case SyntaxKind.ClassDeclaration: + generateNameIfNeeded((node).name); + break; + case SyntaxKind.FunctionDeclaration: + generateNameIfNeeded((node).name); + if (getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { + forEach((node).parameters, generateNames); + generateNames((node).body); } - } + break; + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + forEach((node).elements, generateNames); + break; + case SyntaxKind.ImportDeclaration: + generateNames((node).importClause); + break; + case SyntaxKind.ImportClause: + generateNameIfNeeded((node).name); + generateNames((node).namedBindings); + break; + case SyntaxKind.NamespaceImport: + generateNameIfNeeded((node).name); + break; + case SyntaxKind.NamedImports: + forEach((node).elements, generateNames); + break; + case SyntaxKind.ImportSpecifier: + generateNameIfNeeded((node).propertyName || (node).name); + break; + } + } + function generateMemberNames(node: Node | undefined) { + if (!node) + return; + switch (node.kind) { + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + generateNameIfNeeded((node).name); + break; } - - /** - * Generate the text for a generated identifier. - */ - function generateName(name: GeneratedIdentifier) { - if ((name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) === GeneratedIdentifierFlags.Node) { - // Node names generate unique names based on their original node - // and are cached based on that node's id. - return generateNameCached(getNodeForGeneratedName(name), name.autoGenerateFlags); + } + function generateNameIfNeeded(name: DeclarationName | undefined) { + if (name) { + if (isGeneratedIdentifier(name)) { + generateName(name); } - else { - // Auto, Loop, and Unique names are cached based on their unique - // autoGenerateId. - const autoGenerateId = name.autoGenerateId!; - return autoGeneratedIdToGeneratedName[autoGenerateId] || (autoGeneratedIdToGeneratedName[autoGenerateId] = makeName(name)); + else if (isBindingPattern(name)) { + generateNames(name); } } - - function generateNameCached(node: Node, flags?: GeneratedIdentifierFlags) { - const nodeId = getNodeId(node); - return nodeIdToGeneratedName[nodeId] || (nodeIdToGeneratedName[nodeId] = generateNameForNode(node, flags)); - } - - /** - * Returns a value indicating whether a name is unique globally, within the current file, - * or within the NameGenerator. - */ - function isUniqueName(name: string): boolean { - return isFileLevelUniqueName(name) - && !generatedNames.has(name) - && !(reservedNames && reservedNames.has(name)); - } - - /** - * Returns a value indicating whether a name is unique globally or within the current file. - */ - function isFileLevelUniqueName(name: string) { - return currentSourceFile ? ts.isFileLevelUniqueName(currentSourceFile, name, hasGlobalName) : true; - } - - /** - * Returns a value indicating whether a name is unique within a container. - */ - function isUniqueLocalName(name: string, container: Node): boolean { - for (let node = container; isNodeDescendantOf(node, container); node = node.nextContainer!) { - if (node.locals) { - const local = node.locals.get(escapeLeadingUnderscores(name)); - // We conservatively include alias symbols to cover cases where they're emitted as locals - if (local && local.flags & (SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias)) { - return false; - } + } + /** + * Generate the text for a generated identifier. + */ + function generateName(name: GeneratedIdentifier) { + if ((name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) === GeneratedIdentifierFlags.Node) { + // Node names generate unique names based on their original node + // and are cached based on that node's id. + return generateNameCached(getNodeForGeneratedName(name), name.autoGenerateFlags); + } + else { + // Auto, Loop, and Unique names are cached based on their unique + // autoGenerateId. + const autoGenerateId = name.autoGenerateId!; + return autoGeneratedIdToGeneratedName[autoGenerateId] || (autoGeneratedIdToGeneratedName[autoGenerateId] = makeName(name)); + } + } + function generateNameCached(node: Node, flags?: GeneratedIdentifierFlags) { + const nodeId = getNodeId(node); + return nodeIdToGeneratedName[nodeId] || (nodeIdToGeneratedName[nodeId] = generateNameForNode(node, flags)); + } + /** + * Returns a value indicating whether a name is unique globally, within the current file, + * or within the NameGenerator. + */ + function isUniqueName(name: string): boolean { + return isFileLevelUniqueName(name) + && !generatedNames.has(name) + && !(reservedNames && reservedNames.has(name)); + } + /** + * Returns a value indicating whether a name is unique globally or within the current file. + */ + function isFileLevelUniqueName(name: string) { + return currentSourceFile ? ts.isFileLevelUniqueName(currentSourceFile, name, hasGlobalName) : true; + } + /** + * Returns a value indicating whether a name is unique within a container. + */ + function isUniqueLocalName(name: string, container: Node): boolean { + for (let node = container; isNodeDescendantOf(node, container); node = node.nextContainer!) { + if (node.locals) { + const local = node.locals.get(escapeLeadingUnderscores(name)); + // We conservatively include alias symbols to cover cases where they're emitted as locals + if (local && local.flags & (SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias)) { + return false; } } - return true; } - - /** - * Return the next available name in the pattern _a ... _z, _0, _1, ... - * TempFlags._i or TempFlags._n may be used to express a preference for that dedicated name. - * Note that names generated by makeTempVariableName and makeUniqueName will never conflict. - */ - function makeTempVariableName(flags: TempFlags, reservedInNestedScopes?: boolean): string { - if (flags && !(tempFlags & flags)) { - const name = flags === TempFlags._i ? "_i" : "_n"; + return true; + } + /** + * Return the next available name in the pattern _a ... _z, _0, _1, ... + * TempFlags._i or TempFlags._n may be used to express a preference for that dedicated name. + * Note that names generated by makeTempVariableName and makeUniqueName will never conflict. + */ + function makeTempVariableName(flags: TempFlags, reservedInNestedScopes?: boolean): string { + if (flags && !(tempFlags & flags)) { + const name = flags === TempFlags._i ? "_i" : "_n"; + if (isUniqueName(name)) { + tempFlags |= flags; + if (reservedInNestedScopes) { + reserveNameInNestedScopes(name); + } + return name; + } + } + while (true) { + const count = tempFlags & TempFlags.CountMask; + tempFlags++; + // Skip over 'i' and 'n' + if (count !== 8 && count !== 13) { + const name = count < 26 + ? "_" + String.fromCharCode(CharacterCodes.a + count) + : "_" + (count - 26); if (isUniqueName(name)) { - tempFlags |= flags; if (reservedInNestedScopes) { reserveNameInNestedScopes(name); } return name; } } - while (true) { - const count = tempFlags & TempFlags.CountMask; - tempFlags++; - // Skip over 'i' and 'n' - if (count !== 8 && count !== 13) { - const name = count < 26 - ? "_" + String.fromCharCode(CharacterCodes.a + count) - : "_" + (count - 26); - if (isUniqueName(name)) { - if (reservedInNestedScopes) { - reserveNameInNestedScopes(name); - } - return name; - } - } - } } - - /** - * Generate a name that is unique within the current file and doesn't conflict with any names - * in global scope. The name is formed by adding an '_n' suffix to the specified base name, - * where n is a positive integer. Note that names generated by makeTempVariableName and - * makeUniqueName are guaranteed to never conflict. - * If `optimistic` is set, the first instance will use 'baseName' verbatim instead of 'baseName_1' - */ - function makeUniqueName(baseName: string, checkFn: (name: string) => boolean = isUniqueName, optimistic?: boolean, scoped?: boolean): string { - if (optimistic) { - if (checkFn(baseName)) { - if (scoped) { - reserveNameInNestedScopes(baseName); - } - else { - generatedNames.set(baseName, true); - } - return baseName; + } + /** + * Generate a name that is unique within the current file and doesn't conflict with any names + * in global scope. The name is formed by adding an '_n' suffix to the specified base name, + * where n is a positive integer. Note that names generated by makeTempVariableName and + * makeUniqueName are guaranteed to never conflict. + * If `optimistic` is set, the first instance will use 'baseName' verbatim instead of 'baseName_1' + */ + function makeUniqueName(baseName: string, checkFn: (name: string) => boolean = isUniqueName, optimistic?: boolean, scoped?: boolean): string { + if (optimistic) { + if (checkFn(baseName)) { + if (scoped) { + reserveNameInNestedScopes(baseName); } - } - // Find the first unique 'name_n', where n is a positive number - if (baseName.charCodeAt(baseName.length - 1) !== CharacterCodes._) { - baseName += "_"; - } - let i = 1; - while (true) { - const generatedName = baseName + i; - if (checkFn(generatedName)) { - if (scoped) { - reserveNameInNestedScopes(generatedName); - } - else { - generatedNames.set(generatedName, true); - } - return generatedName; + else { + generatedNames.set(baseName, true); } - i++; - } - } - - function makeFileLevelOptimisticUniqueName(name: string) { - return makeUniqueName(name, isFileLevelUniqueName, /*optimistic*/ true); - } - - /** - * Generates a unique name for a ModuleDeclaration or EnumDeclaration. - */ - function generateNameForModuleOrEnum(node: ModuleDeclaration | EnumDeclaration) { - const name = getTextOfNode(node.name); - // Use module/enum name itself if it is unique, otherwise make a unique variation - return isUniqueLocalName(name, node) ? name : makeUniqueName(name); - } - - /** - * Generates a unique name for an ImportDeclaration or ExportDeclaration. - */ - function generateNameForImportOrExportDeclaration(node: ImportDeclaration | ExportDeclaration) { - const expr = getExternalModuleName(node)!; // TODO: GH#18217 - const baseName = isStringLiteral(expr) ? - makeIdentifierFromModuleName(expr.text) : "module"; - return makeUniqueName(baseName); - } - - /** - * Generates a unique name for a default export. - */ - function generateNameForExportDefault() { - return makeUniqueName("default"); - } - - /** - * Generates a unique name for a class expression. - */ - function generateNameForClassExpression() { - return makeUniqueName("class"); - } - - function generateNameForMethodOrAccessor(node: MethodDeclaration | AccessorDeclaration) { - if (isIdentifier(node.name)) { - return generateNameCached(node.name); + return baseName; } - return makeTempVariableName(TempFlags.Auto); } - - /** - * Generates a unique name from a node. - */ - function generateNameForNode(node: Node, flags?: GeneratedIdentifierFlags): string { - switch (node.kind) { - case SyntaxKind.Identifier: - return makeUniqueName( - getTextOfNode(node), - isUniqueName, - !!(flags! & GeneratedIdentifierFlags.Optimistic), - !!(flags! & GeneratedIdentifierFlags.ReservedInNestedScopes) - ); - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.EnumDeclaration: - return generateNameForModuleOrEnum(node); - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ExportDeclaration: - return generateNameForImportOrExportDeclaration(node); - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ExportAssignment: - return generateNameForExportDefault(); - case SyntaxKind.ClassExpression: - return generateNameForClassExpression(); - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return generateNameForMethodOrAccessor(node); - case SyntaxKind.ComputedPropertyName: - return makeTempVariableName(TempFlags.Auto, /*reserveInNestedScopes*/ true); - default: - return makeTempVariableName(TempFlags.Auto); - } + // Find the first unique 'name_n', where n is a positive number + if (baseName.charCodeAt(baseName.length - 1) !== CharacterCodes._) { + baseName += "_"; } - - /** - * Generates a unique identifier for a node. - */ - function makeName(name: GeneratedIdentifier) { - switch (name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) { - case GeneratedIdentifierFlags.Auto: - return makeTempVariableName(TempFlags.Auto, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes)); - case GeneratedIdentifierFlags.Loop: - return makeTempVariableName(TempFlags._i, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes)); - case GeneratedIdentifierFlags.Unique: - return makeUniqueName( - idText(name), - (name.autoGenerateFlags & GeneratedIdentifierFlags.FileLevel) ? isFileLevelUniqueName : isUniqueName, - !!(name.autoGenerateFlags & GeneratedIdentifierFlags.Optimistic), - !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes) - ); - } - - return Debug.fail("Unsupported GeneratedIdentifierKind."); - } - - /** - * Gets the node from which a name should be generated. - */ - function getNodeForGeneratedName(name: GeneratedIdentifier) { - const autoGenerateId = name.autoGenerateId; - let node = name as Node; - let original = node.original; - while (original) { - node = original; - - // if "node" is a different generated name (having a different - // "autoGenerateId"), use it and stop traversing. - if (isIdentifier(node) - && !!(node.autoGenerateFlags! & GeneratedIdentifierFlags.Node) - && node.autoGenerateId !== autoGenerateId) { - break; - } - - original = node.original; - } - - // otherwise, return the original node for the source; - return node; - } - - // Comments - - function pipelineEmitWithComments(hint: EmitHint, node: Node) { - Debug.assert(lastNode === node || lastSubstitution === node); - enterComment(); - hasWrittenComment = false; - const emitFlags = getEmitFlags(node); - const { pos, end } = getCommentRange(node); - const isEmittedNode = node.kind !== SyntaxKind.NotEmittedStatement; - - // We have to explicitly check that the node is JsxText because if the compilerOptions.jsx is "preserve" we will not do any transformation. - // It is expensive to walk entire tree just to set one kind of node to have no comments. - const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0 || node.kind === SyntaxKind.JsxText; - const skipTrailingComments = end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0 || node.kind === SyntaxKind.JsxText; - - // Save current container state on the stack. - const savedContainerPos = containerPos; - const savedContainerEnd = containerEnd; - const savedDeclarationListContainerEnd = declarationListContainerEnd; - if ((pos > 0 || end > 0) && pos !== end) { - // Emit leading comments if the position is not synthesized and the node - // has not opted out from emitting leading comments. - if (!skipLeadingComments) { - emitLeadingComments(pos, isEmittedNode); - } - - if (!skipLeadingComments || (pos >= 0 && (emitFlags & EmitFlags.NoLeadingComments) !== 0)) { - // Advance the container position if comments get emitted or if they've been disabled explicitly using NoLeadingComments. - containerPos = pos; - } - - if (!skipTrailingComments || (end >= 0 && (emitFlags & EmitFlags.NoTrailingComments) !== 0)) { - // As above. - containerEnd = end; - - // To avoid invalid comment emit in a down-level binding pattern, we - // keep track of the last declaration list container's end - if (node.kind === SyntaxKind.VariableDeclarationList) { - declarationListContainerEnd = end; - } + let i = 1; + while (true) { + const generatedName = baseName + i; + if (checkFn(generatedName)) { + if (scoped) { + reserveNameInNestedScopes(generatedName); } - } - forEach(getSyntheticLeadingComments(node), emitLeadingSynthesizedComment); - exitComment(); - - const pipelinePhase = getNextPipelinePhase(PipelinePhase.Comments, node); - if (emitFlags & EmitFlags.NoNestedComments) { - commentsDisabled = true; - pipelinePhase(hint, node); - commentsDisabled = false; - } - else { - pipelinePhase(hint, node); - } - - enterComment(); - forEach(getSyntheticTrailingComments(node), emitTrailingSynthesizedComment); - if ((pos > 0 || end > 0) && pos !== end) { - // Restore previous container state. - containerPos = savedContainerPos; - containerEnd = savedContainerEnd; - declarationListContainerEnd = savedDeclarationListContainerEnd; - - // Emit trailing comments if the position is not synthesized and the node - // has not opted out from emitting leading comments and is an emitted node. - if (!skipTrailingComments && isEmittedNode) { - emitTrailingComments(end); + else { + generatedNames.set(generatedName, true); } + return generatedName; } - exitComment(); - Debug.assert(lastNode === node || lastSubstitution === node); + i++; } - - function emitLeadingSynthesizedComment(comment: SynthesizedComment) { - if (comment.kind === SyntaxKind.SingleLineCommentTrivia) { - writer.writeLine(); - } - writeSynthesizedComment(comment); - if (comment.hasTrailingNewLine || comment.kind === SyntaxKind.SingleLineCommentTrivia) { - writer.writeLine(); - } - else { - writer.writeSpace(" "); - } + } + function makeFileLevelOptimisticUniqueName(name: string) { + return makeUniqueName(name, isFileLevelUniqueName, /*optimistic*/ true); + } + /** + * Generates a unique name for a ModuleDeclaration or EnumDeclaration. + */ + function generateNameForModuleOrEnum(node: ModuleDeclaration | EnumDeclaration) { + const name = getTextOfNode(node.name); + // Use module/enum name itself if it is unique, otherwise make a unique variation + return isUniqueLocalName(name, node) ? name : makeUniqueName(name); + } + /** + * Generates a unique name for an ImportDeclaration or ExportDeclaration. + */ + function generateNameForImportOrExportDeclaration(node: ImportDeclaration | ExportDeclaration) { + const expr = (getExternalModuleName(node)!); // TODO: GH#18217 + const baseName = isStringLiteral(expr) ? + makeIdentifierFromModuleName(expr.text) : "module"; + return makeUniqueName(baseName); + } + /** + * Generates a unique name for a default export. + */ + function generateNameForExportDefault() { + return makeUniqueName("default"); + } + /** + * Generates a unique name for a class expression. + */ + function generateNameForClassExpression() { + return makeUniqueName("class"); + } + function generateNameForMethodOrAccessor(node: MethodDeclaration | AccessorDeclaration) { + if (isIdentifier(node.name)) { + return generateNameCached(node.name); } - - function emitTrailingSynthesizedComment(comment: SynthesizedComment) { - if (!writer.isAtStartOfLine()) { - writer.writeSpace(" "); - } - writeSynthesizedComment(comment); - if (comment.hasTrailingNewLine) { - writer.writeLine(); - } + return makeTempVariableName(TempFlags.Auto); + } + /** + * Generates a unique name from a node. + */ + function generateNameForNode(node: Node, flags?: GeneratedIdentifierFlags): string { + switch (node.kind) { + case SyntaxKind.Identifier: + return makeUniqueName(getTextOfNode(node), isUniqueName, !!((flags!) & GeneratedIdentifierFlags.Optimistic), !!((flags!) & GeneratedIdentifierFlags.ReservedInNestedScopes)); + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + return generateNameForModuleOrEnum((node)); + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportDeclaration: + return generateNameForImportOrExportDeclaration((node)); + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ExportAssignment: + return generateNameForExportDefault(); + case SyntaxKind.ClassExpression: + return generateNameForClassExpression(); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return generateNameForMethodOrAccessor((node)); + case SyntaxKind.ComputedPropertyName: + return makeTempVariableName(TempFlags.Auto, /*reserveInNestedScopes*/ true); + default: + return makeTempVariableName(TempFlags.Auto); } - - function writeSynthesizedComment(comment: SynthesizedComment) { - const text = formatSynthesizedComment(comment); - const lineMap = comment.kind === SyntaxKind.MultiLineCommentTrivia ? computeLineStarts(text) : undefined; - writeCommentRange(text, lineMap!, writer, 0, text.length, newLine); - } - - function formatSynthesizedComment(comment: SynthesizedComment) { - return comment.kind === SyntaxKind.MultiLineCommentTrivia - ? `/*${comment.text}*/` - : `//${comment.text}`; - } - - function emitBodyWithDetachedComments(node: Node, detachedRange: TextRange, emitCallback: (node: Node) => void) { - enterComment(); - const { pos, end } = detachedRange; - const emitFlags = getEmitFlags(node); - const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0; - const skipTrailingComments = commentsDisabled || end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0; + } + /** + * Generates a unique identifier for a node. + */ + function makeName(name: GeneratedIdentifier) { + switch (name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) { + case GeneratedIdentifierFlags.Auto: + return makeTempVariableName(TempFlags.Auto, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes)); + case GeneratedIdentifierFlags.Loop: + return makeTempVariableName(TempFlags._i, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes)); + case GeneratedIdentifierFlags.Unique: + return makeUniqueName(idText(name), (name.autoGenerateFlags & GeneratedIdentifierFlags.FileLevel) ? isFileLevelUniqueName : isUniqueName, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.Optimistic), !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes)); + } + return Debug.fail("Unsupported GeneratedIdentifierKind."); + } + /** + * Gets the node from which a name should be generated. + */ + function getNodeForGeneratedName(name: GeneratedIdentifier) { + const autoGenerateId = name.autoGenerateId; + let node = (name as Node); + let original = node.original; + while (original) { + node = original; + // if "node" is a different generated name (having a different + // "autoGenerateId"), use it and stop traversing. + if (isIdentifier(node) + && !!((node.autoGenerateFlags!) & GeneratedIdentifierFlags.Node) + && node.autoGenerateId !== autoGenerateId) { + break; + } + original = node.original; + } + // otherwise, return the original node for the source; + return node; + } + // Comments + function pipelineEmitWithComments(hint: EmitHint, node: Node) { + Debug.assert(lastNode === node || lastSubstitution === node); + enterComment(); + hasWrittenComment = false; + const emitFlags = getEmitFlags(node); + const { pos, end } = getCommentRange(node); + const isEmittedNode = node.kind !== SyntaxKind.NotEmittedStatement; + // We have to explicitly check that the node is JsxText because if the compilerOptions.jsx is "preserve" we will not do any transformation. + // It is expensive to walk entire tree just to set one kind of node to have no comments. + const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0 || node.kind === SyntaxKind.JsxText; + const skipTrailingComments = end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0 || node.kind === SyntaxKind.JsxText; + // Save current container state on the stack. + const savedContainerPos = containerPos; + const savedContainerEnd = containerEnd; + const savedDeclarationListContainerEnd = declarationListContainerEnd; + if ((pos > 0 || end > 0) && pos !== end) { + // Emit leading comments if the position is not synthesized and the node + // has not opted out from emitting leading comments. if (!skipLeadingComments) { - emitDetachedCommentsAndUpdateCommentsInfo(detachedRange); - } - - exitComment(); - if (emitFlags & EmitFlags.NoNestedComments && !commentsDisabled) { - commentsDisabled = true; - emitCallback(node); - commentsDisabled = false; + emitLeadingComments(pos, isEmittedNode); } - else { - emitCallback(node); + if (!skipLeadingComments || (pos >= 0 && (emitFlags & EmitFlags.NoLeadingComments) !== 0)) { + // Advance the container position if comments get emitted or if they've been disabled explicitly using NoLeadingComments. + containerPos = pos; } - - enterComment(); - if (!skipTrailingComments) { - emitLeadingComments(detachedRange.end, /*isEmittedNode*/ true); - if (hasWrittenComment && !writer.isAtStartOfLine()) { - writer.writeLine(); + if (!skipTrailingComments || (end >= 0 && (emitFlags & EmitFlags.NoTrailingComments) !== 0)) { + // As above. + containerEnd = end; + // To avoid invalid comment emit in a down-level binding pattern, we + // keep track of the last declaration list container's end + if (node.kind === SyntaxKind.VariableDeclarationList) { + declarationListContainerEnd = end; } } - exitComment(); - - } - - function emitLeadingComments(pos: number, isEmittedNode: boolean) { - hasWrittenComment = false; - - if (isEmittedNode) { - forEachLeadingCommentToEmit(pos, emitLeadingComment); - } - else if (pos === 0) { - // If the node will not be emitted in JS, remove all the comments(normal, pinned and ///) associated with the node, - // unless it is a triple slash comment at the top of the file. - // For Example: - // /// - // declare var x; - // /// - // interface F {} - // The first /// will NOT be removed while the second one will be removed even though both node will not be emitted - forEachLeadingCommentToEmit(pos, emitTripleSlashLeadingComment); - } } - - function emitTripleSlashLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { - if (isTripleSlashComment(commentPos, commentEnd)) { - emitLeadingComment(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos); - } + forEach(getSyntheticLeadingComments(node), emitLeadingSynthesizedComment); + exitComment(); + const pipelinePhase = getNextPipelinePhase(PipelinePhase.Comments, node); + if (emitFlags & EmitFlags.NoNestedComments) { + commentsDisabled = true; + pipelinePhase(hint, node); + commentsDisabled = false; } - - function shouldWriteComment(text: string, pos: number) { - if (printerOptions.onlyPrintJsDocStyle) { - return (isJSDocLikeText(text, pos) || isPinnedComment(text, pos)); - } - return true; + else { + pipelinePhase(hint, node); + } + enterComment(); + forEach(getSyntheticTrailingComments(node), emitTrailingSynthesizedComment); + if ((pos > 0 || end > 0) && pos !== end) { + // Restore previous container state. + containerPos = savedContainerPos; + containerEnd = savedContainerEnd; + declarationListContainerEnd = savedDeclarationListContainerEnd; + // Emit trailing comments if the position is not synthesized and the node + // has not opted out from emitting leading comments and is an emitted node. + if (!skipTrailingComments && isEmittedNode) { + emitTrailingComments(end); + } + } + exitComment(); + Debug.assert(lastNode === node || lastSubstitution === node); + } + function emitLeadingSynthesizedComment(comment: SynthesizedComment) { + if (comment.kind === SyntaxKind.SingleLineCommentTrivia) { + writer.writeLine(); } - - function emitLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { - if (!shouldWriteComment(currentSourceFile!.text, commentPos)) return; - if (!hasWrittenComment) { - emitNewLineBeforeLeadingCommentOfPosition(getCurrentLineMap(), writer, rangePos, commentPos); - hasWrittenComment = true; - } - - // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space - emitPos(commentPos); - writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); - emitPos(commentEnd); - - if (hasTrailingNewLine) { - writer.writeLine(); - } - else if (kind === SyntaxKind.MultiLineCommentTrivia) { - writer.writeSpace(" "); - } + writeSynthesizedComment(comment); + if (comment.hasTrailingNewLine || comment.kind === SyntaxKind.SingleLineCommentTrivia) { + writer.writeLine(); } - - function emitLeadingCommentsOfPosition(pos: number) { - if (commentsDisabled || pos === -1) { - return; - } - - emitLeadingComments(pos, /*isEmittedNode*/ true); - } - - function emitTrailingComments(pos: number) { - forEachTrailingCommentToEmit(pos, emitTrailingComment); - } - - function emitTrailingComment(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { - if (!shouldWriteComment(currentSourceFile!.text, commentPos)) return; - // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment2*/ - if (!writer.isAtStartOfLine()) { - writer.writeSpace(" "); - } - - emitPos(commentPos); - writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); - emitPos(commentEnd); - - if (hasTrailingNewLine) { + else { + writer.writeSpace(" "); + } + } + function emitTrailingSynthesizedComment(comment: SynthesizedComment) { + if (!writer.isAtStartOfLine()) { + writer.writeSpace(" "); + } + writeSynthesizedComment(comment); + if (comment.hasTrailingNewLine) { + writer.writeLine(); + } + } + function writeSynthesizedComment(comment: SynthesizedComment) { + const text = formatSynthesizedComment(comment); + const lineMap = comment.kind === SyntaxKind.MultiLineCommentTrivia ? computeLineStarts(text) : undefined; + writeCommentRange(text, (lineMap!), writer, 0, text.length, newLine); + } + function formatSynthesizedComment(comment: SynthesizedComment) { + return comment.kind === SyntaxKind.MultiLineCommentTrivia + ? `/*${comment.text}*/` + : `//${comment.text}`; + } + function emitBodyWithDetachedComments(node: Node, detachedRange: TextRange, emitCallback: (node: Node) => void) { + enterComment(); + const { pos, end } = detachedRange; + const emitFlags = getEmitFlags(node); + const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0; + const skipTrailingComments = commentsDisabled || end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0; + if (!skipLeadingComments) { + emitDetachedCommentsAndUpdateCommentsInfo(detachedRange); + } + exitComment(); + if (emitFlags & EmitFlags.NoNestedComments && !commentsDisabled) { + commentsDisabled = true; + emitCallback(node); + commentsDisabled = false; + } + else { + emitCallback(node); + } + enterComment(); + if (!skipTrailingComments) { + emitLeadingComments(detachedRange.end, /*isEmittedNode*/ true); + if (hasWrittenComment && !writer.isAtStartOfLine()) { writer.writeLine(); } } - - function emitTrailingCommentsOfPosition(pos: number, prefixSpace?: boolean) { - if (commentsDisabled) { - return; - } - enterComment(); - forEachTrailingCommentToEmit(pos, prefixSpace ? emitTrailingComment : emitTrailingCommentOfPosition); - exitComment(); - } - - function emitTrailingCommentOfPosition(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { - // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space - - emitPos(commentPos); - writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); - emitPos(commentEnd); - - if (hasTrailingNewLine) { - writer.writeLine(); + exitComment(); + } + function emitLeadingComments(pos: number, isEmittedNode: boolean) { + hasWrittenComment = false; + if (isEmittedNode) { + forEachLeadingCommentToEmit(pos, emitLeadingComment); + } + else if (pos === 0) { + // If the node will not be emitted in JS, remove all the comments(normal, pinned and ///) associated with the node, + // unless it is a triple slash comment at the top of the file. + // For Example: + // /// + // declare var x; + // /// + // interface F {} + // The first /// will NOT be removed while the second one will be removed even though both node will not be emitted + forEachLeadingCommentToEmit(pos, emitTripleSlashLeadingComment); + } + } + function emitTripleSlashLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { + if (isTripleSlashComment(commentPos, commentEnd)) { + emitLeadingComment(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos); + } + } + function shouldWriteComment(text: string, pos: number) { + if (printerOptions.onlyPrintJsDocStyle) { + return (isJSDocLikeText(text, pos) || isPinnedComment(text, pos)); + } + return true; + } + function emitLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { + if (!shouldWriteComment(currentSourceFile!.text, commentPos)) + return; + if (!hasWrittenComment) { + emitNewLineBeforeLeadingCommentOfPosition(getCurrentLineMap(), writer, rangePos, commentPos); + hasWrittenComment = true; + } + // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space + emitPos(commentPos); + writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); + if (hasTrailingNewLine) { + writer.writeLine(); + } + else if (kind === SyntaxKind.MultiLineCommentTrivia) { + writer.writeSpace(" "); + } + } + function emitLeadingCommentsOfPosition(pos: number) { + if (commentsDisabled || pos === -1) { + return; + } + emitLeadingComments(pos, /*isEmittedNode*/ true); + } + function emitTrailingComments(pos: number) { + forEachTrailingCommentToEmit(pos, emitTrailingComment); + } + function emitTrailingComment(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { + if (!shouldWriteComment(currentSourceFile!.text, commentPos)) + return; + // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment2*/ + if (!writer.isAtStartOfLine()) { + writer.writeSpace(" "); + } + emitPos(commentPos); + writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); + if (hasTrailingNewLine) { + writer.writeLine(); + } + } + function emitTrailingCommentsOfPosition(pos: number, prefixSpace?: boolean) { + if (commentsDisabled) { + return; + } + enterComment(); + forEachTrailingCommentToEmit(pos, prefixSpace ? emitTrailingComment : emitTrailingCommentOfPosition); + exitComment(); + } + function emitTrailingCommentOfPosition(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { + // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space + emitPos(commentPos); + writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); + if (hasTrailingNewLine) { + writer.writeLine(); + } + else { + writer.writeSpace(" "); + } + } + function forEachLeadingCommentToEmit(pos: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { + // Emit the leading comments only if the container's pos doesn't match because the container should take care of emitting these comments + if (currentSourceFile && (containerPos === -1 || pos !== containerPos)) { + if (hasDetachedComments(pos)) { + forEachLeadingCommentWithoutDetachedComments(cb); } else { - writer.writeSpace(" "); + forEachLeadingCommentRange(currentSourceFile.text, pos, cb, /*state*/ pos); } } - - function forEachLeadingCommentToEmit(pos: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { - // Emit the leading comments only if the container's pos doesn't match because the container should take care of emitting these comments - if (currentSourceFile && (containerPos === -1 || pos !== containerPos)) { - if (hasDetachedComments(pos)) { - forEachLeadingCommentWithoutDetachedComments(cb); - } - else { - forEachLeadingCommentRange(currentSourceFile.text, pos, cb, /*state*/ pos); - } - } + } + function forEachTrailingCommentToEmit(end: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean) => void) { + // Emit the trailing comments only if the container's end doesn't match because the container should take care of emitting these comments + if (currentSourceFile && (containerEnd === -1 || (end !== containerEnd && end !== declarationListContainerEnd))) { + forEachTrailingCommentRange(currentSourceFile.text, end, cb); } - - function forEachTrailingCommentToEmit(end: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean) => void) { - // Emit the trailing comments only if the container's end doesn't match because the container should take care of emitting these comments - if (currentSourceFile && (containerEnd === -1 || (end !== containerEnd && end !== declarationListContainerEnd))) { - forEachTrailingCommentRange(currentSourceFile.text, end, cb); - } + } + function hasDetachedComments(pos: number) { + return detachedCommentsInfo !== undefined && last(detachedCommentsInfo).nodePos === pos; + } + function forEachLeadingCommentWithoutDetachedComments(cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { + // get the leading comments from detachedPos + const pos = last((detachedCommentsInfo!)).detachedCommentEndPos; + if (detachedCommentsInfo!.length - 1) { + detachedCommentsInfo!.pop(); } - - function hasDetachedComments(pos: number) { - return detachedCommentsInfo !== undefined && last(detachedCommentsInfo).nodePos === pos; + else { + detachedCommentsInfo = undefined; } - - function forEachLeadingCommentWithoutDetachedComments(cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { - // get the leading comments from detachedPos - const pos = last(detachedCommentsInfo!).detachedCommentEndPos; - if (detachedCommentsInfo!.length - 1) { - detachedCommentsInfo!.pop(); + forEachLeadingCommentRange(currentSourceFile!.text, pos, cb, /*state*/ pos); + } + function emitDetachedCommentsAndUpdateCommentsInfo(range: TextRange) { + const currentDetachedCommentInfo = emitDetachedComments(currentSourceFile!.text, getCurrentLineMap(), writer, emitComment, range, newLine, commentsDisabled); + if (currentDetachedCommentInfo) { + if (detachedCommentsInfo) { + detachedCommentsInfo.push(currentDetachedCommentInfo); } else { - detachedCommentsInfo = undefined; - } - - forEachLeadingCommentRange(currentSourceFile!.text, pos, cb, /*state*/ pos); - } - - function emitDetachedCommentsAndUpdateCommentsInfo(range: TextRange) { - const currentDetachedCommentInfo = emitDetachedComments(currentSourceFile!.text, getCurrentLineMap(), writer, emitComment, range, newLine, commentsDisabled); - if (currentDetachedCommentInfo) { - if (detachedCommentsInfo) { - detachedCommentsInfo.push(currentDetachedCommentInfo); - } - else { - detachedCommentsInfo = [currentDetachedCommentInfo]; - } + detachedCommentsInfo = [currentDetachedCommentInfo]; } } - - function emitComment(text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) { - if (!shouldWriteComment(currentSourceFile!.text, commentPos)) return; - emitPos(commentPos); - writeCommentRange(text, lineMap, writer, commentPos, commentEnd, newLine); - emitPos(commentEnd); - } - - /** - * Determine if the given comment is a triple-slash - * - * @return true if the comment is a triple-slash comment else false - */ - function isTripleSlashComment(commentPos: number, commentEnd: number) { - return isRecognizedTripleSlashComment(currentSourceFile!.text, commentPos, commentEnd); - } - - // Source Maps - - function getParsedSourceMap(node: UnparsedSource) { - if (node.parsedSourceMap === undefined && node.sourceMapText !== undefined) { - node.parsedSourceMap = tryParseRawSourceMap(node.sourceMapText) || false; + } + function emitComment(text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) { + if (!shouldWriteComment(currentSourceFile!.text, commentPos)) + return; + emitPos(commentPos); + writeCommentRange(text, lineMap, writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); + } + /** + * Determine if the given comment is a triple-slash + * + * @return true if the comment is a triple-slash comment else false + */ + function isTripleSlashComment(commentPos: number, commentEnd: number) { + return isRecognizedTripleSlashComment(currentSourceFile!.text, commentPos, commentEnd); + } + // Source Maps + function getParsedSourceMap(node: UnparsedSource) { + if (node.parsedSourceMap === undefined && node.sourceMapText !== undefined) { + node.parsedSourceMap = tryParseRawSourceMap(node.sourceMapText) || false; + } + return node.parsedSourceMap || undefined; + } + function pipelineEmitWithSourceMap(hint: EmitHint, node: Node) { + Debug.assert(lastNode === node || lastSubstitution === node); + const pipelinePhase = getNextPipelinePhase(PipelinePhase.SourceMaps, node); + if (isUnparsedSource(node) || isUnparsedPrepend(node)) { + pipelinePhase(hint, node); + } + else if (isUnparsedNode(node)) { + const parsed = getParsedSourceMap(node.parent); + if (parsed && sourceMapGenerator) { + sourceMapGenerator.appendSourceMap(writer.getLine(), writer.getColumn(), parsed, node.parent.sourceMapPath!, node.parent.getLineAndCharacterOfPosition(node.pos), node.parent.getLineAndCharacterOfPosition(node.end)); } - return node.parsedSourceMap || undefined; + pipelinePhase(hint, node); } - - function pipelineEmitWithSourceMap(hint: EmitHint, node: Node) { - Debug.assert(lastNode === node || lastSubstitution === node); - const pipelinePhase = getNextPipelinePhase(PipelinePhase.SourceMaps, node); - if (isUnparsedSource(node) || isUnparsedPrepend(node)) { - pipelinePhase(hint, node); + else { + const { pos, end, source = sourceMapSource } = getSourceMapRange(node); + const emitFlags = getEmitFlags(node); + if (node.kind !== SyntaxKind.NotEmittedStatement + && (emitFlags & EmitFlags.NoLeadingSourceMap) === 0 + && pos >= 0) { + emitSourcePos(source, skipSourceTrivia(source, pos)); } - else if (isUnparsedNode(node)) { - const parsed = getParsedSourceMap(node.parent); - if (parsed && sourceMapGenerator) { - sourceMapGenerator.appendSourceMap( - writer.getLine(), - writer.getColumn(), - parsed, - node.parent.sourceMapPath!, - node.parent.getLineAndCharacterOfPosition(node.pos), - node.parent.getLineAndCharacterOfPosition(node.end) - ); - } + if (emitFlags & EmitFlags.NoNestedSourceMaps) { + sourceMapsDisabled = true; pipelinePhase(hint, node); + sourceMapsDisabled = false; } else { - const { pos, end, source = sourceMapSource } = getSourceMapRange(node); - const emitFlags = getEmitFlags(node); - if (node.kind !== SyntaxKind.NotEmittedStatement - && (emitFlags & EmitFlags.NoLeadingSourceMap) === 0 - && pos >= 0) { - emitSourcePos(source, skipSourceTrivia(source, pos)); - } - - if (emitFlags & EmitFlags.NoNestedSourceMaps) { - sourceMapsDisabled = true; - pipelinePhase(hint, node); - sourceMapsDisabled = false; - } - else { - pipelinePhase(hint, node); - } - - if (node.kind !== SyntaxKind.NotEmittedStatement - && (emitFlags & EmitFlags.NoTrailingSourceMap) === 0 - && end >= 0) { - emitSourcePos(source, end); - } - } - Debug.assert(lastNode === node || lastSubstitution === node); - } - - /** - * Skips trivia such as comments and white-space that can be optionally overridden by the source-map source - */ - function skipSourceTrivia(source: SourceMapSource, pos: number): number { - return source.skipTrivia ? source.skipTrivia(pos) : skipTrivia(source.text, pos); - } - - /** - * Emits a mapping. - * - * If the position is synthetic (undefined or a negative value), no mapping will be - * created. - * - * @param pos The position. - */ - function emitPos(pos: number) { - if (sourceMapsDisabled || positionIsSynthesized(pos) || isJsonSourceMapSource(sourceMapSource)) { - return; - } - - const { line: sourceLine, character: sourceCharacter } = getLineAndCharacterOfPosition(sourceMapSource, pos); - sourceMapGenerator!.addMapping( - writer.getLine(), - writer.getColumn(), - sourceMapSourceIndex, - sourceLine, - sourceCharacter, - /*nameIndex*/ undefined); - } - - function emitSourcePos(source: SourceMapSource, pos: number) { - if (source !== sourceMapSource) { - const savedSourceMapSource = sourceMapSource; - setSourceMapSource(source); - emitPos(pos); - setSourceMapSource(savedSourceMapSource); + pipelinePhase(hint, node); } - else { - emitPos(pos); + if (node.kind !== SyntaxKind.NotEmittedStatement + && (emitFlags & EmitFlags.NoTrailingSourceMap) === 0 + && end >= 0) { + emitSourcePos(source, end); } } - - /** - * Emits a token of a node with possible leading and trailing source maps. - * - * @param node The node containing the token. - * @param token The token to emit. - * @param tokenStartPos The start pos of the token. - * @param emitCallback The callback used to emit the token. - */ - function emitTokenWithSourceMap(node: Node | undefined, token: SyntaxKind, writer: (s: string) => void, tokenPos: number, emitCallback: (token: SyntaxKind, writer: (s: string) => void, tokenStartPos: number) => number) { - if (sourceMapsDisabled || node && isInJsonFile(node)) { - return emitCallback(token, writer, tokenPos); - } - - const emitNode = node && node.emitNode; - const emitFlags = emitNode && emitNode.flags || EmitFlags.None; - const range = emitNode && emitNode.tokenSourceMapRanges && emitNode.tokenSourceMapRanges[token]; - const source = range && range.source || sourceMapSource; - - tokenPos = skipSourceTrivia(source, range ? range.pos : tokenPos); - if ((emitFlags & EmitFlags.NoTokenLeadingSourceMaps) === 0 && tokenPos >= 0) { - emitSourcePos(source, tokenPos); - } - - tokenPos = emitCallback(token, writer, tokenPos); - - if (range) tokenPos = range.end; - if ((emitFlags & EmitFlags.NoTokenTrailingSourceMaps) === 0 && tokenPos >= 0) { - emitSourcePos(source, tokenPos); - } - - return tokenPos; + Debug.assert(lastNode === node || lastSubstitution === node); + } + /** + * Skips trivia such as comments and white-space that can be optionally overridden by the source-map source + */ + function skipSourceTrivia(source: SourceMapSource, pos: number): number { + return source.skipTrivia ? source.skipTrivia(pos) : skipTrivia(source.text, pos); + } + /** + * Emits a mapping. + * + * If the position is synthetic (undefined or a negative value), no mapping will be + * created. + * + * @param pos The position. + */ + function emitPos(pos: number) { + if (sourceMapsDisabled || positionIsSynthesized(pos) || isJsonSourceMapSource(sourceMapSource)) { + return; } - - function setSourceMapSource(source: SourceMapSource) { - if (sourceMapsDisabled) { - return; - } - - sourceMapSource = source; - - if (isJsonSourceMapSource(source)) { - return; - } - - sourceMapSourceIndex = sourceMapGenerator!.addSource(source.fileName); - if (printerOptions.inlineSources) { - sourceMapGenerator!.setSourceContent(sourceMapSourceIndex, source.text); - } + const { line: sourceLine, character: sourceCharacter } = getLineAndCharacterOfPosition(sourceMapSource, pos); + sourceMapGenerator!.addMapping(writer.getLine(), writer.getColumn(), sourceMapSourceIndex, sourceLine, sourceCharacter, + /*nameIndex*/ undefined); + } + function emitSourcePos(source: SourceMapSource, pos: number) { + if (source !== sourceMapSource) { + const savedSourceMapSource = sourceMapSource; + setSourceMapSource(source); + emitPos(pos); + setSourceMapSource(savedSourceMapSource); } - - function isJsonSourceMapSource(sourceFile: SourceMapSource) { - return fileExtensionIs(sourceFile.fileName, Extension.Json); + else { + emitPos(pos); } } - - function createBracketsMap() { - const brackets: string[][] = []; - brackets[ListFormat.Braces] = ["{", "}"]; - brackets[ListFormat.Parenthesis] = ["(", ")"]; - brackets[ListFormat.AngleBrackets] = ["<", ">"]; - brackets[ListFormat.SquareBrackets] = ["[", "]"]; - return brackets; - } - - function getOpeningBracket(format: ListFormat) { - return brackets[format & ListFormat.BracketsMask][0]; + /** + * Emits a token of a node with possible leading and trailing source maps. + * + * @param node The node containing the token. + * @param token The token to emit. + * @param tokenStartPos The start pos of the token. + * @param emitCallback The callback used to emit the token. + */ + function emitTokenWithSourceMap(node: Node | undefined, token: SyntaxKind, writer: (s: string) => void, tokenPos: number, emitCallback: (token: SyntaxKind, writer: (s: string) => void, tokenStartPos: number) => number) { + if (sourceMapsDisabled || node && isInJsonFile(node)) { + return emitCallback(token, writer, tokenPos); + } + const emitNode = node && node.emitNode; + const emitFlags = emitNode && emitNode.flags || EmitFlags.None; + const range = emitNode && emitNode.tokenSourceMapRanges && emitNode.tokenSourceMapRanges[token]; + const source = range && range.source || sourceMapSource; + tokenPos = skipSourceTrivia(source, range ? range.pos : tokenPos); + if ((emitFlags & EmitFlags.NoTokenLeadingSourceMaps) === 0 && tokenPos >= 0) { + emitSourcePos(source, tokenPos); + } + tokenPos = emitCallback(token, writer, tokenPos); + if (range) + tokenPos = range.end; + if ((emitFlags & EmitFlags.NoTokenTrailingSourceMaps) === 0 && tokenPos >= 0) { + emitSourcePos(source, tokenPos); + } + return tokenPos; } - - function getClosingBracket(format: ListFormat) { - return brackets[format & ListFormat.BracketsMask][1]; + function setSourceMapSource(source: SourceMapSource) { + if (sourceMapsDisabled) { + return; + } + sourceMapSource = source; + if (isJsonSourceMapSource(source)) { + return; + } + sourceMapSourceIndex = sourceMapGenerator!.addSource(source.fileName); + if (printerOptions.inlineSources) { + sourceMapGenerator!.setSourceContent(sourceMapSourceIndex, source.text); + } } - - // Flags enum to track count of temp variables and a few dedicated names - const enum TempFlags { - Auto = 0x00000000, // No preferred name - CountMask = 0x0FFFFFFF, // Temp variable counter - _i = 0x10000000, // Use/preference flag for '_i' + function isJsonSourceMapSource(sourceFile: SourceMapSource) { + return fileExtensionIs(sourceFile.fileName, Extension.Json); } } +function createBracketsMap() { + const brackets: string[][] = []; + brackets[ListFormat.Braces] = ["{", "}"]; + brackets[ListFormat.Parenthesis] = ["(", ")"]; + brackets[ListFormat.AngleBrackets] = ["<", ">"]; + brackets[ListFormat.SquareBrackets] = ["[", "]"]; + return brackets; +} +function getOpeningBracket(format: ListFormat) { + return brackets[format & ListFormat.BracketsMask][0]; +} +function getClosingBracket(format: ListFormat) { + return brackets[format & ListFormat.BracketsMask][1]; +} +// Flags enum to track count of temp variables and a few dedicated names +const enum TempFlags { + Auto = 0x00000000, + CountMask = 0x0FFFFFFF, + _i = 0x10000000 +} diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 6d98ee3eca4e9..3e1cbb3f2dae2 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1,217 +1,167 @@ +import { TransformationContext, noop, returnUndefined, notImplemented, Expression, createStrictEquality, createVoidZero, createTypeOf, createLiteral, PropertyName, TextRange, MemberExpression, isComputedPropertyName, setTextRange, createElementAccess, isIdentifier, createPropertyAccess, getOrCreateEmitNode, EmitFlags, createCall, createIdentifier, JsxOpeningLikeElement, JsxOpeningFragment, NodeFlags, getParseTreeNode, EntityName, isQualifiedName, idText, LeftHandSideExpression, createNull, setEmitFlags, UnscopedEmitHelper, ForInitializer, Statement, isVariableDeclarationList, first, updateVariableDeclaration, createVariableStatement, updateVariableDeclarationList, createAssignment, createStatement, isBlock, updateBlock, createNodeArray, createBlock, LabeledStatement, updateLabel, SyntaxKind, skipParentheses, ArrayLiteralExpression, ObjectLiteralExpression, Identifier, ScriptTarget, isSuperProperty, createThis, PrimaryExpression, getEmitFlags, PropertyAccessExpression, createTempVariable, ElementAccessExpression, createCommaList, reduceLeft, createComma, getMutableClone, ObjectLiteralElementLike, NodeArray, Declaration, AccessorDeclaration, getAllAccessorDeclarations, createFunctionExpression, setOriginalNode, createPropertyAssignment, createTrue, createObjectLiteral, aggregateTransformFlags, PropertyAssignment, ShorthandPropertyAssignment, getSynthesizedClone, MethodDeclaration, getNameOfDeclaration, isGeneratedIdentifier, getGeneratedNameForNode, hasModifier, ModifierFlags, nodeIsSynthesized, ConciseBody, Block, createReturn, FunctionDeclaration, Debug, getStartsOnNewLine, setStartsOnNewLine, ExpressionStatement, isStringLiteral, Node, VisitResult, isPrologueDirective, append, visitNode, isStatement, firstOrUndefined, skipPartiallyEmittedExpressions, createParen, getOperatorPrecedence, getOperatorAssociativity, getExpressionPrecedence, compareValues, Comparison, Associativity, isBinaryExpression, isLiteralKind, getExpressionAssociativity, BinaryExpression, NewExpression, isLeftHandSideExpression, isUnaryExpression, isCallExpression, TypeNode, createParenthesizedType, sameMap, some, isFunctionOrConstructorTypeNode, PostfixUnaryExpression, ConditionalExpression, TaggedTemplateExpression, CallExpression, AsExpression, NonNullExpression, PartiallyEmittedExpression, Token, CommaListExpression, ParenthesizedExpression, TypeAssertion, isAssertionExpression, AssertionExpression, updateParen, updateTypeAssertion, updateAsExpression, updateNonNullExpression, updatePartiallyEmittedExpression, getSourceMapRange, getCommentRange, getSyntheticLeadingComments, getSyntheticTrailingComments, SourceFile, getOriginalNode, isSourceFile, CompilerOptions, isEffectiveExternalModule, NamedImportBindings, getEmitModuleKind, ModuleKind, getEmitHelpers, pushIfUnique, compareStringsCaseSensitive, createNamedImports, map, isFileLevelUniqueName, createImportSpecifier, createNamespaceImport, createImportDeclaration, createImportClause, externalHelpersModuleNameText, addEmitFlags, createUniqueName, ImportDeclaration, ExportDeclaration, ImportEqualsDeclaration, getNamespaceDeclarationNode, isDefaultImport, getSourceTextOfNodeFromSourceFile, EmitHost, EmitResolver, getExternalModuleName, StringLiteral, LiteralExpression, getExternalModuleNameFromPath, BindingOrAssignmentElement, isDeclarationBindingElement, isPropertyAssignment, isAssignmentExpression, isShorthandPropertyAssignment, isSpreadElement, BindingOrAssignmentElementTarget, isObjectLiteralElementLike, BindingOrAssignmentElementRestIndicator, isSpreadAssignment, isPropertyName, NumericLiteral, BindingOrAssignmentPattern, isBindingElement, createSpread, isExpression, createSpreadAssignment, createShorthandPropertyAssignment, AssignmentPattern, ObjectBindingOrAssignmentPattern, isObjectBindingPattern, isObjectLiteralExpression, ArrayBindingOrAssignmentPattern, isArrayBindingPattern, createArrayLiteral, isArrayLiteralExpression, isBindingPattern } from "./ts"; /* @internal */ -namespace ts { - export const nullTransformationContext: TransformationContext = { - enableEmitNotification: noop, - enableSubstitution: noop, - endLexicalEnvironment: returnUndefined, - getCompilerOptions: notImplemented, - getEmitHost: notImplemented, - getEmitResolver: notImplemented, - hoistFunctionDeclaration: noop, - hoistVariableDeclaration: noop, - isEmitNotificationEnabled: notImplemented, - isSubstitutionEnabled: notImplemented, - onEmitNode: noop, - onSubstituteNode: notImplemented, - readEmitHelpers: notImplemented, - requestEmitHelper: noop, - resumeLexicalEnvironment: noop, - startLexicalEnvironment: noop, - suspendLexicalEnvironment: noop, - addDiagnostic: noop, - }; - - // Compound nodes - - export type TypeOfTag = "undefined" | "number" | "boolean" | "string" | "symbol" | "object" | "function"; - - export function createTypeCheck(value: Expression, tag: TypeOfTag) { - return tag === "undefined" - ? createStrictEquality(value, createVoidZero()) - : createStrictEquality(createTypeOf(value), createLiteral(tag)); - } - - export function createMemberAccessForPropertyName(target: Expression, memberName: PropertyName, location?: TextRange): MemberExpression { - if (isComputedPropertyName(memberName)) { - return setTextRange(createElementAccess(target, memberName.expression), location); - } - else { - const expression = setTextRange( - isIdentifier(memberName) - ? createPropertyAccess(target, memberName) - : createElementAccess(target, memberName), - memberName - ); - getOrCreateEmitNode(expression).flags |= EmitFlags.NoNestedSourceMaps; - return expression; - } +export const nullTransformationContext: TransformationContext = { + enableEmitNotification: noop, + enableSubstitution: noop, + endLexicalEnvironment: returnUndefined, + getCompilerOptions: notImplemented, + getEmitHost: notImplemented, + getEmitResolver: notImplemented, + hoistFunctionDeclaration: noop, + hoistVariableDeclaration: noop, + isEmitNotificationEnabled: notImplemented, + isSubstitutionEnabled: notImplemented, + onEmitNode: noop, + onSubstituteNode: notImplemented, + readEmitHelpers: notImplemented, + requestEmitHelper: noop, + resumeLexicalEnvironment: noop, + startLexicalEnvironment: noop, + suspendLexicalEnvironment: noop, + addDiagnostic: noop, +}; +// Compound nodes +/* @internal */ +export type TypeOfTag = "undefined" | "number" | "boolean" | "string" | "symbol" | "object" | "function"; +/* @internal */ +export function createTypeCheck(value: Expression, tag: TypeOfTag) { + return tag === "undefined" + ? createStrictEquality(value, createVoidZero()) + : createStrictEquality(createTypeOf(value), createLiteral(tag)); +} +/* @internal */ +export function createMemberAccessForPropertyName(target: Expression, memberName: PropertyName, location?: TextRange): MemberExpression { + if (isComputedPropertyName(memberName)) { + return setTextRange(createElementAccess(target, memberName.expression), location); + } + else { + const expression = setTextRange(isIdentifier(memberName) + ? createPropertyAccess(target, memberName) + : createElementAccess(target, memberName), memberName); + getOrCreateEmitNode(expression).flags |= EmitFlags.NoNestedSourceMaps; + return expression; } - - export function createFunctionCall(func: Expression, thisArg: Expression, argumentsList: readonly Expression[], location?: TextRange) { - return setTextRange( - createCall( - createPropertyAccess(func, "call"), - /*typeArguments*/ undefined, - [ - thisArg, - ...argumentsList - ]), - location - ); - } - - export function createFunctionApply(func: Expression, thisArg: Expression, argumentsExpression: Expression, location?: TextRange) { - return setTextRange( - createCall( - createPropertyAccess(func, "apply"), - /*typeArguments*/ undefined, - [ - thisArg, - argumentsExpression - ] - ), - location - ); - } - - export function createArraySlice(array: Expression, start?: number | Expression) { - const argumentsList: Expression[] = []; - if (start !== undefined) { - argumentsList.push(typeof start === "number" ? createLiteral(start) : start); - } - - return createCall(createPropertyAccess(array, "slice"), /*typeArguments*/ undefined, argumentsList); - } - - export function createArrayConcat(array: Expression, values: readonly Expression[]) { - return createCall( - createPropertyAccess(array, "concat"), - /*typeArguments*/ undefined, - values - ); - } - - export function createMathPow(left: Expression, right: Expression, location?: TextRange) { - return setTextRange( - createCall( - createPropertyAccess(createIdentifier("Math"), "pow"), - /*typeArguments*/ undefined, - [left, right] - ), - location - ); - } - - function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment) { - // To ensure the emit resolver can properly resolve the namespace, we need to - // treat this identifier as if it were a source tree node by clearing the `Synthesized` - // flag and setting a parent node. - const react = createIdentifier(reactNamespace || "React"); - react.flags &= ~NodeFlags.Synthesized; - // Set the parent that is in parse tree - // this makes sure that parent chain is intact for checker to traverse complete scope tree - react.parent = getParseTreeNode(parent); - return react; - } - - function createJsxFactoryExpressionFromEntityName(jsxFactory: EntityName, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression { - if (isQualifiedName(jsxFactory)) { - const left = createJsxFactoryExpressionFromEntityName(jsxFactory.left, parent); - const right = createIdentifier(idText(jsxFactory.right)); - right.escapedText = jsxFactory.right.escapedText; - return createPropertyAccess(left, right); +} +/* @internal */ +export function createFunctionCall(func: Expression, thisArg: Expression, argumentsList: readonly Expression[], location?: TextRange) { + return setTextRange(createCall(createPropertyAccess(func, "call"), + /*typeArguments*/ undefined, [ + thisArg, + ...argumentsList + ]), location); +} +/* @internal */ +export function createFunctionApply(func: Expression, thisArg: Expression, argumentsExpression: Expression, location?: TextRange) { + return setTextRange(createCall(createPropertyAccess(func, "apply"), + /*typeArguments*/ undefined, [ + thisArg, + argumentsExpression + ]), location); +} +/* @internal */ +export function createArraySlice(array: Expression, start?: number | Expression) { + const argumentsList: Expression[] = []; + if (start !== undefined) { + argumentsList.push(typeof start === "number" ? createLiteral(start) : start); + } + return createCall(createPropertyAccess(array, "slice"), /*typeArguments*/ undefined, argumentsList); +} +/* @internal */ +export function createArrayConcat(array: Expression, values: readonly Expression[]) { + return createCall(createPropertyAccess(array, "concat"), + /*typeArguments*/ undefined, values); +} +/* @internal */ +export function createMathPow(left: Expression, right: Expression, location?: TextRange) { + return setTextRange(createCall(createPropertyAccess(createIdentifier("Math"), "pow"), + /*typeArguments*/ undefined, [left, right]), location); +} +/* @internal */ +function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment) { + // To ensure the emit resolver can properly resolve the namespace, we need to + // treat this identifier as if it were a source tree node by clearing the `Synthesized` + // flag and setting a parent node. + const react = createIdentifier(reactNamespace || "React"); + react.flags &= ~NodeFlags.Synthesized; + // Set the parent that is in parse tree + // this makes sure that parent chain is intact for checker to traverse complete scope tree + react.parent = getParseTreeNode(parent); + return react; +} +/* @internal */ +function createJsxFactoryExpressionFromEntityName(jsxFactory: EntityName, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression { + if (isQualifiedName(jsxFactory)) { + const left = createJsxFactoryExpressionFromEntityName(jsxFactory.left, parent); + const right = createIdentifier(idText(jsxFactory.right)); + right.escapedText = jsxFactory.right.escapedText; + return createPropertyAccess(left, right); + } + else { + return createReactNamespace(idText(jsxFactory), parent); + } +} +/* @internal */ +function createJsxFactoryExpression(jsxFactoryEntity: EntityName | undefined, reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression { + return jsxFactoryEntity ? + createJsxFactoryExpressionFromEntityName(jsxFactoryEntity, parent) : + createPropertyAccess(createReactNamespace(reactNamespace, parent), "createElement"); +} +/* @internal */ +export function createExpressionForJsxElement(jsxFactoryEntity: EntityName | undefined, reactNamespace: string, tagName: Expression, props: Expression, children: readonly Expression[], parentElement: JsxOpeningLikeElement, location: TextRange): LeftHandSideExpression { + const argumentsList = [tagName]; + if (props) { + argumentsList.push(props); + } + if (children && children.length > 0) { + if (!props) { + argumentsList.push(createNull()); + } + if (children.length > 1) { + for (const child of children) { + startOnNewLine(child); + argumentsList.push(child); + } } else { - return createReactNamespace(idText(jsxFactory), parent); + argumentsList.push(children[0]); } } - - function createJsxFactoryExpression(jsxFactoryEntity: EntityName | undefined, reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression { - return jsxFactoryEntity ? - createJsxFactoryExpressionFromEntityName(jsxFactoryEntity, parent) : - createPropertyAccess( - createReactNamespace(reactNamespace, parent), - "createElement" - ); - } - - export function createExpressionForJsxElement(jsxFactoryEntity: EntityName | undefined, reactNamespace: string, tagName: Expression, props: Expression, children: readonly Expression[], parentElement: JsxOpeningLikeElement, location: TextRange): LeftHandSideExpression { - const argumentsList = [tagName]; - if (props) { - argumentsList.push(props); - } - - if (children && children.length > 0) { - if (!props) { - argumentsList.push(createNull()); - } - - if (children.length > 1) { - for (const child of children) { - startOnNewLine(child); - argumentsList.push(child); - } - } - else { - argumentsList.push(children[0]); + return setTextRange(createCall(createJsxFactoryExpression(jsxFactoryEntity, reactNamespace, parentElement), + /*typeArguments*/ undefined, argumentsList), location); +} +/* @internal */ +export function createExpressionForJsxFragment(jsxFactoryEntity: EntityName | undefined, reactNamespace: string, children: readonly Expression[], parentElement: JsxOpeningFragment, location: TextRange): LeftHandSideExpression { + const tagName = createPropertyAccess(createReactNamespace(reactNamespace, parentElement), "Fragment"); + const argumentsList = [(tagName)]; + argumentsList.push(createNull()); + if (children && children.length > 0) { + if (children.length > 1) { + for (const child of children) { + startOnNewLine(child); + argumentsList.push(child); } } - - return setTextRange( - createCall( - createJsxFactoryExpression(jsxFactoryEntity, reactNamespace, parentElement), - /*typeArguments*/ undefined, - argumentsList - ), - location - ); - } - - export function createExpressionForJsxFragment(jsxFactoryEntity: EntityName | undefined, reactNamespace: string, children: readonly Expression[], parentElement: JsxOpeningFragment, location: TextRange): LeftHandSideExpression { - const tagName = createPropertyAccess( - createReactNamespace(reactNamespace, parentElement), - "Fragment" - ); - - const argumentsList = [tagName]; - argumentsList.push(createNull()); - - if (children && children.length > 0) { - if (children.length > 1) { - for (const child of children) { - startOnNewLine(child); - argumentsList.push(child); - } - } - else { - argumentsList.push(children[0]); - } + else { + argumentsList.push(children[0]); } - - return setTextRange( - createCall( - createJsxFactoryExpression(jsxFactoryEntity, reactNamespace, parentElement), - /*typeArguments*/ undefined, - argumentsList - ), - location - ); - } - - // Helpers - - /** - * Gets an identifier for the name of an *unscoped* emit helper. - */ - export function getUnscopedHelperName(name: string) { - return setEmitFlags(createIdentifier(name), EmitFlags.HelperName | EmitFlags.AdviseOnEmitNode); - } - - export const valuesHelper: UnscopedEmitHelper = { - name: "typescript:values", - importName: "__values", - scoped: false, - text: ` + } + return setTextRange(createCall(createJsxFactoryExpression(jsxFactoryEntity, reactNamespace, parentElement), + /*typeArguments*/ undefined, argumentsList), location); +} +// Helpers +/** + * Gets an identifier for the name of an *unscoped* emit helper. + */ +/* @internal */ +export function getUnscopedHelperName(name: string) { + return setEmitFlags(createIdentifier(name), EmitFlags.HelperName | EmitFlags.AdviseOnEmitNode); +} +/* @internal */ +export const valuesHelper: UnscopedEmitHelper = { + name: "typescript:values", + importName: "__values", + scoped: false, + text: ` var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); @@ -223,25 +173,19 @@ namespace ts { }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); };` - }; - - export function createValuesHelper(context: TransformationContext, expression: Expression, location?: TextRange) { - context.requestEmitHelper(valuesHelper); - return setTextRange( - createCall( - getUnscopedHelperName("__values"), - /*typeArguments*/ undefined, - [expression] - ), - location - ); - } - - export const readHelper: UnscopedEmitHelper = { - name: "typescript:read", - importName: "__read", - scoped: false, - text: ` +}; +/* @internal */ +export function createValuesHelper(context: TransformationContext, expression: Expression, location?: TextRange) { + context.requestEmitHelper(valuesHelper); + return setTextRange(createCall(getUnscopedHelperName("__values"), + /*typeArguments*/ undefined, [expression]), location); +} +/* @internal */ +export const readHelper: UnscopedEmitHelper = { + name: "typescript:read", + importName: "__read", + scoped: false, + text: ` var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; @@ -258,51 +202,39 @@ namespace ts { } return ar; };` - }; - - export function createReadHelper(context: TransformationContext, iteratorRecord: Expression, count: number | undefined, location?: TextRange) { - context.requestEmitHelper(readHelper); - return setTextRange( - createCall( - getUnscopedHelperName("__read"), - /*typeArguments*/ undefined, - count !== undefined - ? [iteratorRecord, createLiteral(count)] - : [iteratorRecord] - ), - location - ); - } - - export const spreadHelper: UnscopedEmitHelper = { - name: "typescript:spread", - importName: "__spread", - scoped: false, - text: ` +}; +/* @internal */ +export function createReadHelper(context: TransformationContext, iteratorRecord: Expression, count: number | undefined, location?: TextRange) { + context.requestEmitHelper(readHelper); + return setTextRange(createCall(getUnscopedHelperName("__read"), + /*typeArguments*/ undefined, count !== undefined + ? [iteratorRecord, createLiteral(count)] + : [iteratorRecord]), location); +} +/* @internal */ +export const spreadHelper: UnscopedEmitHelper = { + name: "typescript:spread", + importName: "__spread", + scoped: false, + text: ` var __spread = (this && this.__spread) || function () { for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i])); return ar; };` - }; - - export function createSpreadHelper(context: TransformationContext, argumentList: readonly Expression[], location?: TextRange) { - context.requestEmitHelper(readHelper); - context.requestEmitHelper(spreadHelper); - return setTextRange( - createCall( - getUnscopedHelperName("__spread"), - /*typeArguments*/ undefined, - argumentList - ), - location - ); - } - - export const spreadArraysHelper: UnscopedEmitHelper = { - name: "typescript:spreadArrays", - importName: "__spreadArrays", - scoped: false, - text: ` +}; +/* @internal */ +export function createSpreadHelper(context: TransformationContext, argumentList: readonly Expression[], location?: TextRange) { + context.requestEmitHelper(readHelper); + context.requestEmitHelper(spreadHelper); + return setTextRange(createCall(getUnscopedHelperName("__spread"), + /*typeArguments*/ undefined, argumentList), location); +} +/* @internal */ +export const spreadArraysHelper: UnscopedEmitHelper = { + name: "typescript:spreadArrays", + importName: "__spreadArrays", + scoped: false, + text: ` var __spreadArrays = (this && this.__spreadArrays) || function () { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) @@ -310,1594 +242,1416 @@ namespace ts { r[k] = a[j]; return r; };` - }; - - export function createSpreadArraysHelper(context: TransformationContext, argumentList: readonly Expression[], location?: TextRange) { - context.requestEmitHelper(spreadArraysHelper); - return setTextRange( - createCall( - getUnscopedHelperName("__spreadArrays"), - /*typeArguments*/ undefined, - argumentList - ), - location - ); - } - - // Utilities - - export function createForOfBindingStatement(node: ForInitializer, boundValue: Expression): Statement { - if (isVariableDeclarationList(node)) { - const firstDeclaration = first(node.declarations); - const updatedDeclaration = updateVariableDeclaration( - firstDeclaration, - firstDeclaration.name, - /*typeNode*/ undefined, - boundValue - ); - return setTextRange( - createVariableStatement( - /*modifiers*/ undefined, - updateVariableDeclarationList(node, [updatedDeclaration]) - ), - /*location*/ node - ); - } - else { - const updatedExpression = setTextRange(createAssignment(node, boundValue), /*location*/ node); - return setTextRange(createStatement(updatedExpression), /*location*/ node); - } +}; +/* @internal */ +export function createSpreadArraysHelper(context: TransformationContext, argumentList: readonly Expression[], location?: TextRange) { + context.requestEmitHelper(spreadArraysHelper); + return setTextRange(createCall(getUnscopedHelperName("__spreadArrays"), + /*typeArguments*/ undefined, argumentList), location); +} +// Utilities +/* @internal */ +export function createForOfBindingStatement(node: ForInitializer, boundValue: Expression): Statement { + if (isVariableDeclarationList(node)) { + const firstDeclaration = first(node.declarations); + const updatedDeclaration = updateVariableDeclaration(firstDeclaration, firstDeclaration.name, + /*typeNode*/ undefined, boundValue); + return setTextRange(createVariableStatement( + /*modifiers*/ undefined, updateVariableDeclarationList(node, [updatedDeclaration])), + /*location*/ node); + } + else { + const updatedExpression = setTextRange(createAssignment(node, boundValue), /*location*/ node); + return setTextRange(createStatement(updatedExpression), /*location*/ node); + } +} +/* @internal */ +export function insertLeadingStatement(dest: Statement, source: Statement) { + if (isBlock(dest)) { + return updateBlock(dest, setTextRange(createNodeArray([source, ...dest.statements]), dest.statements)); } - - export function insertLeadingStatement(dest: Statement, source: Statement) { - if (isBlock(dest)) { - return updateBlock(dest, setTextRange(createNodeArray([source, ...dest.statements]), dest.statements)); - } - else { - return createBlock(createNodeArray([dest, source]), /*multiLine*/ true); - } + else { + return createBlock(createNodeArray([dest, source]), /*multiLine*/ true); } - - export function restoreEnclosingLabel(node: Statement, outermostLabeledStatement: LabeledStatement | undefined, afterRestoreLabelCallback?: (node: LabeledStatement) => void): Statement { - if (!outermostLabeledStatement) { - return node; - } - const updated = updateLabel( - outermostLabeledStatement, - outermostLabeledStatement.label, - outermostLabeledStatement.statement.kind === SyntaxKind.LabeledStatement - ? restoreEnclosingLabel(node, outermostLabeledStatement.statement) - : node - ); - if (afterRestoreLabelCallback) { - afterRestoreLabelCallback(outermostLabeledStatement); - } - return updated; - } - - export interface CallBinding { - target: LeftHandSideExpression; - thisArg: Expression; - } - - function shouldBeCapturedInTempVariable(node: Expression, cacheIdentifiers: boolean): boolean { - const target = skipParentheses(node); - switch (target.kind) { - case SyntaxKind.Identifier: - return cacheIdentifiers; - case SyntaxKind.ThisKeyword: - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.StringLiteral: +} +/* @internal */ +export function restoreEnclosingLabel(node: Statement, outermostLabeledStatement: LabeledStatement | undefined, afterRestoreLabelCallback?: (node: LabeledStatement) => void): Statement { + if (!outermostLabeledStatement) { + return node; + } + const updated = updateLabel(outermostLabeledStatement, outermostLabeledStatement.label, outermostLabeledStatement.statement.kind === SyntaxKind.LabeledStatement + ? restoreEnclosingLabel(node, (outermostLabeledStatement.statement)) + : node); + if (afterRestoreLabelCallback) { + afterRestoreLabelCallback(outermostLabeledStatement); + } + return updated; +} +/* @internal */ +export interface CallBinding { + target: LeftHandSideExpression; + thisArg: Expression; +} +/* @internal */ +function shouldBeCapturedInTempVariable(node: Expression, cacheIdentifiers: boolean): boolean { + const target = skipParentheses(node); + switch (target.kind) { + case SyntaxKind.Identifier: + return cacheIdentifiers; + case SyntaxKind.ThisKeyword: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.StringLiteral: + return false; + case SyntaxKind.ArrayLiteralExpression: + const elements = (target).elements; + if (elements.length === 0) { return false; - case SyntaxKind.ArrayLiteralExpression: - const elements = (target).elements; - if (elements.length === 0) { - return false; - } - return true; - case SyntaxKind.ObjectLiteralExpression: - return (target).properties.length > 0; - default: - return true; - } + } + return true; + case SyntaxKind.ObjectLiteralExpression: + return (target).properties.length > 0; + default: + return true; } - - export function createCallBinding(expression: Expression, recordTempVariable: (temp: Identifier) => void, languageVersion?: ScriptTarget, cacheIdentifiers = false): CallBinding { - const callee = skipOuterExpressions(expression, OuterExpressionKinds.All); - let thisArg: Expression; - let target: LeftHandSideExpression; - if (isSuperProperty(callee)) { - thisArg = createThis(); - target = callee; - } - else if (callee.kind === SyntaxKind.SuperKeyword) { - thisArg = createThis(); - target = languageVersion! < ScriptTarget.ES2015 - ? setTextRange(createIdentifier("_super"), callee) - : callee; - } - else if (getEmitFlags(callee) & EmitFlags.HelperName) { - thisArg = createVoidZero(); - target = parenthesizeForAccess(callee); - } - else { - switch (callee.kind) { - case SyntaxKind.PropertyAccessExpression: { - if (shouldBeCapturedInTempVariable((callee).expression, cacheIdentifiers)) { - // for `a.b()` target is `(_a = a).b` and thisArg is `_a` - thisArg = createTempVariable(recordTempVariable); - target = createPropertyAccess( - setTextRange( - createAssignment( - thisArg, - (callee).expression - ), - (callee).expression - ), - (callee).name - ); - setTextRange(target, callee); - } - else { - thisArg = (callee).expression; - target = callee; - } - break; +} +/* @internal */ +export function createCallBinding(expression: Expression, recordTempVariable: (temp: Identifier) => void, languageVersion?: ScriptTarget, cacheIdentifiers = false): CallBinding { + const callee = skipOuterExpressions(expression, OuterExpressionKinds.All); + let thisArg: Expression; + let target: LeftHandSideExpression; + if (isSuperProperty(callee)) { + thisArg = createThis(); + target = callee; + } + else if (callee.kind === SyntaxKind.SuperKeyword) { + thisArg = createThis(); + target = (languageVersion!) < ScriptTarget.ES2015 + ? setTextRange(createIdentifier("_super"), callee) + : callee; + } + else if (getEmitFlags(callee) & EmitFlags.HelperName) { + thisArg = createVoidZero(); + target = parenthesizeForAccess(callee); + } + else { + switch (callee.kind) { + case SyntaxKind.PropertyAccessExpression: { + if (shouldBeCapturedInTempVariable((callee).expression, cacheIdentifiers)) { + // for `a.b()` target is `(_a = a).b` and thisArg is `_a` + thisArg = createTempVariable(recordTempVariable); + target = createPropertyAccess(setTextRange(createAssignment(thisArg, (callee).expression), (callee).expression), (callee).name); + setTextRange(target, callee); } - - case SyntaxKind.ElementAccessExpression: { - if (shouldBeCapturedInTempVariable((callee).expression, cacheIdentifiers)) { - // for `a[b]()` target is `(_a = a)[b]` and thisArg is `_a` - thisArg = createTempVariable(recordTempVariable); - target = createElementAccess( - setTextRange( - createAssignment( - thisArg, - (callee).expression - ), - (callee).expression - ), - (callee).argumentExpression - ); - setTextRange(target, callee); - } - else { - thisArg = (callee).expression; - target = callee; - } - - break; + else { + thisArg = (callee).expression; + target = (callee); } - - default: { - // for `a()` target is `a` and thisArg is `void 0` - thisArg = createVoidZero(); - target = parenthesizeForAccess(expression); - break; + break; + } + case SyntaxKind.ElementAccessExpression: { + if (shouldBeCapturedInTempVariable((callee).expression, cacheIdentifiers)) { + // for `a[b]()` target is `(_a = a)[b]` and thisArg is `_a` + thisArg = createTempVariable(recordTempVariable); + target = createElementAccess(setTextRange(createAssignment(thisArg, (callee).expression), (callee).expression), (callee).argumentExpression); + setTextRange(target, callee); } + else { + thisArg = (callee).expression; + target = (callee); + } + break; + } + default: { + // for `a()` target is `a` and thisArg is `void 0` + thisArg = createVoidZero(); + target = parenthesizeForAccess(expression); + break; } - } - - return { target, thisArg }; - } - - export function inlineExpressions(expressions: readonly Expression[]) { - // Avoid deeply nested comma expressions as traversing them during emit can result in "Maximum call - // stack size exceeded" errors. - return expressions.length > 10 - ? createCommaList(expressions) - : reduceLeft(expressions, createComma)!; - } - - export function createExpressionFromEntityName(node: EntityName | Expression): Expression { - if (isQualifiedName(node)) { - const left = createExpressionFromEntityName(node.left); - const right = getMutableClone(node.right); - return setTextRange(createPropertyAccess(left, right), node); - } - else { - return getMutableClone(node); } } - - export function createExpressionForPropertyName(memberName: PropertyName): Expression { - if (isIdentifier(memberName)) { - return createLiteral(memberName); - } - else if (isComputedPropertyName(memberName)) { - return getMutableClone(memberName.expression); - } - else { - return getMutableClone(memberName); - } + return { target, thisArg }; +} +/* @internal */ +export function inlineExpressions(expressions: readonly Expression[]) { + // Avoid deeply nested comma expressions as traversing them during emit can result in "Maximum call + // stack size exceeded" errors. + return expressions.length > 10 + ? createCommaList(expressions) + : reduceLeft(expressions, createComma)!; +} +/* @internal */ +export function createExpressionFromEntityName(node: EntityName | Expression): Expression { + if (isQualifiedName(node)) { + const left = createExpressionFromEntityName(node.left); + const right = getMutableClone(node.right); + return setTextRange(createPropertyAccess(left, right), node); } - - export function createExpressionForObjectLiteralElementLike(node: ObjectLiteralExpression, property: ObjectLiteralElementLike, receiver: Expression): Expression | undefined { - switch (property.kind) { - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return createExpressionForAccessorDeclaration(node.properties, property, receiver, !!node.multiLine); - case SyntaxKind.PropertyAssignment: - return createExpressionForPropertyAssignment(property, receiver); - case SyntaxKind.ShorthandPropertyAssignment: - return createExpressionForShorthandPropertyAssignment(property, receiver); - case SyntaxKind.MethodDeclaration: - return createExpressionForMethodDeclaration(property, receiver); - } + else { + return getMutableClone(node); } - - function createExpressionForAccessorDeclaration(properties: NodeArray, property: AccessorDeclaration, receiver: Expression, multiLine: boolean) { - const { firstAccessor, getAccessor, setAccessor } = getAllAccessorDeclarations(properties, property); - if (property === firstAccessor) { - const properties: ObjectLiteralElementLike[] = []; - if (getAccessor) { - const getterFunction = createFunctionExpression( - getAccessor.modifiers, - /*asteriskToken*/ undefined, - /*name*/ undefined, - /*typeParameters*/ undefined, - getAccessor.parameters, - /*type*/ undefined, - getAccessor.body! // TODO: GH#18217 - ); - setTextRange(getterFunction, getAccessor); - setOriginalNode(getterFunction, getAccessor); - const getter = createPropertyAssignment("get", getterFunction); - properties.push(getter); - } - - if (setAccessor) { - const setterFunction = createFunctionExpression( - setAccessor.modifiers, - /*asteriskToken*/ undefined, - /*name*/ undefined, - /*typeParameters*/ undefined, - setAccessor.parameters, - /*type*/ undefined, - setAccessor.body! // TODO: GH#18217 - ); - setTextRange(setterFunction, setAccessor); - setOriginalNode(setterFunction, setAccessor); - const setter = createPropertyAssignment("set", setterFunction); - properties.push(setter); - } - - properties.push(createPropertyAssignment("enumerable", createTrue())); - properties.push(createPropertyAssignment("configurable", createTrue())); - - const expression = setTextRange( - createCall( - createPropertyAccess(createIdentifier("Object"), "defineProperty"), - /*typeArguments*/ undefined, - [ - receiver, - createExpressionForPropertyName(property.name), - createObjectLiteral(properties, multiLine) - ] - ), - /*location*/ firstAccessor +} +/* @internal */ +export function createExpressionForPropertyName(memberName: PropertyName): Expression { + if (isIdentifier(memberName)) { + return createLiteral(memberName); + } + else if (isComputedPropertyName(memberName)) { + return getMutableClone(memberName.expression); + } + else { + return getMutableClone(memberName); + } +} +/* @internal */ +export function createExpressionForObjectLiteralElementLike(node: ObjectLiteralExpression, property: ObjectLiteralElementLike, receiver: Expression): Expression | undefined { + switch (property.kind) { + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return createExpressionForAccessorDeclaration(node.properties, property, receiver, !!node.multiLine); + case SyntaxKind.PropertyAssignment: + return createExpressionForPropertyAssignment(property, receiver); + case SyntaxKind.ShorthandPropertyAssignment: + return createExpressionForShorthandPropertyAssignment(property, receiver); + case SyntaxKind.MethodDeclaration: + return createExpressionForMethodDeclaration(property, receiver); + } +} +/* @internal */ +function createExpressionForAccessorDeclaration(properties: NodeArray, property: AccessorDeclaration, receiver: Expression, multiLine: boolean) { + const { firstAccessor, getAccessor, setAccessor } = getAllAccessorDeclarations(properties, property); + if (property === firstAccessor) { + const properties: ObjectLiteralElementLike[] = []; + if (getAccessor) { + const getterFunction = createFunctionExpression(getAccessor.modifiers, + /*asteriskToken*/ undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, getAccessor.parameters, + /*type*/ undefined, (getAccessor.body!) // TODO: GH#18217 + ); + setTextRange(getterFunction, getAccessor); + setOriginalNode(getterFunction, getAccessor); + const getter = createPropertyAssignment("get", getterFunction); + properties.push(getter); + } + if (setAccessor) { + const setterFunction = createFunctionExpression(setAccessor.modifiers, + /*asteriskToken*/ undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, setAccessor.parameters, + /*type*/ undefined, (setAccessor.body!) // TODO: GH#18217 ); - - return aggregateTransformFlags(expression); + setTextRange(setterFunction, setAccessor); + setOriginalNode(setterFunction, setAccessor); + const setter = createPropertyAssignment("set", setterFunction); + properties.push(setter); + } + properties.push(createPropertyAssignment("enumerable", createTrue())); + properties.push(createPropertyAssignment("configurable", createTrue())); + const expression = setTextRange(createCall(createPropertyAccess(createIdentifier("Object"), "defineProperty"), + /*typeArguments*/ undefined, [ + receiver, + createExpressionForPropertyName(property.name), + createObjectLiteral(properties, multiLine) + ]), + /*location*/ firstAccessor); + return aggregateTransformFlags(expression); + } + return undefined; +} +/* @internal */ +function createExpressionForPropertyAssignment(property: PropertyAssignment, receiver: Expression) { + return aggregateTransformFlags(setOriginalNode(setTextRange(createAssignment(createMemberAccessForPropertyName(receiver, property.name, /*location*/ property.name), property.initializer), property), property)); +} +/* @internal */ +function createExpressionForShorthandPropertyAssignment(property: ShorthandPropertyAssignment, receiver: Expression) { + return aggregateTransformFlags(setOriginalNode(setTextRange(createAssignment(createMemberAccessForPropertyName(receiver, property.name, /*location*/ property.name), getSynthesizedClone(property.name)), + /*location*/ property), + /*original*/ property)); +} +/* @internal */ +function createExpressionForMethodDeclaration(method: MethodDeclaration, receiver: Expression) { + return aggregateTransformFlags(setOriginalNode(setTextRange(createAssignment(createMemberAccessForPropertyName(receiver, method.name, /*location*/ method.name), setOriginalNode(setTextRange(createFunctionExpression(method.modifiers, method.asteriskToken, + /*name*/ undefined, + /*typeParameters*/ undefined, method.parameters, + /*type*/ undefined, (method.body!) // TODO: GH#18217 + ), + /*location*/ method), + /*original*/ method)), + /*location*/ method), + /*original*/ method)); +} +/** + * Gets the internal name of a declaration. This is primarily used for declarations that can be + * referred to by name in the body of an ES5 class function body. An internal name will *never* + * be prefixed with an module or namespace export modifier like "exports." when emitted as an + * expression. An internal name will also *never* be renamed due to a collision with a block + * scoped variable. + * + * @param node The declaration. + * @param allowComments A value indicating whether comments may be emitted for the name. + * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. + */ +/* @internal */ +export function getInternalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) { + return getName(node, allowComments, allowSourceMaps, EmitFlags.LocalName | EmitFlags.InternalName); +} +/** + * Gets whether an identifier should only be referred to by its internal name. + */ +/* @internal */ +export function isInternalName(node: Identifier) { + return (getEmitFlags(node) & EmitFlags.InternalName) !== 0; +} +/** + * Gets the local name of a declaration. This is primarily used for declarations that can be + * referred to by name in the declaration's immediate scope (classes, enums, namespaces). A + * local name will *never* be prefixed with an module or namespace export modifier like + * "exports." when emitted as an expression. + * + * @param node The declaration. + * @param allowComments A value indicating whether comments may be emitted for the name. + * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. + */ +/* @internal */ +export function getLocalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) { + return getName(node, allowComments, allowSourceMaps, EmitFlags.LocalName); +} +/** + * Gets whether an identifier should only be referred to by its local name. + */ +/* @internal */ +export function isLocalName(node: Identifier) { + return (getEmitFlags(node) & EmitFlags.LocalName) !== 0; +} +/** + * Gets the export name of a declaration. This is primarily used for declarations that can be + * referred to by name in the declaration's immediate scope (classes, enums, namespaces). An + * export name will *always* be prefixed with an module or namespace export modifier like + * `"exports."` when emitted as an expression if the name points to an exported symbol. + * + * @param node The declaration. + * @param allowComments A value indicating whether comments may be emitted for the name. + * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. + */ +/* @internal */ +export function getExportName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean): Identifier { + return getName(node, allowComments, allowSourceMaps, EmitFlags.ExportName); +} +/** + * Gets whether an identifier should only be referred to by its export representation if the + * name points to an exported symbol. + */ +/* @internal */ +export function isExportName(node: Identifier) { + return (getEmitFlags(node) & EmitFlags.ExportName) !== 0; +} +/** + * Gets the name of a declaration for use in declarations. + * + * @param node The declaration. + * @param allowComments A value indicating whether comments may be emitted for the name. + * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. + */ +/* @internal */ +export function getDeclarationName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) { + return getName(node, allowComments, allowSourceMaps); +} +/* @internal */ +function getName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean, emitFlags: EmitFlags = 0) { + const nodeName = getNameOfDeclaration(node); + if (nodeName && isIdentifier(nodeName) && !isGeneratedIdentifier(nodeName)) { + const name = getMutableClone(nodeName); + emitFlags |= getEmitFlags(nodeName); + if (!allowSourceMaps) + emitFlags |= EmitFlags.NoSourceMap; + if (!allowComments) + emitFlags |= EmitFlags.NoComments; + if (emitFlags) + setEmitFlags(name, emitFlags); + return name; + } + return getGeneratedNameForNode(node); +} +/** + * Gets the exported name of a declaration for use in expressions. + * + * An exported name will *always* be prefixed with an module or namespace export modifier like + * "exports." if the name points to an exported symbol. + * + * @param ns The namespace identifier. + * @param node The declaration. + * @param allowComments A value indicating whether comments may be emitted for the name. + * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. + */ +/* @internal */ +export function getExternalModuleOrNamespaceExportName(ns: Identifier | undefined, node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean): Identifier | PropertyAccessExpression { + if (ns && hasModifier(node, ModifierFlags.Export)) { + return getNamespaceMemberName(ns, getName(node), allowComments, allowSourceMaps); + } + return getExportName(node, allowComments, allowSourceMaps); +} +/** + * Gets a namespace-qualified name for use in expressions. + * + * @param ns The namespace identifier. + * @param name The name. + * @param allowComments A value indicating whether comments may be emitted for the name. + * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. + */ +/* @internal */ +export function getNamespaceMemberName(ns: Identifier, name: Identifier, allowComments?: boolean, allowSourceMaps?: boolean): PropertyAccessExpression { + const qualifiedName = createPropertyAccess(ns, nodeIsSynthesized(name) ? name : getSynthesizedClone(name)); + setTextRange(qualifiedName, name); + let emitFlags: EmitFlags = 0; + if (!allowSourceMaps) + emitFlags |= EmitFlags.NoSourceMap; + if (!allowComments) + emitFlags |= EmitFlags.NoComments; + if (emitFlags) + setEmitFlags(qualifiedName, emitFlags); + return qualifiedName; +} +/* @internal */ +export function convertToFunctionBody(node: ConciseBody, multiLine?: boolean): Block { + return isBlock(node) ? node : setTextRange(createBlock([setTextRange(createReturn(node), node)], multiLine), node); +} +/* @internal */ +export function convertFunctionDeclarationToExpression(node: FunctionDeclaration) { + if (!node.body) + return Debug.fail(); + const updated = createFunctionExpression(node.modifiers, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, node.body); + setOriginalNode(updated, node); + setTextRange(updated, node); + if (getStartsOnNewLine(node)) { + setStartsOnNewLine(updated, /*newLine*/ true); + } + aggregateTransformFlags(updated); + return updated; +} +/* @internal */ +function isUseStrictPrologue(node: ExpressionStatement): boolean { + return isStringLiteral(node.expression) && node.expression.text === "use strict"; +} +/** + * Add any necessary prologue-directives into target statement-array. + * The function needs to be called during each transformation step. + * This function needs to be called whenever we transform the statement + * list of a source file, namespace, or function-like body. + * + * @param target: result statements array + * @param source: origin statements array + * @param ensureUseStrict: boolean determining whether the function need to add prologue-directives + * @param visitor: Optional callback used to visit any custom prologue directives. + */ +/* @internal */ +export function addPrologue(target: Statement[], source: readonly Statement[], ensureUseStrict?: boolean, visitor?: (node: Node) => VisitResult): number { + const offset = addStandardPrologue(target, source, ensureUseStrict); + return addCustomPrologue(target, source, offset, visitor); +} +/** + * Add just the standard (string-expression) prologue-directives into target statement-array. + * The function needs to be called during each transformation step. + * This function needs to be called whenever we transform the statement + * list of a source file, namespace, or function-like body. + */ +/* @internal */ +export function addStandardPrologue(target: Statement[], source: readonly Statement[], ensureUseStrict?: boolean): number { + Debug.assert(target.length === 0, "Prologue directives should be at the first statement in the target statements array"); + let foundUseStrict = false; + let statementOffset = 0; + const numStatements = source.length; + while (statementOffset < numStatements) { + const statement = source[statementOffset]; + if (isPrologueDirective(statement)) { + if (isUseStrictPrologue(statement)) { + foundUseStrict = true; + } + target.push(statement); } - - return undefined; - } - - function createExpressionForPropertyAssignment(property: PropertyAssignment, receiver: Expression) { - return aggregateTransformFlags( - setOriginalNode( - setTextRange( - createAssignment( - createMemberAccessForPropertyName(receiver, property.name, /*location*/ property.name), - property.initializer - ), - property - ), - property - ) - ); - } - - function createExpressionForShorthandPropertyAssignment(property: ShorthandPropertyAssignment, receiver: Expression) { - return aggregateTransformFlags( - setOriginalNode( - setTextRange( - createAssignment( - createMemberAccessForPropertyName(receiver, property.name, /*location*/ property.name), - getSynthesizedClone(property.name) - ), - /*location*/ property - ), - /*original*/ property - ) - ); - } - - function createExpressionForMethodDeclaration(method: MethodDeclaration, receiver: Expression) { - return aggregateTransformFlags( - setOriginalNode( - setTextRange( - createAssignment( - createMemberAccessForPropertyName(receiver, method.name, /*location*/ method.name), - setOriginalNode( - setTextRange( - createFunctionExpression( - method.modifiers, - method.asteriskToken, - /*name*/ undefined, - /*typeParameters*/ undefined, - method.parameters, - /*type*/ undefined, - method.body! // TODO: GH#18217 - ), - /*location*/ method - ), - /*original*/ method - ) - ), - /*location*/ method - ), - /*original*/ method - ) - ); - } - - /** - * Gets the internal name of a declaration. This is primarily used for declarations that can be - * referred to by name in the body of an ES5 class function body. An internal name will *never* - * be prefixed with an module or namespace export modifier like "exports." when emitted as an - * expression. An internal name will also *never* be renamed due to a collision with a block - * scoped variable. - * - * @param node The declaration. - * @param allowComments A value indicating whether comments may be emitted for the name. - * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. - */ - export function getInternalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) { - return getName(node, allowComments, allowSourceMaps, EmitFlags.LocalName | EmitFlags.InternalName); - } - - /** - * Gets whether an identifier should only be referred to by its internal name. - */ - export function isInternalName(node: Identifier) { - return (getEmitFlags(node) & EmitFlags.InternalName) !== 0; - } - - /** - * Gets the local name of a declaration. This is primarily used for declarations that can be - * referred to by name in the declaration's immediate scope (classes, enums, namespaces). A - * local name will *never* be prefixed with an module or namespace export modifier like - * "exports." when emitted as an expression. - * - * @param node The declaration. - * @param allowComments A value indicating whether comments may be emitted for the name. - * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. - */ - export function getLocalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) { - return getName(node, allowComments, allowSourceMaps, EmitFlags.LocalName); - } - - /** - * Gets whether an identifier should only be referred to by its local name. - */ - export function isLocalName(node: Identifier) { - return (getEmitFlags(node) & EmitFlags.LocalName) !== 0; - } - - /** - * Gets the export name of a declaration. This is primarily used for declarations that can be - * referred to by name in the declaration's immediate scope (classes, enums, namespaces). An - * export name will *always* be prefixed with an module or namespace export modifier like - * `"exports."` when emitted as an expression if the name points to an exported symbol. - * - * @param node The declaration. - * @param allowComments A value indicating whether comments may be emitted for the name. - * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. - */ - export function getExportName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean): Identifier { - return getName(node, allowComments, allowSourceMaps, EmitFlags.ExportName); - } - - /** - * Gets whether an identifier should only be referred to by its export representation if the - * name points to an exported symbol. - */ - export function isExportName(node: Identifier) { - return (getEmitFlags(node) & EmitFlags.ExportName) !== 0; - } - - /** - * Gets the name of a declaration for use in declarations. - * - * @param node The declaration. - * @param allowComments A value indicating whether comments may be emitted for the name. - * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. - */ - export function getDeclarationName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) { - return getName(node, allowComments, allowSourceMaps); - } - - function getName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean, emitFlags: EmitFlags = 0) { - const nodeName = getNameOfDeclaration(node); - if (nodeName && isIdentifier(nodeName) && !isGeneratedIdentifier(nodeName)) { - const name = getMutableClone(nodeName); - emitFlags |= getEmitFlags(nodeName); - if (!allowSourceMaps) emitFlags |= EmitFlags.NoSourceMap; - if (!allowComments) emitFlags |= EmitFlags.NoComments; - if (emitFlags) setEmitFlags(name, emitFlags); - return name; + else { + break; } - return getGeneratedNameForNode(node); + statementOffset++; + } + if (ensureUseStrict && !foundUseStrict) { + target.push(startOnNewLine(createStatement(createLiteral("use strict")))); } - - /** - * Gets the exported name of a declaration for use in expressions. - * - * An exported name will *always* be prefixed with an module or namespace export modifier like - * "exports." if the name points to an exported symbol. - * - * @param ns The namespace identifier. - * @param node The declaration. - * @param allowComments A value indicating whether comments may be emitted for the name. - * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. - */ - export function getExternalModuleOrNamespaceExportName(ns: Identifier | undefined, node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean): Identifier | PropertyAccessExpression { - if (ns && hasModifier(node, ModifierFlags.Export)) { - return getNamespaceMemberName(ns, getName(node), allowComments, allowSourceMaps); + return statementOffset; +} +/** + * Add just the custom prologue-directives into target statement-array. + * The function needs to be called during each transformation step. + * This function needs to be called whenever we transform the statement + * list of a source file, namespace, or function-like body. + */ +/* @internal */ +export function addCustomPrologue(target: Statement[], source: readonly Statement[], statementOffset: number, visitor?: (node: Node) => VisitResult): number; +/* @internal */ +export function addCustomPrologue(target: Statement[], source: readonly Statement[], statementOffset: number | undefined, visitor?: (node: Node) => VisitResult): number | undefined; +/* @internal */ +export function addCustomPrologue(target: Statement[], source: readonly Statement[], statementOffset: number | undefined, visitor?: (node: Node) => VisitResult): number | undefined { + const numStatements = source.length; + while (statementOffset !== undefined && statementOffset < numStatements) { + const statement = source[statementOffset]; + if (getEmitFlags(statement) & EmitFlags.CustomPrologue) { + append(target, visitor ? visitNode(statement, visitor, isStatement) : statement); } - return getExportName(node, allowComments, allowSourceMaps); - } - - /** - * Gets a namespace-qualified name for use in expressions. - * - * @param ns The namespace identifier. - * @param name The name. - * @param allowComments A value indicating whether comments may be emitted for the name. - * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. - */ - export function getNamespaceMemberName(ns: Identifier, name: Identifier, allowComments?: boolean, allowSourceMaps?: boolean): PropertyAccessExpression { - const qualifiedName = createPropertyAccess(ns, nodeIsSynthesized(name) ? name : getSynthesizedClone(name)); - setTextRange(qualifiedName, name); - let emitFlags: EmitFlags = 0; - if (!allowSourceMaps) emitFlags |= EmitFlags.NoSourceMap; - if (!allowComments) emitFlags |= EmitFlags.NoComments; - if (emitFlags) setEmitFlags(qualifiedName, emitFlags); - return qualifiedName; - } - - export function convertToFunctionBody(node: ConciseBody, multiLine?: boolean): Block { - return isBlock(node) ? node : setTextRange(createBlock([setTextRange(createReturn(node), node)], multiLine), node); - } - - export function convertFunctionDeclarationToExpression(node: FunctionDeclaration) { - if (!node.body) return Debug.fail(); - const updated = createFunctionExpression( - node.modifiers, - node.asteriskToken, - node.name, - node.typeParameters, - node.parameters, - node.type, - node.body - ); - setOriginalNode(updated, node); - setTextRange(updated, node); - if (getStartsOnNewLine(node)) { - setStartsOnNewLine(updated, /*newLine*/ true); + else { + break; } - aggregateTransformFlags(updated); - return updated; - } - - function isUseStrictPrologue(node: ExpressionStatement): boolean { - return isStringLiteral(node.expression) && node.expression.text === "use strict"; - } - - /** - * Add any necessary prologue-directives into target statement-array. - * The function needs to be called during each transformation step. - * This function needs to be called whenever we transform the statement - * list of a source file, namespace, or function-like body. - * - * @param target: result statements array - * @param source: origin statements array - * @param ensureUseStrict: boolean determining whether the function need to add prologue-directives - * @param visitor: Optional callback used to visit any custom prologue directives. - */ - export function addPrologue(target: Statement[], source: readonly Statement[], ensureUseStrict?: boolean, visitor?: (node: Node) => VisitResult): number { - const offset = addStandardPrologue(target, source, ensureUseStrict); - return addCustomPrologue(target, source, offset, visitor); - } - - /** - * Add just the standard (string-expression) prologue-directives into target statement-array. - * The function needs to be called during each transformation step. - * This function needs to be called whenever we transform the statement - * list of a source file, namespace, or function-like body. - */ - export function addStandardPrologue(target: Statement[], source: readonly Statement[], ensureUseStrict?: boolean): number { - Debug.assert(target.length === 0, "Prologue directives should be at the first statement in the target statements array"); - let foundUseStrict = false; - let statementOffset = 0; - const numStatements = source.length; - while (statementOffset < numStatements) { - const statement = source[statementOffset]; - if (isPrologueDirective(statement)) { - if (isUseStrictPrologue(statement)) { - foundUseStrict = true; - } - target.push(statement); - } - else { - break; + statementOffset++; + } + return statementOffset; +} +/* @internal */ +export function findUseStrictPrologue(statements: readonly Statement[]): Statement | undefined { + for (const statement of statements) { + if (isPrologueDirective(statement)) { + if (isUseStrictPrologue(statement)) { + return statement; } - statementOffset++; } - if (ensureUseStrict && !foundUseStrict) { - target.push(startOnNewLine(createStatement(createLiteral("use strict")))); + else { + break; } - return statementOffset; - } - - /** - * Add just the custom prologue-directives into target statement-array. - * The function needs to be called during each transformation step. - * This function needs to be called whenever we transform the statement - * list of a source file, namespace, or function-like body. - */ - export function addCustomPrologue(target: Statement[], source: readonly Statement[], statementOffset: number, visitor?: (node: Node) => VisitResult): number; - export function addCustomPrologue(target: Statement[], source: readonly Statement[], statementOffset: number | undefined, visitor?: (node: Node) => VisitResult): number | undefined; - export function addCustomPrologue(target: Statement[], source: readonly Statement[], statementOffset: number | undefined, visitor?: (node: Node) => VisitResult): number | undefined { - const numStatements = source.length; - while (statementOffset !== undefined && statementOffset < numStatements) { - const statement = source[statementOffset]; - if (getEmitFlags(statement) & EmitFlags.CustomPrologue) { - append(target, visitor ? visitNode(statement, visitor, isStatement) : statement); - } - else { - break; + } + return undefined; +} +/* @internal */ +export function startsWithUseStrict(statements: readonly Statement[]) { + const firstStatement = firstOrUndefined(statements); + return firstStatement !== undefined + && isPrologueDirective(firstStatement) + && isUseStrictPrologue(firstStatement); +} +/** + * Ensures "use strict" directive is added + * + * @param statements An array of statements + */ +/* @internal */ +export function ensureUseStrict(statements: NodeArray): NodeArray { + const foundUseStrict = findUseStrictPrologue(statements); + if (!foundUseStrict) { + return setTextRange(createNodeArray([ + startOnNewLine(createStatement(createLiteral("use strict"))), + ...statements + ]), statements); + } + return statements; +} +/** + * Wraps the operand to a BinaryExpression in parentheses if they are needed to preserve the intended + * order of operations. + * + * @param binaryOperator The operator for the BinaryExpression. + * @param operand The operand for the BinaryExpression. + * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the + * BinaryExpression. + */ +/* @internal */ +export function parenthesizeBinaryOperand(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand?: Expression) { + const skipped = skipPartiallyEmittedExpressions(operand); + // If the resulting expression is already parenthesized, we do not need to do any further processing. + if (skipped.kind === SyntaxKind.ParenthesizedExpression) { + return operand; + } + return binaryOperandNeedsParentheses(binaryOperator, operand, isLeftSideOfBinary, leftOperand) + ? createParen(operand) + : operand; +} +/** + * Determines whether the operand to a BinaryExpression needs to be parenthesized. + * + * @param binaryOperator The operator for the BinaryExpression. + * @param operand The operand for the BinaryExpression. + * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the + * BinaryExpression. + */ +/* @internal */ +function binaryOperandNeedsParentheses(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand: Expression | undefined) { + // If the operand has lower precedence, then it needs to be parenthesized to preserve the + // intent of the expression. For example, if the operand is `a + b` and the operator is + // `*`, then we need to parenthesize the operand to preserve the intended order of + // operations: `(a + b) * x`. + // + // If the operand has higher precedence, then it does not need to be parenthesized. For + // example, if the operand is `a * b` and the operator is `+`, then we do not need to + // parenthesize to preserve the intended order of operations: `a * b + x`. + // + // If the operand has the same precedence, then we need to check the associativity of + // the operator based on whether this is the left or right operand of the expression. + // + // For example, if `a / d` is on the right of operator `*`, we need to parenthesize + // to preserve the intended order of operations: `x * (a / d)` + // + // If `a ** d` is on the left of operator `**`, we need to parenthesize to preserve + // the intended order of operations: `(a ** b) ** c` + const binaryOperatorPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, binaryOperator); + const binaryOperatorAssociativity = getOperatorAssociativity(SyntaxKind.BinaryExpression, binaryOperator); + const emittedOperand = skipPartiallyEmittedExpressions(operand); + if (!isLeftSideOfBinary && operand.kind === SyntaxKind.ArrowFunction && binaryOperatorPrecedence > 3) { + // We need to parenthesize arrow functions on the right side to avoid it being + // parsed as parenthesized expression: `a && (() => {})` + return true; + } + const operandPrecedence = getExpressionPrecedence(emittedOperand); + switch (compareValues(operandPrecedence, binaryOperatorPrecedence)) { + case Comparison.LessThan: + // If the operand is the right side of a right-associative binary operation + // and is a yield expression, then we do not need parentheses. + if (!isLeftSideOfBinary + && binaryOperatorAssociativity === Associativity.Right + && operand.kind === SyntaxKind.YieldExpression) { + return false; } - statementOffset++; - } - return statementOffset; - } - - export function findUseStrictPrologue(statements: readonly Statement[]): Statement | undefined { - for (const statement of statements) { - if (isPrologueDirective(statement)) { - if (isUseStrictPrologue(statement)) { - return statement; - } + return true; + case Comparison.GreaterThan: + return false; + case Comparison.EqualTo: + if (isLeftSideOfBinary) { + // No need to parenthesize the left operand when the binary operator is + // left associative: + // (a*b)/x -> a*b/x + // (a**b)/x -> a**b/x + // + // Parentheses are needed for the left operand when the binary operator is + // right associative: + // (a/b)**x -> (a/b)**x + // (a**b)**x -> (a**b)**x + return binaryOperatorAssociativity === Associativity.Right; } else { - break; - } - } - return undefined; - } - - export function startsWithUseStrict(statements: readonly Statement[]) { - const firstStatement = firstOrUndefined(statements); - return firstStatement !== undefined - && isPrologueDirective(firstStatement) - && isUseStrictPrologue(firstStatement); - } - - /** - * Ensures "use strict" directive is added - * - * @param statements An array of statements - */ - export function ensureUseStrict(statements: NodeArray): NodeArray { - const foundUseStrict = findUseStrictPrologue(statements); - - if (!foundUseStrict) { - return setTextRange( - createNodeArray([ - startOnNewLine(createStatement(createLiteral("use strict"))), - ...statements - ]), - statements - ); - } - - return statements; - } - - /** - * Wraps the operand to a BinaryExpression in parentheses if they are needed to preserve the intended - * order of operations. - * - * @param binaryOperator The operator for the BinaryExpression. - * @param operand The operand for the BinaryExpression. - * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the - * BinaryExpression. - */ - export function parenthesizeBinaryOperand(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand?: Expression) { - const skipped = skipPartiallyEmittedExpressions(operand); - - // If the resulting expression is already parenthesized, we do not need to do any further processing. - if (skipped.kind === SyntaxKind.ParenthesizedExpression) { - return operand; - } - - return binaryOperandNeedsParentheses(binaryOperator, operand, isLeftSideOfBinary, leftOperand) - ? createParen(operand) - : operand; - } - - /** - * Determines whether the operand to a BinaryExpression needs to be parenthesized. - * - * @param binaryOperator The operator for the BinaryExpression. - * @param operand The operand for the BinaryExpression. - * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the - * BinaryExpression. - */ - function binaryOperandNeedsParentheses(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand: Expression | undefined) { - // If the operand has lower precedence, then it needs to be parenthesized to preserve the - // intent of the expression. For example, if the operand is `a + b` and the operator is - // `*`, then we need to parenthesize the operand to preserve the intended order of - // operations: `(a + b) * x`. - // - // If the operand has higher precedence, then it does not need to be parenthesized. For - // example, if the operand is `a * b` and the operator is `+`, then we do not need to - // parenthesize to preserve the intended order of operations: `a * b + x`. - // - // If the operand has the same precedence, then we need to check the associativity of - // the operator based on whether this is the left or right operand of the expression. - // - // For example, if `a / d` is on the right of operator `*`, we need to parenthesize - // to preserve the intended order of operations: `x * (a / d)` - // - // If `a ** d` is on the left of operator `**`, we need to parenthesize to preserve - // the intended order of operations: `(a ** b) ** c` - const binaryOperatorPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, binaryOperator); - const binaryOperatorAssociativity = getOperatorAssociativity(SyntaxKind.BinaryExpression, binaryOperator); - const emittedOperand = skipPartiallyEmittedExpressions(operand); - if (!isLeftSideOfBinary && operand.kind === SyntaxKind.ArrowFunction && binaryOperatorPrecedence > 3) { - // We need to parenthesize arrow functions on the right side to avoid it being - // parsed as parenthesized expression: `a && (() => {})` - return true; - } - const operandPrecedence = getExpressionPrecedence(emittedOperand); - switch (compareValues(operandPrecedence, binaryOperatorPrecedence)) { - case Comparison.LessThan: - // If the operand is the right side of a right-associative binary operation - // and is a yield expression, then we do not need parentheses. - if (!isLeftSideOfBinary - && binaryOperatorAssociativity === Associativity.Right - && operand.kind === SyntaxKind.YieldExpression) { - return false; - } - - return true; - - case Comparison.GreaterThan: - return false; - - case Comparison.EqualTo: - if (isLeftSideOfBinary) { - // No need to parenthesize the left operand when the binary operator is - // left associative: - // (a*b)/x -> a*b/x - // (a**b)/x -> a**b/x - // - // Parentheses are needed for the left operand when the binary operator is - // right associative: - // (a/b)**x -> (a/b)**x - // (a**b)**x -> (a**b)**x - return binaryOperatorAssociativity === Associativity.Right; - } - else { - if (isBinaryExpression(emittedOperand) - && emittedOperand.operatorToken.kind === binaryOperator) { - // No need to parenthesize the right operand when the binary operator and - // operand are the same and one of the following: - // x*(a*b) => x*a*b - // x|(a|b) => x|a|b - // x&(a&b) => x&a&b - // x^(a^b) => x^a^b - if (operatorHasAssociativeProperty(binaryOperator)) { + if (isBinaryExpression(emittedOperand) + && emittedOperand.operatorToken.kind === binaryOperator) { + // No need to parenthesize the right operand when the binary operator and + // operand are the same and one of the following: + // x*(a*b) => x*a*b + // x|(a|b) => x|a|b + // x&(a&b) => x&a&b + // x^(a^b) => x^a^b + if (operatorHasAssociativeProperty(binaryOperator)) { + return false; + } + // No need to parenthesize the right operand when the binary operator + // is plus (+) if both the left and right operands consist solely of either + // literals of the same kind or binary plus (+) expressions for literals of + // the same kind (recursively). + // "a"+(1+2) => "a"+(1+2) + // "a"+("b"+"c") => "a"+"b"+"c" + if (binaryOperator === SyntaxKind.PlusToken) { + const leftKind = leftOperand ? getLiteralKindOfBinaryPlusOperand(leftOperand) : SyntaxKind.Unknown; + if (isLiteralKind(leftKind) && leftKind === getLiteralKindOfBinaryPlusOperand(emittedOperand)) { return false; } - - // No need to parenthesize the right operand when the binary operator - // is plus (+) if both the left and right operands consist solely of either - // literals of the same kind or binary plus (+) expressions for literals of - // the same kind (recursively). - // "a"+(1+2) => "a"+(1+2) - // "a"+("b"+"c") => "a"+"b"+"c" - if (binaryOperator === SyntaxKind.PlusToken) { - const leftKind = leftOperand ? getLiteralKindOfBinaryPlusOperand(leftOperand) : SyntaxKind.Unknown; - if (isLiteralKind(leftKind) && leftKind === getLiteralKindOfBinaryPlusOperand(emittedOperand)) { - return false; - } - } } - - // No need to parenthesize the right operand when the operand is right - // associative: - // x/(a**b) -> x/a**b - // x**(a**b) -> x**a**b - // - // Parentheses are needed for the right operand when the operand is left - // associative: - // x/(a*b) -> x/(a*b) - // x**(a/b) -> x**(a/b) - const operandAssociativity = getExpressionAssociativity(emittedOperand); - return operandAssociativity === Associativity.Left; } - } - } - - /** - * Determines whether a binary operator is mathematically associative. - * - * @param binaryOperator The binary operator. - */ - function operatorHasAssociativeProperty(binaryOperator: SyntaxKind) { - // The following operators are associative in JavaScript: - // (a*b)*c -> a*(b*c) -> a*b*c - // (a|b)|c -> a|(b|c) -> a|b|c - // (a&b)&c -> a&(b&c) -> a&b&c - // (a^b)^c -> a^(b^c) -> a^b^c - // - // While addition is associative in mathematics, JavaScript's `+` is not - // guaranteed to be associative as it is overloaded with string concatenation. - return binaryOperator === SyntaxKind.AsteriskToken - || binaryOperator === SyntaxKind.BarToken - || binaryOperator === SyntaxKind.AmpersandToken - || binaryOperator === SyntaxKind.CaretToken; - } - - interface BinaryPlusExpression extends BinaryExpression { - cachedLiteralKind: SyntaxKind; - } - - /** - * This function determines whether an expression consists of a homogeneous set of - * literal expressions or binary plus expressions that all share the same literal kind. - * It is used to determine whether the right-hand operand of a binary plus expression can be - * emitted without parentheses. - */ - function getLiteralKindOfBinaryPlusOperand(node: Expression): SyntaxKind { - node = skipPartiallyEmittedExpressions(node); - - if (isLiteralKind(node.kind)) { - return node.kind; - } - - if (node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.PlusToken) { - if ((node).cachedLiteralKind !== undefined) { - return (node).cachedLiteralKind; + // No need to parenthesize the right operand when the operand is right + // associative: + // x/(a**b) -> x/a**b + // x**(a**b) -> x**a**b + // + // Parentheses are needed for the right operand when the operand is left + // associative: + // x/(a*b) -> x/(a*b) + // x**(a/b) -> x**(a/b) + const operandAssociativity = getExpressionAssociativity(emittedOperand); + return operandAssociativity === Associativity.Left; } - - const leftKind = getLiteralKindOfBinaryPlusOperand((node).left); - const literalKind = isLiteralKind(leftKind) && leftKind === getLiteralKindOfBinaryPlusOperand((node).right) ? leftKind : - SyntaxKind.Unknown; - - (node).cachedLiteralKind = literalKind; - return literalKind; - } - - return SyntaxKind.Unknown; - } - - export function parenthesizeForConditionalHead(condition: Expression) { - const conditionalPrecedence = getOperatorPrecedence(SyntaxKind.ConditionalExpression, SyntaxKind.QuestionToken); - const emittedCondition = skipPartiallyEmittedExpressions(condition); - const conditionPrecedence = getExpressionPrecedence(emittedCondition); - if (compareValues(conditionPrecedence, conditionalPrecedence) !== Comparison.GreaterThan) { - return createParen(condition); - } - return condition; - } - - export function parenthesizeSubexpressionOfConditionalExpression(e: Expression): Expression { - // per ES grammar both 'whenTrue' and 'whenFalse' parts of conditional expression are assignment expressions - // so in case when comma expression is introduced as a part of previous transformations - // if should be wrapped in parens since comma operator has the lowest precedence - const emittedExpression = skipPartiallyEmittedExpressions(e); - return isCommaSequence(emittedExpression) - ? createParen(e) - : e; - } - - /** - * [Per the spec](https://tc39.github.io/ecma262/#prod-ExportDeclaration), `export default` accepts _AssigmentExpression_ but - * has a lookahead restriction for `function`, `async function`, and `class`. - * - * Basically, that means we need to parenthesize in the following cases: - * - * - BinaryExpression of CommaToken - * - CommaList (synthetic list of multiple comma expressions) - * - FunctionExpression - * - ClassExpression - */ - export function parenthesizeDefaultExpression(e: Expression) { - const check = skipPartiallyEmittedExpressions(e); - let needsParens = isCommaSequence(check); - if (!needsParens) { - switch (getLeftmostExpression(check, /*stopAtCallExpression*/ false).kind) { - case SyntaxKind.ClassExpression: - case SyntaxKind.FunctionExpression: - needsParens = true; - } - } - return needsParens ? createParen(e) : e; - } - - /** - * Wraps an expression in parentheses if it is needed in order to use the expression - * as the expression of a NewExpression node. - * - * @param expression The Expression node. - */ - export function parenthesizeForNew(expression: Expression): LeftHandSideExpression { - const leftmostExpr = getLeftmostExpression(expression, /*stopAtCallExpressions*/ true); - switch (leftmostExpr.kind) { - case SyntaxKind.CallExpression: - return createParen(expression); - - case SyntaxKind.NewExpression: - return !(leftmostExpr as NewExpression).arguments - ? createParen(expression) - : expression; - } - - return parenthesizeForAccess(expression); - } - - /** - * Wraps an expression in parentheses if it is needed in order to use the expression for - * property or element access. - * - * @param expr The expression node. - */ - export function parenthesizeForAccess(expression: Expression): LeftHandSideExpression { - // isLeftHandSideExpression is almost the correct criterion for when it is not necessary - // to parenthesize the expression before a dot. The known exception is: - // - // NewExpression: - // new C.x -> not the same as (new C).x - // - const emittedExpression = skipPartiallyEmittedExpressions(expression); - if (isLeftHandSideExpression(emittedExpression) - && (emittedExpression.kind !== SyntaxKind.NewExpression || (emittedExpression).arguments)) { - return expression; - } - - return setTextRange(createParen(expression), expression); } - - export function parenthesizePostfixOperand(operand: Expression) { - return isLeftHandSideExpression(operand) - ? operand - : setTextRange(createParen(operand), operand); - } - - export function parenthesizePrefixOperand(operand: Expression) { - return isUnaryExpression(operand) - ? operand - : setTextRange(createParen(operand), operand); - } - - export function parenthesizeListElements(elements: NodeArray) { - let result: Expression[] | undefined; - for (let i = 0; i < elements.length; i++) { - const element = parenthesizeExpressionForList(elements[i]); - if (result !== undefined || element !== elements[i]) { - if (result === undefined) { - result = elements.slice(0, i); - } - - result.push(element); - } - } - - if (result !== undefined) { - return setTextRange(createNodeArray(result, elements.hasTrailingComma), elements); - } - - return elements; - } - - export function parenthesizeExpressionForList(expression: Expression) { - const emittedExpression = skipPartiallyEmittedExpressions(expression); - const expressionPrecedence = getExpressionPrecedence(emittedExpression); - const commaPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, SyntaxKind.CommaToken); - return expressionPrecedence > commaPrecedence - ? expression - : setTextRange(createParen(expression), expression); - } - - export function parenthesizeExpressionForExpressionStatement(expression: Expression) { - const emittedExpression = skipPartiallyEmittedExpressions(expression); - if (isCallExpression(emittedExpression)) { - const callee = emittedExpression.expression; - const kind = skipPartiallyEmittedExpressions(callee).kind; - if (kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction) { - const mutableCall = getMutableClone(emittedExpression); - mutableCall.expression = setTextRange(createParen(callee), callee); - return recreateOuterExpressions(expression, mutableCall, OuterExpressionKinds.PartiallyEmittedExpressions); +} +/** + * Determines whether a binary operator is mathematically associative. + * + * @param binaryOperator The binary operator. + */ +/* @internal */ +function operatorHasAssociativeProperty(binaryOperator: SyntaxKind) { + // The following operators are associative in JavaScript: + // (a*b)*c -> a*(b*c) -> a*b*c + // (a|b)|c -> a|(b|c) -> a|b|c + // (a&b)&c -> a&(b&c) -> a&b&c + // (a^b)^c -> a^(b^c) -> a^b^c + // + // While addition is associative in mathematics, JavaScript's `+` is not + // guaranteed to be associative as it is overloaded with string concatenation. + return binaryOperator === SyntaxKind.AsteriskToken + || binaryOperator === SyntaxKind.BarToken + || binaryOperator === SyntaxKind.AmpersandToken + || binaryOperator === SyntaxKind.CaretToken; +} +/* @internal */ +interface BinaryPlusExpression extends BinaryExpression { + cachedLiteralKind: SyntaxKind; +} +/** + * This function determines whether an expression consists of a homogeneous set of + * literal expressions or binary plus expressions that all share the same literal kind. + * It is used to determine whether the right-hand operand of a binary plus expression can be + * emitted without parentheses. + */ +/* @internal */ +function getLiteralKindOfBinaryPlusOperand(node: Expression): SyntaxKind { + node = skipPartiallyEmittedExpressions(node); + if (isLiteralKind(node.kind)) { + return node.kind; + } + if (node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.PlusToken) { + if ((node).cachedLiteralKind !== undefined) { + return (node).cachedLiteralKind; + } + const leftKind = getLiteralKindOfBinaryPlusOperand((node).left); + const literalKind = isLiteralKind(leftKind) && leftKind === getLiteralKindOfBinaryPlusOperand((node).right) ? leftKind : + SyntaxKind.Unknown; + (node).cachedLiteralKind = literalKind; + return literalKind; + } + return SyntaxKind.Unknown; +} +/* @internal */ +export function parenthesizeForConditionalHead(condition: Expression) { + const conditionalPrecedence = getOperatorPrecedence(SyntaxKind.ConditionalExpression, SyntaxKind.QuestionToken); + const emittedCondition = skipPartiallyEmittedExpressions(condition); + const conditionPrecedence = getExpressionPrecedence(emittedCondition); + if (compareValues(conditionPrecedence, conditionalPrecedence) !== Comparison.GreaterThan) { + return createParen(condition); + } + return condition; +} +/* @internal */ +export function parenthesizeSubexpressionOfConditionalExpression(e: Expression): Expression { + // per ES grammar both 'whenTrue' and 'whenFalse' parts of conditional expression are assignment expressions + // so in case when comma expression is introduced as a part of previous transformations + // if should be wrapped in parens since comma operator has the lowest precedence + const emittedExpression = skipPartiallyEmittedExpressions(e); + return isCommaSequence(emittedExpression) + ? createParen(e) + : e; +} +/** + * [Per the spec](https://tc39.github.io/ecma262/#prod-ExportDeclaration), `export default` accepts _AssigmentExpression_ but + * has a lookahead restriction for `function`, `async function`, and `class`. + * + * Basically, that means we need to parenthesize in the following cases: + * + * - BinaryExpression of CommaToken + * - CommaList (synthetic list of multiple comma expressions) + * - FunctionExpression + * - ClassExpression + */ +/* @internal */ +export function parenthesizeDefaultExpression(e: Expression) { + const check = skipPartiallyEmittedExpressions(e); + let needsParens = isCommaSequence(check); + if (!needsParens) { + switch (getLeftmostExpression(check, /*stopAtCallExpression*/ false).kind) { + case SyntaxKind.ClassExpression: + case SyntaxKind.FunctionExpression: + needsParens = true; + } + } + return needsParens ? createParen(e) : e; +} +/** + * Wraps an expression in parentheses if it is needed in order to use the expression + * as the expression of a NewExpression node. + * + * @param expression The Expression node. + */ +/* @internal */ +export function parenthesizeForNew(expression: Expression): LeftHandSideExpression { + const leftmostExpr = getLeftmostExpression(expression, /*stopAtCallExpressions*/ true); + switch (leftmostExpr.kind) { + case SyntaxKind.CallExpression: + return createParen(expression); + case SyntaxKind.NewExpression: + return !(leftmostExpr as NewExpression).arguments + ? createParen(expression) + : expression; + } + return parenthesizeForAccess(expression); +} +/** + * Wraps an expression in parentheses if it is needed in order to use the expression for + * property or element access. + * + * @param expr The expression node. + */ +/* @internal */ +export function parenthesizeForAccess(expression: Expression): LeftHandSideExpression { + // isLeftHandSideExpression is almost the correct criterion for when it is not necessary + // to parenthesize the expression before a dot. The known exception is: + // + // NewExpression: + // new C.x -> not the same as (new C).x + // + const emittedExpression = skipPartiallyEmittedExpressions(expression); + if (isLeftHandSideExpression(emittedExpression) + && (emittedExpression.kind !== SyntaxKind.NewExpression || (emittedExpression).arguments)) { + return expression; + } + return setTextRange(createParen(expression), expression); +} +/* @internal */ +export function parenthesizePostfixOperand(operand: Expression) { + return isLeftHandSideExpression(operand) + ? operand + : setTextRange(createParen(operand), operand); +} +/* @internal */ +export function parenthesizePrefixOperand(operand: Expression) { + return isUnaryExpression(operand) + ? operand + : setTextRange(createParen(operand), operand); +} +/* @internal */ +export function parenthesizeListElements(elements: NodeArray) { + let result: Expression[] | undefined; + for (let i = 0; i < elements.length; i++) { + const element = parenthesizeExpressionForList(elements[i]); + if (result !== undefined || element !== elements[i]) { + if (result === undefined) { + result = elements.slice(0, i); } + result.push(element); } - - const leftmostExpressionKind = getLeftmostExpression(emittedExpression, /*stopAtCallExpressions*/ false).kind; - if (leftmostExpressionKind === SyntaxKind.ObjectLiteralExpression || leftmostExpressionKind === SyntaxKind.FunctionExpression) { - return setTextRange(createParen(expression), expression); - } - - return expression; } - - export function parenthesizeConditionalTypeMember(member: TypeNode) { - return member.kind === SyntaxKind.ConditionalType ? createParenthesizedType(member) : member; - } - - export function parenthesizeElementTypeMember(member: TypeNode) { - switch (member.kind) { - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - return createParenthesizedType(member); - } - return parenthesizeConditionalTypeMember(member); - } - - export function parenthesizeArrayTypeMember(member: TypeNode) { - switch (member.kind) { - case SyntaxKind.TypeQuery: - case SyntaxKind.TypeOperator: - case SyntaxKind.InferType: - return createParenthesizedType(member); - } - return parenthesizeElementTypeMember(member); - } - - export function parenthesizeElementTypeMembers(members: readonly TypeNode[]) { - return createNodeArray(sameMap(members, parenthesizeElementTypeMember)); - } - - export function parenthesizeTypeParameters(typeParameters: readonly TypeNode[] | undefined) { - if (some(typeParameters)) { - const params: TypeNode[] = []; - for (let i = 0; i < typeParameters.length; ++i) { - const entry = typeParameters[i]; - params.push(i === 0 && isFunctionOrConstructorTypeNode(entry) && entry.typeParameters ? - createParenthesizedType(entry) : - entry); - } - - return createNodeArray(params); - } + if (result !== undefined) { + return setTextRange(createNodeArray(result, elements.hasTrailingComma), elements); } - - export function getLeftmostExpression(node: Expression, stopAtCallExpressions: boolean) { - while (true) { - switch (node.kind) { - case SyntaxKind.PostfixUnaryExpression: - node = (node).operand; - continue; - - case SyntaxKind.BinaryExpression: - node = (node).left; - continue; - - case SyntaxKind.ConditionalExpression: - node = (node).condition; - continue; - - case SyntaxKind.TaggedTemplateExpression: - node = (node).tag; - continue; - - case SyntaxKind.CallExpression: - if (stopAtCallExpressions) { - return node; - } - // falls through - case SyntaxKind.AsExpression: - case SyntaxKind.ElementAccessExpression: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.NonNullExpression: - case SyntaxKind.PartiallyEmittedExpression: - node = (node).expression; - continue; - } - - return node; - } - + return elements; +} +/* @internal */ +export function parenthesizeExpressionForList(expression: Expression) { + const emittedExpression = skipPartiallyEmittedExpressions(expression); + const expressionPrecedence = getExpressionPrecedence(emittedExpression); + const commaPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, SyntaxKind.CommaToken); + return expressionPrecedence > commaPrecedence + ? expression + : setTextRange(createParen(expression), expression); +} +/* @internal */ +export function parenthesizeExpressionForExpressionStatement(expression: Expression) { + const emittedExpression = skipPartiallyEmittedExpressions(expression); + if (isCallExpression(emittedExpression)) { + const callee = emittedExpression.expression; + const kind = skipPartiallyEmittedExpressions(callee).kind; + if (kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction) { + const mutableCall = getMutableClone(emittedExpression); + mutableCall.expression = setTextRange(createParen(callee), callee); + return recreateOuterExpressions(expression, mutableCall, OuterExpressionKinds.PartiallyEmittedExpressions); + } + } + const leftmostExpressionKind = getLeftmostExpression(emittedExpression, /*stopAtCallExpressions*/ false).kind; + if (leftmostExpressionKind === SyntaxKind.ObjectLiteralExpression || leftmostExpressionKind === SyntaxKind.FunctionExpression) { + return setTextRange(createParen(expression), expression); } - - export function parenthesizeConciseBody(body: ConciseBody): ConciseBody { - if (!isBlock(body) && (isCommaSequence(body) || getLeftmostExpression(body, /*stopAtCallExpressions*/ false).kind === SyntaxKind.ObjectLiteralExpression)) { - return setTextRange(createParen(body), body); - } - - return body; - } - - export function isCommaSequence(node: Expression): node is BinaryExpression & {operatorToken: Token} | CommaListExpression { - return node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.CommaToken || - node.kind === SyntaxKind.CommaListExpression; - } - - export const enum OuterExpressionKinds { - Parentheses = 1 << 0, - Assertions = 1 << 1, - PartiallyEmittedExpressions = 1 << 2, - - All = Parentheses | Assertions | PartiallyEmittedExpressions - } - - export type OuterExpression = ParenthesizedExpression | TypeAssertion | AsExpression | NonNullExpression | PartiallyEmittedExpression; - - export function isOuterExpression(node: Node, kinds = OuterExpressionKinds.All): node is OuterExpression { + return expression; +} +/* @internal */ +export function parenthesizeConditionalTypeMember(member: TypeNode) { + return member.kind === SyntaxKind.ConditionalType ? createParenthesizedType(member) : member; +} +/* @internal */ +export function parenthesizeElementTypeMember(member: TypeNode) { + switch (member.kind) { + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + return createParenthesizedType(member); + } + return parenthesizeConditionalTypeMember(member); +} +/* @internal */ +export function parenthesizeArrayTypeMember(member: TypeNode) { + switch (member.kind) { + case SyntaxKind.TypeQuery: + case SyntaxKind.TypeOperator: + case SyntaxKind.InferType: + return createParenthesizedType(member); + } + return parenthesizeElementTypeMember(member); +} +/* @internal */ +export function parenthesizeElementTypeMembers(members: readonly TypeNode[]) { + return createNodeArray(sameMap(members, parenthesizeElementTypeMember)); +} +/* @internal */ +export function parenthesizeTypeParameters(typeParameters: readonly TypeNode[] | undefined) { + if (some(typeParameters)) { + const params: TypeNode[] = []; + for (let i = 0; i < typeParameters.length; ++i) { + const entry = typeParameters[i]; + params.push(i === 0 && isFunctionOrConstructorTypeNode(entry) && entry.typeParameters ? + createParenthesizedType(entry) : + entry); + } + return createNodeArray(params); + } +} +/* @internal */ +export function getLeftmostExpression(node: Expression, stopAtCallExpressions: boolean) { + while (true) { switch (node.kind) { - case SyntaxKind.ParenthesizedExpression: - return (kinds & OuterExpressionKinds.Parentheses) !== 0; - case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.PostfixUnaryExpression: + node = (node).operand; + continue; + case SyntaxKind.BinaryExpression: + node = (node).left; + continue; + case SyntaxKind.ConditionalExpression: + node = (node).condition; + continue; + case SyntaxKind.TaggedTemplateExpression: + node = (node).tag; + continue; + case SyntaxKind.CallExpression: + if (stopAtCallExpressions) { + return node; + } + // falls through case SyntaxKind.AsExpression: + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.PropertyAccessExpression: case SyntaxKind.NonNullExpression: - return (kinds & OuterExpressionKinds.Assertions) !== 0; case SyntaxKind.PartiallyEmittedExpression: - return (kinds & OuterExpressionKinds.PartiallyEmittedExpressions) !== 0; + node = (node).expression; + continue; } - return false; - } - - export function skipOuterExpressions(node: Expression, kinds?: OuterExpressionKinds): Expression; - export function skipOuterExpressions(node: Node, kinds?: OuterExpressionKinds): Node; - export function skipOuterExpressions(node: Node, kinds = OuterExpressionKinds.All) { - let previousNode: Node; - do { - previousNode = node; - if (kinds & OuterExpressionKinds.Parentheses) { - node = skipParentheses(node); - } - - if (kinds & OuterExpressionKinds.Assertions) { - node = skipAssertions(node); - } - - if (kinds & OuterExpressionKinds.PartiallyEmittedExpressions) { - node = skipPartiallyEmittedExpressions(node); - } - } - while (previousNode !== node); - return node; } - - export function skipAssertions(node: Expression): Expression; - export function skipAssertions(node: Node): Node; - export function skipAssertions(node: Node): Node { - while (isAssertionExpression(node) || node.kind === SyntaxKind.NonNullExpression) { - node = (node).expression; - } - - return node; +} +/* @internal */ +export function parenthesizeConciseBody(body: ConciseBody): ConciseBody { + if (!isBlock(body) && (isCommaSequence(body) || getLeftmostExpression(body, /*stopAtCallExpressions*/ false).kind === SyntaxKind.ObjectLiteralExpression)) { + return setTextRange(createParen(body), body); } - - function updateOuterExpression(outerExpression: OuterExpression, expression: Expression) { - switch (outerExpression.kind) { - case SyntaxKind.ParenthesizedExpression: return updateParen(outerExpression, expression); - case SyntaxKind.TypeAssertionExpression: return updateTypeAssertion(outerExpression, outerExpression.type, expression); - case SyntaxKind.AsExpression: return updateAsExpression(outerExpression, expression, outerExpression.type); - case SyntaxKind.NonNullExpression: return updateNonNullExpression(outerExpression, expression); - case SyntaxKind.PartiallyEmittedExpression: return updatePartiallyEmittedExpression(outerExpression, expression); - } + return body; +} +/* @internal */ +export function isCommaSequence(node: Expression): node is (BinaryExpression & { + operatorToken: Token; +}) | CommaListExpression { + return node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.CommaToken || + node.kind === SyntaxKind.CommaListExpression; +} +/* @internal */ +export const enum OuterExpressionKinds { + Parentheses = 1 << 0, + Assertions = 1 << 1, + PartiallyEmittedExpressions = 1 << 2, + All = Parentheses | Assertions | PartiallyEmittedExpressions +} +/* @internal */ +export type OuterExpression = ParenthesizedExpression | TypeAssertion | AsExpression | NonNullExpression | PartiallyEmittedExpression; +/* @internal */ +export function isOuterExpression(node: Node, kinds = OuterExpressionKinds.All): node is OuterExpression { + switch (node.kind) { + case SyntaxKind.ParenthesizedExpression: + return (kinds & OuterExpressionKinds.Parentheses) !== 0; + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + case SyntaxKind.NonNullExpression: + return (kinds & OuterExpressionKinds.Assertions) !== 0; + case SyntaxKind.PartiallyEmittedExpression: + return (kinds & OuterExpressionKinds.PartiallyEmittedExpressions) !== 0; + } + return false; +} +/* @internal */ +export function skipOuterExpressions(node: Expression, kinds?: OuterExpressionKinds): Expression; +/* @internal */ +export function skipOuterExpressions(node: Node, kinds?: OuterExpressionKinds): Node; +/* @internal */ +export function skipOuterExpressions(node: Node, kinds = OuterExpressionKinds.All) { + let previousNode: Node; + do { + previousNode = node; + if (kinds & OuterExpressionKinds.Parentheses) { + node = skipParentheses(node); + } + if (kinds & OuterExpressionKinds.Assertions) { + node = skipAssertions(node); + } + if (kinds & OuterExpressionKinds.PartiallyEmittedExpressions) { + node = skipPartiallyEmittedExpressions(node); + } + } while (previousNode !== node); + return node; +} +/* @internal */ +export function skipAssertions(node: Expression): Expression; +/* @internal */ +export function skipAssertions(node: Node): Node; +/* @internal */ +export function skipAssertions(node: Node): Node { + while (isAssertionExpression(node) || node.kind === SyntaxKind.NonNullExpression) { + node = (node).expression; } - - /** - * Determines whether a node is a parenthesized expression that can be ignored when recreating outer expressions. - * - * A parenthesized expression can be ignored when all of the following are true: - * - * - It's `pos` and `end` are not -1 - * - It does not have a custom source map range - * - It does not have a custom comment range - * - It does not have synthetic leading or trailing comments - * - * If an outermost parenthesized expression is ignored, but the containing expression requires a parentheses around - * the expression to maintain precedence, a new parenthesized expression should be created automatically when - * the containing expression is created/updated. - */ - function isIgnorableParen(node: Expression) { - return node.kind === SyntaxKind.ParenthesizedExpression - && nodeIsSynthesized(node) - && nodeIsSynthesized(getSourceMapRange(node)) - && nodeIsSynthesized(getCommentRange(node)) - && !some(getSyntheticLeadingComments(node)) - && !some(getSyntheticTrailingComments(node)); - } - - export function recreateOuterExpressions(outerExpression: Expression | undefined, innerExpression: Expression, kinds = OuterExpressionKinds.All): Expression { - if (outerExpression && isOuterExpression(outerExpression, kinds) && !isIgnorableParen(outerExpression)) { - return updateOuterExpression( - outerExpression, - recreateOuterExpressions(outerExpression.expression, innerExpression) - ); - } - return innerExpression; - } - - export function startOnNewLine(node: T): T { - return setStartsOnNewLine(node, /*newLine*/ true); - } - - export function getExternalHelpersModuleName(node: SourceFile) { - const parseNode = getOriginalNode(node, isSourceFile); - const emitNode = parseNode && parseNode.emitNode; - return emitNode && emitNode.externalHelpersModuleName; - } - - export function hasRecordedExternalHelpers(sourceFile: SourceFile) { - const parseNode = getOriginalNode(sourceFile, isSourceFile); - const emitNode = parseNode && parseNode.emitNode; - return !!emitNode && (!!emitNode.externalHelpersModuleName || !!emitNode.externalHelpers); - } - - export function createExternalHelpersImportDeclarationIfNeeded(sourceFile: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStar?: boolean, hasImportDefault?: boolean) { - if (compilerOptions.importHelpers && isEffectiveExternalModule(sourceFile, compilerOptions)) { - let namedBindings: NamedImportBindings | undefined; - const moduleKind = getEmitModuleKind(compilerOptions); - if (moduleKind >= ModuleKind.ES2015 && moduleKind <= ModuleKind.ESNext) { - // use named imports - const helpers = getEmitHelpers(sourceFile); - if (helpers) { - const helperNames: string[] = []; - for (const helper of helpers) { - if (!helper.scoped) { - const importName = (helper as UnscopedEmitHelper).importName; - if (importName) { - pushIfUnique(helperNames, importName); - } + return node; +} +/* @internal */ +function updateOuterExpression(outerExpression: OuterExpression, expression: Expression) { + switch (outerExpression.kind) { + case SyntaxKind.ParenthesizedExpression: return updateParen(outerExpression, expression); + case SyntaxKind.TypeAssertionExpression: return updateTypeAssertion(outerExpression, outerExpression.type, expression); + case SyntaxKind.AsExpression: return updateAsExpression(outerExpression, expression, outerExpression.type); + case SyntaxKind.NonNullExpression: return updateNonNullExpression(outerExpression, expression); + case SyntaxKind.PartiallyEmittedExpression: return updatePartiallyEmittedExpression(outerExpression, expression); + } +} +/** + * Determines whether a node is a parenthesized expression that can be ignored when recreating outer expressions. + * + * A parenthesized expression can be ignored when all of the following are true: + * + * - It's `pos` and `end` are not -1 + * - It does not have a custom source map range + * - It does not have a custom comment range + * - It does not have synthetic leading or trailing comments + * + * If an outermost parenthesized expression is ignored, but the containing expression requires a parentheses around + * the expression to maintain precedence, a new parenthesized expression should be created automatically when + * the containing expression is created/updated. + */ +/* @internal */ +function isIgnorableParen(node: Expression) { + return node.kind === SyntaxKind.ParenthesizedExpression + && nodeIsSynthesized(node) + && nodeIsSynthesized(getSourceMapRange(node)) + && nodeIsSynthesized(getCommentRange(node)) + && !some(getSyntheticLeadingComments(node)) + && !some(getSyntheticTrailingComments(node)); +} +/* @internal */ +export function recreateOuterExpressions(outerExpression: Expression | undefined, innerExpression: Expression, kinds = OuterExpressionKinds.All): Expression { + if (outerExpression && isOuterExpression(outerExpression, kinds) && !isIgnorableParen(outerExpression)) { + return updateOuterExpression(outerExpression, recreateOuterExpressions(outerExpression.expression, innerExpression)); + } + return innerExpression; +} +/* @internal */ +export function startOnNewLine(node: T): T { + return setStartsOnNewLine(node, /*newLine*/ true); +} +/* @internal */ +export function getExternalHelpersModuleName(node: SourceFile) { + const parseNode = getOriginalNode(node, isSourceFile); + const emitNode = parseNode && parseNode.emitNode; + return emitNode && emitNode.externalHelpersModuleName; +} +/* @internal */ +export function hasRecordedExternalHelpers(sourceFile: SourceFile) { + const parseNode = getOriginalNode(sourceFile, isSourceFile); + const emitNode = parseNode && parseNode.emitNode; + return !!emitNode && (!!emitNode.externalHelpersModuleName || !!emitNode.externalHelpers); +} +/* @internal */ +export function createExternalHelpersImportDeclarationIfNeeded(sourceFile: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStar?: boolean, hasImportDefault?: boolean) { + if (compilerOptions.importHelpers && isEffectiveExternalModule(sourceFile, compilerOptions)) { + let namedBindings: NamedImportBindings | undefined; + const moduleKind = getEmitModuleKind(compilerOptions); + if (moduleKind >= ModuleKind.ES2015 && moduleKind <= ModuleKind.ESNext) { + // use named imports + const helpers = getEmitHelpers(sourceFile); + if (helpers) { + const helperNames: string[] = []; + for (const helper of helpers) { + if (!helper.scoped) { + const importName = (helper as UnscopedEmitHelper).importName; + if (importName) { + pushIfUnique(helperNames, importName); } } - if (some(helperNames)) { - helperNames.sort(compareStringsCaseSensitive); - // Alias the imports if the names are used somewhere in the file. - // NOTE: We don't need to care about global import collisions as this is a module. - namedBindings = createNamedImports( - map(helperNames, name => isFileLevelUniqueName(sourceFile, name) - ? createImportSpecifier(/*propertyName*/ undefined, createIdentifier(name)) - : createImportSpecifier(createIdentifier(name), getUnscopedHelperName(name)) - ) - ); - const parseNode = getOriginalNode(sourceFile, isSourceFile); - const emitNode = getOrCreateEmitNode(parseNode); - emitNode.externalHelpers = true; - } } - } - else { - // use a namespace import - const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(sourceFile, compilerOptions, hasExportStarsToExportValues, hasImportStar || hasImportDefault); - if (externalHelpersModuleName) { - namedBindings = createNamespaceImport(externalHelpersModuleName); + if (some(helperNames)) { + helperNames.sort(compareStringsCaseSensitive); + // Alias the imports if the names are used somewhere in the file. + // NOTE: We don't need to care about global import collisions as this is a module. + namedBindings = createNamedImports(map(helperNames, name => isFileLevelUniqueName(sourceFile, name) + ? createImportSpecifier(/*propertyName*/ undefined, createIdentifier(name)) + : createImportSpecifier(createIdentifier(name), getUnscopedHelperName(name)))); + const parseNode = getOriginalNode(sourceFile, isSourceFile); + const emitNode = getOrCreateEmitNode(parseNode); + emitNode.externalHelpers = true; } } - if (namedBindings) { - const externalHelpersImportDeclaration = createImportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - createImportClause(/*name*/ undefined, namedBindings), - createLiteral(externalHelpersModuleNameText) - ); - addEmitFlags(externalHelpersImportDeclaration, EmitFlags.NeverApplyImportHelper); - return externalHelpersImportDeclaration; - } } - } - - export function getOrCreateExternalHelpersModuleNameIfNeeded(node: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStarOrImportDefault?: boolean) { - if (compilerOptions.importHelpers && isEffectiveExternalModule(node, compilerOptions)) { - const externalHelpersModuleName = getExternalHelpersModuleName(node); + else { + // use a namespace import + const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(sourceFile, compilerOptions, hasExportStarsToExportValues, hasImportStar || hasImportDefault); if (externalHelpersModuleName) { - return externalHelpersModuleName; + namedBindings = createNamespaceImport(externalHelpersModuleName); } - - const moduleKind = getEmitModuleKind(compilerOptions); - let create = (hasExportStarsToExportValues || (compilerOptions.esModuleInterop && hasImportStarOrImportDefault)) - && moduleKind !== ModuleKind.System - && moduleKind !== ModuleKind.ES2015 - && moduleKind !== ModuleKind.ESNext; - if (!create) { - const helpers = getEmitHelpers(node); - if (helpers) { - for (const helper of helpers) { - if (!helper.scoped) { - create = true; - break; - } + } + if (namedBindings) { + const externalHelpersImportDeclaration = createImportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, createImportClause(/*name*/ undefined, namedBindings), createLiteral(externalHelpersModuleNameText)); + addEmitFlags(externalHelpersImportDeclaration, EmitFlags.NeverApplyImportHelper); + return externalHelpersImportDeclaration; + } + } +} +/* @internal */ +export function getOrCreateExternalHelpersModuleNameIfNeeded(node: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStarOrImportDefault?: boolean) { + if (compilerOptions.importHelpers && isEffectiveExternalModule(node, compilerOptions)) { + const externalHelpersModuleName = getExternalHelpersModuleName(node); + if (externalHelpersModuleName) { + return externalHelpersModuleName; + } + const moduleKind = getEmitModuleKind(compilerOptions); + let create = (hasExportStarsToExportValues || (compilerOptions.esModuleInterop && hasImportStarOrImportDefault)) + && moduleKind !== ModuleKind.System + && moduleKind !== ModuleKind.ES2015 + && moduleKind !== ModuleKind.ESNext; + if (!create) { + const helpers = getEmitHelpers(node); + if (helpers) { + for (const helper of helpers) { + if (!helper.scoped) { + create = true; + break; } } } - - if (create) { - const parseNode = getOriginalNode(node, isSourceFile); - const emitNode = getOrCreateEmitNode(parseNode); - return emitNode.externalHelpersModuleName || (emitNode.externalHelpersModuleName = createUniqueName(externalHelpersModuleNameText)); - } - } - } - - /** - * Get the name of that target module from an import or export declaration - */ - export function getLocalNameForExternalImport(node: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration, sourceFile: SourceFile): Identifier | undefined { - const namespaceDeclaration = getNamespaceDeclarationNode(node); - if (namespaceDeclaration && !isDefaultImport(node)) { - const name = namespaceDeclaration.name; - return isGeneratedIdentifier(name) ? name : createIdentifier(getSourceTextOfNodeFromSourceFile(sourceFile, name) || idText(name)); } - if (node.kind === SyntaxKind.ImportDeclaration && node.importClause) { - return getGeneratedNameForNode(node); + if (create) { + const parseNode = getOriginalNode(node, isSourceFile); + const emitNode = getOrCreateEmitNode(parseNode); + return emitNode.externalHelpersModuleName || (emitNode.externalHelpersModuleName = createUniqueName(externalHelpersModuleNameText)); } - if (node.kind === SyntaxKind.ExportDeclaration && node.moduleSpecifier) { - return getGeneratedNameForNode(node); - } - return undefined; } - - /** - * Get the name of a target module from an import/export declaration as should be written in the emitted output. - * The emitted output name can be different from the input if: - * 1. The module has a /// - * 2. --out or --outFile is used, making the name relative to the rootDir - * 3- The containing SourceFile has an entry in renamedDependencies for the import as requested by some module loaders (e.g. System). - * Otherwise, a new StringLiteral node representing the module name will be returned. - */ - export function getExternalModuleNameLiteral(importNode: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration, sourceFile: SourceFile, host: EmitHost, resolver: EmitResolver, compilerOptions: CompilerOptions) { - const moduleName = getExternalModuleName(importNode)!; // TODO: GH#18217 - if (moduleName.kind === SyntaxKind.StringLiteral) { - return tryGetModuleNameFromDeclaration(importNode, host, resolver, compilerOptions) - || tryRenameExternalModule(moduleName, sourceFile) - || getSynthesizedClone(moduleName); - } - - return undefined; +} +/** + * Get the name of that target module from an import or export declaration + */ +/* @internal */ +export function getLocalNameForExternalImport(node: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration, sourceFile: SourceFile): Identifier | undefined { + const namespaceDeclaration = getNamespaceDeclarationNode(node); + if (namespaceDeclaration && !isDefaultImport(node)) { + const name = namespaceDeclaration.name; + return isGeneratedIdentifier(name) ? name : createIdentifier(getSourceTextOfNodeFromSourceFile(sourceFile, name) || idText(name)); } - - /** - * Some bundlers (SystemJS builder) sometimes want to rename dependencies. - * Here we check if alternative name was provided for a given moduleName and return it if possible. - */ - function tryRenameExternalModule(moduleName: LiteralExpression, sourceFile: SourceFile) { - const rename = sourceFile.renamedDependencies && sourceFile.renamedDependencies.get(moduleName.text); - return rename && createLiteral(rename); - } - - /** - * Get the name of a module as should be written in the emitted output. - * The emitted output name can be different from the input if: - * 1. The module has a /// - * 2. --out or --outFile is used, making the name relative to the rootDir - * Otherwise, a new StringLiteral node representing the module name will be returned. - */ - export function tryGetModuleNameFromFile(file: SourceFile | undefined, host: EmitHost, options: CompilerOptions): StringLiteral | undefined { - if (!file) { - return undefined; - } - if (file.moduleName) { - return createLiteral(file.moduleName); - } - if (!file.isDeclarationFile && (options.out || options.outFile)) { - return createLiteral(getExternalModuleNameFromPath(host, file.fileName)); - } - return undefined; + if (node.kind === SyntaxKind.ImportDeclaration && node.importClause) { + return getGeneratedNameForNode(node); } - - function tryGetModuleNameFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration, host: EmitHost, resolver: EmitResolver, compilerOptions: CompilerOptions) { - return tryGetModuleNameFromFile(resolver.getExternalModuleFileFromDeclaration(declaration), host, compilerOptions); - } - - /** - * Gets the initializer of an BindingOrAssignmentElement. - */ - export function getInitializerOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): Expression | undefined { - if (isDeclarationBindingElement(bindingElement)) { - // `1` in `let { a = 1 } = ...` - // `1` in `let { a: b = 1 } = ...` - // `1` in `let { a: {b} = 1 } = ...` - // `1` in `let { a: [b] = 1 } = ...` - // `1` in `let [a = 1] = ...` - // `1` in `let [{a} = 1] = ...` - // `1` in `let [[a] = 1] = ...` - return bindingElement.initializer; - } - - if (isPropertyAssignment(bindingElement)) { - // `1` in `({ a: b = 1 } = ...)` - // `1` in `({ a: {b} = 1 } = ...)` - // `1` in `({ a: [b] = 1 } = ...)` - const initializer = bindingElement.initializer; - return isAssignmentExpression(initializer, /*excludeCompoundAssignment*/ true) - ? initializer.right - : undefined; - } - - if (isShorthandPropertyAssignment(bindingElement)) { - // `1` in `({ a = 1 } = ...)` - return bindingElement.objectAssignmentInitializer; - } - - if (isAssignmentExpression(bindingElement, /*excludeCompoundAssignment*/ true)) { - // `1` in `[a = 1] = ...` - // `1` in `[{a} = 1] = ...` - // `1` in `[[a] = 1] = ...` - return bindingElement.right; - } - - if (isSpreadElement(bindingElement)) { - // Recovery consistent with existing emit. - return getInitializerOfBindingOrAssignmentElement(bindingElement.expression); - } + if (node.kind === SyntaxKind.ExportDeclaration && node.moduleSpecifier) { + return getGeneratedNameForNode(node); } - - /** - * Gets the name of an BindingOrAssignmentElement. - */ - export function getTargetOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): BindingOrAssignmentElementTarget | undefined { - if (isDeclarationBindingElement(bindingElement)) { - // `a` in `let { a } = ...` - // `a` in `let { a = 1 } = ...` - // `b` in `let { a: b } = ...` - // `b` in `let { a: b = 1 } = ...` - // `a` in `let { ...a } = ...` - // `{b}` in `let { a: {b} } = ...` - // `{b}` in `let { a: {b} = 1 } = ...` - // `[b]` in `let { a: [b] } = ...` - // `[b]` in `let { a: [b] = 1 } = ...` - // `a` in `let [a] = ...` - // `a` in `let [a = 1] = ...` - // `a` in `let [...a] = ...` - // `{a}` in `let [{a}] = ...` - // `{a}` in `let [{a} = 1] = ...` - // `[a]` in `let [[a]] = ...` - // `[a]` in `let [[a] = 1] = ...` - return bindingElement.name; - } - - if (isObjectLiteralElementLike(bindingElement)) { - switch (bindingElement.kind) { - case SyntaxKind.PropertyAssignment: - // `b` in `({ a: b } = ...)` - // `b` in `({ a: b = 1 } = ...)` - // `{b}` in `({ a: {b} } = ...)` - // `{b}` in `({ a: {b} = 1 } = ...)` - // `[b]` in `({ a: [b] } = ...)` - // `[b]` in `({ a: [b] = 1 } = ...)` - // `b.c` in `({ a: b.c } = ...)` - // `b.c` in `({ a: b.c = 1 } = ...)` - // `b[0]` in `({ a: b[0] } = ...)` - // `b[0]` in `({ a: b[0] = 1 } = ...)` - return getTargetOfBindingOrAssignmentElement(bindingElement.initializer); - - case SyntaxKind.ShorthandPropertyAssignment: - // `a` in `({ a } = ...)` - // `a` in `({ a = 1 } = ...)` - return bindingElement.name; - - case SyntaxKind.SpreadAssignment: - // `a` in `({ ...a } = ...)` - return getTargetOfBindingOrAssignmentElement(bindingElement.expression); - } - - // no target - return undefined; - } - - if (isAssignmentExpression(bindingElement, /*excludeCompoundAssignment*/ true)) { - // `a` in `[a = 1] = ...` - // `{a}` in `[{a} = 1] = ...` - // `[a]` in `[[a] = 1] = ...` - // `a.b` in `[a.b = 1] = ...` - // `a[0]` in `[a[0] = 1] = ...` - return getTargetOfBindingOrAssignmentElement(bindingElement.left); - } - - if (isSpreadElement(bindingElement)) { - // `a` in `[...a] = ...` - return getTargetOfBindingOrAssignmentElement(bindingElement.expression); - } - - // `a` in `[a] = ...` - // `{a}` in `[{a}] = ...` - // `[a]` in `[[a]] = ...` - // `a.b` in `[a.b] = ...` - // `a[0]` in `[a[0]] = ...` - return bindingElement; - } - - /** - * Determines whether an BindingOrAssignmentElement is a rest element. - */ - export function getRestIndicatorOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): BindingOrAssignmentElementRestIndicator | undefined { - switch (bindingElement.kind) { - case SyntaxKind.Parameter: - case SyntaxKind.BindingElement: - // `...` in `let [...a] = ...` - return bindingElement.dotDotDotToken; - - case SyntaxKind.SpreadElement: - case SyntaxKind.SpreadAssignment: - // `...` in `[...a] = ...` - return bindingElement; - } - + return undefined; +} +/** + * Get the name of a target module from an import/export declaration as should be written in the emitted output. + * The emitted output name can be different from the input if: + * 1. The module has a /// + * 2. --out or --outFile is used, making the name relative to the rootDir + * 3- The containing SourceFile has an entry in renamedDependencies for the import as requested by some module loaders (e.g. System). + * Otherwise, a new StringLiteral node representing the module name will be returned. + */ +/* @internal */ +export function getExternalModuleNameLiteral(importNode: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration, sourceFile: SourceFile, host: EmitHost, resolver: EmitResolver, compilerOptions: CompilerOptions) { + const moduleName = (getExternalModuleName(importNode)!); // TODO: GH#18217 + if (moduleName.kind === SyntaxKind.StringLiteral) { + return tryGetModuleNameFromDeclaration(importNode, host, resolver, compilerOptions) + || tryRenameExternalModule((moduleName), sourceFile) + || getSynthesizedClone((moduleName)); + } + return undefined; +} +/** + * Some bundlers (SystemJS builder) sometimes want to rename dependencies. + * Here we check if alternative name was provided for a given moduleName and return it if possible. + */ +/* @internal */ +function tryRenameExternalModule(moduleName: LiteralExpression, sourceFile: SourceFile) { + const rename = sourceFile.renamedDependencies && sourceFile.renamedDependencies.get(moduleName.text); + return rename && createLiteral(rename); +} +/** + * Get the name of a module as should be written in the emitted output. + * The emitted output name can be different from the input if: + * 1. The module has a /// + * 2. --out or --outFile is used, making the name relative to the rootDir + * Otherwise, a new StringLiteral node representing the module name will be returned. + */ +/* @internal */ +export function tryGetModuleNameFromFile(file: SourceFile | undefined, host: EmitHost, options: CompilerOptions): StringLiteral | undefined { + if (!file) { return undefined; } - - /** - * Gets the property name of a BindingOrAssignmentElement - */ - export function getPropertyNameOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): PropertyName | undefined { - const propertyName = tryGetPropertyNameOfBindingOrAssignmentElement(bindingElement); - Debug.assert(!!propertyName || isSpreadAssignment(bindingElement), "Invalid property name for binding element."); - return propertyName; - } - - export function tryGetPropertyNameOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): PropertyName | undefined { + if (file.moduleName) { + return createLiteral(file.moduleName); + } + if (!file.isDeclarationFile && (options.out || options.outFile)) { + return createLiteral(getExternalModuleNameFromPath(host, file.fileName)); + } + return undefined; +} +/* @internal */ +function tryGetModuleNameFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration, host: EmitHost, resolver: EmitResolver, compilerOptions: CompilerOptions) { + return tryGetModuleNameFromFile(resolver.getExternalModuleFileFromDeclaration(declaration), host, compilerOptions); +} +/** + * Gets the initializer of an BindingOrAssignmentElement. + */ +/* @internal */ +export function getInitializerOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): Expression | undefined { + if (isDeclarationBindingElement(bindingElement)) { + // `1` in `let { a = 1 } = ...` + // `1` in `let { a: b = 1 } = ...` + // `1` in `let { a: {b} = 1 } = ...` + // `1` in `let { a: [b] = 1 } = ...` + // `1` in `let [a = 1] = ...` + // `1` in `let [{a} = 1] = ...` + // `1` in `let [[a] = 1] = ...` + return bindingElement.initializer; + } + if (isPropertyAssignment(bindingElement)) { + // `1` in `({ a: b = 1 } = ...)` + // `1` in `({ a: {b} = 1 } = ...)` + // `1` in `({ a: [b] = 1 } = ...)` + const initializer = bindingElement.initializer; + return isAssignmentExpression(initializer, /*excludeCompoundAssignment*/ true) + ? initializer.right + : undefined; + } + if (isShorthandPropertyAssignment(bindingElement)) { + // `1` in `({ a = 1 } = ...)` + return bindingElement.objectAssignmentInitializer; + } + if (isAssignmentExpression(bindingElement, /*excludeCompoundAssignment*/ true)) { + // `1` in `[a = 1] = ...` + // `1` in `[{a} = 1] = ...` + // `1` in `[[a] = 1] = ...` + return bindingElement.right; + } + if (isSpreadElement(bindingElement)) { + // Recovery consistent with existing emit. + return getInitializerOfBindingOrAssignmentElement((bindingElement.expression)); + } +} +/** + * Gets the name of an BindingOrAssignmentElement. + */ +/* @internal */ +export function getTargetOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): BindingOrAssignmentElementTarget | undefined { + if (isDeclarationBindingElement(bindingElement)) { + // `a` in `let { a } = ...` + // `a` in `let { a = 1 } = ...` + // `b` in `let { a: b } = ...` + // `b` in `let { a: b = 1 } = ...` + // `a` in `let { ...a } = ...` + // `{b}` in `let { a: {b} } = ...` + // `{b}` in `let { a: {b} = 1 } = ...` + // `[b]` in `let { a: [b] } = ...` + // `[b]` in `let { a: [b] = 1 } = ...` + // `a` in `let [a] = ...` + // `a` in `let [a = 1] = ...` + // `a` in `let [...a] = ...` + // `{a}` in `let [{a}] = ...` + // `{a}` in `let [{a} = 1] = ...` + // `[a]` in `let [[a]] = ...` + // `[a]` in `let [[a] = 1] = ...` + return bindingElement.name; + } + if (isObjectLiteralElementLike(bindingElement)) { switch (bindingElement.kind) { - case SyntaxKind.BindingElement: - // `a` in `let { a: b } = ...` - // `[a]` in `let { [a]: b } = ...` - // `"a"` in `let { "a": b } = ...` - // `1` in `let { 1: b } = ...` - if (bindingElement.propertyName) { - const propertyName = bindingElement.propertyName; - return isComputedPropertyName(propertyName) && isStringOrNumericLiteral(propertyName.expression) - ? propertyName.expression - : propertyName; - } - - break; - case SyntaxKind.PropertyAssignment: - // `a` in `({ a: b } = ...)` - // `[a]` in `({ [a]: b } = ...)` - // `"a"` in `({ "a": b } = ...)` - // `1` in `({ 1: b } = ...)` - if (bindingElement.name) { - const propertyName = bindingElement.name; - return isComputedPropertyName(propertyName) && isStringOrNumericLiteral(propertyName.expression) - ? propertyName.expression - : propertyName; - } - - break; - + // `b` in `({ a: b } = ...)` + // `b` in `({ a: b = 1 } = ...)` + // `{b}` in `({ a: {b} } = ...)` + // `{b}` in `({ a: {b} = 1 } = ...)` + // `[b]` in `({ a: [b] } = ...)` + // `[b]` in `({ a: [b] = 1 } = ...)` + // `b.c` in `({ a: b.c } = ...)` + // `b.c` in `({ a: b.c = 1 } = ...)` + // `b[0]` in `({ a: b[0] } = ...)` + // `b[0]` in `({ a: b[0] = 1 } = ...)` + return getTargetOfBindingOrAssignmentElement((bindingElement.initializer)); + case SyntaxKind.ShorthandPropertyAssignment: + // `a` in `({ a } = ...)` + // `a` in `({ a = 1 } = ...)` + return bindingElement.name; case SyntaxKind.SpreadAssignment: // `a` in `({ ...a } = ...)` - return bindingElement.name; - } - - const target = getTargetOfBindingOrAssignmentElement(bindingElement); - if (target && isPropertyName(target)) { - return isComputedPropertyName(target) && isStringOrNumericLiteral(target.expression) - ? target.expression - : target; - } - } - - function isStringOrNumericLiteral(node: Node): node is StringLiteral | NumericLiteral { - const kind = node.kind; - return kind === SyntaxKind.StringLiteral - || kind === SyntaxKind.NumericLiteral; - } - - /** - * Gets the elements of a BindingOrAssignmentPattern - */ - export function getElementsOfBindingOrAssignmentPattern(name: BindingOrAssignmentPattern): readonly BindingOrAssignmentElement[] { - switch (name.kind) { - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.ArrayLiteralExpression: - // `a` in `{a}` - // `a` in `[a]` - return name.elements; - - case SyntaxKind.ObjectLiteralExpression: - // `a` in `{a}` - return name.properties; + return getTargetOfBindingOrAssignmentElement((bindingElement.expression)); } + // no target + return undefined; } - - export function convertToArrayAssignmentElement(element: BindingOrAssignmentElement) { - if (isBindingElement(element)) { - if (element.dotDotDotToken) { - Debug.assertNode(element.name, isIdentifier); - return setOriginalNode(setTextRange(createSpread(element.name), element), element); - } - const expression = convertToAssignmentElementTarget(element.name); - return element.initializer - ? setOriginalNode( - setTextRange( - createAssignment(expression, element.initializer), - element - ), - element - ) - : expression; - } - Debug.assertNode(element, isExpression); - return element; - } - - export function convertToObjectAssignmentElement(element: BindingOrAssignmentElement) { - if (isBindingElement(element)) { - if (element.dotDotDotToken) { - Debug.assertNode(element.name, isIdentifier); - return setOriginalNode(setTextRange(createSpreadAssignment(element.name), element), element); + if (isAssignmentExpression(bindingElement, /*excludeCompoundAssignment*/ true)) { + // `a` in `[a = 1] = ...` + // `{a}` in `[{a} = 1] = ...` + // `[a]` in `[[a] = 1] = ...` + // `a.b` in `[a.b = 1] = ...` + // `a[0]` in `[a[0] = 1] = ...` + return getTargetOfBindingOrAssignmentElement((bindingElement.left)); + } + if (isSpreadElement(bindingElement)) { + // `a` in `[...a] = ...` + return getTargetOfBindingOrAssignmentElement((bindingElement.expression)); + } + // `a` in `[a] = ...` + // `{a}` in `[{a}] = ...` + // `[a]` in `[[a]] = ...` + // `a.b` in `[a.b] = ...` + // `a[0]` in `[a[0]] = ...` + return bindingElement; +} +/** + * Determines whether an BindingOrAssignmentElement is a rest element. + */ +/* @internal */ +export function getRestIndicatorOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): BindingOrAssignmentElementRestIndicator | undefined { + switch (bindingElement.kind) { + case SyntaxKind.Parameter: + case SyntaxKind.BindingElement: + // `...` in `let [...a] = ...` + return bindingElement.dotDotDotToken; + case SyntaxKind.SpreadElement: + case SyntaxKind.SpreadAssignment: + // `...` in `[...a] = ...` + return bindingElement; + } + return undefined; +} +/** + * Gets the property name of a BindingOrAssignmentElement + */ +/* @internal */ +export function getPropertyNameOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): PropertyName | undefined { + const propertyName = tryGetPropertyNameOfBindingOrAssignmentElement(bindingElement); + Debug.assert(!!propertyName || isSpreadAssignment(bindingElement), "Invalid property name for binding element."); + return propertyName; +} +/* @internal */ +export function tryGetPropertyNameOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): PropertyName | undefined { + switch (bindingElement.kind) { + case SyntaxKind.BindingElement: + // `a` in `let { a: b } = ...` + // `[a]` in `let { [a]: b } = ...` + // `"a"` in `let { "a": b } = ...` + // `1` in `let { 1: b } = ...` + if (bindingElement.propertyName) { + const propertyName = bindingElement.propertyName; + return isComputedPropertyName(propertyName) && isStringOrNumericLiteral(propertyName.expression) + ? propertyName.expression + : propertyName; } - if (element.propertyName) { - const expression = convertToAssignmentElementTarget(element.name); - return setOriginalNode(setTextRange(createPropertyAssignment(element.propertyName, element.initializer ? createAssignment(expression, element.initializer) : expression), element), element); + break; + case SyntaxKind.PropertyAssignment: + // `a` in `({ a: b } = ...)` + // `[a]` in `({ [a]: b } = ...)` + // `"a"` in `({ "a": b } = ...)` + // `1` in `({ 1: b } = ...)` + if (bindingElement.name) { + const propertyName = bindingElement.name; + return isComputedPropertyName(propertyName) && isStringOrNumericLiteral(propertyName.expression) + ? propertyName.expression + : propertyName; } + break; + case SyntaxKind.SpreadAssignment: + // `a` in `({ ...a } = ...)` + return bindingElement.name; + } + const target = getTargetOfBindingOrAssignmentElement(bindingElement); + if (target && isPropertyName(target)) { + return isComputedPropertyName(target) && isStringOrNumericLiteral(target.expression) + ? target.expression + : target; + } +} +/* @internal */ +function isStringOrNumericLiteral(node: Node): node is StringLiteral | NumericLiteral { + const kind = node.kind; + return kind === SyntaxKind.StringLiteral + || kind === SyntaxKind.NumericLiteral; +} +/** + * Gets the elements of a BindingOrAssignmentPattern + */ +/* @internal */ +export function getElementsOfBindingOrAssignmentPattern(name: BindingOrAssignmentPattern): readonly BindingOrAssignmentElement[] { + switch (name.kind) { + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ArrayLiteralExpression: + // `a` in `{a}` + // `a` in `[a]` + return name.elements; + case SyntaxKind.ObjectLiteralExpression: + // `a` in `{a}` + return name.properties; + } +} +/* @internal */ +export function convertToArrayAssignmentElement(element: BindingOrAssignmentElement) { + if (isBindingElement(element)) { + if (element.dotDotDotToken) { Debug.assertNode(element.name, isIdentifier); - return setOriginalNode(setTextRange(createShorthandPropertyAssignment(element.name, element.initializer), element), element); + return setOriginalNode(setTextRange(createSpread((element.name)), element), element); } - Debug.assertNode(element, isObjectLiteralElementLike); - return element; + const expression = convertToAssignmentElementTarget(element.name); + return element.initializer + ? setOriginalNode(setTextRange(createAssignment(expression, element.initializer), element), element) + : expression; } - - export function convertToAssignmentPattern(node: BindingOrAssignmentPattern): AssignmentPattern { - switch (node.kind) { - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.ArrayLiteralExpression: - return convertToArrayAssignmentPattern(node); - - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ObjectLiteralExpression: - return convertToObjectAssignmentPattern(node); + Debug.assertNode(element, isExpression); + return element; +} +/* @internal */ +export function convertToObjectAssignmentElement(element: BindingOrAssignmentElement) { + if (isBindingElement(element)) { + if (element.dotDotDotToken) { + Debug.assertNode(element.name, isIdentifier); + return setOriginalNode(setTextRange(createSpreadAssignment((element.name)), element), element); } - } - - export function convertToObjectAssignmentPattern(node: ObjectBindingOrAssignmentPattern) { - if (isObjectBindingPattern(node)) { - return setOriginalNode( - setTextRange( - createObjectLiteral(map(node.elements, convertToObjectAssignmentElement)), - node - ), - node - ); + if (element.propertyName) { + const expression = convertToAssignmentElementTarget(element.name); + return setOriginalNode(setTextRange(createPropertyAssignment(element.propertyName, element.initializer ? createAssignment(expression, element.initializer) : expression), element), element); } - Debug.assertNode(node, isObjectLiteralExpression); - return node; + Debug.assertNode(element.name, isIdentifier); + return setOriginalNode(setTextRange(createShorthandPropertyAssignment((element.name), element.initializer), element), element); } - - export function convertToArrayAssignmentPattern(node: ArrayBindingOrAssignmentPattern) { - if (isArrayBindingPattern(node)) { - return setOriginalNode( - setTextRange( - createArrayLiteral(map(node.elements, convertToArrayAssignmentElement)), - node - ), - node - ); - } - Debug.assertNode(node, isArrayLiteralExpression); - return node; + Debug.assertNode(element, isObjectLiteralElementLike); + return element; +} +/* @internal */ +export function convertToAssignmentPattern(node: BindingOrAssignmentPattern): AssignmentPattern { + switch (node.kind) { + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ArrayLiteralExpression: + return convertToArrayAssignmentPattern(node); + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ObjectLiteralExpression: + return convertToObjectAssignmentPattern(node); + } +} +/* @internal */ +export function convertToObjectAssignmentPattern(node: ObjectBindingOrAssignmentPattern) { + if (isObjectBindingPattern(node)) { + return setOriginalNode(setTextRange(createObjectLiteral(map(node.elements, convertToObjectAssignmentElement)), node), node); } - - export function convertToAssignmentElementTarget(node: BindingOrAssignmentElementTarget): Expression { - if (isBindingPattern(node)) { - return convertToAssignmentPattern(node); - } - - Debug.assertNode(node, isExpression); - return node; + Debug.assertNode(node, isObjectLiteralExpression); + return node; +} +/* @internal */ +export function convertToArrayAssignmentPattern(node: ArrayBindingOrAssignmentPattern) { + if (isArrayBindingPattern(node)) { + return setOriginalNode(setTextRange(createArrayLiteral(map(node.elements, convertToArrayAssignmentElement)), node), node); + } + Debug.assertNode(node, isArrayLiteralExpression); + return node; +} +/* @internal */ +export function convertToAssignmentElementTarget(node: BindingOrAssignmentElementTarget): Expression { + if (isBindingPattern(node)) { + return convertToAssignmentPattern(node); } -} \ No newline at end of file + Debug.assertNode(node, isExpression); + return node; +} diff --git a/src/compiler/factoryPublic.ts b/src/compiler/factoryPublic.ts index 3c17ce2bdab7f..b7adcc1ca4848 100644 --- a/src/compiler/factoryPublic.ts +++ b/src/compiler/factoryPublic.ts @@ -1,3669 +1,2999 @@ -namespace ts { - function createSynthesizedNode(kind: SyntaxKind): Node { - const node = createNode(kind, -1, -1); - node.flags |= NodeFlags.Synthesized; - return node; - } - - /* @internal */ - export function updateNode(updated: T, original: T): T { - if (updated !== original) { - setOriginalNode(updated, original); - setTextRange(updated, original); - aggregateTransformFlags(updated); - } - return updated; - } - - /* @internal */ export function createNodeArray(elements?: T[], hasTrailingComma?: boolean): MutableNodeArray; - export function createNodeArray(elements?: readonly T[], hasTrailingComma?: boolean): NodeArray; - /** - * Make `elements` into a `NodeArray`. If `elements` is `undefined`, returns an empty `NodeArray`. - */ - export function createNodeArray(elements?: readonly T[], hasTrailingComma?: boolean): NodeArray { - if (!elements || elements === emptyArray) { - elements = []; - } - else if (isNodeArray(elements)) { - return elements; - } - - const array = >elements; - array.pos = -1; - array.end = -1; - array.hasTrailingComma = hasTrailingComma; - return array; - } - - /** - * Creates a shallow, memberwise clone of a node with no source map location. - */ - /* @internal */ - export function getSynthesizedClone(node: T): T { - // We don't use "clone" from core.ts here, as we need to preserve the prototype chain of - // the original node. We also need to exclude specific properties and only include own- - // properties (to skip members already defined on the shared prototype). - - if (node === undefined) { - return node; - } - - const clone = createSynthesizedNode(node.kind); - clone.flags |= node.flags; - setOriginalNode(clone, node); - - for (const key in node) { - if (clone.hasOwnProperty(key) || !node.hasOwnProperty(key)) { - continue; - } - - (clone)[key] = (node)[key]; - } - - return clone; - } - - // Literals - - /* @internal */ export function createLiteral(value: string | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier, isSingleQuote: boolean): StringLiteral; // eslint-disable-line @typescript-eslint/unified-signatures - /* @internal */ export function createLiteral(value: string | number, isSingleQuote: boolean): StringLiteral | NumericLiteral; - /** If a node is passed, creates a string literal whose source text is read from a source node during emit. */ - export function createLiteral(value: string | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier): StringLiteral; - export function createLiteral(value: number | PseudoBigInt): NumericLiteral; - export function createLiteral(value: boolean): BooleanLiteral; - export function createLiteral(value: string | number | PseudoBigInt | boolean): PrimaryExpression; - export function createLiteral(value: string | number | PseudoBigInt | boolean | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier, isSingleQuote?: boolean): PrimaryExpression { - if (typeof value === "number") { - return createNumericLiteral(value + ""); - } - // eslint-disable-next-line no-in-operator - if (typeof value === "object" && "base10Value" in value) { // PseudoBigInt - return createBigIntLiteral(pseudoBigIntToString(value) + "n"); - } - if (typeof value === "boolean") { - return value ? createTrue() : createFalse(); - } - if (isString(value)) { - const res = createStringLiteral(value); - if (isSingleQuote) res.singleQuote = true; - return res; - } - return createLiteralFromNode(value); - } - - export function createNumericLiteral(value: string, numericLiteralFlags: TokenFlags = TokenFlags.None): NumericLiteral { - const node = createSynthesizedNode(SyntaxKind.NumericLiteral); - node.text = value; - node.numericLiteralFlags = numericLiteralFlags; - return node; - } - - export function createBigIntLiteral(value: string): BigIntLiteral { - const node = createSynthesizedNode(SyntaxKind.BigIntLiteral); - node.text = value; - return node; - } - - export function createStringLiteral(text: string): StringLiteral { - const node = createSynthesizedNode(SyntaxKind.StringLiteral); - node.text = text; - return node; - } - - export function createRegularExpressionLiteral(text: string): RegularExpressionLiteral { - const node = createSynthesizedNode(SyntaxKind.RegularExpressionLiteral); - node.text = text; - return node; - } - - function createLiteralFromNode(sourceNode: PropertyNameLiteral): StringLiteral { - const node = createStringLiteral(getTextOfIdentifierOrLiteral(sourceNode)); - node.textSourceNode = sourceNode; - return node; - } - - - // Identifiers - - export function createIdentifier(text: string): Identifier; - /* @internal */ - export function createIdentifier(text: string, typeArguments: readonly (TypeNode | TypeParameterDeclaration)[] | undefined): Identifier; // eslint-disable-line @typescript-eslint/unified-signatures - export function createIdentifier(text: string, typeArguments?: readonly (TypeNode | TypeParameterDeclaration)[]): Identifier { - const node = createSynthesizedNode(SyntaxKind.Identifier); - node.escapedText = escapeLeadingUnderscores(text); - node.originalKeywordKind = text ? stringToToken(text) : SyntaxKind.Unknown; - node.autoGenerateFlags = GeneratedIdentifierFlags.None; - node.autoGenerateId = 0; - if (typeArguments) { - node.typeArguments = createNodeArray(typeArguments as readonly TypeNode[]); - } - return node; - } - - export function updateIdentifier(node: Identifier): Identifier; - /* @internal */ - export function updateIdentifier(node: Identifier, typeArguments: NodeArray | undefined): Identifier; // eslint-disable-line @typescript-eslint/unified-signatures - export function updateIdentifier(node: Identifier, typeArguments?: NodeArray | undefined): Identifier { - return node.typeArguments !== typeArguments - ? updateNode(createIdentifier(idText(node), typeArguments), node) - : node; - } - - let nextAutoGenerateId = 0; - - /** Create a unique temporary variable. */ - export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined): Identifier; - /* @internal */ export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined, reservedInNestedScopes: boolean): GeneratedIdentifier; - export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined, reservedInNestedScopes?: boolean): GeneratedIdentifier { - const name = createIdentifier("") as GeneratedIdentifier; - name.autoGenerateFlags = GeneratedIdentifierFlags.Auto; - name.autoGenerateId = nextAutoGenerateId; - nextAutoGenerateId++; - if (recordTempVariable) { - recordTempVariable(name); - } - if (reservedInNestedScopes) { - name.autoGenerateFlags |= GeneratedIdentifierFlags.ReservedInNestedScopes; +import { SyntaxKind, Node, createNode, NodeFlags, aggregateTransformFlags, MutableNodeArray, NodeArray, emptyArray, isNodeArray, StringLiteral, NoSubstitutionTemplateLiteral, NumericLiteral, Identifier, PseudoBigInt, BooleanLiteral, PrimaryExpression, pseudoBigIntToString, isString, TokenFlags, BigIntLiteral, RegularExpressionLiteral, PropertyNameLiteral, getTextOfIdentifierOrLiteral, TypeNode, TypeParameterDeclaration, escapeLeadingUnderscores, stringToToken, GeneratedIdentifierFlags, idText, GeneratedIdentifier, isIdentifier, Token, SuperExpression, ThisExpression, NullLiteral, Modifier, ModifierFlags, EntityName, QualifiedName, Expression, isCommaSequence, ComputedPropertyName, Decorator, DotDotDotToken, BindingName, QuestionToken, ParameterDeclaration, parenthesizeExpressionForList, parenthesizeForAccess, PropertyName, PropertySignature, ExclamationToken, PropertyDeclaration, MethodSignature, AsteriskToken, Block, MethodDeclaration, Push, PropertyAssignment, PropertyDescriptorAttributes, Debug, ConstructorDeclaration, GetAccessorDeclaration, SetAccessorDeclaration, CallSignatureDeclaration, ConstructSignatureDeclaration, IndexSignatureDeclaration, SignatureDeclaration, KeywordTypeNode, ThisTypeNode, AssertsToken, TypePredicateNode, TypeReferenceNode, parenthesizeTypeParameters, FunctionTypeNode, ConstructorTypeNode, TypeQueryNode, TypeElement, TypeLiteralNode, ArrayTypeNode, parenthesizeArrayTypeMember, TupleTypeNode, OptionalTypeNode, RestTypeNode, UnionTypeNode, IntersectionTypeNode, parenthesizeElementTypeMembers, UnionOrIntersectionTypeNode, ConditionalTypeNode, parenthesizeConditionalTypeMember, InferTypeNode, ImportTypeNode, ParenthesizedTypeNode, TypeOperatorNode, parenthesizeElementTypeMember, IndexedAccessTypeNode, ReadonlyToken, PlusToken, MinusToken, MappedTypeNode, LiteralTypeNode, BindingElement, ObjectBindingPattern, ArrayBindingElement, ArrayBindingPattern, ArrayLiteralExpression, parenthesizeListElements, ObjectLiteralElementLike, ObjectLiteralExpression, PropertyAccessExpression, EmitFlags, isOptionalChain, getEmitFlags, QuestionDotToken, PropertyAccessChain, ElementAccessExpression, ElementAccessChain, CallExpression, CallChain, NewExpression, parenthesizeForNew, TemplateLiteral, TaggedTemplateExpression, TypeAssertion, parenthesizePrefixOperand, ParenthesizedExpression, FunctionExpression, EqualsGreaterThanToken, ConciseBody, ArrowFunction, parenthesizeConciseBody, DeleteExpression, TypeOfExpression, VoidExpression, AwaitExpression, PrefixUnaryOperator, PrefixUnaryExpression, PostfixUnaryOperator, PostfixUnaryExpression, parenthesizePostfixOperand, BinaryOperator, BinaryOperatorToken, BinaryExpression, parenthesizeBinaryOperand, ConditionalExpression, ColonToken, parenthesizeForConditionalHead, parenthesizeSubexpressionOfConditionalExpression, TemplateHead, TemplateSpan, TemplateExpression, Scanner, TemplateLiteralToken, createScanner, ScriptTarget, LanguageVariant, TemplateLiteralLikeNode, TemplateMiddle, TemplateTail, YieldExpression, SpreadElement, HeritageClause, ClassElement, ClassExpression, OmittedExpression, ExpressionWithTypeArguments, AsExpression, NonNullExpression, MetaProperty, SemicolonClassElement, Statement, VariableDeclarationList, VariableDeclaration, VariableStatement, isArray, EmptyStatement, ExpressionStatement, parenthesizeExpressionForExpressionStatement, IfStatement, DoStatement, WhileStatement, ForInitializer, ForStatement, ForInStatement, AwaitKeywordToken, ForOfStatement, ContinueStatement, BreakStatement, ReturnStatement, WithStatement, CaseBlock, SwitchStatement, LabeledStatement, ThrowStatement, CatchClause, TryStatement, DebuggerStatement, FunctionDeclaration, ClassDeclaration, InterfaceDeclaration, TypeAliasDeclaration, EnumMember, EnumDeclaration, ModuleName, ModuleBody, ModuleDeclaration, ModuleBlock, CaseOrDefaultClause, NamespaceExportDeclaration, ModuleReference, ImportEqualsDeclaration, ImportClause, ImportDeclaration, NamedImportBindings, NamespaceImport, ImportSpecifier, NamedImports, ExportAssignment, parenthesizeDefaultExpression, NamedExports, ExportDeclaration, ExportSpecifier, ExternalModuleReference, JSDocTypeExpression, JSDocTypeTag, JSDocReturnTag, JSDocThisTag, JSDocParameterTag, JSDocTag, JSDoc, JsxOpeningElement, JsxChild, JsxClosingElement, JsxElement, JsxTagNameExpression, JsxAttributes, JsxSelfClosingElement, JsxOpeningFragment, JsxClosingFragment, JsxFragment, JsxText, JsxExpression, JsxAttribute, JsxAttributeLike, JsxSpreadAttribute, CaseClause, DefaultClause, ShorthandPropertyAssignment, SpreadAssignment, SourceFile, NotEmittedStatement, EndOfDeclarationMarker, EmitNode, MergeDeclarationMarker, PartiallyEmittedExpression, nodeIsSynthesized, isParseTreeNode, CommaListExpression, isBinaryExpression, sameFlatMap, SyntheticReferenceExpression, UnparsedSource, InputFiles, Bundle, UnscopedEmitHelper, arrayToMap, valuesHelper, readHelper, spreadHelper, spreadArraysHelper, restHelper, decorateHelper, metadataHelper, paramHelper, awaiterHelper, assignHelper, awaitHelper, asyncGeneratorHelper, asyncDelegator, asyncValues, extendsHelper, templateObjectHelper, generatorHelper, importStarHelper, importDefaultHelper, getLineAndCharacterOfPosition, BundleFileInfo, UnparsedPrologue, FileReference, UnparsedSourceText, BundleFileSectionKind, UnparsedPrepend, UnparsedTextLike, UnparsedSyntheticReference, map, BundleFileSection, UnparsedNode, BundleFileHasNoDefaultLib, BundleFileReference, BuildInfo, createMap, getBuildInfo, DestructuringAssignment, isNotEmittedStatement, getSourceFileOfNode, getParseTreeNode, TextRange, SourceMapRange, objectAllocator, SynthesizedComment, append, EmitHelper, some, appendIfUnique, orderedRemoveItem, Comparison, compareValues, addRange } from "./ts"; +import * as ts from "./ts"; +function createSynthesizedNode(kind: SyntaxKind): Node { + const node = createNode(kind, -1, -1); + node.flags |= NodeFlags.Synthesized; + return node; +} +/* @internal */ +export function updateNode(updated: T, original: T): T { + if (updated !== original) { + setOriginalNode(updated, original); + setTextRange(updated, original); + aggregateTransformFlags(updated); + } + return updated; +} +/* @internal */ export function createNodeArray(elements?: T[], hasTrailingComma?: boolean): MutableNodeArray; +export function createNodeArray(elements?: readonly T[], hasTrailingComma?: boolean): NodeArray; +/** + * Make `elements` into a `NodeArray`. If `elements` is `undefined`, returns an empty `NodeArray`. + */ +export function createNodeArray(elements?: readonly T[], hasTrailingComma?: boolean): NodeArray { + if (!elements || elements === emptyArray) { + elements = []; + } + else if (isNodeArray(elements)) { + return elements; + } + const array = (>elements); + array.pos = -1; + array.end = -1; + array.hasTrailingComma = hasTrailingComma; + return array; +} +/** + * Creates a shallow, memberwise clone of a node with no source map location. + */ +/* @internal */ +export function getSynthesizedClone(node: T): T { + // We don't use "clone" from core.ts here, as we need to preserve the prototype chain of + // the original node. We also need to exclude specific properties and only include own- + // properties (to skip members already defined on the shared prototype). + if (node === undefined) { + return node; + } + const clone = createSynthesizedNode(node.kind); + clone.flags |= node.flags; + setOriginalNode(clone, node); + for (const key in node) { + if (clone.hasOwnProperty(key) || !node.hasOwnProperty(key)) { + continue; } - return name; - } - - /** Create a unique temporary variable for use in a loop. */ - export function createLoopVariable(): Identifier { - const name = createIdentifier(""); - name.autoGenerateFlags = GeneratedIdentifierFlags.Loop; - name.autoGenerateId = nextAutoGenerateId; - nextAutoGenerateId++; - return name; - } - - /** Create a unique name based on the supplied text. */ - export function createUniqueName(text: string): Identifier { - const name = createIdentifier(text); - name.autoGenerateFlags = GeneratedIdentifierFlags.Unique; - name.autoGenerateId = nextAutoGenerateId; - nextAutoGenerateId++; - return name; - } - - /* @internal */ export function createOptimisticUniqueName(text: string): GeneratedIdentifier; - /** Create a unique name based on the supplied text. */ - export function createOptimisticUniqueName(text: string): Identifier; - export function createOptimisticUniqueName(text: string): GeneratedIdentifier { - const name = createIdentifier(text) as GeneratedIdentifier; - name.autoGenerateFlags = GeneratedIdentifierFlags.Unique | GeneratedIdentifierFlags.Optimistic; - name.autoGenerateId = nextAutoGenerateId; - nextAutoGenerateId++; - return name; - } - - /** Create a unique name based on the supplied text. This does not consider names injected by the transformer. */ - export function createFileLevelUniqueName(text: string): Identifier { - const name = createOptimisticUniqueName(text); - name.autoGenerateFlags |= GeneratedIdentifierFlags.FileLevel; - return name; - } - - /** Create a unique name generated for a node. */ - export function getGeneratedNameForNode(node: Node | undefined): Identifier; - /* @internal */ export function getGeneratedNameForNode(node: Node | undefined, flags: GeneratedIdentifierFlags): Identifier; // eslint-disable-line @typescript-eslint/unified-signatures - export function getGeneratedNameForNode(node: Node | undefined, flags?: GeneratedIdentifierFlags): Identifier { - const name = createIdentifier(node && isIdentifier(node) ? idText(node) : ""); - name.autoGenerateFlags = GeneratedIdentifierFlags.Node | flags!; - name.autoGenerateId = nextAutoGenerateId; - name.original = node; - nextAutoGenerateId++; - return name; - } - - // Punctuation - - export function createToken(token: TKind) { - return >createSynthesizedNode(token); - } - - // Reserved words - - export function createSuper() { - return createSynthesizedNode(SyntaxKind.SuperKeyword); - } - - export function createThis() { - return >createSynthesizedNode(SyntaxKind.ThisKeyword); - } - - export function createNull() { - return >createSynthesizedNode(SyntaxKind.NullKeyword); - } - - export function createTrue() { - return >createSynthesizedNode(SyntaxKind.TrueKeyword); - } - - export function createFalse() { - return >createSynthesizedNode(SyntaxKind.FalseKeyword); - } - - // Modifiers - - export function createModifier(kind: T): Token { - return createToken(kind); - } - - export function createModifiersFromModifierFlags(flags: ModifierFlags) { - const result: Modifier[] = []; - if (flags & ModifierFlags.Export) { result.push(createModifier(SyntaxKind.ExportKeyword)); } - if (flags & ModifierFlags.Ambient) { result.push(createModifier(SyntaxKind.DeclareKeyword)); } - if (flags & ModifierFlags.Default) { result.push(createModifier(SyntaxKind.DefaultKeyword)); } - if (flags & ModifierFlags.Const) { result.push(createModifier(SyntaxKind.ConstKeyword)); } - if (flags & ModifierFlags.Public) { result.push(createModifier(SyntaxKind.PublicKeyword)); } - if (flags & ModifierFlags.Private) { result.push(createModifier(SyntaxKind.PrivateKeyword)); } - if (flags & ModifierFlags.Protected) { result.push(createModifier(SyntaxKind.ProtectedKeyword)); } - if (flags & ModifierFlags.Abstract) { result.push(createModifier(SyntaxKind.AbstractKeyword)); } - if (flags & ModifierFlags.Static) { result.push(createModifier(SyntaxKind.StaticKeyword)); } - if (flags & ModifierFlags.Readonly) { result.push(createModifier(SyntaxKind.ReadonlyKeyword)); } - if (flags & ModifierFlags.Async) { result.push(createModifier(SyntaxKind.AsyncKeyword)); } - return result; - } - - // Names - - export function createQualifiedName(left: EntityName, right: string | Identifier) { - const node = createSynthesizedNode(SyntaxKind.QualifiedName); - node.left = left; - node.right = asName(right); - return node; - } - - export function updateQualifiedName(node: QualifiedName, left: EntityName, right: Identifier) { - return node.left !== left - || node.right !== right - ? updateNode(createQualifiedName(left, right), node) - : node; - } - - function parenthesizeForComputedName(expression: Expression): Expression { - return isCommaSequence(expression) - ? createParen(expression) - : expression; - } - - export function createComputedPropertyName(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.ComputedPropertyName); - node.expression = parenthesizeForComputedName(expression); - return node; - } - - export function updateComputedPropertyName(node: ComputedPropertyName, expression: Expression) { - return node.expression !== expression - ? updateNode(createComputedPropertyName(expression), node) - : node; - } - - // Signature elements - - export function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode) { - const node = createSynthesizedNode(SyntaxKind.TypeParameter) as TypeParameterDeclaration; - node.name = asName(name); - node.constraint = constraint; - node.default = defaultType; - return node; + (clone)[key] = (node)[key]; } - - export function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined) { - return node.name !== name - || node.constraint !== constraint - || node.default !== defaultType - ? updateNode(createTypeParameterDeclaration(name, constraint, defaultType), node) - : node; - } - - export function createParameter( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - dotDotDotToken: DotDotDotToken | undefined, - name: string | BindingName, - questionToken?: QuestionToken, - type?: TypeNode, - initializer?: Expression) { - const node = createSynthesizedNode(SyntaxKind.Parameter); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.dotDotDotToken = dotDotDotToken; - node.name = asName(name); - node.questionToken = questionToken; - node.type = type; - node.initializer = initializer ? parenthesizeExpressionForList(initializer) : undefined; - return node; - } - - export function updateParameter( - node: ParameterDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - dotDotDotToken: DotDotDotToken | undefined, - name: string | BindingName, - questionToken: QuestionToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.dotDotDotToken !== dotDotDotToken - || node.name !== name - || node.questionToken !== questionToken - || node.type !== type - || node.initializer !== initializer - ? updateNode(createParameter(decorators, modifiers, dotDotDotToken, name, questionToken, type, initializer), node) - : node; + return clone; +} +// Literals +/* @internal */ export function createLiteral(value: string | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier, isSingleQuote: boolean): StringLiteral; // eslint-disable-line @typescript-eslint/unified-signatures +/* @internal */ export function createLiteral(value: string | number, isSingleQuote: boolean): StringLiteral | NumericLiteral; +/** If a node is passed, creates a string literal whose source text is read from a source node during emit. */ +export function createLiteral(value: string | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier): StringLiteral; +export function createLiteral(value: number | PseudoBigInt): NumericLiteral; +export function createLiteral(value: boolean): BooleanLiteral; +export function createLiteral(value: string | number | PseudoBigInt | boolean): PrimaryExpression; +export function createLiteral(value: string | number | PseudoBigInt | boolean | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier, isSingleQuote?: boolean): PrimaryExpression { + if (typeof value === "number") { + return createNumericLiteral(value + ""); + } + // eslint-disable-next-line no-in-operator + if (typeof value === "object" && "base10Value" in value) { // PseudoBigInt + return createBigIntLiteral(pseudoBigIntToString(value) + "n"); + } + if (typeof value === "boolean") { + return value ? createTrue() : createFalse(); + } + if (isString(value)) { + const res = createStringLiteral(value); + if (isSingleQuote) + res.singleQuote = true; + return res; + } + return createLiteralFromNode(value); +} +export function createNumericLiteral(value: string, numericLiteralFlags: TokenFlags = TokenFlags.None): NumericLiteral { + const node = (createSynthesizedNode(SyntaxKind.NumericLiteral)); + node.text = value; + node.numericLiteralFlags = numericLiteralFlags; + return node; +} +export function createBigIntLiteral(value: string): BigIntLiteral { + const node = (createSynthesizedNode(SyntaxKind.BigIntLiteral)); + node.text = value; + return node; +} +export function createStringLiteral(text: string): StringLiteral { + const node = (createSynthesizedNode(SyntaxKind.StringLiteral)); + node.text = text; + return node; +} +export function createRegularExpressionLiteral(text: string): RegularExpressionLiteral { + const node = (createSynthesizedNode(SyntaxKind.RegularExpressionLiteral)); + node.text = text; + return node; +} +function createLiteralFromNode(sourceNode: PropertyNameLiteral): StringLiteral { + const node = createStringLiteral(getTextOfIdentifierOrLiteral(sourceNode)); + node.textSourceNode = sourceNode; + return node; +} +// Identifiers +export function createIdentifier(text: string): Identifier; +/* @internal */ +export function createIdentifier(text: string, typeArguments: readonly (TypeNode | TypeParameterDeclaration)[] | undefined): Identifier; // eslint-disable-line @typescript-eslint/unified-signatures +export function createIdentifier(text: string, typeArguments?: readonly (TypeNode | TypeParameterDeclaration)[]): Identifier { + const node = (createSynthesizedNode(SyntaxKind.Identifier)); + node.escapedText = escapeLeadingUnderscores(text); + node.originalKeywordKind = text ? stringToToken(text) : SyntaxKind.Unknown; + node.autoGenerateFlags = GeneratedIdentifierFlags.None; + node.autoGenerateId = 0; + if (typeArguments) { + node.typeArguments = createNodeArray((typeArguments as readonly TypeNode[])); + } + return node; +} +export function updateIdentifier(node: Identifier): Identifier; +/* @internal */ +export function updateIdentifier(node: Identifier, typeArguments: NodeArray | undefined): Identifier; // eslint-disable-line @typescript-eslint/unified-signatures +export function updateIdentifier(node: Identifier, typeArguments?: NodeArray | undefined): Identifier { + return node.typeArguments !== typeArguments + ? updateNode(createIdentifier(idText(node), typeArguments), node) + : node; +} +let nextAutoGenerateId = 0; +/** Create a unique temporary variable. */ +export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined): Identifier; +/* @internal */ export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined, reservedInNestedScopes: boolean): GeneratedIdentifier; +export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined, reservedInNestedScopes?: boolean): GeneratedIdentifier { + const name = (createIdentifier("") as GeneratedIdentifier); + name.autoGenerateFlags = GeneratedIdentifierFlags.Auto; + name.autoGenerateId = nextAutoGenerateId; + nextAutoGenerateId++; + if (recordTempVariable) { + recordTempVariable(name); + } + if (reservedInNestedScopes) { + name.autoGenerateFlags |= GeneratedIdentifierFlags.ReservedInNestedScopes; + } + return name; +} +/** Create a unique temporary variable for use in a loop. */ +export function createLoopVariable(): Identifier { + const name = createIdentifier(""); + name.autoGenerateFlags = GeneratedIdentifierFlags.Loop; + name.autoGenerateId = nextAutoGenerateId; + nextAutoGenerateId++; + return name; +} +/** Create a unique name based on the supplied text. */ +export function createUniqueName(text: string): Identifier { + const name = createIdentifier(text); + name.autoGenerateFlags = GeneratedIdentifierFlags.Unique; + name.autoGenerateId = nextAutoGenerateId; + nextAutoGenerateId++; + return name; +} +/* @internal */ export function createOptimisticUniqueName(text: string): GeneratedIdentifier; +/** Create a unique name based on the supplied text. */ +export function createOptimisticUniqueName(text: string): Identifier; +export function createOptimisticUniqueName(text: string): GeneratedIdentifier { + const name = (createIdentifier(text) as GeneratedIdentifier); + name.autoGenerateFlags = GeneratedIdentifierFlags.Unique | GeneratedIdentifierFlags.Optimistic; + name.autoGenerateId = nextAutoGenerateId; + nextAutoGenerateId++; + return name; +} +/** Create a unique name based on the supplied text. This does not consider names injected by the transformer. */ +export function createFileLevelUniqueName(text: string): Identifier { + const name = createOptimisticUniqueName(text); + name.autoGenerateFlags |= GeneratedIdentifierFlags.FileLevel; + return name; +} +/** Create a unique name generated for a node. */ +export function getGeneratedNameForNode(node: Node | undefined): Identifier; +/* @internal */ export function getGeneratedNameForNode(node: Node | undefined, flags: GeneratedIdentifierFlags): Identifier; // eslint-disable-line @typescript-eslint/unified-signatures +export function getGeneratedNameForNode(node: Node | undefined, flags?: GeneratedIdentifierFlags): Identifier { + const name = createIdentifier(node && isIdentifier(node) ? idText(node) : ""); + name.autoGenerateFlags = GeneratedIdentifierFlags.Node | (flags!); + name.autoGenerateId = nextAutoGenerateId; + name.original = node; + nextAutoGenerateId++; + return name; +} +// Punctuation +export function createToken(token: TKind) { + return >createSynthesizedNode(token); +} +// Reserved words +export function createSuper() { + return createSynthesizedNode(SyntaxKind.SuperKeyword); +} +export function createThis() { + return >createSynthesizedNode(SyntaxKind.ThisKeyword); +} +export function createNull() { + return >createSynthesizedNode(SyntaxKind.NullKeyword); +} +export function createTrue() { + return >createSynthesizedNode(SyntaxKind.TrueKeyword); +} +export function createFalse() { + return >createSynthesizedNode(SyntaxKind.FalseKeyword); +} +// Modifiers +export function createModifier(kind: T): Token { + return createToken(kind); +} +export function createModifiersFromModifierFlags(flags: ModifierFlags) { + const result: Modifier[] = []; + if (flags & ModifierFlags.Export) { + result.push(createModifier(SyntaxKind.ExportKeyword)); } - - export function createDecorator(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.Decorator); - node.expression = parenthesizeForAccess(expression); - return node; + if (flags & ModifierFlags.Ambient) { + result.push(createModifier(SyntaxKind.DeclareKeyword)); } - - export function updateDecorator(node: Decorator, expression: Expression) { - return node.expression !== expression - ? updateNode(createDecorator(expression), node) - : node; + if (flags & ModifierFlags.Default) { + result.push(createModifier(SyntaxKind.DefaultKeyword)); } - - - // Type Elements - - export function createPropertySignature( - modifiers: readonly Modifier[] | undefined, - name: PropertyName | string, - questionToken: QuestionToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined): PropertySignature { - const node = createSynthesizedNode(SyntaxKind.PropertySignature) as PropertySignature; - node.modifiers = asNodeArray(modifiers); - node.name = asName(name); - node.questionToken = questionToken; - node.type = type; - node.initializer = initializer; - return node; + if (flags & ModifierFlags.Const) { + result.push(createModifier(SyntaxKind.ConstKeyword)); } - - export function updatePropertySignature( - node: PropertySignature, - modifiers: readonly Modifier[] | undefined, - name: PropertyName, - questionToken: QuestionToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined) { - return node.modifiers !== modifiers - || node.name !== name - || node.questionToken !== questionToken - || node.type !== type - || node.initializer !== initializer - ? updateNode(createPropertySignature(modifiers, name, questionToken, type, initializer), node) - : node; + if (flags & ModifierFlags.Public) { + result.push(createModifier(SyntaxKind.PublicKeyword)); } - - export function createProperty( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - questionOrExclamationToken: QuestionToken | ExclamationToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined) { - const node = createSynthesizedNode(SyntaxKind.PropertyDeclaration); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.name = asName(name); - node.questionToken = questionOrExclamationToken !== undefined && questionOrExclamationToken.kind === SyntaxKind.QuestionToken ? questionOrExclamationToken : undefined; - node.exclamationToken = questionOrExclamationToken !== undefined && questionOrExclamationToken.kind === SyntaxKind.ExclamationToken ? questionOrExclamationToken : undefined; - node.type = type; - node.initializer = initializer; - return node; + if (flags & ModifierFlags.Private) { + result.push(createModifier(SyntaxKind.PrivateKeyword)); } - - export function updateProperty( - node: PropertyDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - questionOrExclamationToken: QuestionToken | ExclamationToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.questionToken !== (questionOrExclamationToken !== undefined && questionOrExclamationToken.kind === SyntaxKind.QuestionToken ? questionOrExclamationToken : undefined) - || node.exclamationToken !== (questionOrExclamationToken !== undefined && questionOrExclamationToken.kind === SyntaxKind.ExclamationToken ? questionOrExclamationToken : undefined) - || node.type !== type - || node.initializer !== initializer - ? updateNode(createProperty(decorators, modifiers, name, questionOrExclamationToken, type, initializer), node) - : node; + if (flags & ModifierFlags.Protected) { + result.push(createModifier(SyntaxKind.ProtectedKeyword)); } - - export function createMethodSignature( - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - name: string | PropertyName, - questionToken: QuestionToken | undefined) { - const node = createSignatureDeclaration(SyntaxKind.MethodSignature, typeParameters, parameters, type) as MethodSignature; - node.name = asName(name); - node.questionToken = questionToken; - return node; + if (flags & ModifierFlags.Abstract) { + result.push(createModifier(SyntaxKind.AbstractKeyword)); } - - export function updateMethodSignature(node: MethodSignature, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined, name: PropertyName, questionToken: QuestionToken | undefined) { - return node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - || node.name !== name - || node.questionToken !== questionToken - ? updateNode(createMethodSignature(typeParameters, parameters, type, name, questionToken), node) - : node; + if (flags & ModifierFlags.Static) { + result.push(createModifier(SyntaxKind.StaticKeyword)); } - - export function createMethod( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: string | PropertyName, - questionToken: QuestionToken | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined) { - const node = createSynthesizedNode(SyntaxKind.MethodDeclaration); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.asteriskToken = asteriskToken; - node.name = asName(name); - node.questionToken = questionToken; - node.typeParameters = asNodeArray(typeParameters); - node.parameters = createNodeArray(parameters); - node.type = type; - node.body = body; - return node; + if (flags & ModifierFlags.Readonly) { + result.push(createModifier(SyntaxKind.ReadonlyKeyword)); } - - function createMethodCall(object: Expression, methodName: string | Identifier, argumentsList: readonly Expression[]) { - return createCall( - createPropertyAccess(object, asName(methodName)), - /*typeArguments*/ undefined, - argumentsList - ); + if (flags & ModifierFlags.Async) { + result.push(createModifier(SyntaxKind.AsyncKeyword)); } - - function createGlobalMethodCall(globalObjectName: string, methodName: string, argumentsList: readonly Expression[]) { - return createMethodCall(createIdentifier(globalObjectName), methodName, argumentsList); + return result; +} +// Names +export function createQualifiedName(left: EntityName, right: string | Identifier) { + const node = (createSynthesizedNode(SyntaxKind.QualifiedName)); + node.left = left; + node.right = asName(right); + return node; +} +export function updateQualifiedName(node: QualifiedName, left: EntityName, right: Identifier) { + return node.left !== left + || node.right !== right + ? updateNode(createQualifiedName(left, right), node) + : node; +} +function parenthesizeForComputedName(expression: Expression): Expression { + return isCommaSequence(expression) + ? createParen(expression) + : expression; +} +export function createComputedPropertyName(expression: Expression) { + const node = (createSynthesizedNode(SyntaxKind.ComputedPropertyName)); + node.expression = parenthesizeForComputedName(expression); + return node; +} +export function updateComputedPropertyName(node: ComputedPropertyName, expression: Expression) { + return node.expression !== expression + ? updateNode(createComputedPropertyName(expression), node) + : node; +} +// Signature elements +export function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode) { + const node = (createSynthesizedNode(SyntaxKind.TypeParameter) as TypeParameterDeclaration); + node.name = asName(name); + node.constraint = constraint; + node.default = defaultType; + return node; +} +export function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined) { + return node.name !== name + || node.constraint !== constraint + || node.default !== defaultType + ? updateNode(createTypeParameterDeclaration(name, constraint, defaultType), node) + : node; +} +export function createParameter(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression) { + const node = (createSynthesizedNode(SyntaxKind.Parameter)); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.dotDotDotToken = dotDotDotToken; + node.name = asName(name); + node.questionToken = questionToken; + node.type = type; + node.initializer = initializer ? parenthesizeExpressionForList(initializer) : undefined; + return node; +} +export function updateParameter(node: ParameterDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.dotDotDotToken !== dotDotDotToken + || node.name !== name + || node.questionToken !== questionToken + || node.type !== type + || node.initializer !== initializer + ? updateNode(createParameter(decorators, modifiers, dotDotDotToken, name, questionToken, type, initializer), node) + : node; +} +export function createDecorator(expression: Expression) { + const node = (createSynthesizedNode(SyntaxKind.Decorator)); + node.expression = parenthesizeForAccess(expression); + return node; +} +export function updateDecorator(node: Decorator, expression: Expression) { + return node.expression !== expression + ? updateNode(createDecorator(expression), node) + : node; +} +// Type Elements +export function createPropertySignature(modifiers: readonly Modifier[] | undefined, name: PropertyName | string, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): PropertySignature { + const node = (createSynthesizedNode(SyntaxKind.PropertySignature) as PropertySignature); + node.modifiers = asNodeArray(modifiers); + node.name = asName(name); + node.questionToken = questionToken; + node.type = type; + node.initializer = initializer; + return node; +} +export function updatePropertySignature(node: PropertySignature, modifiers: readonly Modifier[] | undefined, name: PropertyName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined) { + return node.modifiers !== modifiers + || node.name !== name + || node.questionToken !== questionToken + || node.type !== type + || node.initializer !== initializer + ? updateNode(createPropertySignature(modifiers, name, questionToken, type, initializer), node) + : node; +} +export function createProperty(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | PropertyName, questionOrExclamationToken: QuestionToken | ExclamationToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined) { + const node = (createSynthesizedNode(SyntaxKind.PropertyDeclaration)); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.name = asName(name); + node.questionToken = questionOrExclamationToken !== undefined && questionOrExclamationToken.kind === SyntaxKind.QuestionToken ? questionOrExclamationToken : undefined; + node.exclamationToken = questionOrExclamationToken !== undefined && questionOrExclamationToken.kind === SyntaxKind.ExclamationToken ? questionOrExclamationToken : undefined; + node.type = type; + node.initializer = initializer; + return node; +} +export function updateProperty(node: PropertyDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | PropertyName, questionOrExclamationToken: QuestionToken | ExclamationToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.questionToken !== (questionOrExclamationToken !== undefined && questionOrExclamationToken.kind === SyntaxKind.QuestionToken ? questionOrExclamationToken : undefined) + || node.exclamationToken !== (questionOrExclamationToken !== undefined && questionOrExclamationToken.kind === SyntaxKind.ExclamationToken ? questionOrExclamationToken : undefined) + || node.type !== type + || node.initializer !== initializer + ? updateNode(createProperty(decorators, modifiers, name, questionOrExclamationToken, type, initializer), node) + : node; +} +export function createMethodSignature(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined, name: string | PropertyName, questionToken: QuestionToken | undefined) { + const node = (createSignatureDeclaration(SyntaxKind.MethodSignature, typeParameters, parameters, type) as MethodSignature); + node.name = asName(name); + node.questionToken = questionToken; + return node; +} +export function updateMethodSignature(node: MethodSignature, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined, name: PropertyName, questionToken: QuestionToken | undefined) { + return node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + || node.name !== name + || node.questionToken !== questionToken + ? updateNode(createMethodSignature(typeParameters, parameters, type, name, questionToken), node) + : node; +} +export function createMethod(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, asteriskToken: AsteriskToken | undefined, name: string | PropertyName, questionToken: QuestionToken | undefined, typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) { + const node = (createSynthesizedNode(SyntaxKind.MethodDeclaration)); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.asteriskToken = asteriskToken; + node.name = asName(name); + node.questionToken = questionToken; + node.typeParameters = asNodeArray(typeParameters); + node.parameters = createNodeArray(parameters); + node.type = type; + node.body = body; + return node; +} +function createMethodCall(object: Expression, methodName: string | Identifier, argumentsList: readonly Expression[]) { + return createCall(createPropertyAccess(object, asName(methodName)), + /*typeArguments*/ undefined, argumentsList); +} +function createGlobalMethodCall(globalObjectName: string, methodName: string, argumentsList: readonly Expression[]) { + return createMethodCall(createIdentifier(globalObjectName), methodName, argumentsList); +} +/* @internal */ +export function createObjectDefinePropertyCall(target: Expression, propertyName: string | Expression, attributes: Expression) { + return createGlobalMethodCall("Object", "defineProperty", [target, asExpression(propertyName), attributes]); +} +function tryAddPropertyAssignment(properties: Push, propertyName: string, expression: Expression | undefined) { + if (expression) { + properties.push(createPropertyAssignment(propertyName, expression)); + return true; } - - /* @internal */ - export function createObjectDefinePropertyCall(target: Expression, propertyName: string | Expression, attributes: Expression) { - return createGlobalMethodCall("Object", "defineProperty", [target, asExpression(propertyName), attributes]); + return false; +} +/* @internal */ +export function createPropertyDescriptor(attributes: PropertyDescriptorAttributes, singleLine?: boolean) { + const properties: PropertyAssignment[] = []; + tryAddPropertyAssignment(properties, "enumerable", asExpression(attributes.enumerable)); + tryAddPropertyAssignment(properties, "configurable", asExpression(attributes.configurable)); + let isData = tryAddPropertyAssignment(properties, "writable", asExpression(attributes.writable)); + isData = tryAddPropertyAssignment(properties, "value", attributes.value) || isData; + let isAccessor = tryAddPropertyAssignment(properties, "get", attributes.get); + isAccessor = tryAddPropertyAssignment(properties, "set", attributes.set) || isAccessor; + Debug.assert(!(isData && isAccessor), "A PropertyDescriptor may not be both an accessor descriptor and a data descriptor."); + return createObjectLiteral(properties, !singleLine); +} +export function updateMethod(node: MethodDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, asteriskToken: AsteriskToken | undefined, name: PropertyName, questionToken: QuestionToken | undefined, typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.asteriskToken !== asteriskToken + || node.name !== name + || node.questionToken !== questionToken + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + || node.body !== body + ? updateNode(createMethod(decorators, modifiers, asteriskToken, name, questionToken, typeParameters, parameters, type, body), node) + : node; +} +export function createConstructor(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, parameters: readonly ParameterDeclaration[], body: Block | undefined) { + const node = (createSynthesizedNode(SyntaxKind.Constructor)); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.typeParameters = undefined; + node.parameters = createNodeArray(parameters); + node.type = undefined; + node.body = body; + return node; +} +export function updateConstructor(node: ConstructorDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, parameters: readonly ParameterDeclaration[], body: Block | undefined) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.parameters !== parameters + || node.body !== body + ? updateNode(createConstructor(decorators, modifiers, parameters, body), node) + : node; +} +export function createGetAccessor(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | PropertyName, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) { + const node = (createSynthesizedNode(SyntaxKind.GetAccessor)); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.name = asName(name); + node.typeParameters = undefined; + node.parameters = createNodeArray(parameters); + node.type = type; + node.body = body; + return node; +} +export function updateGetAccessor(node: GetAccessorDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: PropertyName, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.parameters !== parameters + || node.type !== type + || node.body !== body + ? updateNode(createGetAccessor(decorators, modifiers, name, parameters, type, body), node) + : node; +} +export function createSetAccessor(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | PropertyName, parameters: readonly ParameterDeclaration[], body: Block | undefined) { + const node = (createSynthesizedNode(SyntaxKind.SetAccessor)); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.name = asName(name); + node.typeParameters = undefined; + node.parameters = createNodeArray(parameters); + node.body = body; + return node; +} +export function updateSetAccessor(node: SetAccessorDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: PropertyName, parameters: readonly ParameterDeclaration[], body: Block | undefined) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.parameters !== parameters + || node.body !== body + ? updateNode(createSetAccessor(decorators, modifiers, name, parameters, body), node) + : node; +} +export function createCallSignature(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined) { + return createSignatureDeclaration(SyntaxKind.CallSignature, typeParameters, parameters, type) as CallSignatureDeclaration; +} +export function updateCallSignature(node: CallSignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { + return updateSignatureDeclaration(node, typeParameters, parameters, type); +} +export function createConstructSignature(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined) { + return createSignatureDeclaration(SyntaxKind.ConstructSignature, typeParameters, parameters, type) as ConstructSignatureDeclaration; +} +export function updateConstructSignature(node: ConstructSignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { + return updateSignatureDeclaration(node, typeParameters, parameters, type); +} +export function createIndexSignature(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode): IndexSignatureDeclaration { + const node = (createSynthesizedNode(SyntaxKind.IndexSignature) as IndexSignatureDeclaration); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.parameters = createNodeArray(parameters); + node.type = type; + return node; +} +export function updateIndexSignature(node: IndexSignatureDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode) { + return node.parameters !== parameters + || node.type !== type + || node.decorators !== decorators + || node.modifiers !== modifiers + ? updateNode(createIndexSignature(decorators, modifiers, parameters, type), node) + : node; +} +/* @internal */ +export function createSignatureDeclaration(kind: SyntaxKind, typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined, typeArguments?: readonly TypeNode[] | undefined) { + const node = (createSynthesizedNode(kind) as SignatureDeclaration); + node.typeParameters = asNodeArray(typeParameters); + node.parameters = asNodeArray(parameters); + node.type = type; + node.typeArguments = asNodeArray(typeArguments); + return node; +} +function updateSignatureDeclaration(node: T, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): T { + return node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + ? updateNode(createSignatureDeclaration(node.kind, typeParameters, parameters, type), node) + : node; +} +// Types +export function createKeywordTypeNode(kind: KeywordTypeNode["kind"]) { + return createSynthesizedNode(kind); +} +export function createTypePredicateNode(parameterName: Identifier | ThisTypeNode | string, type: TypeNode) { + return createTypePredicateNodeWithModifier(/*assertsModifier*/ undefined, parameterName, type); +} +export function createTypePredicateNodeWithModifier(assertsModifier: AssertsToken | undefined, parameterName: Identifier | ThisTypeNode | string, type: TypeNode | undefined) { + const node = (createSynthesizedNode(SyntaxKind.TypePredicate) as TypePredicateNode); + node.assertsModifier = assertsModifier; + node.parameterName = asName(parameterName); + node.type = type; + return node; +} +export function updateTypePredicateNode(node: TypePredicateNode, parameterName: Identifier | ThisTypeNode, type: TypeNode) { + return updateTypePredicateNodeWithModifier(node, node.assertsModifier, parameterName, type); +} +export function updateTypePredicateNodeWithModifier(node: TypePredicateNode, assertsModifier: AssertsToken | undefined, parameterName: Identifier | ThisTypeNode, type: TypeNode | undefined) { + return node.assertsModifier !== assertsModifier + || node.parameterName !== parameterName + || node.type !== type + ? updateNode(createTypePredicateNodeWithModifier(assertsModifier, parameterName, type), node) + : node; +} +export function createTypeReferenceNode(typeName: string | EntityName, typeArguments: readonly TypeNode[] | undefined) { + const node = (createSynthesizedNode(SyntaxKind.TypeReference) as TypeReferenceNode); + node.typeName = asName(typeName); + node.typeArguments = typeArguments && parenthesizeTypeParameters(typeArguments); + return node; +} +export function updateTypeReferenceNode(node: TypeReferenceNode, typeName: EntityName, typeArguments: NodeArray | undefined) { + return node.typeName !== typeName + || node.typeArguments !== typeArguments + ? updateNode(createTypeReferenceNode(typeName, typeArguments), node) + : node; +} +export function createFunctionTypeNode(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined) { + return createSignatureDeclaration(SyntaxKind.FunctionType, typeParameters, parameters, type) as FunctionTypeNode; +} +export function updateFunctionTypeNode(node: FunctionTypeNode, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { + return updateSignatureDeclaration(node, typeParameters, parameters, type); +} +export function createConstructorTypeNode(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined) { + return createSignatureDeclaration(SyntaxKind.ConstructorType, typeParameters, parameters, type) as ConstructorTypeNode; +} +export function updateConstructorTypeNode(node: ConstructorTypeNode, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { + return updateSignatureDeclaration(node, typeParameters, parameters, type); +} +export function createTypeQueryNode(exprName: EntityName) { + const node = (createSynthesizedNode(SyntaxKind.TypeQuery) as TypeQueryNode); + node.exprName = exprName; + return node; +} +export function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName) { + return node.exprName !== exprName + ? updateNode(createTypeQueryNode(exprName), node) + : node; +} +export function createTypeLiteralNode(members: readonly TypeElement[] | undefined) { + const node = (createSynthesizedNode(SyntaxKind.TypeLiteral) as TypeLiteralNode); + node.members = createNodeArray(members); + return node; +} +export function updateTypeLiteralNode(node: TypeLiteralNode, members: NodeArray) { + return node.members !== members + ? updateNode(createTypeLiteralNode(members), node) + : node; +} +export function createArrayTypeNode(elementType: TypeNode) { + const node = (createSynthesizedNode(SyntaxKind.ArrayType) as ArrayTypeNode); + node.elementType = parenthesizeArrayTypeMember(elementType); + return node; +} +export function updateArrayTypeNode(node: ArrayTypeNode, elementType: TypeNode): ArrayTypeNode { + return node.elementType !== elementType + ? updateNode(createArrayTypeNode(elementType), node) + : node; +} +export function createTupleTypeNode(elementTypes: readonly TypeNode[]) { + const node = (createSynthesizedNode(SyntaxKind.TupleType) as TupleTypeNode); + node.elementTypes = createNodeArray(elementTypes); + return node; +} +export function updateTupleTypeNode(node: TupleTypeNode, elementTypes: readonly TypeNode[]) { + return node.elementTypes !== elementTypes + ? updateNode(createTupleTypeNode(elementTypes), node) + : node; +} +export function createOptionalTypeNode(type: TypeNode) { + const node = (createSynthesizedNode(SyntaxKind.OptionalType) as OptionalTypeNode); + node.type = parenthesizeArrayTypeMember(type); + return node; +} +export function updateOptionalTypeNode(node: OptionalTypeNode, type: TypeNode): OptionalTypeNode { + return node.type !== type + ? updateNode(createOptionalTypeNode(type), node) + : node; +} +export function createRestTypeNode(type: TypeNode) { + const node = (createSynthesizedNode(SyntaxKind.RestType) as RestTypeNode); + node.type = type; + return node; +} +export function updateRestTypeNode(node: RestTypeNode, type: TypeNode): RestTypeNode { + return node.type !== type + ? updateNode(createRestTypeNode(type), node) + : node; +} +export function createUnionTypeNode(types: readonly TypeNode[]): UnionTypeNode { + return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, types); +} +export function updateUnionTypeNode(node: UnionTypeNode, types: NodeArray) { + return updateUnionOrIntersectionTypeNode(node, types); +} +export function createIntersectionTypeNode(types: readonly TypeNode[]): IntersectionTypeNode { + return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, types); +} +export function updateIntersectionTypeNode(node: IntersectionTypeNode, types: NodeArray) { + return updateUnionOrIntersectionTypeNode(node, types); +} +export function createUnionOrIntersectionTypeNode(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, types: readonly TypeNode[]) { + const node = (createSynthesizedNode(kind) as UnionTypeNode | IntersectionTypeNode); + node.types = parenthesizeElementTypeMembers(types); + return node; +} +function updateUnionOrIntersectionTypeNode(node: T, types: NodeArray): T { + return node.types !== types + ? updateNode(createUnionOrIntersectionTypeNode(node.kind, types), node) + : node; +} +export function createConditionalTypeNode(checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) { + const node = (createSynthesizedNode(SyntaxKind.ConditionalType) as ConditionalTypeNode); + node.checkType = parenthesizeConditionalTypeMember(checkType); + node.extendsType = parenthesizeConditionalTypeMember(extendsType); + node.trueType = trueType; + node.falseType = falseType; + return node; +} +export function updateConditionalTypeNode(node: ConditionalTypeNode, checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) { + return node.checkType !== checkType + || node.extendsType !== extendsType + || node.trueType !== trueType + || node.falseType !== falseType + ? updateNode(createConditionalTypeNode(checkType, extendsType, trueType, falseType), node) + : node; +} +export function createInferTypeNode(typeParameter: TypeParameterDeclaration) { + const node = (createSynthesizedNode(SyntaxKind.InferType)); + node.typeParameter = typeParameter; + return node; +} +export function updateInferTypeNode(node: InferTypeNode, typeParameter: TypeParameterDeclaration) { + return node.typeParameter !== typeParameter + ? updateNode(createInferTypeNode(typeParameter), node) + : node; +} +export function createImportTypeNode(argument: TypeNode, qualifier?: EntityName, typeArguments?: readonly TypeNode[], isTypeOf?: boolean) { + const node = (createSynthesizedNode(SyntaxKind.ImportType)); + node.argument = argument; + node.qualifier = qualifier; + node.typeArguments = parenthesizeTypeParameters(typeArguments); + node.isTypeOf = isTypeOf; + return node; +} +export function updateImportTypeNode(node: ImportTypeNode, argument: TypeNode, qualifier?: EntityName, typeArguments?: readonly TypeNode[], isTypeOf?: boolean) { + return node.argument !== argument + || node.qualifier !== qualifier + || node.typeArguments !== typeArguments + || node.isTypeOf !== isTypeOf + ? updateNode(createImportTypeNode(argument, qualifier, typeArguments, isTypeOf), node) + : node; +} +export function createParenthesizedType(type: TypeNode) { + const node = (createSynthesizedNode(SyntaxKind.ParenthesizedType)); + node.type = type; + return node; +} +export function updateParenthesizedType(node: ParenthesizedTypeNode, type: TypeNode) { + return node.type !== type + ? updateNode(createParenthesizedType(type), node) + : node; +} +export function createThisTypeNode() { + return createSynthesizedNode(SyntaxKind.ThisType); +} +export function createTypeOperatorNode(type: TypeNode): TypeOperatorNode; +export function createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword, type: TypeNode): TypeOperatorNode; +export function createTypeOperatorNode(operatorOrType: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword | TypeNode, type?: TypeNode) { + const node = (createSynthesizedNode(SyntaxKind.TypeOperator) as TypeOperatorNode); + node.operator = typeof operatorOrType === "number" ? operatorOrType : SyntaxKind.KeyOfKeyword; + node.type = parenthesizeElementTypeMember(typeof operatorOrType === "number" ? type! : operatorOrType); + return node; +} +export function updateTypeOperatorNode(node: TypeOperatorNode, type: TypeNode) { + return node.type !== type ? updateNode(createTypeOperatorNode(node.operator, type), node) : node; +} +export function createIndexedAccessTypeNode(objectType: TypeNode, indexType: TypeNode) { + const node = (createSynthesizedNode(SyntaxKind.IndexedAccessType) as IndexedAccessTypeNode); + node.objectType = parenthesizeElementTypeMember(objectType); + node.indexType = indexType; + return node; +} +export function updateIndexedAccessTypeNode(node: IndexedAccessTypeNode, objectType: TypeNode, indexType: TypeNode) { + return node.objectType !== objectType + || node.indexType !== indexType + ? updateNode(createIndexedAccessTypeNode(objectType, indexType), node) + : node; +} +export function createMappedTypeNode(readonlyToken: ReadonlyToken | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode { + const node = (createSynthesizedNode(SyntaxKind.MappedType) as MappedTypeNode); + node.readonlyToken = readonlyToken; + node.typeParameter = typeParameter; + node.questionToken = questionToken; + node.type = type; + return node; +} +export function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyToken | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode { + return node.readonlyToken !== readonlyToken + || node.typeParameter !== typeParameter + || node.questionToken !== questionToken + || node.type !== type + ? updateNode(createMappedTypeNode(readonlyToken, typeParameter, questionToken, type), node) + : node; +} +export function createLiteralTypeNode(literal: LiteralTypeNode["literal"]) { + const node = (createSynthesizedNode(SyntaxKind.LiteralType) as LiteralTypeNode); + node.literal = literal; + return node; +} +export function updateLiteralTypeNode(node: LiteralTypeNode, literal: LiteralTypeNode["literal"]) { + return node.literal !== literal + ? updateNode(createLiteralTypeNode(literal), node) + : node; +} +// Binding Patterns +export function createObjectBindingPattern(elements: readonly BindingElement[]) { + const node = (createSynthesizedNode(SyntaxKind.ObjectBindingPattern)); + node.elements = createNodeArray(elements); + return node; +} +export function updateObjectBindingPattern(node: ObjectBindingPattern, elements: readonly BindingElement[]) { + return node.elements !== elements + ? updateNode(createObjectBindingPattern(elements), node) + : node; +} +export function createArrayBindingPattern(elements: readonly ArrayBindingElement[]) { + const node = (createSynthesizedNode(SyntaxKind.ArrayBindingPattern)); + node.elements = createNodeArray(elements); + return node; +} +export function updateArrayBindingPattern(node: ArrayBindingPattern, elements: readonly ArrayBindingElement[]) { + return node.elements !== elements + ? updateNode(createArrayBindingPattern(elements), node) + : node; +} +export function createBindingElement(dotDotDotToken: DotDotDotToken | undefined, propertyName: string | PropertyName | undefined, name: string | BindingName, initializer?: Expression) { + const node = (createSynthesizedNode(SyntaxKind.BindingElement)); + node.dotDotDotToken = dotDotDotToken; + node.propertyName = asName(propertyName); + node.name = asName(name); + node.initializer = initializer; + return node; +} +export function updateBindingElement(node: BindingElement, dotDotDotToken: DotDotDotToken | undefined, propertyName: PropertyName | undefined, name: BindingName, initializer: Expression | undefined) { + return node.propertyName !== propertyName + || node.dotDotDotToken !== dotDotDotToken + || node.name !== name + || node.initializer !== initializer + ? updateNode(createBindingElement(dotDotDotToken, propertyName, name, initializer), node) + : node; +} +// Expression +export function createArrayLiteral(elements?: readonly Expression[], multiLine?: boolean) { + const node = (createSynthesizedNode(SyntaxKind.ArrayLiteralExpression)); + node.elements = parenthesizeListElements(createNodeArray(elements)); + if (multiLine) + node.multiLine = true; + return node; +} +export function updateArrayLiteral(node: ArrayLiteralExpression, elements: readonly Expression[]) { + return node.elements !== elements + ? updateNode(createArrayLiteral(elements, node.multiLine), node) + : node; +} +export function createObjectLiteral(properties?: readonly ObjectLiteralElementLike[], multiLine?: boolean) { + const node = (createSynthesizedNode(SyntaxKind.ObjectLiteralExpression)); + node.properties = createNodeArray(properties); + if (multiLine) + node.multiLine = true; + return node; +} +export function updateObjectLiteral(node: ObjectLiteralExpression, properties: readonly ObjectLiteralElementLike[]) { + return node.properties !== properties + ? updateNode(createObjectLiteral(properties, node.multiLine), node) + : node; +} +export function createPropertyAccess(expression: Expression, name: string | Identifier) { + const node = (createSynthesizedNode(SyntaxKind.PropertyAccessExpression)); + node.expression = parenthesizeForAccess(expression); + node.name = asName(name); + setEmitFlags(node, EmitFlags.NoIndentation); + return node; +} +export function updatePropertyAccess(node: PropertyAccessExpression, expression: Expression, name: Identifier) { + if (isOptionalChain(node)) { + return updatePropertyAccessChain(node, expression, node.questionDotToken, name); + } + // Because we are updating existed propertyAccess we want to inherit its emitFlags + // instead of using the default from createPropertyAccess + return node.expression !== expression + || node.name !== name + ? updateNode(setEmitFlags(createPropertyAccess(expression, name), getEmitFlags(node)), node) + : node; +} +export function createPropertyAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, name: string | Identifier) { + const node = (createSynthesizedNode(SyntaxKind.PropertyAccessExpression)); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizeForAccess(expression); + node.questionDotToken = questionDotToken; + node.name = asName(name); + setEmitFlags(node, EmitFlags.NoIndentation); + return node; +} +export function updatePropertyAccessChain(node: PropertyAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, name: Identifier) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a PropertyAccessExpression using updatePropertyAccessChain. Use updatePropertyAccess instead."); + // Because we are updating an existing PropertyAccessChain we want to inherit its emitFlags + // instead of using the default from createPropertyAccess + return node.expression !== expression + || node.questionDotToken !== questionDotToken + || node.name !== name + ? updateNode(setEmitFlags(createPropertyAccessChain(expression, questionDotToken, name), getEmitFlags(node)), node) + : node; +} +export function createElementAccess(expression: Expression, index: number | Expression) { + const node = (createSynthesizedNode(SyntaxKind.ElementAccessExpression)); + node.expression = parenthesizeForAccess(expression); + node.argumentExpression = asExpression(index); + return node; +} +export function updateElementAccess(node: ElementAccessExpression, expression: Expression, argumentExpression: Expression) { + if (isOptionalChain(node)) { + return updateElementAccessChain(node, expression, node.questionDotToken, argumentExpression); + } + return node.expression !== expression + || node.argumentExpression !== argumentExpression + ? updateNode(createElementAccess(expression, argumentExpression), node) + : node; +} +export function createElementAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, index: number | Expression) { + const node = (createSynthesizedNode(SyntaxKind.ElementAccessExpression)); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizeForAccess(expression); + node.questionDotToken = questionDotToken; + node.argumentExpression = asExpression(index); + return node; +} +export function updateElementAccessChain(node: ElementAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, argumentExpression: Expression) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update an ElementAccessExpression using updateElementAccessChain. Use updateElementAccess instead."); + return node.expression !== expression + || node.questionDotToken !== questionDotToken + || node.argumentExpression !== argumentExpression + ? updateNode(createElementAccessChain(expression, questionDotToken, argumentExpression), node) + : node; +} +export function createCall(expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { + const node = (createSynthesizedNode(SyntaxKind.CallExpression)); + node.expression = parenthesizeForAccess(expression); + node.typeArguments = asNodeArray(typeArguments); + node.arguments = parenthesizeListElements(createNodeArray(argumentsArray)); + return node; +} +export function updateCall(node: CallExpression, expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[]) { + if (isOptionalChain(node)) { + return updateCallChain(node, expression, node.questionDotToken, typeArguments, argumentsArray); + } + return node.expression !== expression + || node.typeArguments !== typeArguments + || node.arguments !== argumentsArray + ? updateNode(createCall(expression, typeArguments, argumentsArray), node) + : node; +} +export function createCallChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { + const node = (createSynthesizedNode(SyntaxKind.CallExpression)); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizeForAccess(expression); + node.questionDotToken = questionDotToken; + node.typeArguments = asNodeArray(typeArguments); + node.arguments = parenthesizeListElements(createNodeArray(argumentsArray)); + return node; +} +export function updateCallChain(node: CallChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[]) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a CallExpression using updateCallChain. Use updateCall instead."); + return node.expression !== expression + || node.questionDotToken !== questionDotToken + || node.typeArguments !== typeArguments + || node.arguments !== argumentsArray + ? updateNode(createCallChain(expression, questionDotToken, typeArguments, argumentsArray), node) + : node; +} +export function createNew(expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { + const node = (createSynthesizedNode(SyntaxKind.NewExpression)); + node.expression = parenthesizeForNew(expression); + node.typeArguments = asNodeArray(typeArguments); + node.arguments = argumentsArray ? parenthesizeListElements(createNodeArray(argumentsArray)) : undefined; + return node; +} +export function updateNew(node: NewExpression, expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { + return node.expression !== expression + || node.typeArguments !== typeArguments + || node.arguments !== argumentsArray + ? updateNode(createNew(expression, typeArguments, argumentsArray), node) + : node; +} +/** @deprecated */ export function createTaggedTemplate(tag: Expression, template: TemplateLiteral): TaggedTemplateExpression; +export function createTaggedTemplate(tag: Expression, typeArguments: readonly TypeNode[] | undefined, template: TemplateLiteral): TaggedTemplateExpression; +/** @internal */ +export function createTaggedTemplate(tag: Expression, typeArgumentsOrTemplate: readonly TypeNode[] | TemplateLiteral | undefined, template?: TemplateLiteral): TaggedTemplateExpression; +export function createTaggedTemplate(tag: Expression, typeArgumentsOrTemplate: readonly TypeNode[] | TemplateLiteral | undefined, template?: TemplateLiteral) { + const node = (createSynthesizedNode(SyntaxKind.TaggedTemplateExpression)); + node.tag = parenthesizeForAccess(tag); + if (template) { + node.typeArguments = asNodeArray((typeArgumentsOrTemplate as readonly TypeNode[])); + node.template = template; + } + else { + node.typeArguments = undefined; + node.template = (typeArgumentsOrTemplate as TemplateLiteral); + } + return node; +} +/** @deprecated */ export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, template: TemplateLiteral): TaggedTemplateExpression; +export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, typeArguments: readonly TypeNode[] | undefined, template: TemplateLiteral): TaggedTemplateExpression; +export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, typeArgumentsOrTemplate: readonly TypeNode[] | TemplateLiteral | undefined, template?: TemplateLiteral) { + return node.tag !== tag + || (template + ? node.typeArguments !== typeArgumentsOrTemplate || node.template !== template + : node.typeArguments !== undefined || node.template !== typeArgumentsOrTemplate) + ? updateNode(createTaggedTemplate(tag, typeArgumentsOrTemplate, template), node) + : node; +} +export function createTypeAssertion(type: TypeNode, expression: Expression) { + const node = (createSynthesizedNode(SyntaxKind.TypeAssertionExpression)); + node.type = type; + node.expression = parenthesizePrefixOperand(expression); + return node; +} +export function updateTypeAssertion(node: TypeAssertion, type: TypeNode, expression: Expression) { + return node.type !== type + || node.expression !== expression + ? updateNode(createTypeAssertion(type, expression), node) + : node; +} +export function createParen(expression: Expression) { + const node = (createSynthesizedNode(SyntaxKind.ParenthesizedExpression)); + node.expression = expression; + return node; +} +export function updateParen(node: ParenthesizedExpression, expression: Expression) { + return node.expression !== expression + ? updateNode(createParen(expression), node) + : node; +} +export function createFunctionExpression(modifiers: readonly Modifier[] | undefined, asteriskToken: AsteriskToken | undefined, name: string | Identifier | undefined, typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[] | undefined, type: TypeNode | undefined, body: Block) { + const node = (createSynthesizedNode(SyntaxKind.FunctionExpression)); + node.modifiers = asNodeArray(modifiers); + node.asteriskToken = asteriskToken; + node.name = asName(name); + node.typeParameters = asNodeArray(typeParameters); + node.parameters = createNodeArray(parameters); + node.type = type; + node.body = body; + return node; +} +export function updateFunctionExpression(node: FunctionExpression, modifiers: readonly Modifier[] | undefined, asteriskToken: AsteriskToken | undefined, name: Identifier | undefined, typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined, body: Block) { + return node.name !== name + || node.modifiers !== modifiers + || node.asteriskToken !== asteriskToken + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + || node.body !== body + ? updateNode(createFunctionExpression(modifiers, asteriskToken, name, typeParameters, parameters, type, body), node) + : node; +} +export function createArrowFunction(modifiers: readonly Modifier[] | undefined, typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined, equalsGreaterThanToken: EqualsGreaterThanToken | undefined, body: ConciseBody) { + const node = (createSynthesizedNode(SyntaxKind.ArrowFunction)); + node.modifiers = asNodeArray(modifiers); + node.typeParameters = asNodeArray(typeParameters); + node.parameters = createNodeArray(parameters); + node.type = type; + node.equalsGreaterThanToken = equalsGreaterThanToken || createToken(SyntaxKind.EqualsGreaterThanToken); + node.body = parenthesizeConciseBody(body); + return node; +} +export function updateArrowFunction(node: ArrowFunction, modifiers: readonly Modifier[] | undefined, typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined, equalsGreaterThanToken: Token, body: ConciseBody): ArrowFunction { + return node.modifiers !== modifiers + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + || node.equalsGreaterThanToken !== equalsGreaterThanToken + || node.body !== body + ? updateNode(createArrowFunction(modifiers, typeParameters, parameters, type, equalsGreaterThanToken, body), node) + : node; +} +export function createDelete(expression: Expression) { + const node = (createSynthesizedNode(SyntaxKind.DeleteExpression)); + node.expression = parenthesizePrefixOperand(expression); + return node; +} +export function updateDelete(node: DeleteExpression, expression: Expression) { + return node.expression !== expression + ? updateNode(createDelete(expression), node) + : node; +} +export function createTypeOf(expression: Expression) { + const node = (createSynthesizedNode(SyntaxKind.TypeOfExpression)); + node.expression = parenthesizePrefixOperand(expression); + return node; +} +export function updateTypeOf(node: TypeOfExpression, expression: Expression) { + return node.expression !== expression + ? updateNode(createTypeOf(expression), node) + : node; +} +export function createVoid(expression: Expression) { + const node = (createSynthesizedNode(SyntaxKind.VoidExpression)); + node.expression = parenthesizePrefixOperand(expression); + return node; +} +export function updateVoid(node: VoidExpression, expression: Expression) { + return node.expression !== expression + ? updateNode(createVoid(expression), node) + : node; +} +export function createAwait(expression: Expression) { + const node = (createSynthesizedNode(SyntaxKind.AwaitExpression)); + node.expression = parenthesizePrefixOperand(expression); + return node; +} +export function updateAwait(node: AwaitExpression, expression: Expression) { + return node.expression !== expression + ? updateNode(createAwait(expression), node) + : node; +} +export function createPrefix(operator: PrefixUnaryOperator, operand: Expression) { + const node = (createSynthesizedNode(SyntaxKind.PrefixUnaryExpression)); + node.operator = operator; + node.operand = parenthesizePrefixOperand(operand); + return node; +} +export function updatePrefix(node: PrefixUnaryExpression, operand: Expression) { + return node.operand !== operand + ? updateNode(createPrefix(node.operator, operand), node) + : node; +} +export function createPostfix(operand: Expression, operator: PostfixUnaryOperator) { + const node = (createSynthesizedNode(SyntaxKind.PostfixUnaryExpression)); + node.operand = parenthesizePostfixOperand(operand); + node.operator = operator; + return node; +} +export function updatePostfix(node: PostfixUnaryExpression, operand: Expression) { + return node.operand !== operand + ? updateNode(createPostfix(operand, node.operator), node) + : node; +} +export function createBinary(left: Expression, operator: BinaryOperator | BinaryOperatorToken, right: Expression) { + const node = (createSynthesizedNode(SyntaxKind.BinaryExpression)); + const operatorToken = asToken(operator); + const operatorKind = operatorToken.kind; + node.left = parenthesizeBinaryOperand(operatorKind, left, /*isLeftSideOfBinary*/ true, /*leftOperand*/ undefined); + node.operatorToken = operatorToken; + node.right = parenthesizeBinaryOperand(operatorKind, right, /*isLeftSideOfBinary*/ false, node.left); + return node; +} +export function updateBinary(node: BinaryExpression, left: Expression, right: Expression, operator?: BinaryOperator | BinaryOperatorToken) { + return node.left !== left + || node.right !== right + ? updateNode(createBinary(left, operator || node.operatorToken, right), node) + : node; +} +/** @deprecated */ export function createConditional(condition: Expression, whenTrue: Expression, whenFalse: Expression): ConditionalExpression; +export function createConditional(condition: Expression, questionToken: QuestionToken, whenTrue: Expression, colonToken: ColonToken, whenFalse: Expression): ConditionalExpression; +export function createConditional(condition: Expression, questionTokenOrWhenTrue: QuestionToken | Expression, whenTrueOrWhenFalse: Expression, colonToken?: ColonToken, whenFalse?: Expression) { + const node = (createSynthesizedNode(SyntaxKind.ConditionalExpression)); + node.condition = parenthesizeForConditionalHead(condition); + node.questionToken = whenFalse ? questionTokenOrWhenTrue : createToken(SyntaxKind.QuestionToken); + node.whenTrue = parenthesizeSubexpressionOfConditionalExpression(whenFalse ? whenTrueOrWhenFalse : questionTokenOrWhenTrue); + node.colonToken = whenFalse ? colonToken! : createToken(SyntaxKind.ColonToken); + node.whenFalse = parenthesizeSubexpressionOfConditionalExpression(whenFalse ? whenFalse : whenTrueOrWhenFalse); + return node; +} +export function updateConditional(node: ConditionalExpression, condition: Expression, questionToken: Token, whenTrue: Expression, colonToken: Token, whenFalse: Expression): ConditionalExpression { + return node.condition !== condition + || node.questionToken !== questionToken + || node.whenTrue !== whenTrue + || node.colonToken !== colonToken + || node.whenFalse !== whenFalse + ? updateNode(createConditional(condition, questionToken, whenTrue, colonToken, whenFalse), node) + : node; +} +export function createTemplateExpression(head: TemplateHead, templateSpans: readonly TemplateSpan[]) { + const node = (createSynthesizedNode(SyntaxKind.TemplateExpression)); + node.head = head; + node.templateSpans = createNodeArray(templateSpans); + return node; +} +export function updateTemplateExpression(node: TemplateExpression, head: TemplateHead, templateSpans: readonly TemplateSpan[]) { + return node.head !== head + || node.templateSpans !== templateSpans + ? updateNode(createTemplateExpression(head, templateSpans), node) + : node; +} +let rawTextScanner: Scanner | undefined; +const invalidValueSentinel: object = {}; +function getCookedText(kind: TemplateLiteralToken["kind"], rawText: string) { + if (!rawTextScanner) { + rawTextScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, LanguageVariant.Standard); + } + switch (kind) { + case SyntaxKind.NoSubstitutionTemplateLiteral: + rawTextScanner.setText("`" + rawText + "`"); + break; + case SyntaxKind.TemplateHead: + rawTextScanner.setText("`" + rawText + "${"); + break; + case SyntaxKind.TemplateMiddle: + rawTextScanner.setText("}" + rawText + "${"); + break; + case SyntaxKind.TemplateTail: + rawTextScanner.setText("}" + rawText + "`"); + break; + } + let token = rawTextScanner.scan(); + if (token === SyntaxKind.CloseBracketToken) { + token = rawTextScanner.reScanTemplateToken(); + } + if (rawTextScanner.isUnterminated()) { + rawTextScanner.setText(undefined); + return invalidValueSentinel; + } + let tokenValue: string | undefined; + switch (token) { + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TemplateHead: + case SyntaxKind.TemplateMiddle: + case SyntaxKind.TemplateTail: + tokenValue = rawTextScanner.getTokenValue(); + break; + } + if (rawTextScanner.scan() !== SyntaxKind.EndOfFileToken) { + rawTextScanner.setText(undefined); + return invalidValueSentinel; } - - function tryAddPropertyAssignment(properties: Push, propertyName: string, expression: Expression | undefined) { - if (expression) { - properties.push(createPropertyAssignment(propertyName, expression)); - return true; + rawTextScanner.setText(undefined); + return tokenValue; +} +function createTemplateLiteralLikeNode(kind: TemplateLiteralToken["kind"], text: string, rawText: string | undefined) { + const node = (createSynthesizedNode(kind)); + node.text = text; + if (rawText === undefined || text === rawText) { + node.rawText = rawText; + } + else { + const cooked = getCookedText(kind, rawText); + if (typeof cooked === "object") { + return Debug.fail("Invalid raw text"); } - return false; - } - - /* @internal */ - export function createPropertyDescriptor(attributes: PropertyDescriptorAttributes, singleLine?: boolean) { - const properties: PropertyAssignment[] = []; - tryAddPropertyAssignment(properties, "enumerable", asExpression(attributes.enumerable)); - tryAddPropertyAssignment(properties, "configurable", asExpression(attributes.configurable)); - - let isData = tryAddPropertyAssignment(properties, "writable", asExpression(attributes.writable)); - isData = tryAddPropertyAssignment(properties, "value", attributes.value) || isData; - - let isAccessor = tryAddPropertyAssignment(properties, "get", attributes.get); - isAccessor = tryAddPropertyAssignment(properties, "set", attributes.set) || isAccessor; - - Debug.assert(!(isData && isAccessor), "A PropertyDescriptor may not be both an accessor descriptor and a data descriptor."); - return createObjectLiteral(properties, !singleLine); - } - - export function updateMethod( - node: MethodDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: PropertyName, - questionToken: QuestionToken | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.asteriskToken !== asteriskToken - || node.name !== name - || node.questionToken !== questionToken - || node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - || node.body !== body - ? updateNode(createMethod(decorators, modifiers, asteriskToken, name, questionToken, typeParameters, parameters, type, body), node) - : node; - } - - export function createConstructor(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, parameters: readonly ParameterDeclaration[], body: Block | undefined) { - const node = createSynthesizedNode(SyntaxKind.Constructor); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.typeParameters = undefined; - node.parameters = createNodeArray(parameters); - node.type = undefined; - node.body = body; - return node; - } - - export function updateConstructor( - node: ConstructorDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - parameters: readonly ParameterDeclaration[], - body: Block | undefined) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.parameters !== parameters - || node.body !== body - ? updateNode(createConstructor(decorators, modifiers, parameters, body), node) - : node; - } - - export function createGetAccessor( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined) { - const node = createSynthesizedNode(SyntaxKind.GetAccessor); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.name = asName(name); - node.typeParameters = undefined; - node.parameters = createNodeArray(parameters); - node.type = type; - node.body = body; - return node; - } - - export function updateGetAccessor( - node: GetAccessorDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: PropertyName, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.parameters !== parameters - || node.type !== type - || node.body !== body - ? updateNode(createGetAccessor(decorators, modifiers, name, parameters, type, body), node) - : node; - } - - export function createSetAccessor( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - parameters: readonly ParameterDeclaration[], - body: Block | undefined) { - const node = createSynthesizedNode(SyntaxKind.SetAccessor); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.name = asName(name); - node.typeParameters = undefined; - node.parameters = createNodeArray(parameters); - node.body = body; - return node; - } - - export function updateSetAccessor( - node: SetAccessorDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: PropertyName, - parameters: readonly ParameterDeclaration[], - body: Block | undefined) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.parameters !== parameters - || node.body !== body - ? updateNode(createSetAccessor(decorators, modifiers, name, parameters, body), node) - : node; - } - - export function createCallSignature(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined) { - return createSignatureDeclaration(SyntaxKind.CallSignature, typeParameters, parameters, type) as CallSignatureDeclaration; - } - - export function updateCallSignature(node: CallSignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { - return updateSignatureDeclaration(node, typeParameters, parameters, type); - } - - export function createConstructSignature(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined) { - return createSignatureDeclaration(SyntaxKind.ConstructSignature, typeParameters, parameters, type) as ConstructSignatureDeclaration; - } - - export function updateConstructSignature(node: ConstructSignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { - return updateSignatureDeclaration(node, typeParameters, parameters, type); - } - - export function createIndexSignature( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode): IndexSignatureDeclaration { - const node = createSynthesizedNode(SyntaxKind.IndexSignature) as IndexSignatureDeclaration; - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.parameters = createNodeArray(parameters); - node.type = type; - return node; - } - - export function updateIndexSignature( - node: IndexSignatureDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode) { - return node.parameters !== parameters - || node.type !== type - || node.decorators !== decorators - || node.modifiers !== modifiers - ? updateNode(createIndexSignature(decorators, modifiers, parameters, type), node) - : node; - } - - /* @internal */ - export function createSignatureDeclaration(kind: SyntaxKind, typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined, typeArguments?: readonly TypeNode[] | undefined) { - const node = createSynthesizedNode(kind) as SignatureDeclaration; - node.typeParameters = asNodeArray(typeParameters); - node.parameters = asNodeArray(parameters); - node.type = type; - node.typeArguments = asNodeArray(typeArguments); - return node; - } - - function updateSignatureDeclaration(node: T, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): T { - return node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - ? updateNode(createSignatureDeclaration(node.kind, typeParameters, parameters, type), node) - : node; - } - - // Types - - export function createKeywordTypeNode(kind: KeywordTypeNode["kind"]) { - return createSynthesizedNode(kind); - } - - export function createTypePredicateNode(parameterName: Identifier | ThisTypeNode | string, type: TypeNode) { - return createTypePredicateNodeWithModifier(/*assertsModifier*/ undefined, parameterName, type); - } - - export function createTypePredicateNodeWithModifier(assertsModifier: AssertsToken | undefined, parameterName: Identifier | ThisTypeNode | string, type: TypeNode | undefined) { - const node = createSynthesizedNode(SyntaxKind.TypePredicate) as TypePredicateNode; - node.assertsModifier = assertsModifier; - node.parameterName = asName(parameterName); - node.type = type; - return node; - } - - export function updateTypePredicateNode(node: TypePredicateNode, parameterName: Identifier | ThisTypeNode, type: TypeNode) { - return updateTypePredicateNodeWithModifier(node, node.assertsModifier, parameterName, type); - } - - export function updateTypePredicateNodeWithModifier(node: TypePredicateNode, assertsModifier: AssertsToken | undefined, parameterName: Identifier | ThisTypeNode, type: TypeNode | undefined) { - return node.assertsModifier !== assertsModifier - || node.parameterName !== parameterName - || node.type !== type - ? updateNode(createTypePredicateNodeWithModifier(assertsModifier, parameterName, type), node) - : node; - } - - export function createTypeReferenceNode(typeName: string | EntityName, typeArguments: readonly TypeNode[] | undefined) { - const node = createSynthesizedNode(SyntaxKind.TypeReference) as TypeReferenceNode; - node.typeName = asName(typeName); - node.typeArguments = typeArguments && parenthesizeTypeParameters(typeArguments); - return node; - } - - export function updateTypeReferenceNode(node: TypeReferenceNode, typeName: EntityName, typeArguments: NodeArray | undefined) { - return node.typeName !== typeName - || node.typeArguments !== typeArguments - ? updateNode(createTypeReferenceNode(typeName, typeArguments), node) - : node; + Debug.assert(text === cooked, "Expected argument 'text' to be the normalized (i.e. 'cooked') version of argument 'rawText'."); + node.rawText = rawText; } - - export function createFunctionTypeNode(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined) { - return createSignatureDeclaration(SyntaxKind.FunctionType, typeParameters, parameters, type) as FunctionTypeNode; - } - - export function updateFunctionTypeNode(node: FunctionTypeNode, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { - return updateSignatureDeclaration(node, typeParameters, parameters, type); - } - - export function createConstructorTypeNode(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined) { - return createSignatureDeclaration(SyntaxKind.ConstructorType, typeParameters, parameters, type) as ConstructorTypeNode; - } - - export function updateConstructorTypeNode(node: ConstructorTypeNode, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { - return updateSignatureDeclaration(node, typeParameters, parameters, type); - } - - export function createTypeQueryNode(exprName: EntityName) { - const node = createSynthesizedNode(SyntaxKind.TypeQuery) as TypeQueryNode; - node.exprName = exprName; - return node; - } - - export function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName) { - return node.exprName !== exprName - ? updateNode(createTypeQueryNode(exprName), node) - : node; - } - - export function createTypeLiteralNode(members: readonly TypeElement[] | undefined) { - const node = createSynthesizedNode(SyntaxKind.TypeLiteral) as TypeLiteralNode; - node.members = createNodeArray(members); - return node; - } - - export function updateTypeLiteralNode(node: TypeLiteralNode, members: NodeArray) { - return node.members !== members - ? updateNode(createTypeLiteralNode(members), node) - : node; - } - - export function createArrayTypeNode(elementType: TypeNode) { - const node = createSynthesizedNode(SyntaxKind.ArrayType) as ArrayTypeNode; - node.elementType = parenthesizeArrayTypeMember(elementType); - return node; - } - - export function updateArrayTypeNode(node: ArrayTypeNode, elementType: TypeNode): ArrayTypeNode { - return node.elementType !== elementType - ? updateNode(createArrayTypeNode(elementType), node) - : node; - } - - export function createTupleTypeNode(elementTypes: readonly TypeNode[]) { - const node = createSynthesizedNode(SyntaxKind.TupleType) as TupleTypeNode; - node.elementTypes = createNodeArray(elementTypes); - return node; - } - - export function updateTupleTypeNode(node: TupleTypeNode, elementTypes: readonly TypeNode[]) { - return node.elementTypes !== elementTypes - ? updateNode(createTupleTypeNode(elementTypes), node) - : node; - } - - export function createOptionalTypeNode(type: TypeNode) { - const node = createSynthesizedNode(SyntaxKind.OptionalType) as OptionalTypeNode; - node.type = parenthesizeArrayTypeMember(type); - return node; - } - - export function updateOptionalTypeNode(node: OptionalTypeNode, type: TypeNode): OptionalTypeNode { - return node.type !== type - ? updateNode(createOptionalTypeNode(type), node) - : node; - } - - export function createRestTypeNode(type: TypeNode) { - const node = createSynthesizedNode(SyntaxKind.RestType) as RestTypeNode; - node.type = type; - return node; - } - - export function updateRestTypeNode(node: RestTypeNode, type: TypeNode): RestTypeNode { - return node.type !== type - ? updateNode(createRestTypeNode(type), node) - : node; - } - - export function createUnionTypeNode(types: readonly TypeNode[]): UnionTypeNode { - return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, types); - } - - export function updateUnionTypeNode(node: UnionTypeNode, types: NodeArray) { - return updateUnionOrIntersectionTypeNode(node, types); - } - - export function createIntersectionTypeNode(types: readonly TypeNode[]): IntersectionTypeNode { - return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, types); - } - - export function updateIntersectionTypeNode(node: IntersectionTypeNode, types: NodeArray) { - return updateUnionOrIntersectionTypeNode(node, types); - } - - export function createUnionOrIntersectionTypeNode(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, types: readonly TypeNode[]) { - const node = createSynthesizedNode(kind) as UnionTypeNode | IntersectionTypeNode; - node.types = parenthesizeElementTypeMembers(types); - return node; - } - - function updateUnionOrIntersectionTypeNode(node: T, types: NodeArray): T { - return node.types !== types - ? updateNode(createUnionOrIntersectionTypeNode(node.kind, types), node) - : node; - } - - export function createConditionalTypeNode(checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) { - const node = createSynthesizedNode(SyntaxKind.ConditionalType) as ConditionalTypeNode; - node.checkType = parenthesizeConditionalTypeMember(checkType); - node.extendsType = parenthesizeConditionalTypeMember(extendsType); - node.trueType = trueType; - node.falseType = falseType; - return node; - } - - export function updateConditionalTypeNode(node: ConditionalTypeNode, checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) { - return node.checkType !== checkType - || node.extendsType !== extendsType - || node.trueType !== trueType - || node.falseType !== falseType - ? updateNode(createConditionalTypeNode(checkType, extendsType, trueType, falseType), node) - : node; - } - - export function createInferTypeNode(typeParameter: TypeParameterDeclaration) { - const node = createSynthesizedNode(SyntaxKind.InferType); - node.typeParameter = typeParameter; - return node; - } - - export function updateInferTypeNode(node: InferTypeNode, typeParameter: TypeParameterDeclaration) { - return node.typeParameter !== typeParameter - ? updateNode(createInferTypeNode(typeParameter), node) - : node; - } - - export function createImportTypeNode(argument: TypeNode, qualifier?: EntityName, typeArguments?: readonly TypeNode[], isTypeOf?: boolean) { - const node = createSynthesizedNode(SyntaxKind.ImportType); - node.argument = argument; - node.qualifier = qualifier; - node.typeArguments = parenthesizeTypeParameters(typeArguments); - node.isTypeOf = isTypeOf; - return node; - } - - export function updateImportTypeNode(node: ImportTypeNode, argument: TypeNode, qualifier?: EntityName, typeArguments?: readonly TypeNode[], isTypeOf?: boolean) { - return node.argument !== argument - || node.qualifier !== qualifier - || node.typeArguments !== typeArguments - || node.isTypeOf !== isTypeOf - ? updateNode(createImportTypeNode(argument, qualifier, typeArguments, isTypeOf), node) - : node; - } - - export function createParenthesizedType(type: TypeNode) { - const node = createSynthesizedNode(SyntaxKind.ParenthesizedType); - node.type = type; - return node; - } - - export function updateParenthesizedType(node: ParenthesizedTypeNode, type: TypeNode) { - return node.type !== type - ? updateNode(createParenthesizedType(type), node) - : node; - } - - export function createThisTypeNode() { - return createSynthesizedNode(SyntaxKind.ThisType); - } - - export function createTypeOperatorNode(type: TypeNode): TypeOperatorNode; - export function createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword, type: TypeNode): TypeOperatorNode; - export function createTypeOperatorNode(operatorOrType: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword | TypeNode, type?: TypeNode) { - const node = createSynthesizedNode(SyntaxKind.TypeOperator) as TypeOperatorNode; - node.operator = typeof operatorOrType === "number" ? operatorOrType : SyntaxKind.KeyOfKeyword; - node.type = parenthesizeElementTypeMember(typeof operatorOrType === "number" ? type! : operatorOrType); - return node; - } - - export function updateTypeOperatorNode(node: TypeOperatorNode, type: TypeNode) { - return node.type !== type ? updateNode(createTypeOperatorNode(node.operator, type), node) : node; - } - - export function createIndexedAccessTypeNode(objectType: TypeNode, indexType: TypeNode) { - const node = createSynthesizedNode(SyntaxKind.IndexedAccessType) as IndexedAccessTypeNode; - node.objectType = parenthesizeElementTypeMember(objectType); - node.indexType = indexType; - return node; - } - - export function updateIndexedAccessTypeNode(node: IndexedAccessTypeNode, objectType: TypeNode, indexType: TypeNode) { - return node.objectType !== objectType - || node.indexType !== indexType - ? updateNode(createIndexedAccessTypeNode(objectType, indexType), node) - : node; - } - - export function createMappedTypeNode(readonlyToken: ReadonlyToken | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode { - const node = createSynthesizedNode(SyntaxKind.MappedType) as MappedTypeNode; - node.readonlyToken = readonlyToken; - node.typeParameter = typeParameter; - node.questionToken = questionToken; - node.type = type; - return node; - } - - export function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyToken | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode { - return node.readonlyToken !== readonlyToken - || node.typeParameter !== typeParameter - || node.questionToken !== questionToken - || node.type !== type - ? updateNode(createMappedTypeNode(readonlyToken, typeParameter, questionToken, type), node) - : node; - } - - export function createLiteralTypeNode(literal: LiteralTypeNode["literal"]) { - const node = createSynthesizedNode(SyntaxKind.LiteralType) as LiteralTypeNode; - node.literal = literal; - return node; - } - - export function updateLiteralTypeNode(node: LiteralTypeNode, literal: LiteralTypeNode["literal"]) { - return node.literal !== literal - ? updateNode(createLiteralTypeNode(literal), node) - : node; - } - - // Binding Patterns - - export function createObjectBindingPattern(elements: readonly BindingElement[]) { - const node = createSynthesizedNode(SyntaxKind.ObjectBindingPattern); - node.elements = createNodeArray(elements); - return node; - } - - export function updateObjectBindingPattern(node: ObjectBindingPattern, elements: readonly BindingElement[]) { - return node.elements !== elements - ? updateNode(createObjectBindingPattern(elements), node) - : node; - } - - export function createArrayBindingPattern(elements: readonly ArrayBindingElement[]) { - const node = createSynthesizedNode(SyntaxKind.ArrayBindingPattern); - node.elements = createNodeArray(elements); - return node; - } - - export function updateArrayBindingPattern(node: ArrayBindingPattern, elements: readonly ArrayBindingElement[]) { - return node.elements !== elements - ? updateNode(createArrayBindingPattern(elements), node) - : node; - } - - export function createBindingElement(dotDotDotToken: DotDotDotToken | undefined, propertyName: string | PropertyName | undefined, name: string | BindingName, initializer?: Expression) { - const node = createSynthesizedNode(SyntaxKind.BindingElement); - node.dotDotDotToken = dotDotDotToken; - node.propertyName = asName(propertyName); - node.name = asName(name); - node.initializer = initializer; - return node; - } - - export function updateBindingElement(node: BindingElement, dotDotDotToken: DotDotDotToken | undefined, propertyName: PropertyName | undefined, name: BindingName, initializer: Expression | undefined) { - return node.propertyName !== propertyName - || node.dotDotDotToken !== dotDotDotToken - || node.name !== name - || node.initializer !== initializer - ? updateNode(createBindingElement(dotDotDotToken, propertyName, name, initializer), node) - : node; - } - - // Expression - - export function createArrayLiteral(elements?: readonly Expression[], multiLine?: boolean) { - const node = createSynthesizedNode(SyntaxKind.ArrayLiteralExpression); - node.elements = parenthesizeListElements(createNodeArray(elements)); - if (multiLine) node.multiLine = true; - return node; - } - - export function updateArrayLiteral(node: ArrayLiteralExpression, elements: readonly Expression[]) { - return node.elements !== elements - ? updateNode(createArrayLiteral(elements, node.multiLine), node) - : node; - } - - export function createObjectLiteral(properties?: readonly ObjectLiteralElementLike[], multiLine?: boolean) { - const node = createSynthesizedNode(SyntaxKind.ObjectLiteralExpression); - node.properties = createNodeArray(properties); - if (multiLine) node.multiLine = true; - return node; - } - - export function updateObjectLiteral(node: ObjectLiteralExpression, properties: readonly ObjectLiteralElementLike[]) { - return node.properties !== properties - ? updateNode(createObjectLiteral(properties, node.multiLine), node) - : node; - } - - export function createPropertyAccess(expression: Expression, name: string | Identifier) { - const node = createSynthesizedNode(SyntaxKind.PropertyAccessExpression); - node.expression = parenthesizeForAccess(expression); - node.name = asName(name); - setEmitFlags(node, EmitFlags.NoIndentation); - return node; - } - - export function updatePropertyAccess(node: PropertyAccessExpression, expression: Expression, name: Identifier) { - if (isOptionalChain(node)) { - return updatePropertyAccessChain(node, expression, node.questionDotToken, name); - } - // Because we are updating existed propertyAccess we want to inherit its emitFlags - // instead of using the default from createPropertyAccess - return node.expression !== expression - || node.name !== name - ? updateNode(setEmitFlags(createPropertyAccess(expression, name), getEmitFlags(node)), node) - : node; - } - - export function createPropertyAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, name: string | Identifier) { - const node = createSynthesizedNode(SyntaxKind.PropertyAccessExpression); - node.flags |= NodeFlags.OptionalChain; - node.expression = parenthesizeForAccess(expression); - node.questionDotToken = questionDotToken; - node.name = asName(name); - setEmitFlags(node, EmitFlags.NoIndentation); - return node; - } - - export function updatePropertyAccessChain(node: PropertyAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, name: Identifier) { - Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a PropertyAccessExpression using updatePropertyAccessChain. Use updatePropertyAccess instead."); - // Because we are updating an existing PropertyAccessChain we want to inherit its emitFlags - // instead of using the default from createPropertyAccess - return node.expression !== expression - || node.questionDotToken !== questionDotToken - || node.name !== name - ? updateNode(setEmitFlags(createPropertyAccessChain(expression, questionDotToken, name), getEmitFlags(node)), node) - : node; - } - - export function createElementAccess(expression: Expression, index: number | Expression) { - const node = createSynthesizedNode(SyntaxKind.ElementAccessExpression); - node.expression = parenthesizeForAccess(expression); - node.argumentExpression = asExpression(index); - return node; - } - - export function updateElementAccess(node: ElementAccessExpression, expression: Expression, argumentExpression: Expression) { - if (isOptionalChain(node)) { - return updateElementAccessChain(node, expression, node.questionDotToken, argumentExpression); - } - return node.expression !== expression - || node.argumentExpression !== argumentExpression - ? updateNode(createElementAccess(expression, argumentExpression), node) - : node; - } - - export function createElementAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, index: number | Expression) { - const node = createSynthesizedNode(SyntaxKind.ElementAccessExpression); - node.flags |= NodeFlags.OptionalChain; - node.expression = parenthesizeForAccess(expression); - node.questionDotToken = questionDotToken; - node.argumentExpression = asExpression(index); - return node; - } - - export function updateElementAccessChain(node: ElementAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, argumentExpression: Expression) { - Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update an ElementAccessExpression using updateElementAccessChain. Use updateElementAccess instead."); - return node.expression !== expression - || node.questionDotToken !== questionDotToken - || node.argumentExpression !== argumentExpression - ? updateNode(createElementAccessChain(expression, questionDotToken, argumentExpression), node) - : node; - } - - export function createCall(expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { - const node = createSynthesizedNode(SyntaxKind.CallExpression); - node.expression = parenthesizeForAccess(expression); - node.typeArguments = asNodeArray(typeArguments); - node.arguments = parenthesizeListElements(createNodeArray(argumentsArray)); - return node; - } - - export function updateCall(node: CallExpression, expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[]) { - if (isOptionalChain(node)) { - return updateCallChain(node, expression, node.questionDotToken, typeArguments, argumentsArray); - } - return node.expression !== expression - || node.typeArguments !== typeArguments - || node.arguments !== argumentsArray - ? updateNode(createCall(expression, typeArguments, argumentsArray), node) - : node; - } - - export function createCallChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { - const node = createSynthesizedNode(SyntaxKind.CallExpression); - node.flags |= NodeFlags.OptionalChain; - node.expression = parenthesizeForAccess(expression); - node.questionDotToken = questionDotToken; - node.typeArguments = asNodeArray(typeArguments); - node.arguments = parenthesizeListElements(createNodeArray(argumentsArray)); - return node; - } - - export function updateCallChain(node: CallChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[]) { - Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a CallExpression using updateCallChain. Use updateCall instead."); - return node.expression !== expression - || node.questionDotToken !== questionDotToken - || node.typeArguments !== typeArguments - || node.arguments !== argumentsArray - ? updateNode(createCallChain(expression, questionDotToken, typeArguments, argumentsArray), node) - : node; - } - - export function createNew(expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { - const node = createSynthesizedNode(SyntaxKind.NewExpression); - node.expression = parenthesizeForNew(expression); - node.typeArguments = asNodeArray(typeArguments); - node.arguments = argumentsArray ? parenthesizeListElements(createNodeArray(argumentsArray)) : undefined; - return node; - } - - export function updateNew(node: NewExpression, expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { - return node.expression !== expression - || node.typeArguments !== typeArguments - || node.arguments !== argumentsArray - ? updateNode(createNew(expression, typeArguments, argumentsArray), node) - : node; - } - - /** @deprecated */ export function createTaggedTemplate(tag: Expression, template: TemplateLiteral): TaggedTemplateExpression; - export function createTaggedTemplate(tag: Expression, typeArguments: readonly TypeNode[] | undefined, template: TemplateLiteral): TaggedTemplateExpression; - /** @internal */ - export function createTaggedTemplate(tag: Expression, typeArgumentsOrTemplate: readonly TypeNode[] | TemplateLiteral | undefined, template?: TemplateLiteral): TaggedTemplateExpression; - export function createTaggedTemplate(tag: Expression, typeArgumentsOrTemplate: readonly TypeNode[] | TemplateLiteral | undefined, template?: TemplateLiteral) { - const node = createSynthesizedNode(SyntaxKind.TaggedTemplateExpression); - node.tag = parenthesizeForAccess(tag); - if (template) { - node.typeArguments = asNodeArray(typeArgumentsOrTemplate as readonly TypeNode[]); - node.template = template; - } - else { - node.typeArguments = undefined; - node.template = typeArgumentsOrTemplate as TemplateLiteral; - } - return node; - } - - /** @deprecated */ export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, template: TemplateLiteral): TaggedTemplateExpression; - export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, typeArguments: readonly TypeNode[] | undefined, template: TemplateLiteral): TaggedTemplateExpression; - export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, typeArgumentsOrTemplate: readonly TypeNode[] | TemplateLiteral | undefined, template?: TemplateLiteral) { - return node.tag !== tag - || (template - ? node.typeArguments !== typeArgumentsOrTemplate || node.template !== template - : node.typeArguments !== undefined || node.template !== typeArgumentsOrTemplate) - ? updateNode(createTaggedTemplate(tag, typeArgumentsOrTemplate, template), node) - : node; - } - - export function createTypeAssertion(type: TypeNode, expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.TypeAssertionExpression); - node.type = type; - node.expression = parenthesizePrefixOperand(expression); - return node; - } - - export function updateTypeAssertion(node: TypeAssertion, type: TypeNode, expression: Expression) { - return node.type !== type - || node.expression !== expression - ? updateNode(createTypeAssertion(type, expression), node) - : node; - } - - export function createParen(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.ParenthesizedExpression); - node.expression = expression; - return node; - } - - export function updateParen(node: ParenthesizedExpression, expression: Expression) { - return node.expression !== expression - ? updateNode(createParen(expression), node) - : node; - } - - export function createFunctionExpression( - modifiers: readonly Modifier[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: string | Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[] | undefined, - type: TypeNode | undefined, - body: Block) { - const node = createSynthesizedNode(SyntaxKind.FunctionExpression); - node.modifiers = asNodeArray(modifiers); - node.asteriskToken = asteriskToken; - node.name = asName(name); - node.typeParameters = asNodeArray(typeParameters); - node.parameters = createNodeArray(parameters); - node.type = type; - node.body = body; - return node; - } - - export function updateFunctionExpression( - node: FunctionExpression, - modifiers: readonly Modifier[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block) { - return node.name !== name - || node.modifiers !== modifiers - || node.asteriskToken !== asteriskToken - || node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - || node.body !== body - ? updateNode(createFunctionExpression(modifiers, asteriskToken, name, typeParameters, parameters, type, body), node) - : node; - } - - export function createArrowFunction( - modifiers: readonly Modifier[] | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - equalsGreaterThanToken: EqualsGreaterThanToken | undefined, - body: ConciseBody) { - const node = createSynthesizedNode(SyntaxKind.ArrowFunction); - node.modifiers = asNodeArray(modifiers); - node.typeParameters = asNodeArray(typeParameters); - node.parameters = createNodeArray(parameters); - node.type = type; - node.equalsGreaterThanToken = equalsGreaterThanToken || createToken(SyntaxKind.EqualsGreaterThanToken); - node.body = parenthesizeConciseBody(body); - return node; - } - export function updateArrowFunction( - node: ArrowFunction, - modifiers: readonly Modifier[] | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - equalsGreaterThanToken: Token, - body: ConciseBody - ): ArrowFunction { - return node.modifiers !== modifiers - || node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - || node.equalsGreaterThanToken !== equalsGreaterThanToken - || node.body !== body - ? updateNode(createArrowFunction(modifiers, typeParameters, parameters, type, equalsGreaterThanToken, body), node) - : node; - } - - export function createDelete(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.DeleteExpression); - node.expression = parenthesizePrefixOperand(expression); - return node; - } - - export function updateDelete(node: DeleteExpression, expression: Expression) { - return node.expression !== expression - ? updateNode(createDelete(expression), node) - : node; - } - - export function createTypeOf(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.TypeOfExpression); - node.expression = parenthesizePrefixOperand(expression); - return node; - } - - export function updateTypeOf(node: TypeOfExpression, expression: Expression) { - return node.expression !== expression - ? updateNode(createTypeOf(expression), node) - : node; - } - - export function createVoid(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.VoidExpression); - node.expression = parenthesizePrefixOperand(expression); - return node; - } - - export function updateVoid(node: VoidExpression, expression: Expression) { - return node.expression !== expression - ? updateNode(createVoid(expression), node) - : node; - } - - export function createAwait(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.AwaitExpression); - node.expression = parenthesizePrefixOperand(expression); - return node; - } - - export function updateAwait(node: AwaitExpression, expression: Expression) { - return node.expression !== expression - ? updateNode(createAwait(expression), node) - : node; - } - - export function createPrefix(operator: PrefixUnaryOperator, operand: Expression) { - const node = createSynthesizedNode(SyntaxKind.PrefixUnaryExpression); - node.operator = operator; - node.operand = parenthesizePrefixOperand(operand); - return node; - } - - export function updatePrefix(node: PrefixUnaryExpression, operand: Expression) { - return node.operand !== operand - ? updateNode(createPrefix(node.operator, operand), node) - : node; - } - - export function createPostfix(operand: Expression, operator: PostfixUnaryOperator) { - const node = createSynthesizedNode(SyntaxKind.PostfixUnaryExpression); - node.operand = parenthesizePostfixOperand(operand); - node.operator = operator; - return node; - } - - export function updatePostfix(node: PostfixUnaryExpression, operand: Expression) { - return node.operand !== operand - ? updateNode(createPostfix(operand, node.operator), node) - : node; - } - - export function createBinary(left: Expression, operator: BinaryOperator | BinaryOperatorToken, right: Expression) { - const node = createSynthesizedNode(SyntaxKind.BinaryExpression); - const operatorToken = asToken(operator); - const operatorKind = operatorToken.kind; - node.left = parenthesizeBinaryOperand(operatorKind, left, /*isLeftSideOfBinary*/ true, /*leftOperand*/ undefined); - node.operatorToken = operatorToken; - node.right = parenthesizeBinaryOperand(operatorKind, right, /*isLeftSideOfBinary*/ false, node.left); - return node; - } - - export function updateBinary(node: BinaryExpression, left: Expression, right: Expression, operator?: BinaryOperator | BinaryOperatorToken) { - return node.left !== left - || node.right !== right - ? updateNode(createBinary(left, operator || node.operatorToken, right), node) - : node; - } - - /** @deprecated */ export function createConditional(condition: Expression, whenTrue: Expression, whenFalse: Expression): ConditionalExpression; - export function createConditional(condition: Expression, questionToken: QuestionToken, whenTrue: Expression, colonToken: ColonToken, whenFalse: Expression): ConditionalExpression; - export function createConditional(condition: Expression, questionTokenOrWhenTrue: QuestionToken | Expression, whenTrueOrWhenFalse: Expression, colonToken?: ColonToken, whenFalse?: Expression) { - const node = createSynthesizedNode(SyntaxKind.ConditionalExpression); - node.condition = parenthesizeForConditionalHead(condition); - node.questionToken = whenFalse ? questionTokenOrWhenTrue : createToken(SyntaxKind.QuestionToken); - node.whenTrue = parenthesizeSubexpressionOfConditionalExpression(whenFalse ? whenTrueOrWhenFalse : questionTokenOrWhenTrue); - node.colonToken = whenFalse ? colonToken! : createToken(SyntaxKind.ColonToken); - node.whenFalse = parenthesizeSubexpressionOfConditionalExpression(whenFalse ? whenFalse : whenTrueOrWhenFalse); - return node; - } - export function updateConditional( - node: ConditionalExpression, - condition: Expression, - questionToken: Token, - whenTrue: Expression, - colonToken: Token, - whenFalse: Expression - ): ConditionalExpression { - return node.condition !== condition - || node.questionToken !== questionToken - || node.whenTrue !== whenTrue - || node.colonToken !== colonToken - || node.whenFalse !== whenFalse - ? updateNode(createConditional(condition, questionToken, whenTrue, colonToken, whenFalse), node) - : node; - } - - export function createTemplateExpression(head: TemplateHead, templateSpans: readonly TemplateSpan[]) { - const node = createSynthesizedNode(SyntaxKind.TemplateExpression); - node.head = head; - node.templateSpans = createNodeArray(templateSpans); - return node; - } - - export function updateTemplateExpression(node: TemplateExpression, head: TemplateHead, templateSpans: readonly TemplateSpan[]) { - return node.head !== head - || node.templateSpans !== templateSpans - ? updateNode(createTemplateExpression(head, templateSpans), node) - : node; - } - - let rawTextScanner: Scanner | undefined; - const invalidValueSentinel: object = {}; - - function getCookedText(kind: TemplateLiteralToken["kind"], rawText: string) { - if (!rawTextScanner) { - rawTextScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, LanguageVariant.Standard); - } - switch (kind) { - case SyntaxKind.NoSubstitutionTemplateLiteral: - rawTextScanner.setText("`" + rawText + "`"); - break; - case SyntaxKind.TemplateHead: - rawTextScanner.setText("`" + rawText + "${"); - break; - case SyntaxKind.TemplateMiddle: - rawTextScanner.setText("}" + rawText + "${"); - break; - case SyntaxKind.TemplateTail: - rawTextScanner.setText("}" + rawText + "`"); - break; - } - - let token = rawTextScanner.scan(); - if (token === SyntaxKind.CloseBracketToken) { - token = rawTextScanner.reScanTemplateToken(); - } - - if (rawTextScanner.isUnterminated()) { - rawTextScanner.setText(undefined); - return invalidValueSentinel; - } - - let tokenValue: string | undefined; - switch (token) { - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.TemplateHead: - case SyntaxKind.TemplateMiddle: - case SyntaxKind.TemplateTail: - tokenValue = rawTextScanner.getTokenValue(); - break; - } - - if (rawTextScanner.scan() !== SyntaxKind.EndOfFileToken) { - rawTextScanner.setText(undefined); - return invalidValueSentinel; - } - - rawTextScanner.setText(undefined); - return tokenValue; - } - - function createTemplateLiteralLikeNode(kind: TemplateLiteralToken["kind"], text: string, rawText: string | undefined) { - const node = createSynthesizedNode(kind); - node.text = text; - if (rawText === undefined || text === rawText) { - node.rawText = rawText; - } - else { - const cooked = getCookedText(kind, rawText); - if (typeof cooked === "object") { - return Debug.fail("Invalid raw text"); - } - - Debug.assert(text === cooked, "Expected argument 'text' to be the normalized (i.e. 'cooked') version of argument 'rawText'."); - node.rawText = rawText; - } - return node; - } - - export function createTemplateHead(text: string, rawText?: string) { - const node = createTemplateLiteralLikeNode(SyntaxKind.TemplateHead, text, rawText); - node.text = text; - return node; - } - - export function createTemplateMiddle(text: string, rawText?: string) { - const node = createTemplateLiteralLikeNode(SyntaxKind.TemplateMiddle, text, rawText); - node.text = text; - return node; - } - - export function createTemplateTail(text: string, rawText?: string) { - const node = createTemplateLiteralLikeNode(SyntaxKind.TemplateTail, text, rawText); - node.text = text; - return node; - } - - export function createNoSubstitutionTemplateLiteral(text: string, rawText?: string) { - const node = createTemplateLiteralLikeNode(SyntaxKind.NoSubstitutionTemplateLiteral, text, rawText); - return node; - } - - export function createYield(expression?: Expression): YieldExpression; - export function createYield(asteriskToken: AsteriskToken | undefined, expression: Expression): YieldExpression; - export function createYield(asteriskTokenOrExpression?: AsteriskToken | undefined | Expression, expression?: Expression) { - const node = createSynthesizedNode(SyntaxKind.YieldExpression); - node.asteriskToken = asteriskTokenOrExpression && asteriskTokenOrExpression.kind === SyntaxKind.AsteriskToken ? asteriskTokenOrExpression : undefined; - node.expression = asteriskTokenOrExpression && asteriskTokenOrExpression.kind !== SyntaxKind.AsteriskToken ? asteriskTokenOrExpression : expression; - return node; - } - - export function updateYield(node: YieldExpression, asteriskToken: AsteriskToken | undefined, expression: Expression) { - return node.expression !== expression - || node.asteriskToken !== asteriskToken - ? updateNode(createYield(asteriskToken, expression), node) - : node; - } - - export function createSpread(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.SpreadElement); - node.expression = parenthesizeExpressionForList(expression); - return node; - } - - export function updateSpread(node: SpreadElement, expression: Expression) { - return node.expression !== expression - ? updateNode(createSpread(expression), node) - : node; - } - - export function createClassExpression( - modifiers: readonly Modifier[] | undefined, - name: string | Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly ClassElement[]) { - const node = createSynthesizedNode(SyntaxKind.ClassExpression); - node.decorators = undefined; - node.modifiers = asNodeArray(modifiers); - node.name = asName(name); - node.typeParameters = asNodeArray(typeParameters); - node.heritageClauses = asNodeArray(heritageClauses); - node.members = createNodeArray(members); - return node; - } - - export function updateClassExpression( - node: ClassExpression, - modifiers: readonly Modifier[] | undefined, - name: Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly ClassElement[]) { - return node.modifiers !== modifiers - || node.name !== name - || node.typeParameters !== typeParameters - || node.heritageClauses !== heritageClauses - || node.members !== members - ? updateNode(createClassExpression(modifiers, name, typeParameters, heritageClauses, members), node) - : node; - } - - export function createOmittedExpression() { - return createSynthesizedNode(SyntaxKind.OmittedExpression); - } - - export function createExpressionWithTypeArguments(typeArguments: readonly TypeNode[] | undefined, expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.ExpressionWithTypeArguments); - node.expression = parenthesizeForAccess(expression); - node.typeArguments = asNodeArray(typeArguments); - return node; - } - - export function updateExpressionWithTypeArguments(node: ExpressionWithTypeArguments, typeArguments: readonly TypeNode[] | undefined, expression: Expression) { - return node.typeArguments !== typeArguments - || node.expression !== expression - ? updateNode(createExpressionWithTypeArguments(typeArguments, expression), node) - : node; - } - - export function createAsExpression(expression: Expression, type: TypeNode) { - const node = createSynthesizedNode(SyntaxKind.AsExpression); - node.expression = expression; - node.type = type; - return node; - } - - export function updateAsExpression(node: AsExpression, expression: Expression, type: TypeNode) { - return node.expression !== expression - || node.type !== type - ? updateNode(createAsExpression(expression, type), node) - : node; - } - - export function createNonNullExpression(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.NonNullExpression); - node.expression = parenthesizeForAccess(expression); - return node; - } - - export function updateNonNullExpression(node: NonNullExpression, expression: Expression) { - return node.expression !== expression - ? updateNode(createNonNullExpression(expression), node) - : node; - } - - export function createMetaProperty(keywordToken: MetaProperty["keywordToken"], name: Identifier) { - const node = createSynthesizedNode(SyntaxKind.MetaProperty); - node.keywordToken = keywordToken; - node.name = name; - return node; - } - - export function updateMetaProperty(node: MetaProperty, name: Identifier) { - return node.name !== name - ? updateNode(createMetaProperty(node.keywordToken, name), node) - : node; - } - - // Misc - - export function createTemplateSpan(expression: Expression, literal: TemplateMiddle | TemplateTail) { - const node = createSynthesizedNode(SyntaxKind.TemplateSpan); - node.expression = expression; - node.literal = literal; - return node; - } - - export function updateTemplateSpan(node: TemplateSpan, expression: Expression, literal: TemplateMiddle | TemplateTail) { - return node.expression !== expression - || node.literal !== literal - ? updateNode(createTemplateSpan(expression, literal), node) - : node; - } - - export function createSemicolonClassElement() { - return createSynthesizedNode(SyntaxKind.SemicolonClassElement); - } - - // Element - - export function createBlock(statements: readonly Statement[], multiLine?: boolean): Block { - const block = createSynthesizedNode(SyntaxKind.Block); - block.statements = createNodeArray(statements); - if (multiLine) block.multiLine = multiLine; - return block; - } - - export function updateBlock(node: Block, statements: readonly Statement[]) { - return node.statements !== statements - ? updateNode(createBlock(statements, node.multiLine), node) - : node; - } - - export function createVariableStatement(modifiers: readonly Modifier[] | undefined, declarationList: VariableDeclarationList | readonly VariableDeclaration[]) { - const node = createSynthesizedNode(SyntaxKind.VariableStatement); - node.decorators = undefined; - node.modifiers = asNodeArray(modifiers); - node.declarationList = isArray(declarationList) ? createVariableDeclarationList(declarationList) : declarationList; - return node; - } - - export function updateVariableStatement(node: VariableStatement, modifiers: readonly Modifier[] | undefined, declarationList: VariableDeclarationList) { - return node.modifiers !== modifiers - || node.declarationList !== declarationList - ? updateNode(createVariableStatement(modifiers, declarationList), node) - : node; - } - - export function createEmptyStatement() { - return createSynthesizedNode(SyntaxKind.EmptyStatement); - } - - export function createExpressionStatement(expression: Expression): ExpressionStatement { - const node = createSynthesizedNode(SyntaxKind.ExpressionStatement); - node.expression = parenthesizeExpressionForExpressionStatement(expression); - return node; - } - - export function updateExpressionStatement(node: ExpressionStatement, expression: Expression) { - return node.expression !== expression - ? updateNode(createExpressionStatement(expression), node) - : node; - } - - /** @deprecated Use `createExpressionStatement` instead. */ - export const createStatement = createExpressionStatement; - /** @deprecated Use `updateExpressionStatement` instead. */ - export const updateStatement = updateExpressionStatement; - - export function createIf(expression: Expression, thenStatement: Statement, elseStatement?: Statement) { - const node = createSynthesizedNode(SyntaxKind.IfStatement); - node.expression = expression; - node.thenStatement = asEmbeddedStatement(thenStatement); - node.elseStatement = asEmbeddedStatement(elseStatement); - return node; - } - - export function updateIf(node: IfStatement, expression: Expression, thenStatement: Statement, elseStatement: Statement | undefined) { - return node.expression !== expression - || node.thenStatement !== thenStatement - || node.elseStatement !== elseStatement - ? updateNode(createIf(expression, thenStatement, elseStatement), node) - : node; - } - - export function createDo(statement: Statement, expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.DoStatement); - node.statement = asEmbeddedStatement(statement); - node.expression = expression; - return node; - } - - export function updateDo(node: DoStatement, statement: Statement, expression: Expression) { - return node.statement !== statement - || node.expression !== expression - ? updateNode(createDo(statement, expression), node) - : node; - } - - export function createWhile(expression: Expression, statement: Statement) { - const node = createSynthesizedNode(SyntaxKind.WhileStatement); - node.expression = expression; - node.statement = asEmbeddedStatement(statement); - return node; - } - - export function updateWhile(node: WhileStatement, expression: Expression, statement: Statement) { - return node.expression !== expression - || node.statement !== statement - ? updateNode(createWhile(expression, statement), node) - : node; - } - - export function createFor(initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement) { - const node = createSynthesizedNode(SyntaxKind.ForStatement); - node.initializer = initializer; - node.condition = condition; - node.incrementor = incrementor; - node.statement = asEmbeddedStatement(statement); - return node; - } - - export function updateFor(node: ForStatement, initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement) { - return node.initializer !== initializer - || node.condition !== condition - || node.incrementor !== incrementor - || node.statement !== statement - ? updateNode(createFor(initializer, condition, incrementor, statement), node) - : node; - } - - export function createForIn(initializer: ForInitializer, expression: Expression, statement: Statement) { - const node = createSynthesizedNode(SyntaxKind.ForInStatement); - node.initializer = initializer; - node.expression = expression; - node.statement = asEmbeddedStatement(statement); - return node; - } - - export function updateForIn(node: ForInStatement, initializer: ForInitializer, expression: Expression, statement: Statement) { - return node.initializer !== initializer - || node.expression !== expression - || node.statement !== statement - ? updateNode(createForIn(initializer, expression, statement), node) - : node; - } - - export function createForOf(awaitModifier: AwaitKeywordToken | undefined, initializer: ForInitializer, expression: Expression, statement: Statement) { - const node = createSynthesizedNode(SyntaxKind.ForOfStatement); - node.awaitModifier = awaitModifier; - node.initializer = initializer; - node.expression = isCommaSequence(expression) ? createParen(expression) : expression; - node.statement = asEmbeddedStatement(statement); - return node; - } - - export function updateForOf(node: ForOfStatement, awaitModifier: AwaitKeywordToken | undefined, initializer: ForInitializer, expression: Expression, statement: Statement) { - return node.awaitModifier !== awaitModifier - || node.initializer !== initializer - || node.expression !== expression - || node.statement !== statement - ? updateNode(createForOf(awaitModifier, initializer, expression, statement), node) - : node; - } - - export function createContinue(label?: string | Identifier): ContinueStatement { - const node = createSynthesizedNode(SyntaxKind.ContinueStatement); - node.label = asName(label); - return node; - } - - export function updateContinue(node: ContinueStatement, label: Identifier | undefined) { - return node.label !== label - ? updateNode(createContinue(label), node) - : node; - } - - export function createBreak(label?: string | Identifier): BreakStatement { - const node = createSynthesizedNode(SyntaxKind.BreakStatement); - node.label = asName(label); - return node; - } - - export function updateBreak(node: BreakStatement, label: Identifier | undefined) { - return node.label !== label - ? updateNode(createBreak(label), node) - : node; - } - - export function createReturn(expression?: Expression): ReturnStatement { - const node = createSynthesizedNode(SyntaxKind.ReturnStatement); - node.expression = expression; - return node; - } - - export function updateReturn(node: ReturnStatement, expression: Expression | undefined) { - return node.expression !== expression - ? updateNode(createReturn(expression), node) - : node; - } - - export function createWith(expression: Expression, statement: Statement) { - const node = createSynthesizedNode(SyntaxKind.WithStatement); - node.expression = expression; - node.statement = asEmbeddedStatement(statement); - return node; - } - - export function updateWith(node: WithStatement, expression: Expression, statement: Statement) { - return node.expression !== expression - || node.statement !== statement - ? updateNode(createWith(expression, statement), node) - : node; - } - - export function createSwitch(expression: Expression, caseBlock: CaseBlock): SwitchStatement { - const node = createSynthesizedNode(SyntaxKind.SwitchStatement); - node.expression = parenthesizeExpressionForList(expression); - node.caseBlock = caseBlock; - return node; - } - - export function updateSwitch(node: SwitchStatement, expression: Expression, caseBlock: CaseBlock) { - return node.expression !== expression - || node.caseBlock !== caseBlock - ? updateNode(createSwitch(expression, caseBlock), node) - : node; - } - - export function createLabel(label: string | Identifier, statement: Statement) { - const node = createSynthesizedNode(SyntaxKind.LabeledStatement); - node.label = asName(label); - node.statement = asEmbeddedStatement(statement); - return node; - } - - export function updateLabel(node: LabeledStatement, label: Identifier, statement: Statement) { - return node.label !== label - || node.statement !== statement - ? updateNode(createLabel(label, statement), node) - : node; - } - - export function createThrow(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.ThrowStatement); - node.expression = expression; - return node; - } - - export function updateThrow(node: ThrowStatement, expression: Expression) { - return node.expression !== expression - ? updateNode(createThrow(expression), node) - : node; - } - - export function createTry(tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined) { - const node = createSynthesizedNode(SyntaxKind.TryStatement); - node.tryBlock = tryBlock; - node.catchClause = catchClause; - node.finallyBlock = finallyBlock; - return node; - } - - export function updateTry(node: TryStatement, tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined) { - return node.tryBlock !== tryBlock - || node.catchClause !== catchClause - || node.finallyBlock !== finallyBlock - ? updateNode(createTry(tryBlock, catchClause, finallyBlock), node) - : node; - } - - export function createDebuggerStatement() { - return createSynthesizedNode(SyntaxKind.DebuggerStatement); - } - - export function createVariableDeclaration(name: string | BindingName, type?: TypeNode, initializer?: Expression) { - /* Internally, one should probably use createTypeScriptVariableDeclaration instead and handle definite assignment assertions */ - const node = createSynthesizedNode(SyntaxKind.VariableDeclaration); - node.name = asName(name); - node.type = type; - node.initializer = initializer !== undefined ? parenthesizeExpressionForList(initializer) : undefined; - return node; - } - - export function updateVariableDeclaration(node: VariableDeclaration, name: BindingName, type: TypeNode | undefined, initializer: Expression | undefined) { - /* Internally, one should probably use updateTypeScriptVariableDeclaration instead and handle definite assignment assertions */ - return node.name !== name - || node.type !== type - || node.initializer !== initializer - ? updateNode(createVariableDeclaration(name, type, initializer), node) - : node; - } - - /* @internal */ - export function createTypeScriptVariableDeclaration(name: string | BindingName, exclaimationToken?: Token, type?: TypeNode, initializer?: Expression) { - const node = createSynthesizedNode(SyntaxKind.VariableDeclaration); - node.name = asName(name); - node.type = type; - node.initializer = initializer !== undefined ? parenthesizeExpressionForList(initializer) : undefined; - node.exclamationToken = exclaimationToken; - return node; - } - - /* @internal */ - export function updateTypeScriptVariableDeclaration(node: VariableDeclaration, name: BindingName, exclaimationToken: Token | undefined, type: TypeNode | undefined, initializer: Expression | undefined) { - return node.name !== name - || node.type !== type - || node.initializer !== initializer - || node.exclamationToken !== exclaimationToken - ? updateNode(createTypeScriptVariableDeclaration(name, exclaimationToken, type, initializer), node) - : node; - } - - export function createVariableDeclarationList(declarations: readonly VariableDeclaration[], flags = NodeFlags.None) { - const node = createSynthesizedNode(SyntaxKind.VariableDeclarationList); - node.flags |= flags & NodeFlags.BlockScoped; - node.declarations = createNodeArray(declarations); - return node; - } - - export function updateVariableDeclarationList(node: VariableDeclarationList, declarations: readonly VariableDeclaration[]) { - return node.declarations !== declarations - ? updateNode(createVariableDeclarationList(declarations, node.flags), node) - : node; - } - - export function createFunctionDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: string | Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined) { - const node = createSynthesizedNode(SyntaxKind.FunctionDeclaration); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.asteriskToken = asteriskToken; - node.name = asName(name); - node.typeParameters = asNodeArray(typeParameters); - node.parameters = createNodeArray(parameters); - node.type = type; - node.body = body; - return node; - } - - export function updateFunctionDeclaration( - node: FunctionDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.asteriskToken !== asteriskToken - || node.name !== name - || node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - || node.body !== body - ? updateNode(createFunctionDeclaration(decorators, modifiers, asteriskToken, name, typeParameters, parameters, type, body), node) - : node; - } - - export function createClassDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly ClassElement[]) { - const node = createSynthesizedNode(SyntaxKind.ClassDeclaration); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.name = asName(name); - node.typeParameters = asNodeArray(typeParameters); - node.heritageClauses = asNodeArray(heritageClauses); - node.members = createNodeArray(members); - return node; - } - - export function updateClassDeclaration( - node: ClassDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly ClassElement[]) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.typeParameters !== typeParameters - || node.heritageClauses !== heritageClauses - || node.members !== members - ? updateNode(createClassDeclaration(decorators, modifiers, name, typeParameters, heritageClauses, members), node) - : node; - } - - export function createInterfaceDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | Identifier, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly TypeElement[]) { - const node = createSynthesizedNode(SyntaxKind.InterfaceDeclaration); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.name = asName(name); - node.typeParameters = asNodeArray(typeParameters); - node.heritageClauses = asNodeArray(heritageClauses); - node.members = createNodeArray(members); - return node; - } - - export function updateInterfaceDeclaration( - node: InterfaceDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: Identifier, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly TypeElement[]) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.typeParameters !== typeParameters - || node.heritageClauses !== heritageClauses - || node.members !== members - ? updateNode(createInterfaceDeclaration(decorators, modifiers, name, typeParameters, heritageClauses, members), node) - : node; - } - - export function createTypeAliasDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | Identifier, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - type: TypeNode) { - const node = createSynthesizedNode(SyntaxKind.TypeAliasDeclaration); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.name = asName(name); - node.typeParameters = asNodeArray(typeParameters); - node.type = type; - return node; - } - - export function updateTypeAliasDeclaration( - node: TypeAliasDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: Identifier, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - type: TypeNode) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.typeParameters !== typeParameters - || node.type !== type - ? updateNode(createTypeAliasDeclaration(decorators, modifiers, name, typeParameters, type), node) - : node; - } - - export function createEnumDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | Identifier, - members: readonly EnumMember[]) { - const node = createSynthesizedNode(SyntaxKind.EnumDeclaration); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.name = asName(name); - node.members = createNodeArray(members); - return node; - } - - export function updateEnumDeclaration( - node: EnumDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: Identifier, - members: readonly EnumMember[]) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.members !== members - ? updateNode(createEnumDeclaration(decorators, modifiers, name, members), node) - : node; - } - - export function createModuleDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: ModuleName, body: ModuleBody | undefined, flags = NodeFlags.None) { - const node = createSynthesizedNode(SyntaxKind.ModuleDeclaration); - node.flags |= flags & (NodeFlags.Namespace | NodeFlags.NestedNamespace | NodeFlags.GlobalAugmentation); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.name = name; - node.body = body; - return node; - } - - export function updateModuleDeclaration(node: ModuleDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: ModuleName, body: ModuleBody | undefined) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.body !== body - ? updateNode(createModuleDeclaration(decorators, modifiers, name, body, node.flags), node) - : node; - } - - export function createModuleBlock(statements: readonly Statement[]) { - const node = createSynthesizedNode(SyntaxKind.ModuleBlock); - node.statements = createNodeArray(statements); - return node; - } - - export function updateModuleBlock(node: ModuleBlock, statements: readonly Statement[]) { - return node.statements !== statements - ? updateNode(createModuleBlock(statements), node) - : node; - } - - export function createCaseBlock(clauses: readonly CaseOrDefaultClause[]): CaseBlock { - const node = createSynthesizedNode(SyntaxKind.CaseBlock); - node.clauses = createNodeArray(clauses); - return node; - } - - export function updateCaseBlock(node: CaseBlock, clauses: readonly CaseOrDefaultClause[]) { - return node.clauses !== clauses - ? updateNode(createCaseBlock(clauses), node) - : node; - } - - export function createNamespaceExportDeclaration(name: string | Identifier) { - const node = createSynthesizedNode(SyntaxKind.NamespaceExportDeclaration); - node.name = asName(name); - return node; - } - - export function updateNamespaceExportDeclaration(node: NamespaceExportDeclaration, name: Identifier) { - return node.name !== name - ? updateNode(createNamespaceExportDeclaration(name), node) - : node; - } - - export function createImportEqualsDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | Identifier, moduleReference: ModuleReference) { - const node = createSynthesizedNode(SyntaxKind.ImportEqualsDeclaration); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.name = asName(name); - node.moduleReference = moduleReference; - return node; - } - - export function updateImportEqualsDeclaration(node: ImportEqualsDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: Identifier, moduleReference: ModuleReference) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.moduleReference !== moduleReference - ? updateNode(createImportEqualsDeclaration(decorators, modifiers, name, moduleReference), node) - : node; - } - - export function createImportDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - importClause: ImportClause | undefined, - moduleSpecifier: Expression): ImportDeclaration { - const node = createSynthesizedNode(SyntaxKind.ImportDeclaration); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.importClause = importClause; - node.moduleSpecifier = moduleSpecifier; - return node; - } - - export function updateImportDeclaration( - node: ImportDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - importClause: ImportClause | undefined, - moduleSpecifier: Expression) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.importClause !== importClause - || node.moduleSpecifier !== moduleSpecifier - ? updateNode(createImportDeclaration(decorators, modifiers, importClause, moduleSpecifier), node) - : node; - } - - export function createImportClause(name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause { - const node = createSynthesizedNode(SyntaxKind.ImportClause); - node.name = name; - node.namedBindings = namedBindings; - return node; - } - - export function updateImportClause(node: ImportClause, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined) { - return node.name !== name - || node.namedBindings !== namedBindings - ? updateNode(createImportClause(name, namedBindings), node) - : node; - } - - export function createNamespaceImport(name: Identifier): NamespaceImport { - const node = createSynthesizedNode(SyntaxKind.NamespaceImport); - node.name = name; - return node; - } - - export function updateNamespaceImport(node: NamespaceImport, name: Identifier) { - return node.name !== name - ? updateNode(createNamespaceImport(name), node) - : node; - } - - export function createNamedImports(elements: readonly ImportSpecifier[]): NamedImports { - const node = createSynthesizedNode(SyntaxKind.NamedImports); - node.elements = createNodeArray(elements); - return node; - } - - export function updateNamedImports(node: NamedImports, elements: readonly ImportSpecifier[]) { - return node.elements !== elements - ? updateNode(createNamedImports(elements), node) - : node; - } - - export function createImportSpecifier(propertyName: Identifier | undefined, name: Identifier) { - const node = createSynthesizedNode(SyntaxKind.ImportSpecifier); - node.propertyName = propertyName; - node.name = name; - return node; - } - - export function updateImportSpecifier(node: ImportSpecifier, propertyName: Identifier | undefined, name: Identifier) { - return node.propertyName !== propertyName - || node.name !== name - ? updateNode(createImportSpecifier(propertyName, name), node) - : node; - } - - export function createExportAssignment(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isExportEquals: boolean | undefined, expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.ExportAssignment); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.isExportEquals = isExportEquals; - node.expression = isExportEquals ? parenthesizeBinaryOperand(SyntaxKind.EqualsToken, expression, /*isLeftSideOfBinary*/ false, /*leftOperand*/ undefined) : parenthesizeDefaultExpression(expression); - return node; - } - - export function updateExportAssignment(node: ExportAssignment, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, expression: Expression) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.expression !== expression - ? updateNode(createExportAssignment(decorators, modifiers, node.isExportEquals, expression), node) - : node; - } - - export function createExportDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, exportClause: NamedExports | undefined, moduleSpecifier?: Expression) { - const node = createSynthesizedNode(SyntaxKind.ExportDeclaration); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.exportClause = exportClause; - node.moduleSpecifier = moduleSpecifier; - return node; - } - - export function updateExportDeclaration( - node: ExportDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - exportClause: NamedExports | undefined, - moduleSpecifier: Expression | undefined) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.exportClause !== exportClause - || node.moduleSpecifier !== moduleSpecifier - ? updateNode(createExportDeclaration(decorators, modifiers, exportClause, moduleSpecifier), node) - : node; - } - - /* @internal */ - export function createEmptyExports() { - return createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createNamedExports([]), /*moduleSpecifier*/ undefined); - } - - export function createNamedExports(elements: readonly ExportSpecifier[]) { - const node = createSynthesizedNode(SyntaxKind.NamedExports); - node.elements = createNodeArray(elements); - return node; - } - - export function updateNamedExports(node: NamedExports, elements: readonly ExportSpecifier[]) { - return node.elements !== elements - ? updateNode(createNamedExports(elements), node) - : node; - } - - export function createExportSpecifier(propertyName: string | Identifier | undefined, name: string | Identifier) { - const node = createSynthesizedNode(SyntaxKind.ExportSpecifier); - node.propertyName = asName(propertyName); - node.name = asName(name); - return node; - } - - export function updateExportSpecifier(node: ExportSpecifier, propertyName: Identifier | undefined, name: Identifier) { - return node.propertyName !== propertyName - || node.name !== name - ? updateNode(createExportSpecifier(propertyName, name), node) - : node; - } - - // Module references - - export function createExternalModuleReference(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.ExternalModuleReference); - node.expression = expression; - return node; - } - - export function updateExternalModuleReference(node: ExternalModuleReference, expression: Expression) { - return node.expression !== expression - ? updateNode(createExternalModuleReference(expression), node) - : node; - } - - // JSDoc - - /* @internal */ - export function createJSDocTypeExpression(type: TypeNode): JSDocTypeExpression { - const node = createSynthesizedNode(SyntaxKind.JSDocTypeExpression) as JSDocTypeExpression; - node.type = type; - return node; - } - - /* @internal */ - export function createJSDocTypeTag(typeExpression: JSDocTypeExpression, comment?: string): JSDocTypeTag { - const tag = createJSDocTag(SyntaxKind.JSDocTypeTag, "type"); - tag.typeExpression = typeExpression; - tag.comment = comment; - return tag; - } - - /* @internal */ - export function createJSDocReturnTag(typeExpression?: JSDocTypeExpression, comment?: string): JSDocReturnTag { - const tag = createJSDocTag(SyntaxKind.JSDocReturnTag, "returns"); - tag.typeExpression = typeExpression; - tag.comment = comment; - return tag; - } - - /** @internal */ - export function createJSDocThisTag(typeExpression?: JSDocTypeExpression): JSDocThisTag { - const tag = createJSDocTag(SyntaxKind.JSDocThisTag, "this"); - tag.typeExpression = typeExpression; - return tag; - } - - /* @internal */ - export function createJSDocParamTag(name: EntityName, isBracketed: boolean, typeExpression?: JSDocTypeExpression, comment?: string): JSDocParameterTag { - const tag = createJSDocTag(SyntaxKind.JSDocParameterTag, "param"); - tag.typeExpression = typeExpression; - tag.name = name; - tag.isBracketed = isBracketed; - tag.comment = comment; - return tag; - } - - /* @internal */ - export function createJSDocComment(comment?: string | undefined, tags?: NodeArray | undefined) { - const node = createSynthesizedNode(SyntaxKind.JSDocComment) as JSDoc; - node.comment = comment; - node.tags = tags; - return node; - } - - /* @internal */ - function createJSDocTag(kind: T["kind"], tagName: string): T { - const node = createSynthesizedNode(kind) as T; - node.tagName = createIdentifier(tagName); - return node; - } - - // JSX - - export function createJsxElement(openingElement: JsxOpeningElement, children: readonly JsxChild[], closingElement: JsxClosingElement) { - const node = createSynthesizedNode(SyntaxKind.JsxElement); - node.openingElement = openingElement; - node.children = createNodeArray(children); - node.closingElement = closingElement; - return node; - } - - export function updateJsxElement(node: JsxElement, openingElement: JsxOpeningElement, children: readonly JsxChild[], closingElement: JsxClosingElement) { - return node.openingElement !== openingElement - || node.children !== children - || node.closingElement !== closingElement - ? updateNode(createJsxElement(openingElement, children, closingElement), node) - : node; - } - - export function createJsxSelfClosingElement(tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { - const node = createSynthesizedNode(SyntaxKind.JsxSelfClosingElement); - node.tagName = tagName; - node.typeArguments = asNodeArray(typeArguments); - node.attributes = attributes; - return node; - } - - export function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { - return node.tagName !== tagName - || node.typeArguments !== typeArguments - || node.attributes !== attributes - ? updateNode(createJsxSelfClosingElement(tagName, typeArguments, attributes), node) - : node; - } - - export function createJsxOpeningElement(tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { - const node = createSynthesizedNode(SyntaxKind.JsxOpeningElement); - node.tagName = tagName; - node.typeArguments = asNodeArray(typeArguments); - node.attributes = attributes; - return node; - } - - export function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { - return node.tagName !== tagName - || node.typeArguments !== typeArguments - || node.attributes !== attributes - ? updateNode(createJsxOpeningElement(tagName, typeArguments, attributes), node) - : node; - } - - export function createJsxClosingElement(tagName: JsxTagNameExpression) { - const node = createSynthesizedNode(SyntaxKind.JsxClosingElement); - node.tagName = tagName; - return node; - } - - export function updateJsxClosingElement(node: JsxClosingElement, tagName: JsxTagNameExpression) { - return node.tagName !== tagName - ? updateNode(createJsxClosingElement(tagName), node) - : node; - } - - export function createJsxFragment(openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) { - const node = createSynthesizedNode(SyntaxKind.JsxFragment); - node.openingFragment = openingFragment; - node.children = createNodeArray(children); - node.closingFragment = closingFragment; - return node; - } - - export function createJsxText(text: string, containsOnlyTriviaWhiteSpaces?: boolean) { - const node = createSynthesizedNode(SyntaxKind.JsxText); - node.text = text; - node.containsOnlyTriviaWhiteSpaces = !!containsOnlyTriviaWhiteSpaces; - return node; - } - - export function updateJsxText(node: JsxText, text: string, containsOnlyTriviaWhiteSpaces?: boolean) { - return node.text !== text - || node.containsOnlyTriviaWhiteSpaces !== containsOnlyTriviaWhiteSpaces - ? updateNode(createJsxText(text, containsOnlyTriviaWhiteSpaces), node) - : node; - } - - export function createJsxOpeningFragment() { - return createSynthesizedNode(SyntaxKind.JsxOpeningFragment); - } - - export function createJsxJsxClosingFragment() { - return createSynthesizedNode(SyntaxKind.JsxClosingFragment); - } - - export function updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) { - return node.openingFragment !== openingFragment - || node.children !== children - || node.closingFragment !== closingFragment - ? updateNode(createJsxFragment(openingFragment, children, closingFragment), node) - : node; - } - - export function createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression) { - const node = createSynthesizedNode(SyntaxKind.JsxAttribute); - node.name = name; - node.initializer = initializer; - return node; - } - - export function updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression) { - return node.name !== name - || node.initializer !== initializer - ? updateNode(createJsxAttribute(name, initializer), node) - : node; - } - - export function createJsxAttributes(properties: readonly JsxAttributeLike[]) { - const node = createSynthesizedNode(SyntaxKind.JsxAttributes); - node.properties = createNodeArray(properties); - return node; - } - - export function updateJsxAttributes(node: JsxAttributes, properties: readonly JsxAttributeLike[]) { - return node.properties !== properties - ? updateNode(createJsxAttributes(properties), node) - : node; - } - - export function createJsxSpreadAttribute(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.JsxSpreadAttribute); - node.expression = expression; - return node; - } - - export function updateJsxSpreadAttribute(node: JsxSpreadAttribute, expression: Expression) { - return node.expression !== expression - ? updateNode(createJsxSpreadAttribute(expression), node) - : node; - } - - export function createJsxExpression(dotDotDotToken: DotDotDotToken | undefined, expression: Expression | undefined) { - const node = createSynthesizedNode(SyntaxKind.JsxExpression); - node.dotDotDotToken = dotDotDotToken; - node.expression = expression; - return node; - } - - export function updateJsxExpression(node: JsxExpression, expression: Expression | undefined) { - return node.expression !== expression - ? updateNode(createJsxExpression(node.dotDotDotToken, expression), node) - : node; - } - - // Clauses - - export function createCaseClause(expression: Expression, statements: readonly Statement[]) { - const node = createSynthesizedNode(SyntaxKind.CaseClause); - node.expression = parenthesizeExpressionForList(expression); - node.statements = createNodeArray(statements); - return node; - } - - export function updateCaseClause(node: CaseClause, expression: Expression, statements: readonly Statement[]) { - return node.expression !== expression - || node.statements !== statements - ? updateNode(createCaseClause(expression, statements), node) - : node; - } - - export function createDefaultClause(statements: readonly Statement[]) { - const node = createSynthesizedNode(SyntaxKind.DefaultClause); - node.statements = createNodeArray(statements); - return node; - } - - export function updateDefaultClause(node: DefaultClause, statements: readonly Statement[]) { - return node.statements !== statements - ? updateNode(createDefaultClause(statements), node) - : node; - } - - export function createHeritageClause(token: HeritageClause["token"], types: readonly ExpressionWithTypeArguments[]) { - const node = createSynthesizedNode(SyntaxKind.HeritageClause); - node.token = token; - node.types = createNodeArray(types); - return node; - } - - export function updateHeritageClause(node: HeritageClause, types: readonly ExpressionWithTypeArguments[]) { - return node.types !== types - ? updateNode(createHeritageClause(node.token, types), node) - : node; - } - - export function createCatchClause(variableDeclaration: string | VariableDeclaration | undefined, block: Block) { - const node = createSynthesizedNode(SyntaxKind.CatchClause); - node.variableDeclaration = isString(variableDeclaration) ? createVariableDeclaration(variableDeclaration) : variableDeclaration; - node.block = block; - return node; - } - - export function updateCatchClause(node: CatchClause, variableDeclaration: VariableDeclaration | undefined, block: Block) { - return node.variableDeclaration !== variableDeclaration - || node.block !== block - ? updateNode(createCatchClause(variableDeclaration, block), node) - : node; - } - - // Property assignments - - export function createPropertyAssignment(name: string | PropertyName, initializer: Expression) { - const node = createSynthesizedNode(SyntaxKind.PropertyAssignment); - node.name = asName(name); - node.questionToken = undefined; - node.initializer = parenthesizeExpressionForList(initializer); - return node; - } - - export function updatePropertyAssignment(node: PropertyAssignment, name: PropertyName, initializer: Expression) { - return node.name !== name - || node.initializer !== initializer - ? updateNode(createPropertyAssignment(name, initializer), node) - : node; - } - - export function createShorthandPropertyAssignment(name: string | Identifier, objectAssignmentInitializer?: Expression) { - const node = createSynthesizedNode(SyntaxKind.ShorthandPropertyAssignment); - node.name = asName(name); - node.objectAssignmentInitializer = objectAssignmentInitializer !== undefined ? parenthesizeExpressionForList(objectAssignmentInitializer) : undefined; - return node; - } - - export function updateShorthandPropertyAssignment(node: ShorthandPropertyAssignment, name: Identifier, objectAssignmentInitializer: Expression | undefined) { - return node.name !== name - || node.objectAssignmentInitializer !== objectAssignmentInitializer - ? updateNode(createShorthandPropertyAssignment(name, objectAssignmentInitializer), node) - : node; - } - - export function createSpreadAssignment(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.SpreadAssignment); - node.expression = parenthesizeExpressionForList(expression); - return node; - } - - export function updateSpreadAssignment(node: SpreadAssignment, expression: Expression) { - return node.expression !== expression - ? updateNode(createSpreadAssignment(expression), node) - : node; - } - - // Enum - - export function createEnumMember(name: string | PropertyName, initializer?: Expression) { - const node = createSynthesizedNode(SyntaxKind.EnumMember); - node.name = asName(name); - node.initializer = initializer && parenthesizeExpressionForList(initializer); - return node; - } - - export function updateEnumMember(node: EnumMember, name: PropertyName, initializer: Expression | undefined) { - return node.name !== name - || node.initializer !== initializer - ? updateNode(createEnumMember(name, initializer), node) - : node; - } - - // Top-level nodes - - export function updateSourceFileNode(node: SourceFile, statements: readonly Statement[], isDeclarationFile?: boolean, referencedFiles?: SourceFile["referencedFiles"], typeReferences?: SourceFile["typeReferenceDirectives"], hasNoDefaultLib?: boolean, libReferences?: SourceFile["libReferenceDirectives"]) { - if ( - node.statements !== statements || - (isDeclarationFile !== undefined && node.isDeclarationFile !== isDeclarationFile) || - (referencedFiles !== undefined && node.referencedFiles !== referencedFiles) || - (typeReferences !== undefined && node.typeReferenceDirectives !== typeReferences) || - (libReferences !== undefined && node.libReferenceDirectives !== libReferences) || - (hasNoDefaultLib !== undefined && node.hasNoDefaultLib !== hasNoDefaultLib) - ) { - const updated = createSynthesizedNode(SyntaxKind.SourceFile); - updated.flags |= node.flags; - updated.statements = createNodeArray(statements); - updated.endOfFileToken = node.endOfFileToken; - updated.fileName = node.fileName; - updated.path = node.path; - updated.text = node.text; - updated.isDeclarationFile = isDeclarationFile === undefined ? node.isDeclarationFile : isDeclarationFile; - updated.referencedFiles = referencedFiles === undefined ? node.referencedFiles : referencedFiles; - updated.typeReferenceDirectives = typeReferences === undefined ? node.typeReferenceDirectives : typeReferences; - updated.hasNoDefaultLib = hasNoDefaultLib === undefined ? node.hasNoDefaultLib : hasNoDefaultLib; - updated.libReferenceDirectives = libReferences === undefined ? node.libReferenceDirectives : libReferences; - if (node.amdDependencies !== undefined) updated.amdDependencies = node.amdDependencies; - if (node.moduleName !== undefined) updated.moduleName = node.moduleName; - if (node.languageVariant !== undefined) updated.languageVariant = node.languageVariant; - if (node.renamedDependencies !== undefined) updated.renamedDependencies = node.renamedDependencies; - if (node.languageVersion !== undefined) updated.languageVersion = node.languageVersion; - if (node.scriptKind !== undefined) updated.scriptKind = node.scriptKind; - if (node.externalModuleIndicator !== undefined) updated.externalModuleIndicator = node.externalModuleIndicator; - if (node.commonJsModuleIndicator !== undefined) updated.commonJsModuleIndicator = node.commonJsModuleIndicator; - if (node.identifiers !== undefined) updated.identifiers = node.identifiers; - if (node.nodeCount !== undefined) updated.nodeCount = node.nodeCount; - if (node.identifierCount !== undefined) updated.identifierCount = node.identifierCount; - if (node.symbolCount !== undefined) updated.symbolCount = node.symbolCount; - if (node.parseDiagnostics !== undefined) updated.parseDiagnostics = node.parseDiagnostics; - if (node.bindDiagnostics !== undefined) updated.bindDiagnostics = node.bindDiagnostics; - if (node.bindSuggestionDiagnostics !== undefined) updated.bindSuggestionDiagnostics = node.bindSuggestionDiagnostics; - if (node.lineMap !== undefined) updated.lineMap = node.lineMap; - if (node.classifiableNames !== undefined) updated.classifiableNames = node.classifiableNames; - if (node.resolvedModules !== undefined) updated.resolvedModules = node.resolvedModules; - if (node.resolvedTypeReferenceDirectiveNames !== undefined) updated.resolvedTypeReferenceDirectiveNames = node.resolvedTypeReferenceDirectiveNames; - if (node.imports !== undefined) updated.imports = node.imports; - if (node.moduleAugmentations !== undefined) updated.moduleAugmentations = node.moduleAugmentations; - if (node.pragmas !== undefined) updated.pragmas = node.pragmas; - if (node.localJsxFactory !== undefined) updated.localJsxFactory = node.localJsxFactory; - if (node.localJsxNamespace !== undefined) updated.localJsxNamespace = node.localJsxNamespace; - return updateNode(updated, node); - } - - return node; - } - - /** - * Creates a shallow, memberwise clone of a node for mutation. - */ - export function getMutableClone(node: T): T { - const clone = getSynthesizedClone(node); - clone.pos = node.pos; - clone.end = node.end; - clone.parent = node.parent; - return clone; - } - - // Transformation nodes - - /** - * Creates a synthetic statement to act as a placeholder for a not-emitted statement in - * order to preserve comments. - * - * @param original The original statement. - */ - export function createNotEmittedStatement(original: Node) { - const node = createSynthesizedNode(SyntaxKind.NotEmittedStatement); - node.original = original; - setTextRange(node, original); - return node; - } - - /** - * Creates a synthetic element to act as a placeholder for the end of an emitted declaration in - * order to properly emit exports. - */ - /* @internal */ - export function createEndOfDeclarationMarker(original: Node) { - const node = createSynthesizedNode(SyntaxKind.EndOfDeclarationMarker); - node.emitNode = {} as EmitNode; - node.original = original; - return node; - } - - /** - * Creates a synthetic element to act as a placeholder for the beginning of a merged declaration in - * order to properly emit exports. - */ - /* @internal */ - export function createMergeDeclarationMarker(original: Node) { - const node = createSynthesizedNode(SyntaxKind.MergeDeclarationMarker); - node.emitNode = {} as EmitNode; - node.original = original; - return node; - } - - /** - * Creates a synthetic expression to act as a placeholder for a not-emitted expression in - * order to preserve comments or sourcemap positions. - * - * @param expression The inner expression to emit. - * @param original The original outer expression. - * @param location The location for the expression. Defaults to the positions from "original" if provided. - */ - export function createPartiallyEmittedExpression(expression: Expression, original?: Node) { - const node = createSynthesizedNode(SyntaxKind.PartiallyEmittedExpression); - node.expression = expression; - node.original = original; - setTextRange(node, original); - return node; - } - - export function updatePartiallyEmittedExpression(node: PartiallyEmittedExpression, expression: Expression) { - if (node.expression !== expression) { - return updateNode(createPartiallyEmittedExpression(expression, node.original), node); - } - return node; - } - - function flattenCommaElements(node: Expression): Expression | readonly Expression[] { - if (nodeIsSynthesized(node) && !isParseTreeNode(node) && !node.original && !node.emitNode && !node.id) { - if (node.kind === SyntaxKind.CommaListExpression) { - return (node).elements; - } - if (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.CommaToken) { - return [node.left, node.right]; - } - } - return node; - } - - export function createCommaList(elements: readonly Expression[]) { - const node = createSynthesizedNode(SyntaxKind.CommaListExpression); - node.elements = createNodeArray(sameFlatMap(elements, flattenCommaElements)); - return node; - } - - export function updateCommaList(node: CommaListExpression, elements: readonly Expression[]) { - return node.elements !== elements - ? updateNode(createCommaList(elements), node) - : node; - } - - /* @internal */ - export function createSyntheticReferenceExpression(expression: Expression, thisArg: Expression) { - const node = createSynthesizedNode(SyntaxKind.SyntheticReferenceExpression); - node.expression = expression; - node.thisArg = thisArg; - return node; - } - - /* @internal */ - export function updateSyntheticReferenceExpression(node: SyntheticReferenceExpression, expression: Expression, thisArg: Expression) { - return node.expression !== expression - || node.thisArg !== thisArg - ? updateNode(createSyntheticReferenceExpression(expression, thisArg), node) - : node; - } - - export function createBundle(sourceFiles: readonly SourceFile[], prepends: readonly (UnparsedSource | InputFiles)[] = emptyArray) { - const node = createNode(SyntaxKind.Bundle); - node.prepends = prepends; - node.sourceFiles = sourceFiles; - return node; - } - - let allUnscopedEmitHelpers: ReadonlyMap | undefined; - function getAllUnscopedEmitHelpers() { - return allUnscopedEmitHelpers || (allUnscopedEmitHelpers = arrayToMap([ - valuesHelper, - readHelper, - spreadHelper, - spreadArraysHelper, - restHelper, - decorateHelper, - metadataHelper, - paramHelper, - awaiterHelper, - assignHelper, - awaitHelper, - asyncGeneratorHelper, - asyncDelegator, - asyncValues, - extendsHelper, - templateObjectHelper, - generatorHelper, - importStarHelper, - importDefaultHelper - ], helper => helper.name)); - } - - function createUnparsedSource() { - const node = createNode(SyntaxKind.UnparsedSource); - node.prologues = emptyArray; - node.referencedFiles = emptyArray; - node.libReferenceDirectives = emptyArray; - node.getLineAndCharacterOfPosition = pos => getLineAndCharacterOfPosition(node, pos); - return node; + return node; +} +export function createTemplateHead(text: string, rawText?: string) { + const node = (createTemplateLiteralLikeNode(SyntaxKind.TemplateHead, text, rawText)); + node.text = text; + return node; +} +export function createTemplateMiddle(text: string, rawText?: string) { + const node = (createTemplateLiteralLikeNode(SyntaxKind.TemplateMiddle, text, rawText)); + node.text = text; + return node; +} +export function createTemplateTail(text: string, rawText?: string) { + const node = (createTemplateLiteralLikeNode(SyntaxKind.TemplateTail, text, rawText)); + node.text = text; + return node; +} +export function createNoSubstitutionTemplateLiteral(text: string, rawText?: string) { + const node = (createTemplateLiteralLikeNode(SyntaxKind.NoSubstitutionTemplateLiteral, text, rawText)); + return node; +} +export function createYield(expression?: Expression): YieldExpression; +export function createYield(asteriskToken: AsteriskToken | undefined, expression: Expression): YieldExpression; +export function createYield(asteriskTokenOrExpression?: AsteriskToken | undefined | Expression, expression?: Expression) { + const node = (createSynthesizedNode(SyntaxKind.YieldExpression)); + node.asteriskToken = asteriskTokenOrExpression && asteriskTokenOrExpression.kind === SyntaxKind.AsteriskToken ? asteriskTokenOrExpression : undefined; + node.expression = asteriskTokenOrExpression && asteriskTokenOrExpression.kind !== SyntaxKind.AsteriskToken ? asteriskTokenOrExpression : expression; + return node; +} +export function updateYield(node: YieldExpression, asteriskToken: AsteriskToken | undefined, expression: Expression) { + return node.expression !== expression + || node.asteriskToken !== asteriskToken + ? updateNode(createYield(asteriskToken, expression), node) + : node; +} +export function createSpread(expression: Expression) { + const node = (createSynthesizedNode(SyntaxKind.SpreadElement)); + node.expression = parenthesizeExpressionForList(expression); + return node; +} +export function updateSpread(node: SpreadElement, expression: Expression) { + return node.expression !== expression + ? updateNode(createSpread(expression), node) + : node; +} +export function createClassExpression(modifiers: readonly Modifier[] | undefined, name: string | Identifier | undefined, typeParameters: readonly TypeParameterDeclaration[] | undefined, heritageClauses: readonly HeritageClause[] | undefined, members: readonly ClassElement[]) { + const node = (createSynthesizedNode(SyntaxKind.ClassExpression)); + node.decorators = undefined; + node.modifiers = asNodeArray(modifiers); + node.name = asName(name); + node.typeParameters = asNodeArray(typeParameters); + node.heritageClauses = asNodeArray(heritageClauses); + node.members = createNodeArray(members); + return node; +} +export function updateClassExpression(node: ClassExpression, modifiers: readonly Modifier[] | undefined, name: Identifier | undefined, typeParameters: readonly TypeParameterDeclaration[] | undefined, heritageClauses: readonly HeritageClause[] | undefined, members: readonly ClassElement[]) { + return node.modifiers !== modifiers + || node.name !== name + || node.typeParameters !== typeParameters + || node.heritageClauses !== heritageClauses + || node.members !== members + ? updateNode(createClassExpression(modifiers, name, typeParameters, heritageClauses, members), node) + : node; +} +export function createOmittedExpression() { + return createSynthesizedNode(SyntaxKind.OmittedExpression); +} +export function createExpressionWithTypeArguments(typeArguments: readonly TypeNode[] | undefined, expression: Expression) { + const node = (createSynthesizedNode(SyntaxKind.ExpressionWithTypeArguments)); + node.expression = parenthesizeForAccess(expression); + node.typeArguments = asNodeArray(typeArguments); + return node; +} +export function updateExpressionWithTypeArguments(node: ExpressionWithTypeArguments, typeArguments: readonly TypeNode[] | undefined, expression: Expression) { + return node.typeArguments !== typeArguments + || node.expression !== expression + ? updateNode(createExpressionWithTypeArguments(typeArguments, expression), node) + : node; +} +export function createAsExpression(expression: Expression, type: TypeNode) { + const node = (createSynthesizedNode(SyntaxKind.AsExpression)); + node.expression = expression; + node.type = type; + return node; +} +export function updateAsExpression(node: AsExpression, expression: Expression, type: TypeNode) { + return node.expression !== expression + || node.type !== type + ? updateNode(createAsExpression(expression, type), node) + : node; +} +export function createNonNullExpression(expression: Expression) { + const node = (createSynthesizedNode(SyntaxKind.NonNullExpression)); + node.expression = parenthesizeForAccess(expression); + return node; +} +export function updateNonNullExpression(node: NonNullExpression, expression: Expression) { + return node.expression !== expression + ? updateNode(createNonNullExpression(expression), node) + : node; +} +export function createMetaProperty(keywordToken: MetaProperty["keywordToken"], name: Identifier) { + const node = (createSynthesizedNode(SyntaxKind.MetaProperty)); + node.keywordToken = keywordToken; + node.name = name; + return node; +} +export function updateMetaProperty(node: MetaProperty, name: Identifier) { + return node.name !== name + ? updateNode(createMetaProperty(node.keywordToken, name), node) + : node; +} +// Misc +export function createTemplateSpan(expression: Expression, literal: TemplateMiddle | TemplateTail) { + const node = (createSynthesizedNode(SyntaxKind.TemplateSpan)); + node.expression = expression; + node.literal = literal; + return node; +} +export function updateTemplateSpan(node: TemplateSpan, expression: Expression, literal: TemplateMiddle | TemplateTail) { + return node.expression !== expression + || node.literal !== literal + ? updateNode(createTemplateSpan(expression, literal), node) + : node; +} +export function createSemicolonClassElement() { + return createSynthesizedNode(SyntaxKind.SemicolonClassElement); +} +// Element +export function createBlock(statements: readonly Statement[], multiLine?: boolean): Block { + const block = (createSynthesizedNode(SyntaxKind.Block)); + block.statements = createNodeArray(statements); + if (multiLine) + block.multiLine = multiLine; + return block; +} +export function updateBlock(node: Block, statements: readonly Statement[]) { + return node.statements !== statements + ? updateNode(createBlock(statements, node.multiLine), node) + : node; +} +export function createVariableStatement(modifiers: readonly Modifier[] | undefined, declarationList: VariableDeclarationList | readonly VariableDeclaration[]) { + const node = (createSynthesizedNode(SyntaxKind.VariableStatement)); + node.decorators = undefined; + node.modifiers = asNodeArray(modifiers); + node.declarationList = isArray(declarationList) ? createVariableDeclarationList(declarationList) : declarationList; + return node; +} +export function updateVariableStatement(node: VariableStatement, modifiers: readonly Modifier[] | undefined, declarationList: VariableDeclarationList) { + return node.modifiers !== modifiers + || node.declarationList !== declarationList + ? updateNode(createVariableStatement(modifiers, declarationList), node) + : node; +} +export function createEmptyStatement() { + return createSynthesizedNode(SyntaxKind.EmptyStatement); +} +export function createExpressionStatement(expression: Expression): ExpressionStatement { + const node = (createSynthesizedNode(SyntaxKind.ExpressionStatement)); + node.expression = parenthesizeExpressionForExpressionStatement(expression); + return node; +} +export function updateExpressionStatement(node: ExpressionStatement, expression: Expression) { + return node.expression !== expression + ? updateNode(createExpressionStatement(expression), node) + : node; +} +/** @deprecated Use `createExpressionStatement` instead. */ +export const createStatement = createExpressionStatement; +/** @deprecated Use `updateExpressionStatement` instead. */ +export const updateStatement = updateExpressionStatement; +export function createIf(expression: Expression, thenStatement: Statement, elseStatement?: Statement) { + const node = (createSynthesizedNode(SyntaxKind.IfStatement)); + node.expression = expression; + node.thenStatement = asEmbeddedStatement(thenStatement); + node.elseStatement = asEmbeddedStatement(elseStatement); + return node; +} +export function updateIf(node: IfStatement, expression: Expression, thenStatement: Statement, elseStatement: Statement | undefined) { + return node.expression !== expression + || node.thenStatement !== thenStatement + || node.elseStatement !== elseStatement + ? updateNode(createIf(expression, thenStatement, elseStatement), node) + : node; +} +export function createDo(statement: Statement, expression: Expression) { + const node = (createSynthesizedNode(SyntaxKind.DoStatement)); + node.statement = asEmbeddedStatement(statement); + node.expression = expression; + return node; +} +export function updateDo(node: DoStatement, statement: Statement, expression: Expression) { + return node.statement !== statement + || node.expression !== expression + ? updateNode(createDo(statement, expression), node) + : node; +} +export function createWhile(expression: Expression, statement: Statement) { + const node = (createSynthesizedNode(SyntaxKind.WhileStatement)); + node.expression = expression; + node.statement = asEmbeddedStatement(statement); + return node; +} +export function updateWhile(node: WhileStatement, expression: Expression, statement: Statement) { + return node.expression !== expression + || node.statement !== statement + ? updateNode(createWhile(expression, statement), node) + : node; +} +export function createFor(initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement) { + const node = (createSynthesizedNode(SyntaxKind.ForStatement)); + node.initializer = initializer; + node.condition = condition; + node.incrementor = incrementor; + node.statement = asEmbeddedStatement(statement); + return node; +} +export function updateFor(node: ForStatement, initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement) { + return node.initializer !== initializer + || node.condition !== condition + || node.incrementor !== incrementor + || node.statement !== statement + ? updateNode(createFor(initializer, condition, incrementor, statement), node) + : node; +} +export function createForIn(initializer: ForInitializer, expression: Expression, statement: Statement) { + const node = (createSynthesizedNode(SyntaxKind.ForInStatement)); + node.initializer = initializer; + node.expression = expression; + node.statement = asEmbeddedStatement(statement); + return node; +} +export function updateForIn(node: ForInStatement, initializer: ForInitializer, expression: Expression, statement: Statement) { + return node.initializer !== initializer + || node.expression !== expression + || node.statement !== statement + ? updateNode(createForIn(initializer, expression, statement), node) + : node; +} +export function createForOf(awaitModifier: AwaitKeywordToken | undefined, initializer: ForInitializer, expression: Expression, statement: Statement) { + const node = (createSynthesizedNode(SyntaxKind.ForOfStatement)); + node.awaitModifier = awaitModifier; + node.initializer = initializer; + node.expression = isCommaSequence(expression) ? createParen(expression) : expression; + node.statement = asEmbeddedStatement(statement); + return node; +} +export function updateForOf(node: ForOfStatement, awaitModifier: AwaitKeywordToken | undefined, initializer: ForInitializer, expression: Expression, statement: Statement) { + return node.awaitModifier !== awaitModifier + || node.initializer !== initializer + || node.expression !== expression + || node.statement !== statement + ? updateNode(createForOf(awaitModifier, initializer, expression, statement), node) + : node; +} +export function createContinue(label?: string | Identifier): ContinueStatement { + const node = (createSynthesizedNode(SyntaxKind.ContinueStatement)); + node.label = asName(label); + return node; +} +export function updateContinue(node: ContinueStatement, label: Identifier | undefined) { + return node.label !== label + ? updateNode(createContinue(label), node) + : node; +} +export function createBreak(label?: string | Identifier): BreakStatement { + const node = (createSynthesizedNode(SyntaxKind.BreakStatement)); + node.label = asName(label); + return node; +} +export function updateBreak(node: BreakStatement, label: Identifier | undefined) { + return node.label !== label + ? updateNode(createBreak(label), node) + : node; +} +export function createReturn(expression?: Expression): ReturnStatement { + const node = (createSynthesizedNode(SyntaxKind.ReturnStatement)); + node.expression = expression; + return node; +} +export function updateReturn(node: ReturnStatement, expression: Expression | undefined) { + return node.expression !== expression + ? updateNode(createReturn(expression), node) + : node; +} +export function createWith(expression: Expression, statement: Statement) { + const node = (createSynthesizedNode(SyntaxKind.WithStatement)); + node.expression = expression; + node.statement = asEmbeddedStatement(statement); + return node; +} +export function updateWith(node: WithStatement, expression: Expression, statement: Statement) { + return node.expression !== expression + || node.statement !== statement + ? updateNode(createWith(expression, statement), node) + : node; +} +export function createSwitch(expression: Expression, caseBlock: CaseBlock): SwitchStatement { + const node = (createSynthesizedNode(SyntaxKind.SwitchStatement)); + node.expression = parenthesizeExpressionForList(expression); + node.caseBlock = caseBlock; + return node; +} +export function updateSwitch(node: SwitchStatement, expression: Expression, caseBlock: CaseBlock) { + return node.expression !== expression + || node.caseBlock !== caseBlock + ? updateNode(createSwitch(expression, caseBlock), node) + : node; +} +export function createLabel(label: string | Identifier, statement: Statement) { + const node = (createSynthesizedNode(SyntaxKind.LabeledStatement)); + node.label = asName(label); + node.statement = asEmbeddedStatement(statement); + return node; +} +export function updateLabel(node: LabeledStatement, label: Identifier, statement: Statement) { + return node.label !== label + || node.statement !== statement + ? updateNode(createLabel(label, statement), node) + : node; +} +export function createThrow(expression: Expression) { + const node = (createSynthesizedNode(SyntaxKind.ThrowStatement)); + node.expression = expression; + return node; +} +export function updateThrow(node: ThrowStatement, expression: Expression) { + return node.expression !== expression + ? updateNode(createThrow(expression), node) + : node; +} +export function createTry(tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined) { + const node = (createSynthesizedNode(SyntaxKind.TryStatement)); + node.tryBlock = tryBlock; + node.catchClause = catchClause; + node.finallyBlock = finallyBlock; + return node; +} +export function updateTry(node: TryStatement, tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined) { + return node.tryBlock !== tryBlock + || node.catchClause !== catchClause + || node.finallyBlock !== finallyBlock + ? updateNode(createTry(tryBlock, catchClause, finallyBlock), node) + : node; +} +export function createDebuggerStatement() { + return createSynthesizedNode(SyntaxKind.DebuggerStatement); +} +export function createVariableDeclaration(name: string | BindingName, type?: TypeNode, initializer?: Expression) { + /* Internally, one should probably use createTypeScriptVariableDeclaration instead and handle definite assignment assertions */ + const node = (createSynthesizedNode(SyntaxKind.VariableDeclaration)); + node.name = asName(name); + node.type = type; + node.initializer = initializer !== undefined ? parenthesizeExpressionForList(initializer) : undefined; + return node; +} +export function updateVariableDeclaration(node: VariableDeclaration, name: BindingName, type: TypeNode | undefined, initializer: Expression | undefined) { + /* Internally, one should probably use updateTypeScriptVariableDeclaration instead and handle definite assignment assertions */ + return node.name !== name + || node.type !== type + || node.initializer !== initializer + ? updateNode(createVariableDeclaration(name, type, initializer), node) + : node; +} +/* @internal */ +export function createTypeScriptVariableDeclaration(name: string | BindingName, exclaimationToken?: Token, type?: TypeNode, initializer?: Expression) { + const node = (createSynthesizedNode(SyntaxKind.VariableDeclaration)); + node.name = asName(name); + node.type = type; + node.initializer = initializer !== undefined ? parenthesizeExpressionForList(initializer) : undefined; + node.exclamationToken = exclaimationToken; + return node; +} +/* @internal */ +export function updateTypeScriptVariableDeclaration(node: VariableDeclaration, name: BindingName, exclaimationToken: Token | undefined, type: TypeNode | undefined, initializer: Expression | undefined) { + return node.name !== name + || node.type !== type + || node.initializer !== initializer + || node.exclamationToken !== exclaimationToken + ? updateNode(createTypeScriptVariableDeclaration(name, exclaimationToken, type, initializer), node) + : node; +} +export function createVariableDeclarationList(declarations: readonly VariableDeclaration[], flags = NodeFlags.None) { + const node = (createSynthesizedNode(SyntaxKind.VariableDeclarationList)); + node.flags |= flags & NodeFlags.BlockScoped; + node.declarations = createNodeArray(declarations); + return node; +} +export function updateVariableDeclarationList(node: VariableDeclarationList, declarations: readonly VariableDeclaration[]) { + return node.declarations !== declarations + ? updateNode(createVariableDeclarationList(declarations, node.flags), node) + : node; +} +export function createFunctionDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, asteriskToken: AsteriskToken | undefined, name: string | Identifier | undefined, typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) { + const node = (createSynthesizedNode(SyntaxKind.FunctionDeclaration)); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.asteriskToken = asteriskToken; + node.name = asName(name); + node.typeParameters = asNodeArray(typeParameters); + node.parameters = createNodeArray(parameters); + node.type = type; + node.body = body; + return node; +} +export function updateFunctionDeclaration(node: FunctionDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, asteriskToken: AsteriskToken | undefined, name: Identifier | undefined, typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.asteriskToken !== asteriskToken + || node.name !== name + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + || node.body !== body + ? updateNode(createFunctionDeclaration(decorators, modifiers, asteriskToken, name, typeParameters, parameters, type, body), node) + : node; +} +export function createClassDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | Identifier | undefined, typeParameters: readonly TypeParameterDeclaration[] | undefined, heritageClauses: readonly HeritageClause[] | undefined, members: readonly ClassElement[]) { + const node = (createSynthesizedNode(SyntaxKind.ClassDeclaration)); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.name = asName(name); + node.typeParameters = asNodeArray(typeParameters); + node.heritageClauses = asNodeArray(heritageClauses); + node.members = createNodeArray(members); + return node; +} +export function updateClassDeclaration(node: ClassDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: Identifier | undefined, typeParameters: readonly TypeParameterDeclaration[] | undefined, heritageClauses: readonly HeritageClause[] | undefined, members: readonly ClassElement[]) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.typeParameters !== typeParameters + || node.heritageClauses !== heritageClauses + || node.members !== members + ? updateNode(createClassDeclaration(decorators, modifiers, name, typeParameters, heritageClauses, members), node) + : node; +} +export function createInterfaceDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | Identifier, typeParameters: readonly TypeParameterDeclaration[] | undefined, heritageClauses: readonly HeritageClause[] | undefined, members: readonly TypeElement[]) { + const node = (createSynthesizedNode(SyntaxKind.InterfaceDeclaration)); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.name = asName(name); + node.typeParameters = asNodeArray(typeParameters); + node.heritageClauses = asNodeArray(heritageClauses); + node.members = createNodeArray(members); + return node; +} +export function updateInterfaceDeclaration(node: InterfaceDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: Identifier, typeParameters: readonly TypeParameterDeclaration[] | undefined, heritageClauses: readonly HeritageClause[] | undefined, members: readonly TypeElement[]) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.typeParameters !== typeParameters + || node.heritageClauses !== heritageClauses + || node.members !== members + ? updateNode(createInterfaceDeclaration(decorators, modifiers, name, typeParameters, heritageClauses, members), node) + : node; +} +export function createTypeAliasDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | Identifier, typeParameters: readonly TypeParameterDeclaration[] | undefined, type: TypeNode) { + const node = (createSynthesizedNode(SyntaxKind.TypeAliasDeclaration)); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.name = asName(name); + node.typeParameters = asNodeArray(typeParameters); + node.type = type; + return node; +} +export function updateTypeAliasDeclaration(node: TypeAliasDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: Identifier, typeParameters: readonly TypeParameterDeclaration[] | undefined, type: TypeNode) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.typeParameters !== typeParameters + || node.type !== type + ? updateNode(createTypeAliasDeclaration(decorators, modifiers, name, typeParameters, type), node) + : node; +} +export function createEnumDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | Identifier, members: readonly EnumMember[]) { + const node = (createSynthesizedNode(SyntaxKind.EnumDeclaration)); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.name = asName(name); + node.members = createNodeArray(members); + return node; +} +export function updateEnumDeclaration(node: EnumDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: Identifier, members: readonly EnumMember[]) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.members !== members + ? updateNode(createEnumDeclaration(decorators, modifiers, name, members), node) + : node; +} +export function createModuleDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: ModuleName, body: ModuleBody | undefined, flags = NodeFlags.None) { + const node = (createSynthesizedNode(SyntaxKind.ModuleDeclaration)); + node.flags |= flags & (NodeFlags.Namespace | NodeFlags.NestedNamespace | NodeFlags.GlobalAugmentation); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.name = name; + node.body = body; + return node; +} +export function updateModuleDeclaration(node: ModuleDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: ModuleName, body: ModuleBody | undefined) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.body !== body + ? updateNode(createModuleDeclaration(decorators, modifiers, name, body, node.flags), node) + : node; +} +export function createModuleBlock(statements: readonly Statement[]) { + const node = (createSynthesizedNode(SyntaxKind.ModuleBlock)); + node.statements = createNodeArray(statements); + return node; +} +export function updateModuleBlock(node: ModuleBlock, statements: readonly Statement[]) { + return node.statements !== statements + ? updateNode(createModuleBlock(statements), node) + : node; +} +export function createCaseBlock(clauses: readonly CaseOrDefaultClause[]): CaseBlock { + const node = (createSynthesizedNode(SyntaxKind.CaseBlock)); + node.clauses = createNodeArray(clauses); + return node; +} +export function updateCaseBlock(node: CaseBlock, clauses: readonly CaseOrDefaultClause[]) { + return node.clauses !== clauses + ? updateNode(createCaseBlock(clauses), node) + : node; +} +export function createNamespaceExportDeclaration(name: string | Identifier) { + const node = (createSynthesizedNode(SyntaxKind.NamespaceExportDeclaration)); + node.name = asName(name); + return node; +} +export function updateNamespaceExportDeclaration(node: NamespaceExportDeclaration, name: Identifier) { + return node.name !== name + ? updateNode(createNamespaceExportDeclaration(name), node) + : node; +} +export function createImportEqualsDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | Identifier, moduleReference: ModuleReference) { + const node = (createSynthesizedNode(SyntaxKind.ImportEqualsDeclaration)); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.name = asName(name); + node.moduleReference = moduleReference; + return node; +} +export function updateImportEqualsDeclaration(node: ImportEqualsDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: Identifier, moduleReference: ModuleReference) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.moduleReference !== moduleReference + ? updateNode(createImportEqualsDeclaration(decorators, modifiers, name, moduleReference), node) + : node; +} +export function createImportDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression): ImportDeclaration { + const node = (createSynthesizedNode(SyntaxKind.ImportDeclaration)); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.importClause = importClause; + node.moduleSpecifier = moduleSpecifier; + return node; +} +export function updateImportDeclaration(node: ImportDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.importClause !== importClause + || node.moduleSpecifier !== moduleSpecifier + ? updateNode(createImportDeclaration(decorators, modifiers, importClause, moduleSpecifier), node) + : node; +} +export function createImportClause(name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause { + const node = (createSynthesizedNode(SyntaxKind.ImportClause)); + node.name = name; + node.namedBindings = namedBindings; + return node; +} +export function updateImportClause(node: ImportClause, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined) { + return node.name !== name + || node.namedBindings !== namedBindings + ? updateNode(createImportClause(name, namedBindings), node) + : node; +} +export function createNamespaceImport(name: Identifier): NamespaceImport { + const node = (createSynthesizedNode(SyntaxKind.NamespaceImport)); + node.name = name; + return node; +} +export function updateNamespaceImport(node: NamespaceImport, name: Identifier) { + return node.name !== name + ? updateNode(createNamespaceImport(name), node) + : node; +} +export function createNamedImports(elements: readonly ImportSpecifier[]): NamedImports { + const node = (createSynthesizedNode(SyntaxKind.NamedImports)); + node.elements = createNodeArray(elements); + return node; +} +export function updateNamedImports(node: NamedImports, elements: readonly ImportSpecifier[]) { + return node.elements !== elements + ? updateNode(createNamedImports(elements), node) + : node; +} +export function createImportSpecifier(propertyName: Identifier | undefined, name: Identifier) { + const node = (createSynthesizedNode(SyntaxKind.ImportSpecifier)); + node.propertyName = propertyName; + node.name = name; + return node; +} +export function updateImportSpecifier(node: ImportSpecifier, propertyName: Identifier | undefined, name: Identifier) { + return node.propertyName !== propertyName + || node.name !== name + ? updateNode(createImportSpecifier(propertyName, name), node) + : node; +} +export function createExportAssignment(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isExportEquals: boolean | undefined, expression: Expression) { + const node = (createSynthesizedNode(SyntaxKind.ExportAssignment)); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.isExportEquals = isExportEquals; + node.expression = isExportEquals ? parenthesizeBinaryOperand(SyntaxKind.EqualsToken, expression, /*isLeftSideOfBinary*/ false, /*leftOperand*/ undefined) : parenthesizeDefaultExpression(expression); + return node; +} +export function updateExportAssignment(node: ExportAssignment, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, expression: Expression) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.expression !== expression + ? updateNode(createExportAssignment(decorators, modifiers, node.isExportEquals, expression), node) + : node; +} +export function createExportDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, exportClause: NamedExports | undefined, moduleSpecifier?: Expression) { + const node = (createSynthesizedNode(SyntaxKind.ExportDeclaration)); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.exportClause = exportClause; + node.moduleSpecifier = moduleSpecifier; + return node; +} +export function updateExportDeclaration(node: ExportDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, exportClause: NamedExports | undefined, moduleSpecifier: Expression | undefined) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.exportClause !== exportClause + || node.moduleSpecifier !== moduleSpecifier + ? updateNode(createExportDeclaration(decorators, modifiers, exportClause, moduleSpecifier), node) + : node; +} +/* @internal */ +export function createEmptyExports() { + return createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createNamedExports([]), /*moduleSpecifier*/ undefined); +} +export function createNamedExports(elements: readonly ExportSpecifier[]) { + const node = (createSynthesizedNode(SyntaxKind.NamedExports)); + node.elements = createNodeArray(elements); + return node; +} +export function updateNamedExports(node: NamedExports, elements: readonly ExportSpecifier[]) { + return node.elements !== elements + ? updateNode(createNamedExports(elements), node) + : node; +} +export function createExportSpecifier(propertyName: string | Identifier | undefined, name: string | Identifier) { + const node = (createSynthesizedNode(SyntaxKind.ExportSpecifier)); + node.propertyName = asName(propertyName); + node.name = asName(name); + return node; +} +export function updateExportSpecifier(node: ExportSpecifier, propertyName: Identifier | undefined, name: Identifier) { + return node.propertyName !== propertyName + || node.name !== name + ? updateNode(createExportSpecifier(propertyName, name), node) + : node; +} +// Module references +export function createExternalModuleReference(expression: Expression) { + const node = (createSynthesizedNode(SyntaxKind.ExternalModuleReference)); + node.expression = expression; + return node; +} +export function updateExternalModuleReference(node: ExternalModuleReference, expression: Expression) { + return node.expression !== expression + ? updateNode(createExternalModuleReference(expression), node) + : node; +} +// JSDoc +/* @internal */ +export function createJSDocTypeExpression(type: TypeNode): JSDocTypeExpression { + const node = (createSynthesizedNode(SyntaxKind.JSDocTypeExpression) as JSDocTypeExpression); + node.type = type; + return node; +} +/* @internal */ +export function createJSDocTypeTag(typeExpression: JSDocTypeExpression, comment?: string): JSDocTypeTag { + const tag = createJSDocTag(SyntaxKind.JSDocTypeTag, "type"); + tag.typeExpression = typeExpression; + tag.comment = comment; + return tag; +} +/* @internal */ +export function createJSDocReturnTag(typeExpression?: JSDocTypeExpression, comment?: string): JSDocReturnTag { + const tag = createJSDocTag(SyntaxKind.JSDocReturnTag, "returns"); + tag.typeExpression = typeExpression; + tag.comment = comment; + return tag; +} +/** @internal */ +export function createJSDocThisTag(typeExpression?: JSDocTypeExpression): JSDocThisTag { + const tag = createJSDocTag(SyntaxKind.JSDocThisTag, "this"); + tag.typeExpression = typeExpression; + return tag; +} +/* @internal */ +export function createJSDocParamTag(name: EntityName, isBracketed: boolean, typeExpression?: JSDocTypeExpression, comment?: string): JSDocParameterTag { + const tag = createJSDocTag(SyntaxKind.JSDocParameterTag, "param"); + tag.typeExpression = typeExpression; + tag.name = name; + tag.isBracketed = isBracketed; + tag.comment = comment; + return tag; +} +/* @internal */ +export function createJSDocComment(comment?: string | undefined, tags?: NodeArray | undefined) { + const node = (createSynthesizedNode(SyntaxKind.JSDocComment) as JSDoc); + node.comment = comment; + node.tags = tags; + return node; +} +/* @internal */ +function createJSDocTag(kind: T["kind"], tagName: string): T { + const node = createSynthesizedNode(kind) as T; + node.tagName = createIdentifier(tagName); + return node; +} +// JSX +export function createJsxElement(openingElement: JsxOpeningElement, children: readonly JsxChild[], closingElement: JsxClosingElement) { + const node = (createSynthesizedNode(SyntaxKind.JsxElement)); + node.openingElement = openingElement; + node.children = createNodeArray(children); + node.closingElement = closingElement; + return node; +} +export function updateJsxElement(node: JsxElement, openingElement: JsxOpeningElement, children: readonly JsxChild[], closingElement: JsxClosingElement) { + return node.openingElement !== openingElement + || node.children !== children + || node.closingElement !== closingElement + ? updateNode(createJsxElement(openingElement, children, closingElement), node) + : node; +} +export function createJsxSelfClosingElement(tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { + const node = (createSynthesizedNode(SyntaxKind.JsxSelfClosingElement)); + node.tagName = tagName; + node.typeArguments = asNodeArray(typeArguments); + node.attributes = attributes; + return node; +} +export function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { + return node.tagName !== tagName + || node.typeArguments !== typeArguments + || node.attributes !== attributes + ? updateNode(createJsxSelfClosingElement(tagName, typeArguments, attributes), node) + : node; +} +export function createJsxOpeningElement(tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { + const node = (createSynthesizedNode(SyntaxKind.JsxOpeningElement)); + node.tagName = tagName; + node.typeArguments = asNodeArray(typeArguments); + node.attributes = attributes; + return node; +} +export function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { + return node.tagName !== tagName + || node.typeArguments !== typeArguments + || node.attributes !== attributes + ? updateNode(createJsxOpeningElement(tagName, typeArguments, attributes), node) + : node; +} +export function createJsxClosingElement(tagName: JsxTagNameExpression) { + const node = (createSynthesizedNode(SyntaxKind.JsxClosingElement)); + node.tagName = tagName; + return node; +} +export function updateJsxClosingElement(node: JsxClosingElement, tagName: JsxTagNameExpression) { + return node.tagName !== tagName + ? updateNode(createJsxClosingElement(tagName), node) + : node; +} +export function createJsxFragment(openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) { + const node = (createSynthesizedNode(SyntaxKind.JsxFragment)); + node.openingFragment = openingFragment; + node.children = createNodeArray(children); + node.closingFragment = closingFragment; + return node; +} +export function createJsxText(text: string, containsOnlyTriviaWhiteSpaces?: boolean) { + const node = (createSynthesizedNode(SyntaxKind.JsxText)); + node.text = text; + node.containsOnlyTriviaWhiteSpaces = !!containsOnlyTriviaWhiteSpaces; + return node; +} +export function updateJsxText(node: JsxText, text: string, containsOnlyTriviaWhiteSpaces?: boolean) { + return node.text !== text + || node.containsOnlyTriviaWhiteSpaces !== containsOnlyTriviaWhiteSpaces + ? updateNode(createJsxText(text, containsOnlyTriviaWhiteSpaces), node) + : node; +} +export function createJsxOpeningFragment() { + return createSynthesizedNode(SyntaxKind.JsxOpeningFragment); +} +export function createJsxJsxClosingFragment() { + return createSynthesizedNode(SyntaxKind.JsxClosingFragment); +} +export function updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) { + return node.openingFragment !== openingFragment + || node.children !== children + || node.closingFragment !== closingFragment + ? updateNode(createJsxFragment(openingFragment, children, closingFragment), node) + : node; +} +export function createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression) { + const node = (createSynthesizedNode(SyntaxKind.JsxAttribute)); + node.name = name; + node.initializer = initializer; + return node; +} +export function updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression) { + return node.name !== name + || node.initializer !== initializer + ? updateNode(createJsxAttribute(name, initializer), node) + : node; +} +export function createJsxAttributes(properties: readonly JsxAttributeLike[]) { + const node = (createSynthesizedNode(SyntaxKind.JsxAttributes)); + node.properties = createNodeArray(properties); + return node; +} +export function updateJsxAttributes(node: JsxAttributes, properties: readonly JsxAttributeLike[]) { + return node.properties !== properties + ? updateNode(createJsxAttributes(properties), node) + : node; +} +export function createJsxSpreadAttribute(expression: Expression) { + const node = (createSynthesizedNode(SyntaxKind.JsxSpreadAttribute)); + node.expression = expression; + return node; +} +export function updateJsxSpreadAttribute(node: JsxSpreadAttribute, expression: Expression) { + return node.expression !== expression + ? updateNode(createJsxSpreadAttribute(expression), node) + : node; +} +export function createJsxExpression(dotDotDotToken: DotDotDotToken | undefined, expression: Expression | undefined) { + const node = (createSynthesizedNode(SyntaxKind.JsxExpression)); + node.dotDotDotToken = dotDotDotToken; + node.expression = expression; + return node; +} +export function updateJsxExpression(node: JsxExpression, expression: Expression | undefined) { + return node.expression !== expression + ? updateNode(createJsxExpression(node.dotDotDotToken, expression), node) + : node; +} +// Clauses +export function createCaseClause(expression: Expression, statements: readonly Statement[]) { + const node = (createSynthesizedNode(SyntaxKind.CaseClause)); + node.expression = parenthesizeExpressionForList(expression); + node.statements = createNodeArray(statements); + return node; +} +export function updateCaseClause(node: CaseClause, expression: Expression, statements: readonly Statement[]) { + return node.expression !== expression + || node.statements !== statements + ? updateNode(createCaseClause(expression, statements), node) + : node; +} +export function createDefaultClause(statements: readonly Statement[]) { + const node = (createSynthesizedNode(SyntaxKind.DefaultClause)); + node.statements = createNodeArray(statements); + return node; +} +export function updateDefaultClause(node: DefaultClause, statements: readonly Statement[]) { + return node.statements !== statements + ? updateNode(createDefaultClause(statements), node) + : node; +} +export function createHeritageClause(token: HeritageClause["token"], types: readonly ExpressionWithTypeArguments[]) { + const node = (createSynthesizedNode(SyntaxKind.HeritageClause)); + node.token = token; + node.types = createNodeArray(types); + return node; +} +export function updateHeritageClause(node: HeritageClause, types: readonly ExpressionWithTypeArguments[]) { + return node.types !== types + ? updateNode(createHeritageClause(node.token, types), node) + : node; +} +export function createCatchClause(variableDeclaration: string | VariableDeclaration | undefined, block: Block) { + const node = (createSynthesizedNode(SyntaxKind.CatchClause)); + node.variableDeclaration = isString(variableDeclaration) ? createVariableDeclaration(variableDeclaration) : variableDeclaration; + node.block = block; + return node; +} +export function updateCatchClause(node: CatchClause, variableDeclaration: VariableDeclaration | undefined, block: Block) { + return node.variableDeclaration !== variableDeclaration + || node.block !== block + ? updateNode(createCatchClause(variableDeclaration, block), node) + : node; +} +// Property assignments +export function createPropertyAssignment(name: string | PropertyName, initializer: Expression) { + const node = (createSynthesizedNode(SyntaxKind.PropertyAssignment)); + node.name = asName(name); + node.questionToken = undefined; + node.initializer = parenthesizeExpressionForList(initializer); + return node; +} +export function updatePropertyAssignment(node: PropertyAssignment, name: PropertyName, initializer: Expression) { + return node.name !== name + || node.initializer !== initializer + ? updateNode(createPropertyAssignment(name, initializer), node) + : node; +} +export function createShorthandPropertyAssignment(name: string | Identifier, objectAssignmentInitializer?: Expression) { + const node = (createSynthesizedNode(SyntaxKind.ShorthandPropertyAssignment)); + node.name = asName(name); + node.objectAssignmentInitializer = objectAssignmentInitializer !== undefined ? parenthesizeExpressionForList(objectAssignmentInitializer) : undefined; + return node; +} +export function updateShorthandPropertyAssignment(node: ShorthandPropertyAssignment, name: Identifier, objectAssignmentInitializer: Expression | undefined) { + return node.name !== name + || node.objectAssignmentInitializer !== objectAssignmentInitializer + ? updateNode(createShorthandPropertyAssignment(name, objectAssignmentInitializer), node) + : node; +} +export function createSpreadAssignment(expression: Expression) { + const node = (createSynthesizedNode(SyntaxKind.SpreadAssignment)); + node.expression = parenthesizeExpressionForList(expression); + return node; +} +export function updateSpreadAssignment(node: SpreadAssignment, expression: Expression) { + return node.expression !== expression + ? updateNode(createSpreadAssignment(expression), node) + : node; +} +// Enum +export function createEnumMember(name: string | PropertyName, initializer?: Expression) { + const node = (createSynthesizedNode(SyntaxKind.EnumMember)); + node.name = asName(name); + node.initializer = initializer && parenthesizeExpressionForList(initializer); + return node; +} +export function updateEnumMember(node: EnumMember, name: PropertyName, initializer: Expression | undefined) { + return node.name !== name + || node.initializer !== initializer + ? updateNode(createEnumMember(name, initializer), node) + : node; +} +// Top-level nodes +export function updateSourceFileNode(node: SourceFile, statements: readonly Statement[], isDeclarationFile?: boolean, referencedFiles?: SourceFile["referencedFiles"], typeReferences?: SourceFile["typeReferenceDirectives"], hasNoDefaultLib?: boolean, libReferences?: SourceFile["libReferenceDirectives"]) { + if (node.statements !== statements || + (isDeclarationFile !== undefined && node.isDeclarationFile !== isDeclarationFile) || + (referencedFiles !== undefined && node.referencedFiles !== referencedFiles) || + (typeReferences !== undefined && node.typeReferenceDirectives !== typeReferences) || + (libReferences !== undefined && node.libReferenceDirectives !== libReferences) || + (hasNoDefaultLib !== undefined && node.hasNoDefaultLib !== hasNoDefaultLib)) { + const updated = (createSynthesizedNode(SyntaxKind.SourceFile)); + updated.flags |= node.flags; + updated.statements = createNodeArray(statements); + updated.endOfFileToken = node.endOfFileToken; + updated.fileName = node.fileName; + updated.path = node.path; + updated.text = node.text; + updated.isDeclarationFile = isDeclarationFile === undefined ? node.isDeclarationFile : isDeclarationFile; + updated.referencedFiles = referencedFiles === undefined ? node.referencedFiles : referencedFiles; + updated.typeReferenceDirectives = typeReferences === undefined ? node.typeReferenceDirectives : typeReferences; + updated.hasNoDefaultLib = hasNoDefaultLib === undefined ? node.hasNoDefaultLib : hasNoDefaultLib; + updated.libReferenceDirectives = libReferences === undefined ? node.libReferenceDirectives : libReferences; + if (node.amdDependencies !== undefined) + updated.amdDependencies = node.amdDependencies; + if (node.moduleName !== undefined) + updated.moduleName = node.moduleName; + if (node.languageVariant !== undefined) + updated.languageVariant = node.languageVariant; + if (node.renamedDependencies !== undefined) + updated.renamedDependencies = node.renamedDependencies; + if (node.languageVersion !== undefined) + updated.languageVersion = node.languageVersion; + if (node.scriptKind !== undefined) + updated.scriptKind = node.scriptKind; + if (node.externalModuleIndicator !== undefined) + updated.externalModuleIndicator = node.externalModuleIndicator; + if (node.commonJsModuleIndicator !== undefined) + updated.commonJsModuleIndicator = node.commonJsModuleIndicator; + if (node.identifiers !== undefined) + updated.identifiers = node.identifiers; + if (node.nodeCount !== undefined) + updated.nodeCount = node.nodeCount; + if (node.identifierCount !== undefined) + updated.identifierCount = node.identifierCount; + if (node.symbolCount !== undefined) + updated.symbolCount = node.symbolCount; + if (node.parseDiagnostics !== undefined) + updated.parseDiagnostics = node.parseDiagnostics; + if (node.bindDiagnostics !== undefined) + updated.bindDiagnostics = node.bindDiagnostics; + if (node.bindSuggestionDiagnostics !== undefined) + updated.bindSuggestionDiagnostics = node.bindSuggestionDiagnostics; + if (node.lineMap !== undefined) + updated.lineMap = node.lineMap; + if (node.classifiableNames !== undefined) + updated.classifiableNames = node.classifiableNames; + if (node.resolvedModules !== undefined) + updated.resolvedModules = node.resolvedModules; + if (node.resolvedTypeReferenceDirectiveNames !== undefined) + updated.resolvedTypeReferenceDirectiveNames = node.resolvedTypeReferenceDirectiveNames; + if (node.imports !== undefined) + updated.imports = node.imports; + if (node.moduleAugmentations !== undefined) + updated.moduleAugmentations = node.moduleAugmentations; + if (node.pragmas !== undefined) + updated.pragmas = node.pragmas; + if (node.localJsxFactory !== undefined) + updated.localJsxFactory = node.localJsxFactory; + if (node.localJsxNamespace !== undefined) + updated.localJsxNamespace = node.localJsxNamespace; + return updateNode(updated, node); + } + return node; +} +/** + * Creates a shallow, memberwise clone of a node for mutation. + */ +export function getMutableClone(node: T): T { + const clone = getSynthesizedClone(node); + clone.pos = node.pos; + clone.end = node.end; + clone.parent = node.parent; + return clone; +} +// Transformation nodes +/** + * Creates a synthetic statement to act as a placeholder for a not-emitted statement in + * order to preserve comments. + * + * @param original The original statement. + */ +export function createNotEmittedStatement(original: Node) { + const node = (createSynthesizedNode(SyntaxKind.NotEmittedStatement)); + node.original = original; + setTextRange(node, original); + return node; +} +/** + * Creates a synthetic element to act as a placeholder for the end of an emitted declaration in + * order to properly emit exports. + */ +/* @internal */ +export function createEndOfDeclarationMarker(original: Node) { + const node = (createSynthesizedNode(SyntaxKind.EndOfDeclarationMarker)); + node.emitNode = ({} as EmitNode); + node.original = original; + return node; +} +/** + * Creates a synthetic element to act as a placeholder for the beginning of a merged declaration in + * order to properly emit exports. + */ +/* @internal */ +export function createMergeDeclarationMarker(original: Node) { + const node = (createSynthesizedNode(SyntaxKind.MergeDeclarationMarker)); + node.emitNode = ({} as EmitNode); + node.original = original; + return node; +} +/** + * Creates a synthetic expression to act as a placeholder for a not-emitted expression in + * order to preserve comments or sourcemap positions. + * + * @param expression The inner expression to emit. + * @param original The original outer expression. + * @param location The location for the expression. Defaults to the positions from "original" if provided. + */ +export function createPartiallyEmittedExpression(expression: Expression, original?: Node) { + const node = (createSynthesizedNode(SyntaxKind.PartiallyEmittedExpression)); + node.expression = expression; + node.original = original; + setTextRange(node, original); + return node; +} +export function updatePartiallyEmittedExpression(node: PartiallyEmittedExpression, expression: Expression) { + if (node.expression !== expression) { + return updateNode(createPartiallyEmittedExpression(expression, node.original), node); } - - export function createUnparsedSourceFile(text: string): UnparsedSource; - export function createUnparsedSourceFile(inputFile: InputFiles, type: "js" | "dts", stripInternal?: boolean): UnparsedSource; - export function createUnparsedSourceFile(text: string, mapPath: string | undefined, map: string | undefined): UnparsedSource; - export function createUnparsedSourceFile(textOrInputFiles: string | InputFiles, mapPathOrType?: string, mapTextOrStripInternal?: string | boolean): UnparsedSource { - const node = createUnparsedSource(); - let stripInternal: boolean | undefined; - let bundleFileInfo: BundleFileInfo | undefined; - if (!isString(textOrInputFiles)) { - Debug.assert(mapPathOrType === "js" || mapPathOrType === "dts"); - node.fileName = (mapPathOrType === "js" ? textOrInputFiles.javascriptPath : textOrInputFiles.declarationPath) || ""; - node.sourceMapPath = mapPathOrType === "js" ? textOrInputFiles.javascriptMapPath : textOrInputFiles.declarationMapPath; - Object.defineProperties(node, { - text: { get() { return mapPathOrType === "js" ? textOrInputFiles.javascriptText : textOrInputFiles.declarationText; } }, - sourceMapText: { get() { return mapPathOrType === "js" ? textOrInputFiles.javascriptMapText : textOrInputFiles.declarationMapText; } }, - }); - - - if (textOrInputFiles.buildInfo && textOrInputFiles.buildInfo.bundle) { - node.oldFileOfCurrentEmit = textOrInputFiles.oldFileOfCurrentEmit; - Debug.assert(mapTextOrStripInternal === undefined || typeof mapTextOrStripInternal === "boolean"); - stripInternal = mapTextOrStripInternal as boolean | undefined; - bundleFileInfo = mapPathOrType === "js" ? textOrInputFiles.buildInfo.bundle.js : textOrInputFiles.buildInfo.bundle.dts; - if (node.oldFileOfCurrentEmit) { - parseOldFileOfCurrentEmit(node, Debug.assertDefined(bundleFileInfo)); - return node; - } - } + return node; +} +function flattenCommaElements(node: Expression): Expression | readonly Expression[] { + if (nodeIsSynthesized(node) && !isParseTreeNode(node) && !node.original && !node.emitNode && !node.id) { + if (node.kind === SyntaxKind.CommaListExpression) { + return (node).elements; } - else { - node.fileName = ""; - node.text = textOrInputFiles; - node.sourceMapPath = mapPathOrType; - node.sourceMapText = mapTextOrStripInternal as string; + if (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.CommaToken) { + return [node.left, node.right]; } - Debug.assert(!node.oldFileOfCurrentEmit); - parseUnparsedSourceFile(node, bundleFileInfo, stripInternal); - return node; } - - function parseUnparsedSourceFile(node: UnparsedSource, bundleFileInfo: BundleFileInfo | undefined, stripInternal: boolean | undefined) { - let prologues: UnparsedPrologue[] | undefined; - let helpers: UnscopedEmitHelper[] | undefined; - let referencedFiles: FileReference[] | undefined; - let typeReferenceDirectives: string[] | undefined; - let libReferenceDirectives: FileReference[] | undefined; - let texts: UnparsedSourceText[] | undefined; - - for (const section of bundleFileInfo ? bundleFileInfo.sections : emptyArray) { - switch (section.kind) { - case BundleFileSectionKind.Prologue: - (prologues || (prologues = [])).push(createUnparsedNode(section, node) as UnparsedPrologue); - break; - case BundleFileSectionKind.EmitHelpers: - (helpers || (helpers = [])).push(getAllUnscopedEmitHelpers().get(section.data)!); - break; - case BundleFileSectionKind.NoDefaultLib: - node.hasNoDefaultLib = true; - break; - case BundleFileSectionKind.Reference: - (referencedFiles || (referencedFiles = [])).push({ pos: -1, end: -1, fileName: section.data }); - break; - case BundleFileSectionKind.Type: - (typeReferenceDirectives || (typeReferenceDirectives = [])).push(section.data); - break; - case BundleFileSectionKind.Lib: - (libReferenceDirectives || (libReferenceDirectives = [])).push({ pos: -1, end: -1, fileName: section.data }); - break; - case BundleFileSectionKind.Prepend: - const prependNode = createUnparsedNode(section, node) as UnparsedPrepend; - let prependTexts: UnparsedTextLike[] | undefined; - for (const text of section.texts) { - if (!stripInternal || text.kind !== BundleFileSectionKind.Internal) { - (prependTexts || (prependTexts = [])).push(createUnparsedNode(text, node) as UnparsedTextLike); - } - } - prependNode.texts = prependTexts || emptyArray; - (texts || (texts = [])).push(prependNode); - break; - case BundleFileSectionKind.Internal: - if (stripInternal) { - if (!texts) texts = []; - break; - } - // falls through - - case BundleFileSectionKind.Text: - (texts || (texts = [])).push(createUnparsedNode(section, node) as UnparsedTextLike); - break; - default: - Debug.assertNever(section); + return node; +} +export function createCommaList(elements: readonly Expression[]) { + const node = (createSynthesizedNode(SyntaxKind.CommaListExpression)); + node.elements = createNodeArray(sameFlatMap(elements, flattenCommaElements)); + return node; +} +export function updateCommaList(node: CommaListExpression, elements: readonly Expression[]) { + return node.elements !== elements + ? updateNode(createCommaList(elements), node) + : node; +} +/* @internal */ +export function createSyntheticReferenceExpression(expression: Expression, thisArg: Expression) { + const node = (createSynthesizedNode(SyntaxKind.SyntheticReferenceExpression)); + node.expression = expression; + node.thisArg = thisArg; + return node; +} +/* @internal */ +export function updateSyntheticReferenceExpression(node: SyntheticReferenceExpression, expression: Expression, thisArg: Expression) { + return node.expression !== expression + || node.thisArg !== thisArg + ? updateNode(createSyntheticReferenceExpression(expression, thisArg), node) + : node; +} +export function createBundle(sourceFiles: readonly SourceFile[], prepends: readonly (UnparsedSource | InputFiles)[] = emptyArray) { + const node = (createNode(SyntaxKind.Bundle)); + node.prepends = prepends; + node.sourceFiles = sourceFiles; + return node; +} +let allUnscopedEmitHelpers: ts.ReadonlyMap | undefined; +function getAllUnscopedEmitHelpers() { + return allUnscopedEmitHelpers || (allUnscopedEmitHelpers = arrayToMap([ + valuesHelper, + readHelper, + spreadHelper, + spreadArraysHelper, + restHelper, + decorateHelper, + metadataHelper, + paramHelper, + awaiterHelper, + assignHelper, + awaitHelper, + asyncGeneratorHelper, + asyncDelegator, + asyncValues, + extendsHelper, + templateObjectHelper, + generatorHelper, + importStarHelper, + importDefaultHelper + ], helper => helper.name)); +} +function createUnparsedSource() { + const node = (createNode(SyntaxKind.UnparsedSource)); + node.prologues = emptyArray; + node.referencedFiles = emptyArray; + node.libReferenceDirectives = emptyArray; + node.getLineAndCharacterOfPosition = pos => getLineAndCharacterOfPosition(node, pos); + return node; +} +export function createUnparsedSourceFile(text: string): UnparsedSource; +export function createUnparsedSourceFile(inputFile: InputFiles, type: "js" | "dts", stripInternal?: boolean): UnparsedSource; +export function createUnparsedSourceFile(text: string, mapPath: string | undefined, map: string | undefined): UnparsedSource; +export function createUnparsedSourceFile(textOrInputFiles: string | InputFiles, mapPathOrType?: string, mapTextOrStripInternal?: string | boolean): UnparsedSource { + const node = createUnparsedSource(); + let stripInternal: boolean | undefined; + let bundleFileInfo: BundleFileInfo | undefined; + if (!isString(textOrInputFiles)) { + Debug.assert(mapPathOrType === "js" || mapPathOrType === "dts"); + node.fileName = (mapPathOrType === "js" ? textOrInputFiles.javascriptPath : textOrInputFiles.declarationPath) || ""; + node.sourceMapPath = mapPathOrType === "js" ? textOrInputFiles.javascriptMapPath : textOrInputFiles.declarationMapPath; + Object.defineProperties(node, { + text: { get() { return mapPathOrType === "js" ? textOrInputFiles.javascriptText : textOrInputFiles.declarationText; } }, + sourceMapText: { get() { return mapPathOrType === "js" ? textOrInputFiles.javascriptMapText : textOrInputFiles.declarationMapText; } }, + }); + if (textOrInputFiles.buildInfo && textOrInputFiles.buildInfo.bundle) { + node.oldFileOfCurrentEmit = textOrInputFiles.oldFileOfCurrentEmit; + Debug.assert(mapTextOrStripInternal === undefined || typeof mapTextOrStripInternal === "boolean"); + stripInternal = mapTextOrStripInternal as boolean | undefined; + bundleFileInfo = mapPathOrType === "js" ? textOrInputFiles.buildInfo.bundle.js : textOrInputFiles.buildInfo.bundle.dts; + if (node.oldFileOfCurrentEmit) { + parseOldFileOfCurrentEmit(node, Debug.assertDefined(bundleFileInfo)); + return node; } } - - node.prologues = prologues || emptyArray; - node.helpers = helpers; - node.referencedFiles = referencedFiles || emptyArray; - node.typeReferenceDirectives = typeReferenceDirectives; - node.libReferenceDirectives = libReferenceDirectives || emptyArray; - node.texts = texts || [createUnparsedNode({ kind: BundleFileSectionKind.Text, pos: 0, end: node.text.length }, node)]; } - - function parseOldFileOfCurrentEmit(node: UnparsedSource, bundleFileInfo: BundleFileInfo) { - Debug.assert(!!node.oldFileOfCurrentEmit); - let texts: UnparsedTextLike[] | undefined; - let syntheticReferences: UnparsedSyntheticReference[] | undefined; - for (const section of bundleFileInfo.sections) { - switch (section.kind) { - case BundleFileSectionKind.Internal: - case BundleFileSectionKind.Text: - (texts || (texts = [])).push(createUnparsedNode(section, node) as UnparsedTextLike); - break; - - case BundleFileSectionKind.NoDefaultLib: - case BundleFileSectionKind.Reference: - case BundleFileSectionKind.Type: - case BundleFileSectionKind.Lib: - (syntheticReferences || (syntheticReferences = [])).push(createUnparsedSyntheticReference(section, node)); - break; - - // Ignore - case BundleFileSectionKind.Prologue: - case BundleFileSectionKind.EmitHelpers: - case BundleFileSectionKind.Prepend: - break; - - default: - Debug.assertNever(section); - } - } - node.texts = texts || emptyArray; - node.helpers = map(bundleFileInfo.sources && bundleFileInfo.sources.helpers, name => getAllUnscopedEmitHelpers().get(name)!); - node.syntheticReferences = syntheticReferences; - return node; + else { + node.fileName = ""; + node.text = textOrInputFiles; + node.sourceMapPath = mapPathOrType; + node.sourceMapText = mapTextOrStripInternal as string; } - - function mapBundleFileSectionKindToSyntaxKind(kind: BundleFileSectionKind): SyntaxKind { - switch (kind) { - case BundleFileSectionKind.Prologue: return SyntaxKind.UnparsedPrologue; - case BundleFileSectionKind.Prepend: return SyntaxKind.UnparsedPrepend; - case BundleFileSectionKind.Internal: return SyntaxKind.UnparsedInternalText; - case BundleFileSectionKind.Text: return SyntaxKind.UnparsedText; - + Debug.assert(!node.oldFileOfCurrentEmit); + parseUnparsedSourceFile(node, bundleFileInfo, stripInternal); + return node; +} +function parseUnparsedSourceFile(node: UnparsedSource, bundleFileInfo: BundleFileInfo | undefined, stripInternal: boolean | undefined) { + let prologues: UnparsedPrologue[] | undefined; + let helpers: UnscopedEmitHelper[] | undefined; + let referencedFiles: FileReference[] | undefined; + let typeReferenceDirectives: string[] | undefined; + let libReferenceDirectives: FileReference[] | undefined; + let texts: UnparsedSourceText[] | undefined; + for (const section of bundleFileInfo ? bundleFileInfo.sections : emptyArray) { + switch (section.kind) { + case BundleFileSectionKind.Prologue: + (prologues || (prologues = [])).push((createUnparsedNode(section, node) as UnparsedPrologue)); + break; case BundleFileSectionKind.EmitHelpers: + (helpers || (helpers = [])).push(getAllUnscopedEmitHelpers().get(section.data)!); + break; case BundleFileSectionKind.NoDefaultLib: + node.hasNoDefaultLib = true; + break; case BundleFileSectionKind.Reference: + (referencedFiles || (referencedFiles = [])).push({ pos: -1, end: -1, fileName: section.data }); + break; case BundleFileSectionKind.Type: + (typeReferenceDirectives || (typeReferenceDirectives = [])).push(section.data); + break; case BundleFileSectionKind.Lib: - return Debug.fail(`BundleFileSectionKind: ${kind} not yet mapped to SyntaxKind`); - - default: - return Debug.assertNever(kind); - } - } - - function createUnparsedNode(section: BundleFileSection, parent: UnparsedSource): UnparsedNode { - const node = createNode(mapBundleFileSectionKindToSyntaxKind(section.kind), section.pos, section.end) as UnparsedNode; - node.parent = parent; - node.data = section.data; - return node; - } - - function createUnparsedSyntheticReference(section: BundleFileHasNoDefaultLib | BundleFileReference, parent: UnparsedSource) { - const node = createNode(SyntaxKind.UnparsedSyntheticReference, section.pos, section.end) as UnparsedSyntheticReference; - node.parent = parent; - node.data = section.data; - node.section = section; - return node; - } - - export function createInputFiles( - javascriptText: string, - declarationText: string - ): InputFiles; - export function createInputFiles( - readFileText: (path: string) => string | undefined, - javascriptPath: string, - javascriptMapPath: string | undefined, - declarationPath: string, - declarationMapPath: string | undefined, - buildInfoPath: string | undefined - ): InputFiles; - export function createInputFiles( - javascriptText: string, - declarationText: string, - javascriptMapPath: string | undefined, - javascriptMapText: string | undefined, - declarationMapPath: string | undefined, - declarationMapText: string | undefined - ): InputFiles; - /*@internal*/ - export function createInputFiles( - javascriptText: string, - declarationText: string, - javascriptMapPath: string | undefined, - javascriptMapText: string | undefined, - declarationMapPath: string | undefined, - declarationMapText: string | undefined, - javascriptPath: string | undefined, - declarationPath: string | undefined, - buildInfoPath?: string | undefined, - buildInfo?: BuildInfo, - oldFileOfCurrentEmit?: boolean - ): InputFiles; - export function createInputFiles( - javascriptTextOrReadFileText: string | ((path: string) => string | undefined), - declarationTextOrJavascriptPath: string, - javascriptMapPath?: string, - javascriptMapTextOrDeclarationPath?: string, - declarationMapPath?: string, - declarationMapTextOrBuildInfoPath?: string, - javascriptPath?: string | undefined, - declarationPath?: string | undefined, - buildInfoPath?: string | undefined, - buildInfo?: BuildInfo, - oldFileOfCurrentEmit?: boolean - ): InputFiles { - const node = createNode(SyntaxKind.InputFiles); - if (!isString(javascriptTextOrReadFileText)) { - const cache = createMap(); - const textGetter = (path: string | undefined) => { - if (path === undefined) return undefined; - let value = cache.get(path); - if (value === undefined) { - value = javascriptTextOrReadFileText(path); - cache.set(path, value !== undefined ? value : false); + (libReferenceDirectives || (libReferenceDirectives = [])).push({ pos: -1, end: -1, fileName: section.data }); + break; + case BundleFileSectionKind.Prepend: + const prependNode = (createUnparsedNode(section, node) as UnparsedPrepend); + let prependTexts: UnparsedTextLike[] | undefined; + for (const text of section.texts) { + if (!stripInternal || text.kind !== BundleFileSectionKind.Internal) { + (prependTexts || (prependTexts = [])).push((createUnparsedNode(text, node) as UnparsedTextLike)); + } } - return value !== false ? value as string : undefined; - }; - const definedTextGetter = (path: string) => { - const result = textGetter(path); - return result !== undefined ? result : `/* Input file ${path} was missing */\r\n`; - }; - let buildInfo: BuildInfo | false; - const getAndCacheBuildInfo = (getText: () => string | undefined) => { - if (buildInfo === undefined) { - const result = getText(); - buildInfo = result !== undefined ? getBuildInfo(result) : false; + prependNode.texts = prependTexts || emptyArray; + (texts || (texts = [])).push(prependNode); + break; + case BundleFileSectionKind.Internal: + if (stripInternal) { + if (!texts) + texts = []; + break; } - return buildInfo || undefined; - }; - node.javascriptPath = declarationTextOrJavascriptPath; - node.javascriptMapPath = javascriptMapPath; - node.declarationPath = Debug.assertDefined(javascriptMapTextOrDeclarationPath); - node.declarationMapPath = declarationMapPath; - node.buildInfoPath = declarationMapTextOrBuildInfoPath; - Object.defineProperties(node, { - javascriptText: { get() { return definedTextGetter(declarationTextOrJavascriptPath); } }, - javascriptMapText: { get() { return textGetter(javascriptMapPath); } }, // TODO:: if there is inline sourceMap in jsFile, use that - declarationText: { get() { return definedTextGetter(Debug.assertDefined(javascriptMapTextOrDeclarationPath)); } }, - declarationMapText: { get() { return textGetter(declarationMapPath); } }, // TODO:: if there is inline sourceMap in dtsFile, use that - buildInfo: { get() { return getAndCacheBuildInfo(() => textGetter(declarationMapTextOrBuildInfoPath)); } } - }); - } - else { - node.javascriptText = javascriptTextOrReadFileText; - node.javascriptMapPath = javascriptMapPath; - node.javascriptMapText = javascriptMapTextOrDeclarationPath; - node.declarationText = declarationTextOrJavascriptPath; - node.declarationMapPath = declarationMapPath; - node.declarationMapText = declarationMapTextOrBuildInfoPath; - node.javascriptPath = javascriptPath; - node.declarationPath = declarationPath; - node.buildInfoPath = buildInfoPath; - node.buildInfo = buildInfo; - node.oldFileOfCurrentEmit = oldFileOfCurrentEmit; + // falls through + case BundleFileSectionKind.Text: + (texts || (texts = [])).push((createUnparsedNode(section, node) as UnparsedTextLike)); + break; + default: + Debug.assertNever(section); } - return node; } - - export function updateBundle(node: Bundle, sourceFiles: readonly SourceFile[], prepends: readonly (UnparsedSource | InputFiles)[] = emptyArray) { - if (node.sourceFiles !== sourceFiles || node.prepends !== prepends) { - return createBundle(sourceFiles, prepends); + node.prologues = prologues || emptyArray; + node.helpers = helpers; + node.referencedFiles = referencedFiles || emptyArray; + node.typeReferenceDirectives = typeReferenceDirectives; + node.libReferenceDirectives = libReferenceDirectives || emptyArray; + node.texts = texts || [(createUnparsedNode({ kind: BundleFileSectionKind.Text, pos: 0, end: node.text.length }, node))]; +} +function parseOldFileOfCurrentEmit(node: UnparsedSource, bundleFileInfo: BundleFileInfo) { + Debug.assert(!!node.oldFileOfCurrentEmit); + let texts: UnparsedTextLike[] | undefined; + let syntheticReferences: UnparsedSyntheticReference[] | undefined; + for (const section of bundleFileInfo.sections) { + switch (section.kind) { + case BundleFileSectionKind.Internal: + case BundleFileSectionKind.Text: + (texts || (texts = [])).push((createUnparsedNode(section, node) as UnparsedTextLike)); + break; + case BundleFileSectionKind.NoDefaultLib: + case BundleFileSectionKind.Reference: + case BundleFileSectionKind.Type: + case BundleFileSectionKind.Lib: + (syntheticReferences || (syntheticReferences = [])).push(createUnparsedSyntheticReference(section, node)); + break; + // Ignore + case BundleFileSectionKind.Prologue: + case BundleFileSectionKind.EmitHelpers: + case BundleFileSectionKind.Prepend: + break; + default: + Debug.assertNever(section); } - return node; - } - - // Compound nodes - - export function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[]): CallExpression; - export function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; - export function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[], param?: ParameterDeclaration, paramValue?: Expression) { - return createCall( - createFunctionExpression( - /*modifiers*/ undefined, - /*asteriskToken*/ undefined, - /*name*/ undefined, - /*typeParameters*/ undefined, - /*parameters*/ param ? [param] : [], - /*type*/ undefined, - createBlock(statements, /*multiLine*/ true) - ), - /*typeArguments*/ undefined, - /*argumentsArray*/ paramValue ? [paramValue] : [] - ); - } - - export function createImmediatelyInvokedArrowFunction(statements: readonly Statement[]): CallExpression; - export function createImmediatelyInvokedArrowFunction(statements: readonly Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; - export function createImmediatelyInvokedArrowFunction(statements: readonly Statement[], param?: ParameterDeclaration, paramValue?: Expression) { - return createCall( - createArrowFunction( - /*modifiers*/ undefined, - /*typeParameters*/ undefined, - /*parameters*/ param ? [param] : [], - /*type*/ undefined, - /*equalsGreaterThanToken*/ undefined, - createBlock(statements, /*multiLine*/ true) - ), - /*typeArguments*/ undefined, - /*argumentsArray*/ paramValue ? [paramValue] : [] - ); - } - - - export function createComma(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.CommaToken, right); - } - - export function createLessThan(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.LessThanToken, right); - } - - export function createAssignment(left: ObjectLiteralExpression | ArrayLiteralExpression, right: Expression): DestructuringAssignment; - export function createAssignment(left: Expression, right: Expression): BinaryExpression; - export function createAssignment(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.EqualsToken, right); - } - - export function createStrictEquality(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.EqualsEqualsEqualsToken, right); } - - export function createStrictInequality(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.ExclamationEqualsEqualsToken, right); - } - - export function createAdd(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.PlusToken, right); - } - - export function createSubtract(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.MinusToken, right); - } - - export function createPostfixIncrement(operand: Expression) { - return createPostfix(operand, SyntaxKind.PlusPlusToken); - } - - export function createLogicalAnd(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.AmpersandAmpersandToken, right); - } - - export function createLogicalOr(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.BarBarToken, right); - } - - export function createNullishCoalesce(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.QuestionQuestionToken, right); - } - - export function createLogicalNot(operand: Expression) { - return createPrefix(SyntaxKind.ExclamationToken, operand); - } - - export function createVoidZero() { - return createVoid(createLiteral(0)); - } - - export function createExportDefault(expression: Expression) { - return createExportAssignment(/*decorators*/ undefined, /*modifiers*/ undefined, /*isExportEquals*/ false, expression); - } - - export function createExternalModuleExport(exportName: Identifier) { - return createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createNamedExports([createExportSpecifier(/*propertyName*/ undefined, exportName)])); + node.texts = texts || emptyArray; + node.helpers = map(bundleFileInfo.sources && bundleFileInfo.sources.helpers, name => getAllUnscopedEmitHelpers().get(name)!); + node.syntheticReferences = syntheticReferences; + return node; +} +function mapBundleFileSectionKindToSyntaxKind(kind: BundleFileSectionKind): SyntaxKind { + switch (kind) { + case BundleFileSectionKind.Prologue: return SyntaxKind.UnparsedPrologue; + case BundleFileSectionKind.Prepend: return SyntaxKind.UnparsedPrepend; + case BundleFileSectionKind.Internal: return SyntaxKind.UnparsedInternalText; + case BundleFileSectionKind.Text: return SyntaxKind.UnparsedText; + case BundleFileSectionKind.EmitHelpers: + case BundleFileSectionKind.NoDefaultLib: + case BundleFileSectionKind.Reference: + case BundleFileSectionKind.Type: + case BundleFileSectionKind.Lib: + return Debug.fail(`BundleFileSectionKind: ${kind} not yet mapped to SyntaxKind`); + default: + return Debug.assertNever(kind); } - - // Utilities - - function asName(name: string | T): T | Identifier { - return isString(name) ? createIdentifier(name) : name; +} +function createUnparsedNode(section: BundleFileSection, parent: UnparsedSource): UnparsedNode { + const node = (createNode(mapBundleFileSectionKindToSyntaxKind(section.kind), section.pos, section.end) as UnparsedNode); + node.parent = parent; + node.data = section.data; + return node; +} +function createUnparsedSyntheticReference(section: BundleFileHasNoDefaultLib | BundleFileReference, parent: UnparsedSource) { + const node = (createNode(SyntaxKind.UnparsedSyntheticReference, section.pos, section.end) as UnparsedSyntheticReference); + node.parent = parent; + node.data = section.data; + node.section = section; + return node; +} +export function createInputFiles(javascriptText: string, declarationText: string): InputFiles; +export function createInputFiles(readFileText: (path: string) => string | undefined, javascriptPath: string, javascriptMapPath: string | undefined, declarationPath: string, declarationMapPath: string | undefined, buildInfoPath: string | undefined): InputFiles; +export function createInputFiles(javascriptText: string, declarationText: string, javascriptMapPath: string | undefined, javascriptMapText: string | undefined, declarationMapPath: string | undefined, declarationMapText: string | undefined): InputFiles; +/*@internal*/ +export function createInputFiles(javascriptText: string, declarationText: string, javascriptMapPath: string | undefined, javascriptMapText: string | undefined, declarationMapPath: string | undefined, declarationMapText: string | undefined, javascriptPath: string | undefined, declarationPath: string | undefined, buildInfoPath?: string | undefined, buildInfo?: BuildInfo, oldFileOfCurrentEmit?: boolean): InputFiles; +export function createInputFiles(javascriptTextOrReadFileText: string | ((path: string) => string | undefined), declarationTextOrJavascriptPath: string, javascriptMapPath?: string, javascriptMapTextOrDeclarationPath?: string, declarationMapPath?: string, declarationMapTextOrBuildInfoPath?: string, javascriptPath?: string | undefined, declarationPath?: string | undefined, buildInfoPath?: string | undefined, buildInfo?: BuildInfo, oldFileOfCurrentEmit?: boolean): InputFiles { + const node = (createNode(SyntaxKind.InputFiles)); + if (!isString(javascriptTextOrReadFileText)) { + const cache = createMap(); + const textGetter = (path: string | undefined) => { + if (path === undefined) + return undefined; + let value = cache.get(path); + if (value === undefined) { + value = javascriptTextOrReadFileText(path); + cache.set(path, value !== undefined ? value : false); + } + return value !== false ? value as string : undefined; + }; + const definedTextGetter = (path: string) => { + const result = textGetter(path); + return result !== undefined ? result : `/* Input file ${path} was missing */\r\n`; + }; + let buildInfo: BuildInfo | false; + const getAndCacheBuildInfo = (getText: () => string | undefined) => { + if (buildInfo === undefined) { + const result = getText(); + buildInfo = result !== undefined ? getBuildInfo(result) : false; + } + return buildInfo || undefined; + }; + node.javascriptPath = declarationTextOrJavascriptPath; + node.javascriptMapPath = javascriptMapPath; + node.declarationPath = Debug.assertDefined(javascriptMapTextOrDeclarationPath); + node.declarationMapPath = declarationMapPath; + node.buildInfoPath = declarationMapTextOrBuildInfoPath; + Object.defineProperties(node, { + javascriptText: { get() { return definedTextGetter(declarationTextOrJavascriptPath); } }, + javascriptMapText: { get() { return textGetter(javascriptMapPath); } }, + declarationText: { get() { return definedTextGetter(Debug.assertDefined(javascriptMapTextOrDeclarationPath)); } }, + declarationMapText: { get() { return textGetter(declarationMapPath); } }, + buildInfo: { get() { return getAndCacheBuildInfo(() => textGetter(declarationMapTextOrBuildInfoPath)); } } + }); + } + else { + node.javascriptText = javascriptTextOrReadFileText; + node.javascriptMapPath = javascriptMapPath; + node.javascriptMapText = javascriptMapTextOrDeclarationPath; + node.declarationText = declarationTextOrJavascriptPath; + node.declarationMapPath = declarationMapPath; + node.declarationMapText = declarationMapTextOrBuildInfoPath; + node.javascriptPath = javascriptPath; + node.declarationPath = declarationPath; + node.buildInfoPath = buildInfoPath; + node.buildInfo = buildInfo; + node.oldFileOfCurrentEmit = oldFileOfCurrentEmit; + } + return node; +} +export function updateBundle(node: Bundle, sourceFiles: readonly SourceFile[], prepends: readonly (UnparsedSource | InputFiles)[] = emptyArray) { + if (node.sourceFiles !== sourceFiles || node.prepends !== prepends) { + return createBundle(sourceFiles, prepends); } - - function asExpression(value: string | number | boolean | T): T | StringLiteral | NumericLiteral | BooleanLiteral { - return typeof value === "string" ? createStringLiteral(value) : - typeof value === "number" ? createNumericLiteral(""+value) : + return node; +} +// Compound nodes +export function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[]): CallExpression; +export function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; +export function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[], param?: ParameterDeclaration, paramValue?: Expression) { + return createCall(createFunctionExpression( + /*modifiers*/ undefined, + /*asteriskToken*/ undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, + /*parameters*/ param ? [param] : [], + /*type*/ undefined, createBlock(statements, /*multiLine*/ true)), + /*typeArguments*/ undefined, + /*argumentsArray*/ paramValue ? [paramValue] : []); +} +export function createImmediatelyInvokedArrowFunction(statements: readonly Statement[]): CallExpression; +export function createImmediatelyInvokedArrowFunction(statements: readonly Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; +export function createImmediatelyInvokedArrowFunction(statements: readonly Statement[], param?: ParameterDeclaration, paramValue?: Expression) { + return createCall(createArrowFunction( + /*modifiers*/ undefined, + /*typeParameters*/ undefined, + /*parameters*/ param ? [param] : [], + /*type*/ undefined, + /*equalsGreaterThanToken*/ undefined, createBlock(statements, /*multiLine*/ true)), + /*typeArguments*/ undefined, + /*argumentsArray*/ paramValue ? [paramValue] : []); +} +export function createComma(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.CommaToken, right); +} +export function createLessThan(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.LessThanToken, right); +} +export function createAssignment(left: ObjectLiteralExpression | ArrayLiteralExpression, right: Expression): DestructuringAssignment; +export function createAssignment(left: Expression, right: Expression): BinaryExpression; +export function createAssignment(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.EqualsToken, right); +} +export function createStrictEquality(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.EqualsEqualsEqualsToken, right); +} +export function createStrictInequality(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.ExclamationEqualsEqualsToken, right); +} +export function createAdd(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.PlusToken, right); +} +export function createSubtract(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.MinusToken, right); +} +export function createPostfixIncrement(operand: Expression) { + return createPostfix(operand, SyntaxKind.PlusPlusToken); +} +export function createLogicalAnd(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.AmpersandAmpersandToken, right); +} +export function createLogicalOr(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.BarBarToken, right); +} +export function createNullishCoalesce(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.QuestionQuestionToken, right); +} +export function createLogicalNot(operand: Expression) { + return createPrefix(SyntaxKind.ExclamationToken, operand); +} +export function createVoidZero() { + return createVoid(createLiteral(0)); +} +export function createExportDefault(expression: Expression) { + return createExportAssignment(/*decorators*/ undefined, /*modifiers*/ undefined, /*isExportEquals*/ false, expression); +} +export function createExternalModuleExport(exportName: Identifier) { + return createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createNamedExports([createExportSpecifier(/*propertyName*/ undefined, exportName)])); +} +// Utilities +function asName(name: string | T): T | Identifier { + return isString(name) ? createIdentifier(name) : name; +} +function asExpression(value: string | number | boolean | T): T | StringLiteral | NumericLiteral | BooleanLiteral { + return typeof value === "string" ? createStringLiteral(value) : + typeof value === "number" ? createNumericLiteral("" + value) : typeof value === "boolean" ? value ? createTrue() : createFalse() : - value; - } - - function asNodeArray(array: readonly T[]): NodeArray; - function asNodeArray(array: readonly T[] | undefined): NodeArray | undefined; - function asNodeArray(array: readonly T[] | undefined): NodeArray | undefined { - return array ? createNodeArray(array) : undefined; - } - - function asToken(value: TKind | Token): Token { - return typeof value === "number" ? createToken(value) : value; - } - - function asEmbeddedStatement(statement: T): T | EmptyStatement; - function asEmbeddedStatement(statement: T | undefined): T | EmptyStatement | undefined; - function asEmbeddedStatement(statement: T | undefined): T | EmptyStatement | undefined { - return statement && isNotEmittedStatement(statement) ? setTextRange(setOriginalNode(createEmptyStatement(), statement), statement) : statement; - } - - /** - * Clears any EmitNode entries from parse-tree nodes. - * @param sourceFile A source file. - */ - export function disposeEmitNodes(sourceFile: SourceFile) { - // During transformation we may need to annotate a parse tree node with transient - // transformation properties. As parse tree nodes live longer than transformation - // nodes, we need to make sure we reclaim any memory allocated for custom ranges - // from these nodes to ensure we do not hold onto entire subtrees just for position - // information. We also need to reset these nodes to a pre-transformation state - // for incremental parsing scenarios so that we do not impact later emit. - sourceFile = getSourceFileOfNode(getParseTreeNode(sourceFile)); - const emitNode = sourceFile && sourceFile.emitNode; - const annotatedNodes = emitNode && emitNode.annotatedNodes; - if (annotatedNodes) { - for (const node of annotatedNodes) { - node.emitNode = undefined; - } + value; +} +function asNodeArray(array: readonly T[]): NodeArray; +function asNodeArray(array: readonly T[] | undefined): NodeArray | undefined; +function asNodeArray(array: readonly T[] | undefined): NodeArray | undefined { + return array ? createNodeArray(array) : undefined; +} +function asToken(value: TKind | Token): Token { + return typeof value === "number" ? createToken(value) : value; +} +function asEmbeddedStatement(statement: T): T | EmptyStatement; +function asEmbeddedStatement(statement: T | undefined): T | EmptyStatement | undefined; +function asEmbeddedStatement(statement: T | undefined): T | EmptyStatement | undefined { + return statement && isNotEmittedStatement(statement) ? setTextRange(setOriginalNode(createEmptyStatement(), statement), statement) : statement; +} +/** + * Clears any EmitNode entries from parse-tree nodes. + * @param sourceFile A source file. + */ +export function disposeEmitNodes(sourceFile: SourceFile) { + // During transformation we may need to annotate a parse tree node with transient + // transformation properties. As parse tree nodes live longer than transformation + // nodes, we need to make sure we reclaim any memory allocated for custom ranges + // from these nodes to ensure we do not hold onto entire subtrees just for position + // information. We also need to reset these nodes to a pre-transformation state + // for incremental parsing scenarios so that we do not impact later emit. + sourceFile = getSourceFileOfNode(getParseTreeNode(sourceFile)); + const emitNode = sourceFile && sourceFile.emitNode; + const annotatedNodes = emitNode && emitNode.annotatedNodes; + if (annotatedNodes) { + for (const node of annotatedNodes) { + node.emitNode = undefined; } } - - /** - * Associates a node with the current transformation, initializing - * various transient transformation properties. - */ - /* @internal */ - export function getOrCreateEmitNode(node: Node): EmitNode { - if (!node.emitNode) { - if (isParseTreeNode(node)) { - // To avoid holding onto transformation artifacts, we keep track of any - // parse tree node we are annotating. This allows us to clean them up after - // all transformations have completed. - if (node.kind === SyntaxKind.SourceFile) { - return node.emitNode = { annotatedNodes: [node] } as EmitNode; - } - - const sourceFile = getSourceFileOfNode(getParseTreeNode(getSourceFileOfNode(node))); - getOrCreateEmitNode(sourceFile).annotatedNodes!.push(node); +} +/** + * Associates a node with the current transformation, initializing + * various transient transformation properties. + */ +/* @internal */ +export function getOrCreateEmitNode(node: Node): EmitNode { + if (!node.emitNode) { + if (isParseTreeNode(node)) { + // To avoid holding onto transformation artifacts, we keep track of any + // parse tree node we are annotating. This allows us to clean them up after + // all transformations have completed. + if (node.kind === SyntaxKind.SourceFile) { + return node.emitNode = ({ annotatedNodes: [node] } as EmitNode); } - - node.emitNode = {} as EmitNode; - } - - return node.emitNode; - } - - /** - * Sets `EmitFlags.NoComments` on a node and removes any leading and trailing synthetic comments. - * @internal - */ - export function removeAllComments(node: T): T { - const emitNode = getOrCreateEmitNode(node); - emitNode.flags |= EmitFlags.NoComments; - emitNode.leadingComments = undefined; - emitNode.trailingComments = undefined; - return node; - } - - export function setTextRange(range: T, location: TextRange | undefined): T { - if (location) { - range.pos = location.pos; - range.end = location.end; + const sourceFile = getSourceFileOfNode(getParseTreeNode(getSourceFileOfNode(node))); + getOrCreateEmitNode(sourceFile).annotatedNodes!.push(node); } - return range; - } - - /** - * Sets flags that control emit behavior of a node. - */ - export function setEmitFlags(node: T, emitFlags: EmitFlags) { - getOrCreateEmitNode(node).flags = emitFlags; - return node; - } - - /** - * Sets flags that control emit behavior of a node. - */ - /* @internal */ - export function addEmitFlags(node: T, emitFlags: EmitFlags) { - const emitNode = getOrCreateEmitNode(node); - emitNode.flags = emitNode.flags | emitFlags; - return node; - } - - /** - * Gets a custom text range to use when emitting source maps. - */ - export function getSourceMapRange(node: Node): SourceMapRange { - const emitNode = node.emitNode; - return (emitNode && emitNode.sourceMapRange) || node; - } - - /** - * Sets a custom text range to use when emitting source maps. - */ - export function setSourceMapRange(node: T, range: SourceMapRange | undefined) { - getOrCreateEmitNode(node).sourceMapRange = range; - return node; - } - - let SourceMapSource: new (fileName: string, text: string, skipTrivia?: (pos: number) => number) => SourceMapSource; - - /** - * Create an external source map source file reference - */ - export function createSourceMapSource(fileName: string, text: string, skipTrivia?: (pos: number) => number): SourceMapSource { - return new (SourceMapSource || (SourceMapSource = objectAllocator.getSourceMapSourceConstructor()))(fileName, text, skipTrivia); - } - - /** - * Gets the TextRange to use for source maps for a token of a node. - */ - export function getTokenSourceMapRange(node: Node, token: SyntaxKind): SourceMapRange | undefined { - const emitNode = node.emitNode; - const tokenSourceMapRanges = emitNode && emitNode.tokenSourceMapRanges; - return tokenSourceMapRanges && tokenSourceMapRanges[token]; - } - - /** - * Sets the TextRange to use for source maps for a token of a node. - */ - export function setTokenSourceMapRange(node: T, token: SyntaxKind, range: SourceMapRange | undefined) { - const emitNode = getOrCreateEmitNode(node); - const tokenSourceMapRanges = emitNode.tokenSourceMapRanges || (emitNode.tokenSourceMapRanges = []); - tokenSourceMapRanges[token] = range; - return node; - } - - /** - * Gets a custom text range to use when emitting comments. - */ - /*@internal*/ - export function getStartsOnNewLine(node: Node) { - const emitNode = node.emitNode; - return emitNode && emitNode.startsOnNewLine; - } - - /** - * Sets a custom text range to use when emitting comments. - */ - /*@internal*/ - export function setStartsOnNewLine(node: T, newLine: boolean) { - getOrCreateEmitNode(node).startsOnNewLine = newLine; - return node; - } - - /** - * Gets a custom text range to use when emitting comments. - */ - export function getCommentRange(node: Node) { - const emitNode = node.emitNode; - return (emitNode && emitNode.commentRange) || node; - } - - /** - * Sets a custom text range to use when emitting comments. - */ - export function setCommentRange(node: T, range: TextRange) { - getOrCreateEmitNode(node).commentRange = range; - return node; - } - - export function getSyntheticLeadingComments(node: Node): SynthesizedComment[] | undefined { - const emitNode = node.emitNode; - return emitNode && emitNode.leadingComments; - } - - export function setSyntheticLeadingComments(node: T, comments: SynthesizedComment[] | undefined) { - getOrCreateEmitNode(node).leadingComments = comments; - return node; - } - - export function addSyntheticLeadingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) { - return setSyntheticLeadingComments(node, append(getSyntheticLeadingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text })); - } - - export function getSyntheticTrailingComments(node: Node): SynthesizedComment[] | undefined { - const emitNode = node.emitNode; - return emitNode && emitNode.trailingComments; - } - - export function setSyntheticTrailingComments(node: T, comments: SynthesizedComment[] | undefined) { - getOrCreateEmitNode(node).trailingComments = comments; - return node; - } - - export function addSyntheticTrailingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) { - return setSyntheticTrailingComments(node, append(getSyntheticTrailingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text })); - } - - export function moveSyntheticComments(node: T, original: Node): T { - setSyntheticLeadingComments(node, getSyntheticLeadingComments(original)); - setSyntheticTrailingComments(node, getSyntheticTrailingComments(original)); - const emit = getOrCreateEmitNode(original); - emit.leadingComments = undefined; - emit.trailingComments = undefined; - return node; - } - - /** - * Gets the constant value to emit for an expression. - */ - export function getConstantValue(node: PropertyAccessExpression | ElementAccessExpression): string | number | undefined { - const emitNode = node.emitNode; - return emitNode && emitNode.constantValue; + node.emitNode = ({} as EmitNode); } - - /** - * Sets the constant value to emit for an expression. - */ - export function setConstantValue(node: PropertyAccessExpression | ElementAccessExpression, value: string | number): PropertyAccessExpression | ElementAccessExpression { - const emitNode = getOrCreateEmitNode(node); - emitNode.constantValue = value; - return node; + return node.emitNode; +} +/** + * Sets `EmitFlags.NoComments` on a node and removes any leading and trailing synthetic comments. + * @internal + */ +export function removeAllComments(node: T): T { + const emitNode = getOrCreateEmitNode(node); + emitNode.flags |= EmitFlags.NoComments; + emitNode.leadingComments = undefined; + emitNode.trailingComments = undefined; + return node; +} +export function setTextRange(range: T, location: TextRange | undefined): T { + if (location) { + range.pos = location.pos; + range.end = location.end; } - - /** - * Adds an EmitHelper to a node. - */ - export function addEmitHelper(node: T, helper: EmitHelper): T { + return range; +} +/** + * Sets flags that control emit behavior of a node. + */ +export function setEmitFlags(node: T, emitFlags: EmitFlags) { + getOrCreateEmitNode(node).flags = emitFlags; + return node; +} +/** + * Sets flags that control emit behavior of a node. + */ +/* @internal */ +export function addEmitFlags(node: T, emitFlags: EmitFlags) { + const emitNode = getOrCreateEmitNode(node); + emitNode.flags = emitNode.flags | emitFlags; + return node; +} +/** + * Gets a custom text range to use when emitting source maps. + */ +export function getSourceMapRange(node: Node): SourceMapRange { + const emitNode = node.emitNode; + return (emitNode && emitNode.sourceMapRange) || node; +} +/** + * Sets a custom text range to use when emitting source maps. + */ +export function setSourceMapRange(node: T, range: SourceMapRange | undefined) { + getOrCreateEmitNode(node).sourceMapRange = range; + return node; +} +let SourceMapSource: new (fileName: string, text: string, skipTrivia?: (pos: number) => number) => ts.SourceMapSource; +/** + * Create an external source map source file reference + */ +export function createSourceMapSource(fileName: string, text: string, skipTrivia?: (pos: number) => number): ts.SourceMapSource { + return new (SourceMapSource || (SourceMapSource = objectAllocator.getSourceMapSourceConstructor()))(fileName, text, skipTrivia); +} +/** + * Gets the TextRange to use for source maps for a token of a node. + */ +export function getTokenSourceMapRange(node: Node, token: SyntaxKind): SourceMapRange | undefined { + const emitNode = node.emitNode; + const tokenSourceMapRanges = emitNode && emitNode.tokenSourceMapRanges; + return tokenSourceMapRanges && tokenSourceMapRanges[token]; +} +/** + * Sets the TextRange to use for source maps for a token of a node. + */ +export function setTokenSourceMapRange(node: T, token: SyntaxKind, range: SourceMapRange | undefined) { + const emitNode = getOrCreateEmitNode(node); + const tokenSourceMapRanges = emitNode.tokenSourceMapRanges || (emitNode.tokenSourceMapRanges = []); + tokenSourceMapRanges[token] = range; + return node; +} +/** + * Gets a custom text range to use when emitting comments. + */ +/*@internal*/ +export function getStartsOnNewLine(node: Node) { + const emitNode = node.emitNode; + return emitNode && emitNode.startsOnNewLine; +} +/** + * Sets a custom text range to use when emitting comments. + */ +/*@internal*/ +export function setStartsOnNewLine(node: T, newLine: boolean) { + getOrCreateEmitNode(node).startsOnNewLine = newLine; + return node; +} +/** + * Gets a custom text range to use when emitting comments. + */ +export function getCommentRange(node: Node) { + const emitNode = node.emitNode; + return (emitNode && emitNode.commentRange) || node; +} +/** + * Sets a custom text range to use when emitting comments. + */ +export function setCommentRange(node: T, range: TextRange) { + getOrCreateEmitNode(node).commentRange = range; + return node; +} +export function getSyntheticLeadingComments(node: Node): SynthesizedComment[] | undefined { + const emitNode = node.emitNode; + return emitNode && emitNode.leadingComments; +} +export function setSyntheticLeadingComments(node: T, comments: SynthesizedComment[] | undefined) { + getOrCreateEmitNode(node).leadingComments = comments; + return node; +} +export function addSyntheticLeadingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) { + return setSyntheticLeadingComments(node, append(getSyntheticLeadingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text })); +} +export function getSyntheticTrailingComments(node: Node): SynthesizedComment[] | undefined { + const emitNode = node.emitNode; + return emitNode && emitNode.trailingComments; +} +export function setSyntheticTrailingComments(node: T, comments: SynthesizedComment[] | undefined) { + getOrCreateEmitNode(node).trailingComments = comments; + return node; +} +export function addSyntheticTrailingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) { + return setSyntheticTrailingComments(node, append(getSyntheticTrailingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text })); +} +export function moveSyntheticComments(node: T, original: Node): T { + setSyntheticLeadingComments(node, getSyntheticLeadingComments(original)); + setSyntheticTrailingComments(node, getSyntheticTrailingComments(original)); + const emit = getOrCreateEmitNode(original); + emit.leadingComments = undefined; + emit.trailingComments = undefined; + return node; +} +/** + * Gets the constant value to emit for an expression. + */ +export function getConstantValue(node: PropertyAccessExpression | ElementAccessExpression): string | number | undefined { + const emitNode = node.emitNode; + return emitNode && emitNode.constantValue; +} +/** + * Sets the constant value to emit for an expression. + */ +export function setConstantValue(node: PropertyAccessExpression | ElementAccessExpression, value: string | number): PropertyAccessExpression | ElementAccessExpression { + const emitNode = getOrCreateEmitNode(node); + emitNode.constantValue = value; + return node; +} +/** + * Adds an EmitHelper to a node. + */ +export function addEmitHelper(node: T, helper: EmitHelper): T { + const emitNode = getOrCreateEmitNode(node); + emitNode.helpers = append(emitNode.helpers, helper); + return node; +} +/** + * Add EmitHelpers to a node. + */ +export function addEmitHelpers(node: T, helpers: EmitHelper[] | undefined): T { + if (some(helpers)) { const emitNode = getOrCreateEmitNode(node); - emitNode.helpers = append(emitNode.helpers, helper); - return node; - } - - /** - * Add EmitHelpers to a node. - */ - export function addEmitHelpers(node: T, helpers: EmitHelper[] | undefined): T { - if (some(helpers)) { - const emitNode = getOrCreateEmitNode(node); - for (const helper of helpers) { - emitNode.helpers = appendIfUnique(emitNode.helpers, helper); - } + for (const helper of helpers) { + emitNode.helpers = appendIfUnique(emitNode.helpers, helper); } - return node; } - - /** - * Removes an EmitHelper from a node. - */ - export function removeEmitHelper(node: Node, helper: EmitHelper): boolean { - const emitNode = node.emitNode; - if (emitNode) { - const helpers = emitNode.helpers; - if (helpers) { - return orderedRemoveItem(helpers, helper); - } + return node; +} +/** + * Removes an EmitHelper from a node. + */ +export function removeEmitHelper(node: Node, helper: EmitHelper): boolean { + const emitNode = node.emitNode; + if (emitNode) { + const helpers = emitNode.helpers; + if (helpers) { + return orderedRemoveItem(helpers, helper); } - return false; } - - /** - * Gets the EmitHelpers of a node. - */ - export function getEmitHelpers(node: Node): EmitHelper[] | undefined { - const emitNode = node.emitNode; - return emitNode && emitNode.helpers; - } - - /** - * Moves matching emit helpers from a source node to a target node. - */ - export function moveEmitHelpers(source: Node, target: Node, predicate: (helper: EmitHelper) => boolean) { - const sourceEmitNode = source.emitNode; - const sourceEmitHelpers = sourceEmitNode && sourceEmitNode.helpers; - if (!some(sourceEmitHelpers)) return; - - const targetEmitNode = getOrCreateEmitNode(target); - let helpersRemoved = 0; - for (let i = 0; i < sourceEmitHelpers.length; i++) { - const helper = sourceEmitHelpers[i]; - if (predicate(helper)) { - helpersRemoved++; - targetEmitNode.helpers = appendIfUnique(targetEmitNode.helpers, helper); - } - else if (helpersRemoved > 0) { - sourceEmitHelpers[i - helpersRemoved] = helper; - } - } - - if (helpersRemoved > 0) { - sourceEmitHelpers.length -= helpersRemoved; + return false; +} +/** + * Gets the EmitHelpers of a node. + */ +export function getEmitHelpers(node: Node): EmitHelper[] | undefined { + const emitNode = node.emitNode; + return emitNode && emitNode.helpers; +} +/** + * Moves matching emit helpers from a source node to a target node. + */ +export function moveEmitHelpers(source: Node, target: Node, predicate: (helper: EmitHelper) => boolean) { + const sourceEmitNode = source.emitNode; + const sourceEmitHelpers = sourceEmitNode && sourceEmitNode.helpers; + if (!some(sourceEmitHelpers)) + return; + const targetEmitNode = getOrCreateEmitNode(target); + let helpersRemoved = 0; + for (let i = 0; i < sourceEmitHelpers.length; i++) { + const helper = sourceEmitHelpers[i]; + if (predicate(helper)) { + helpersRemoved++; + targetEmitNode.helpers = appendIfUnique(targetEmitNode.helpers, helper); } - } - - /* @internal */ - export function compareEmitHelpers(x: EmitHelper, y: EmitHelper) { - if (x === y) return Comparison.EqualTo; - if (x.priority === y.priority) return Comparison.EqualTo; - if (x.priority === undefined) return Comparison.GreaterThan; - if (y.priority === undefined) return Comparison.LessThan; - return compareValues(x.priority, y.priority); - } - - export function setOriginalNode(node: T, original: Node | undefined): T { - node.original = original; - if (original) { - const emitNode = original.emitNode; - if (emitNode) node.emitNode = mergeEmitNode(emitNode, node.emitNode); + else if (helpersRemoved > 0) { + sourceEmitHelpers[i - helpersRemoved] = helper; } - return node; } - - function mergeEmitNode(sourceEmitNode: EmitNode, destEmitNode: EmitNode | undefined) { - const { - flags, - leadingComments, - trailingComments, - commentRange, - sourceMapRange, - tokenSourceMapRanges, - constantValue, - helpers, - startsOnNewLine, - } = sourceEmitNode; - if (!destEmitNode) destEmitNode = {} as EmitNode; - // We are using `.slice()` here in case `destEmitNode.leadingComments` is pushed to later. - if (leadingComments) destEmitNode.leadingComments = addRange(leadingComments.slice(), destEmitNode.leadingComments); - if (trailingComments) destEmitNode.trailingComments = addRange(trailingComments.slice(), destEmitNode.trailingComments); - if (flags) destEmitNode.flags = flags; - if (commentRange) destEmitNode.commentRange = commentRange; - if (sourceMapRange) destEmitNode.sourceMapRange = sourceMapRange; - if (tokenSourceMapRanges) destEmitNode.tokenSourceMapRanges = mergeTokenSourceMapRanges(tokenSourceMapRanges, destEmitNode.tokenSourceMapRanges!); - if (constantValue !== undefined) destEmitNode.constantValue = constantValue; - if (helpers) destEmitNode.helpers = addRange(destEmitNode.helpers, helpers); - if (startsOnNewLine !== undefined) destEmitNode.startsOnNewLine = startsOnNewLine; - return destEmitNode; + if (helpersRemoved > 0) { + sourceEmitHelpers.length -= helpersRemoved; } - - function mergeTokenSourceMapRanges(sourceRanges: (TextRange | undefined)[], destRanges: (TextRange | undefined)[]) { - if (!destRanges) destRanges = []; - for (const key in sourceRanges) { - destRanges[key] = sourceRanges[key]; - } - return destRanges; +} +/* @internal */ +export function compareEmitHelpers(x: EmitHelper, y: EmitHelper) { + if (x === y) + return Comparison.EqualTo; + if (x.priority === y.priority) + return Comparison.EqualTo; + if (x.priority === undefined) + return Comparison.GreaterThan; + if (y.priority === undefined) + return Comparison.LessThan; + return compareValues(x.priority, y.priority); +} +export function setOriginalNode(node: T, original: Node | undefined): T { + node.original = original; + if (original) { + const emitNode = original.emitNode; + if (emitNode) + node.emitNode = mergeEmitNode(emitNode, node.emitNode); + } + return node; +} +function mergeEmitNode(sourceEmitNode: EmitNode, destEmitNode: EmitNode | undefined) { + const { flags, leadingComments, trailingComments, commentRange, sourceMapRange, tokenSourceMapRanges, constantValue, helpers, startsOnNewLine, } = sourceEmitNode; + if (!destEmitNode) + destEmitNode = ({} as EmitNode); + // We are using `.slice()` here in case `destEmitNode.leadingComments` is pushed to later. + if (leadingComments) + destEmitNode.leadingComments = addRange(leadingComments.slice(), destEmitNode.leadingComments); + if (trailingComments) + destEmitNode.trailingComments = addRange(trailingComments.slice(), destEmitNode.trailingComments); + if (flags) + destEmitNode.flags = flags; + if (commentRange) + destEmitNode.commentRange = commentRange; + if (sourceMapRange) + destEmitNode.sourceMapRange = sourceMapRange; + if (tokenSourceMapRanges) + destEmitNode.tokenSourceMapRanges = mergeTokenSourceMapRanges(tokenSourceMapRanges, destEmitNode.tokenSourceMapRanges!); + if (constantValue !== undefined) + destEmitNode.constantValue = constantValue; + if (helpers) + destEmitNode.helpers = addRange(destEmitNode.helpers, helpers); + if (startsOnNewLine !== undefined) + destEmitNode.startsOnNewLine = startsOnNewLine; + return destEmitNode; +} +function mergeTokenSourceMapRanges(sourceRanges: (TextRange | undefined)[], destRanges: (TextRange | undefined)[]) { + if (!destRanges) + destRanges = []; + for (const key in sourceRanges) { + destRanges[key] = sourceRanges[key]; } + return destRanges; } diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index aad8f9acd872d..fc85c34f29b37 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -1,1521 +1,1351 @@ -namespace ts { - /* @internal */ - export function trace(host: ModuleResolutionHost, message: DiagnosticMessage, ...args: any[]): void; - export function trace(host: ModuleResolutionHost): void { - host.trace!(formatMessage.apply(undefined, arguments)); - } - - /* @internal */ - export function isTraceEnabled(compilerOptions: CompilerOptions, host: ModuleResolutionHost): boolean { - return !!compilerOptions.traceResolution && host.trace !== undefined; - } - - function withPackageId(packageInfo: PackageJsonInfo | undefined, r: PathAndExtension | undefined): Resolved | undefined { - let packageId: PackageId | undefined; - if (r && packageInfo) { - const packageJsonContent = packageInfo.packageJsonContent as PackageJson; - if (typeof packageJsonContent.name === "string" && typeof packageJsonContent.version === "string") { - packageId = { - name: packageJsonContent.name, - subModuleName: r.path.slice(packageInfo.packageDirectory.length + directorySeparator.length), - version: packageJsonContent.version - }; - } - } - return r && { path: r.path, extension: r.ext, packageId }; +import { ModuleResolutionHost, DiagnosticMessage, formatMessage, CompilerOptions, PackageId, directorySeparator, Debug, Extension, extensionIsTS, ResolvedModuleWithFailedLookupLocations, Push, MapLike, hasProperty, Diagnostics, normalizePath, combinePaths, VersionRange, versionMajorMinor, Version, version, GetEffectiveTypeRootsHost, getDirectoryPath, forEachAncestorDirectory, ResolvedProjectReference, ResolvedTypeReferenceDirectiveWithFailedLookupLocations, ResolvedTypeReferenceDirective, packageIdToString, firstDefined, directoryProbablyExists, isExternalModuleNameRelative, normalizePathAndParts, readJson, getBaseFileName, CharacterCodes, createMap, optionsHaveModuleResolutionChanges, GetCanonicalFileName, toPath, Path, getRootLength, getEmitModuleKind, ModuleKind, ModuleResolutionKind, perfLogger, pathIsRelative, endsWith, startsWith, forEach, contains, hasTrailingDirectorySeparator, stringContains, tryRemoveExtension, hasJSFileExtension, removeFileExtension, containsPath, getRelativePathFromDirectory, tryGetExtensionFromPath, normalizeSlashes, matchPatternOrExact, getOwnKeys, isString, matchedText, patternText, removePrefix } from "./ts"; +import * as ts from "./ts"; +/* @internal */ +export function trace(host: ModuleResolutionHost, message: DiagnosticMessage, ...args: any[]): void; +export function trace(host: ModuleResolutionHost): void { + host.trace!(formatMessage.apply(undefined, arguments)); +} +/* @internal */ +export function isTraceEnabled(compilerOptions: CompilerOptions, host: ModuleResolutionHost): boolean { + return !!compilerOptions.traceResolution && host.trace !== undefined; +} +function withPackageId(packageInfo: PackageJsonInfo | undefined, r: PathAndExtension | undefined): Resolved | undefined { + let packageId: PackageId | undefined; + if (r && packageInfo) { + const packageJsonContent = packageInfo.packageJsonContent as PackageJson; + if (typeof packageJsonContent.name === "string" && typeof packageJsonContent.version === "string") { + packageId = { + name: packageJsonContent.name, + subModuleName: r.path.slice(packageInfo.packageDirectory.length + directorySeparator.length), + version: packageJsonContent.version + }; + } + } + return r && { path: r.path, extension: r.ext, packageId }; +} +function noPackageId(r: PathAndExtension | undefined): Resolved | undefined { + return withPackageId(/*packageInfo*/ undefined, r); +} +function removeIgnoredPackageId(r: Resolved | undefined): PathAndExtension | undefined { + if (r) { + Debug.assert(r.packageId === undefined); + return { path: r.path, ext: r.extension }; } - - function noPackageId(r: PathAndExtension | undefined): Resolved | undefined { - return withPackageId(/*packageInfo*/ undefined, r); +} +/** Result of trying to resolve a module. */ +interface Resolved { + path: string; + extension: Extension; + packageId: PackageId | undefined; + /** + * When the resolved is not created from cache, the value is + * - string if original Path if it is symbolic link to the resolved path + * - undefined if path is not a symbolic link + * When the resolved is created using value from cache of ResolvedModuleWithFailedLookupLocations, the value is: + * - string if original Path if it is symbolic link to the resolved path + * - true if path is not a symbolic link - this indicates that the originalPath calculation is already done and needs to be skipped + */ + originalPath?: string | true; +} +/** Result of trying to resolve a module at a file. Needs to have 'packageId' added later. */ +interface PathAndExtension { + path: string; + // (Use a different name than `extension` to make sure Resolved isn't assignable to PathAndExtension.) + ext: Extension; +} +/** + * Kinds of file that we are currently looking for. + * Typically there is one pass with Extensions.TypeScript, then a second pass with Extensions.JavaScript. + */ +enum Extensions { + TypeScript, + JavaScript, + Json, + TSConfig, + DtsOnly /** Only '.d.ts' */ +} +interface PathAndPackageId { + readonly fileName: string; + readonly packageId: PackageId | undefined; +} +/** Used with `Extensions.DtsOnly` to extract the path from TypeScript results. */ +function resolvedTypeScriptOnly(resolved: Resolved | undefined): PathAndPackageId | undefined { + if (!resolved) { + return undefined; } - - function removeIgnoredPackageId(r: Resolved | undefined): PathAndExtension | undefined { - if (r) { - Debug.assert(r.packageId === undefined); - return { path: r.path, ext: r.extension }; + Debug.assert(extensionIsTS(resolved.extension)); + return { fileName: resolved.path, packageId: resolved.packageId }; +} +function createResolvedModuleWithFailedLookupLocations(resolved: Resolved | undefined, isExternalLibraryImport: boolean, failedLookupLocations: string[]): ResolvedModuleWithFailedLookupLocations { + return { + resolvedModule: resolved && { resolvedFileName: resolved.path, originalPath: resolved.originalPath === true ? undefined : resolved.originalPath, extension: resolved.extension, isExternalLibraryImport, packageId: resolved.packageId }, + failedLookupLocations + }; +} +interface ModuleResolutionState { + host: ModuleResolutionHost; + compilerOptions: CompilerOptions; + traceEnabled: boolean; + failedLookupLocations: Push; +} +/** Just the fields that we use for module resolution. */ +interface PackageJsonPathFields { + typings?: string; + types?: string; + typesVersions?: MapLike>; + main?: string; + tsconfig?: string; +} +interface PackageJson extends PackageJsonPathFields { + name?: string; + version?: string; +} +type MatchingKeys = K extends (TRecord[K] extends TMatch ? K : never) ? K : never; +function readPackageJsonField>(jsonContent: PackageJson, fieldName: K, typeOfTag: "string", state: ModuleResolutionState): PackageJson[K] | undefined; +function readPackageJsonField>(jsonContent: PackageJson, fieldName: K, typeOfTag: "object", state: ModuleResolutionState): PackageJson[K] | undefined; +function readPackageJsonField(jsonContent: PackageJson, fieldName: K, typeOfTag: "string" | "object", state: ModuleResolutionState): PackageJson[K] | undefined { + if (!hasProperty(jsonContent, fieldName)) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_does_not_have_a_0_field, fieldName); } + return; } - - /** Result of trying to resolve a module. */ - interface Resolved { - path: string; - extension: Extension; - packageId: PackageId | undefined; - /** - * When the resolved is not created from cache, the value is - * - string if original Path if it is symbolic link to the resolved path - * - undefined if path is not a symbolic link - * When the resolved is created using value from cache of ResolvedModuleWithFailedLookupLocations, the value is: - * - string if original Path if it is symbolic link to the resolved path - * - true if path is not a symbolic link - this indicates that the originalPath calculation is already done and needs to be skipped - */ - originalPath?: string | true; + const value = jsonContent[fieldName]; + if (typeof value !== typeOfTag || value === null) { // eslint-disable-line no-null/no-null + if (state.traceEnabled) { + // eslint-disable-next-line no-null/no-null + trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, fieldName, typeOfTag, value === null ? "null" : typeof value); + } + return; } - - /** Result of trying to resolve a module at a file. Needs to have 'packageId' added later. */ - interface PathAndExtension { - path: string; - // (Use a different name than `extension` to make sure Resolved isn't assignable to PathAndExtension.) - ext: Extension; + return value; +} +function readPackageJsonPathField(jsonContent: PackageJson, fieldName: K, baseDirectory: string, state: ModuleResolutionState): PackageJson[K] | undefined { + const fileName = readPackageJsonField(jsonContent, fieldName, "string", state); + if (fileName === undefined) { + return; } - - /** - * Kinds of file that we are currently looking for. - * Typically there is one pass with Extensions.TypeScript, then a second pass with Extensions.JavaScript. - */ - enum Extensions { - TypeScript, /** '.ts', '.tsx', or '.d.ts' */ - JavaScript, /** '.js' or '.jsx' */ - Json, /** '.json' */ - TSConfig, /** '.json' with `tsconfig` used instead of `index` */ - DtsOnly /** Only '.d.ts' */ - } - - interface PathAndPackageId { - readonly fileName: string; - readonly packageId: PackageId | undefined; - } - /** Used with `Extensions.DtsOnly` to extract the path from TypeScript results. */ - function resolvedTypeScriptOnly(resolved: Resolved | undefined): PathAndPackageId | undefined { - if (!resolved) { - return undefined; - } - Debug.assert(extensionIsTS(resolved.extension)); - return { fileName: resolved.path, packageId: resolved.packageId }; - } - - function createResolvedModuleWithFailedLookupLocations(resolved: Resolved | undefined, isExternalLibraryImport: boolean, failedLookupLocations: string[]): ResolvedModuleWithFailedLookupLocations { - return { - resolvedModule: resolved && { resolvedFileName: resolved.path, originalPath: resolved.originalPath === true ? undefined : resolved.originalPath, extension: resolved.extension, isExternalLibraryImport, packageId: resolved.packageId }, - failedLookupLocations - }; - } - - interface ModuleResolutionState { - host: ModuleResolutionHost; - compilerOptions: CompilerOptions; - traceEnabled: boolean; - failedLookupLocations: Push; - } - - /** Just the fields that we use for module resolution. */ - interface PackageJsonPathFields { - typings?: string; - types?: string; - typesVersions?: MapLike>; - main?: string; - tsconfig?: string; - } - - interface PackageJson extends PackageJsonPathFields { - name?: string; - version?: string; - } - - type MatchingKeys = K extends (TRecord[K] extends TMatch ? K : never) ? K : never; - - function readPackageJsonField>(jsonContent: PackageJson, fieldName: K, typeOfTag: "string", state: ModuleResolutionState): PackageJson[K] | undefined; - function readPackageJsonField>(jsonContent: PackageJson, fieldName: K, typeOfTag: "object", state: ModuleResolutionState): PackageJson[K] | undefined; - function readPackageJsonField(jsonContent: PackageJson, fieldName: K, typeOfTag: "string" | "object", state: ModuleResolutionState): PackageJson[K] | undefined { - if (!hasProperty(jsonContent, fieldName)) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_does_not_have_a_0_field, fieldName); - } - return; + if (!fileName) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_had_a_falsy_0_field, fieldName); } - const value = jsonContent[fieldName]; - if (typeof value !== typeOfTag || value === null) { // eslint-disable-line no-null/no-null - if (state.traceEnabled) { - // eslint-disable-next-line no-null/no-null - trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, fieldName, typeOfTag, value === null ? "null" : typeof value); + return; + } + const path = normalizePath(combinePaths(baseDirectory, fileName)); + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, fileName, path); + } + return path; +} +function readPackageJsonTypesFields(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { + return readPackageJsonPathField(jsonContent, "typings", baseDirectory, state) + || readPackageJsonPathField(jsonContent, "types", baseDirectory, state); +} +function readPackageJsonTSConfigField(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { + return readPackageJsonPathField(jsonContent, "tsconfig", baseDirectory, state); +} +function readPackageJsonMainField(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { + return readPackageJsonPathField(jsonContent, "main", baseDirectory, state); +} +function readPackageJsonTypesVersionsField(jsonContent: PackageJson, state: ModuleResolutionState) { + const typesVersions = readPackageJsonField(jsonContent, "typesVersions", "object", state); + if (typesVersions === undefined) + return; + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_has_a_typesVersions_field_with_version_specific_path_mappings); + } + return typesVersions; +} +interface VersionPaths { + version: string; + paths: MapLike; +} +function readPackageJsonTypesVersionPaths(jsonContent: PackageJson, state: ModuleResolutionState): VersionPaths | undefined { + const typesVersions = readPackageJsonTypesVersionsField(jsonContent, state); + if (typesVersions === undefined) + return; + if (state.traceEnabled) { + for (const key in typesVersions) { + if (hasProperty(typesVersions, key) && !VersionRange.tryParse(key)) { + trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_is_not_a_valid_semver_range, key); } - return; } - return value; } - - function readPackageJsonPathField(jsonContent: PackageJson, fieldName: K, baseDirectory: string, state: ModuleResolutionState): PackageJson[K] | undefined { - const fileName = readPackageJsonField(jsonContent, fieldName, "string", state); - if (fileName === undefined) { - return; - } - if (!fileName) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_had_a_falsy_0_field, fieldName); - } - return; + const result = getPackageJsonTypesVersionsPaths(typesVersions); + if (!result) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_does_not_have_a_typesVersions_entry_that_matches_version_0, versionMajorMinor); } - const path = normalizePath(combinePaths(baseDirectory, fileName)); + return; + } + const { version: bestVersionKey, paths: bestVersionPaths } = result; + if (typeof bestVersionPaths !== "object") { if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, fileName, path); + trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, `typesVersions['${bestVersionKey}']`, "object", typeof bestVersionPaths); } - return path; + return; } - - function readPackageJsonTypesFields(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { - return readPackageJsonPathField(jsonContent, "typings", baseDirectory, state) - || readPackageJsonPathField(jsonContent, "types", baseDirectory, state); + return result; +} +let typeScriptVersion: Version | undefined; +/* @internal */ +export function getPackageJsonTypesVersionsPaths(typesVersions: MapLike>) { + if (!typeScriptVersion) + typeScriptVersion = new Version(version); + for (const key in typesVersions) { + if (!hasProperty(typesVersions, key)) + continue; + const keyRange = VersionRange.tryParse(key); + if (keyRange === undefined) { + continue; + } + // return the first entry whose range matches the current compiler version. + if (keyRange.test(typeScriptVersion)) { + return { version: key, paths: typesVersions[key] }; + } } - - function readPackageJsonTSConfigField(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { - return readPackageJsonPathField(jsonContent, "tsconfig", baseDirectory, state); +} +export function getEffectiveTypeRoots(options: CompilerOptions, host: GetEffectiveTypeRootsHost): string[] | undefined { + if (options.typeRoots) { + return options.typeRoots; } - - function readPackageJsonMainField(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { - return readPackageJsonPathField(jsonContent, "main", baseDirectory, state); + let currentDirectory: string | undefined; + if (options.configFilePath) { + currentDirectory = getDirectoryPath(options.configFilePath); } - - function readPackageJsonTypesVersionsField(jsonContent: PackageJson, state: ModuleResolutionState) { - const typesVersions = readPackageJsonField(jsonContent, "typesVersions", "object", state); - if (typesVersions === undefined) return; - - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_has_a_typesVersions_field_with_version_specific_path_mappings); - } - - return typesVersions; + else if (host.getCurrentDirectory) { + currentDirectory = host.getCurrentDirectory(); } - - interface VersionPaths { - version: string; - paths: MapLike; + if (currentDirectory !== undefined) { + return getDefaultTypeRoots(currentDirectory, host); } - - function readPackageJsonTypesVersionPaths(jsonContent: PackageJson, state: ModuleResolutionState): VersionPaths | undefined { - const typesVersions = readPackageJsonTypesVersionsField(jsonContent, state); - if (typesVersions === undefined) return; - - if (state.traceEnabled) { - for (const key in typesVersions) { - if (hasProperty(typesVersions, key) && !VersionRange.tryParse(key)) { - trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_is_not_a_valid_semver_range, key); - } - } +} +/** + * Returns the path to every node_modules/@types directory from some ancestor directory. + * Returns undefined if there are none. + */ +function getDefaultTypeRoots(currentDirectory: string, host: { + directoryExists?: (directoryName: string) => boolean; +}): string[] | undefined { + if (!host.directoryExists) { + return [combinePaths(currentDirectory, nodeModulesAtTypes)]; + // And if it doesn't exist, tough. + } + let typeRoots: string[] | undefined; + forEachAncestorDirectory(normalizePath(currentDirectory), directory => { + const atTypes = combinePaths(directory, nodeModulesAtTypes); + if (host.directoryExists!(atTypes)) { + (typeRoots || (typeRoots = [])).push(atTypes); } - - const result = getPackageJsonTypesVersionsPaths(typesVersions); - if (!result) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_does_not_have_a_typesVersions_entry_that_matches_version_0, versionMajorMinor); + return undefined; + }); + return typeRoots; +} +const nodeModulesAtTypes = combinePaths("node_modules", "@types"); +/** + * @param {string | undefined} containingFile - file that contains type reference directive, can be undefined if containing file is unknown. + * This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups + * is assumed to be the same as root directory of the project. + */ +export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference): ResolvedTypeReferenceDirectiveWithFailedLookupLocations { + const traceEnabled = isTraceEnabled(options, host); + if (redirectedReference) { + options = redirectedReference.commandLine.options; + } + const failedLookupLocations: string[] = []; + const moduleResolutionState: ModuleResolutionState = { compilerOptions: options, host, traceEnabled, failedLookupLocations }; + const typeRoots = getEffectiveTypeRoots(options, host); + if (traceEnabled) { + if (containingFile === undefined) { + if (typeRoots === undefined) { + trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_not_set, typeReferenceDirectiveName); } - return; - } - - const { version: bestVersionKey, paths: bestVersionPaths } = result; - if (typeof bestVersionPaths !== "object") { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, `typesVersions['${bestVersionKey}']`, "object", typeof bestVersionPaths); + else { + trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_1, typeReferenceDirectiveName, typeRoots); } - return; } - - return result; - } - - let typeScriptVersion: Version | undefined; - - /* @internal */ - export function getPackageJsonTypesVersionsPaths(typesVersions: MapLike>) { - if (!typeScriptVersion) typeScriptVersion = new Version(version); - - for (const key in typesVersions) { - if (!hasProperty(typesVersions, key)) continue; - - const keyRange = VersionRange.tryParse(key); - if (keyRange === undefined) { - continue; + else { + if (typeRoots === undefined) { + trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_not_set, typeReferenceDirectiveName, containingFile); } - - // return the first entry whose range matches the current compiler version. - if (keyRange.test(typeScriptVersion)) { - return { version: key, paths: typesVersions[key] }; + else { + trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_2, typeReferenceDirectiveName, containingFile, typeRoots); } } - } - - export function getEffectiveTypeRoots(options: CompilerOptions, host: GetEffectiveTypeRootsHost): string[] | undefined { - if (options.typeRoots) { - return options.typeRoots; - } - - let currentDirectory: string | undefined; - if (options.configFilePath) { - currentDirectory = getDirectoryPath(options.configFilePath); - } - else if (host.getCurrentDirectory) { - currentDirectory = host.getCurrentDirectory(); - } - - if (currentDirectory !== undefined) { - return getDefaultTypeRoots(currentDirectory, host); + if (redirectedReference) { + trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); } } - - /** - * Returns the path to every node_modules/@types directory from some ancestor directory. - * Returns undefined if there are none. - */ - function getDefaultTypeRoots(currentDirectory: string, host: { directoryExists?: (directoryName: string) => boolean }): string[] | undefined { - if (!host.directoryExists) { - return [combinePaths(currentDirectory, nodeModulesAtTypes)]; - // And if it doesn't exist, tough. - } - - let typeRoots: string[] | undefined; - forEachAncestorDirectory(normalizePath(currentDirectory), directory => { - const atTypes = combinePaths(directory, nodeModulesAtTypes); - if (host.directoryExists!(atTypes)) { - (typeRoots || (typeRoots = [])).push(atTypes); - } - return undefined; - }); - return typeRoots; + let resolved = primaryLookup(); + let primary = true; + if (!resolved) { + resolved = secondaryLookup(); + primary = false; } - const nodeModulesAtTypes = combinePaths("node_modules", "@types"); - - /** - * @param {string | undefined} containingFile - file that contains type reference directive, can be undefined if containing file is unknown. - * This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups - * is assumed to be the same as root directory of the project. - */ - export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference): ResolvedTypeReferenceDirectiveWithFailedLookupLocations { - const traceEnabled = isTraceEnabled(options, host); - if (redirectedReference) { - options = redirectedReference.commandLine.options; - } - const failedLookupLocations: string[] = []; - const moduleResolutionState: ModuleResolutionState = { compilerOptions: options, host, traceEnabled, failedLookupLocations }; - - const typeRoots = getEffectiveTypeRoots(options, host); + let resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined; + if (resolved) { + const { fileName, packageId } = resolved; + const resolvedFileName = options.preserveSymlinks ? fileName : realPath(fileName, host, traceEnabled); if (traceEnabled) { - if (containingFile === undefined) { - if (typeRoots === undefined) { - trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_not_set, typeReferenceDirectiveName); - } - else { - trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_1, typeReferenceDirectiveName, typeRoots); - } + if (packageId) { + trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_with_Package_ID_2_primary_Colon_3, typeReferenceDirectiveName, resolvedFileName, packageIdToString(packageId), primary); } else { - if (typeRoots === undefined) { - trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_not_set, typeReferenceDirectiveName, containingFile); - } - else { - trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_2, typeReferenceDirectiveName, containingFile, typeRoots); - } + trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, resolvedFileName, primary); } - if (redirectedReference) { - trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); - } - } - - let resolved = primaryLookup(); - let primary = true; - if (!resolved) { - resolved = secondaryLookup(); - primary = false; } - - let resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined; - if (resolved) { - const { fileName, packageId } = resolved; - const resolvedFileName = options.preserveSymlinks ? fileName : realPath(fileName, host, traceEnabled); + resolvedTypeReferenceDirective = { primary, resolvedFileName, packageId, isExternalLibraryImport: pathContainsNodeModules(fileName) }; + } + return { resolvedTypeReferenceDirective, failedLookupLocations }; + function primaryLookup(): PathAndPackageId | undefined { + // Check primary library paths + if (typeRoots && typeRoots.length) { if (traceEnabled) { - if (packageId) { - trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_with_Package_ID_2_primary_Colon_3, typeReferenceDirectiveName, resolvedFileName, packageIdToString(packageId), primary); - } - else { - trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, resolvedFileName, primary); + trace(host, Diagnostics.Resolving_with_primary_search_path_0, typeRoots.join(", ")); + } + return firstDefined(typeRoots, typeRoot => { + const candidate = combinePaths(typeRoot, typeReferenceDirectiveName); + const candidateDirectory = getDirectoryPath(candidate); + const directoryExists = directoryProbablyExists(candidateDirectory, host); + if (!directoryExists && traceEnabled) { + trace(host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, candidateDirectory); } + return resolvedTypeScriptOnly(loadNodeModuleFromDirectory(Extensions.DtsOnly, candidate, !directoryExists, moduleResolutionState)); + }); + } + else { + if (traceEnabled) { + trace(host, Diagnostics.Root_directory_cannot_be_determined_skipping_primary_search_paths); } - resolvedTypeReferenceDirective = { primary, resolvedFileName, packageId, isExternalLibraryImport: pathContainsNodeModules(fileName) }; - } - - return { resolvedTypeReferenceDirective, failedLookupLocations }; - - function primaryLookup(): PathAndPackageId | undefined { - // Check primary library paths - if (typeRoots && typeRoots.length) { - if (traceEnabled) { - trace(host, Diagnostics.Resolving_with_primary_search_path_0, typeRoots.join(", ")); - } - return firstDefined(typeRoots, typeRoot => { - const candidate = combinePaths(typeRoot, typeReferenceDirectiveName); - const candidateDirectory = getDirectoryPath(candidate); - const directoryExists = directoryProbablyExists(candidateDirectory, host); - if (!directoryExists && traceEnabled) { - trace(host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, candidateDirectory); - } - return resolvedTypeScriptOnly( - loadNodeModuleFromDirectory(Extensions.DtsOnly, candidate, - !directoryExists, moduleResolutionState)); - }); + } + } + function secondaryLookup(): PathAndPackageId | undefined { + const initialLocationForSecondaryLookup = containingFile && getDirectoryPath(containingFile); + if (initialLocationForSecondaryLookup !== undefined) { + // check secondary locations + if (traceEnabled) { + trace(host, Diagnostics.Looking_up_in_node_modules_folder_initial_location_0, initialLocationForSecondaryLookup); + } + let result: Resolved | undefined; + if (!isExternalModuleNameRelative(typeReferenceDirectiveName)) { + const searchResult = loadModuleFromNearestNodeModulesDirectory(Extensions.DtsOnly, typeReferenceDirectiveName, initialLocationForSecondaryLookup, moduleResolutionState, /*cache*/ undefined, /*redirectedReference*/ undefined); + result = searchResult && searchResult.value; } else { - if (traceEnabled) { - trace(host, Diagnostics.Root_directory_cannot_be_determined_skipping_primary_search_paths); - } + const { path: candidate } = normalizePathAndParts(combinePaths(initialLocationForSecondaryLookup, typeReferenceDirectiveName)); + result = nodeLoadModuleByRelativeName(Extensions.DtsOnly, candidate, /*onlyRecordFailures*/ false, moduleResolutionState, /*considerPackageJson*/ true); } - } - - function secondaryLookup(): PathAndPackageId | undefined { - const initialLocationForSecondaryLookup = containingFile && getDirectoryPath(containingFile); - - if (initialLocationForSecondaryLookup !== undefined) { - // check secondary locations - if (traceEnabled) { - trace(host, Diagnostics.Looking_up_in_node_modules_folder_initial_location_0, initialLocationForSecondaryLookup); - } - let result: Resolved | undefined; - if (!isExternalModuleNameRelative(typeReferenceDirectiveName)) { - const searchResult = loadModuleFromNearestNodeModulesDirectory(Extensions.DtsOnly, typeReferenceDirectiveName, initialLocationForSecondaryLookup, moduleResolutionState, /*cache*/ undefined, /*redirectedReference*/ undefined); - result = searchResult && searchResult.value; - } - else { - const { path: candidate } = normalizePathAndParts(combinePaths(initialLocationForSecondaryLookup, typeReferenceDirectiveName)); - result = nodeLoadModuleByRelativeName(Extensions.DtsOnly, candidate, /*onlyRecordFailures*/ false, moduleResolutionState, /*considerPackageJson*/ true); - } - const resolvedFile = resolvedTypeScriptOnly(result); - if (!resolvedFile && traceEnabled) { - trace(host, Diagnostics.Type_reference_directive_0_was_not_resolved, typeReferenceDirectiveName); - } - return resolvedFile; + const resolvedFile = resolvedTypeScriptOnly(result); + if (!resolvedFile && traceEnabled) { + trace(host, Diagnostics.Type_reference_directive_0_was_not_resolved, typeReferenceDirectiveName); } - else { - if (traceEnabled) { - trace(host, Diagnostics.Containing_file_is_not_specified_and_root_directory_cannot_be_determined_skipping_lookup_in_node_modules_folder); - } + return resolvedFile; + } + else { + if (traceEnabled) { + trace(host, Diagnostics.Containing_file_is_not_specified_and_root_directory_cannot_be_determined_skipping_lookup_in_node_modules_folder); } } } - - /** - * Given a set of options, returns the set of type directive names - * that should be included for this program automatically. - * This list could either come from the config file, - * or from enumerating the types root + initial secondary types lookup location. - * More type directives might appear in the program later as a result of loading actual source files; - * this list is only the set of defaults that are implicitly included. - */ - export function getAutomaticTypeDirectiveNames(options: CompilerOptions, host: ModuleResolutionHost): string[] { - // Use explicit type list from tsconfig.json - if (options.types) { - return options.types; - } - - // Walk the primary type lookup locations - const result: string[] = []; - if (host.directoryExists && host.getDirectories) { - const typeRoots = getEffectiveTypeRoots(options, host); - if (typeRoots) { - for (const root of typeRoots) { - if (host.directoryExists(root)) { - for (const typeDirectivePath of host.getDirectories(root)) { - const normalized = normalizePath(typeDirectivePath); - const packageJsonPath = combinePaths(root, normalized, "package.json"); - // `types-publisher` sometimes creates packages with `"typings": null` for packages that don't provide their own types. - // See `createNotNeededPackageJSON` in the types-publisher` repo. - // eslint-disable-next-line no-null/no-null - const isNotNeededPackage = host.fileExists(packageJsonPath) && (readJson(packageJsonPath, host) as PackageJson).typings === null; - if (!isNotNeededPackage) { - const baseFileName = getBaseFileName(normalized); - - // At this stage, skip results with leading dot. - if (baseFileName.charCodeAt(0) !== CharacterCodes.dot) { - // Return just the type directive names - result.push(baseFileName); - } +} +/** + * Given a set of options, returns the set of type directive names + * that should be included for this program automatically. + * This list could either come from the config file, + * or from enumerating the types root + initial secondary types lookup location. + * More type directives might appear in the program later as a result of loading actual source files; + * this list is only the set of defaults that are implicitly included. + */ +export function getAutomaticTypeDirectiveNames(options: CompilerOptions, host: ModuleResolutionHost): string[] { + // Use explicit type list from tsconfig.json + if (options.types) { + return options.types; + } + // Walk the primary type lookup locations + const result: string[] = []; + if (host.directoryExists && host.getDirectories) { + const typeRoots = getEffectiveTypeRoots(options, host); + if (typeRoots) { + for (const root of typeRoots) { + if (host.directoryExists(root)) { + for (const typeDirectivePath of host.getDirectories(root)) { + const normalized = normalizePath(typeDirectivePath); + const packageJsonPath = combinePaths(root, normalized, "package.json"); + // `types-publisher` sometimes creates packages with `"typings": null` for packages that don't provide their own types. + // See `createNotNeededPackageJSON` in the types-publisher` repo. + // eslint-disable-next-line no-null/no-null + const isNotNeededPackage = host.fileExists(packageJsonPath) && (readJson(packageJsonPath, host) as PackageJson).typings === null; + if (!isNotNeededPackage) { + const baseFileName = getBaseFileName(normalized); + // At this stage, skip results with leading dot. + if (baseFileName.charCodeAt(0) !== CharacterCodes.dot) { + // Return just the type directive names + result.push(baseFileName); } } } } } } - return result; } - - /** - * Cached module resolutions per containing directory. - * This assumes that any module id will have the same resolution for sibling files located in the same folder. - */ - export interface ModuleResolutionCache extends NonRelativeModuleNameResolutionCache { - getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference): Map; - /*@internal*/ directoryToModuleNameMap: CacheWithRedirects>; + return result; +} +/** + * Cached module resolutions per containing directory. + * This assumes that any module id will have the same resolution for sibling files located in the same folder. + */ +export interface ModuleResolutionCache extends NonRelativeModuleNameResolutionCache { + getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference): ts.Map; + /*@internal*/ directoryToModuleNameMap: CacheWithRedirects>; +} +/** + * Stored map from non-relative module name to a table: directory -> result of module lookup in this directory + * We support only non-relative module names because resolution of relative module names is usually more deterministic and thus less expensive. + */ +export interface NonRelativeModuleNameResolutionCache { + getOrCreateCacheForModuleName(nonRelativeModuleName: string, redirectedReference?: ResolvedProjectReference): PerModuleNameCache; + /*@internal*/ moduleNameToDirectoryMap: CacheWithRedirects; +} +export interface PerModuleNameCache { + get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined; + set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void; +} +export function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string, options?: CompilerOptions): ModuleResolutionCache { + return createModuleResolutionCacheWithMaps(createCacheWithRedirects(options), createCacheWithRedirects(options), currentDirectory, getCanonicalFileName); +} +/*@internal*/ +export interface CacheWithRedirects { + ownMap: ts.Map; + redirectsMap: ts.Map>; + getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined): ts.Map; + clear(): void; + setOwnOptions(newOptions: CompilerOptions): void; + setOwnMap(newOwnMap: ts.Map): void; +} +/*@internal*/ +export function createCacheWithRedirects(options?: CompilerOptions): CacheWithRedirects { + let ownMap: ts.Map = createMap(); + const redirectsMap: ts.Map> = createMap(); + return { + ownMap, + redirectsMap, + getOrCreateMapOfCacheRedirects, + clear, + setOwnOptions, + setOwnMap + }; + function setOwnOptions(newOptions: CompilerOptions) { + options = newOptions; + } + function setOwnMap(newOwnMap: ts.Map) { + ownMap = newOwnMap; + } + function getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined) { + if (!redirectedReference) { + return ownMap; + } + const path = redirectedReference.sourceFile.path; + let redirects = redirectsMap.get(path); + if (!redirects) { + // Reuse map if redirected reference map uses same resolution + redirects = !options || optionsHaveModuleResolutionChanges(options, redirectedReference.commandLine.options) ? createMap() : ownMap; + redirectsMap.set(path, redirects); + } + return redirects; + } + function clear() { + ownMap.clear(); + redirectsMap.clear(); } - - /** - * Stored map from non-relative module name to a table: directory -> result of module lookup in this directory - * We support only non-relative module names because resolution of relative module names is usually more deterministic and thus less expensive. - */ - export interface NonRelativeModuleNameResolutionCache { - getOrCreateCacheForModuleName(nonRelativeModuleName: string, redirectedReference?: ResolvedProjectReference): PerModuleNameCache; - /*@internal*/ moduleNameToDirectoryMap: CacheWithRedirects; - } - - export interface PerModuleNameCache { - get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined; - set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void; - } - - export function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string, options?: CompilerOptions): ModuleResolutionCache { - return createModuleResolutionCacheWithMaps( - createCacheWithRedirects(options), - createCacheWithRedirects(options), - currentDirectory, - getCanonicalFileName - ); - } - - - /*@internal*/ - export interface CacheWithRedirects { - ownMap: Map; - redirectsMap: Map>; - getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined): Map; - clear(): void; - setOwnOptions(newOptions: CompilerOptions): void; - setOwnMap(newOwnMap: Map): void; - } - - /*@internal*/ - export function createCacheWithRedirects(options?: CompilerOptions): CacheWithRedirects { - let ownMap: Map = createMap(); - const redirectsMap: Map> = createMap(); - return { - ownMap, - redirectsMap, - getOrCreateMapOfCacheRedirects, - clear, - setOwnOptions, - setOwnMap - }; - - function setOwnOptions(newOptions: CompilerOptions) { - options = newOptions; - } - - function setOwnMap(newOwnMap: Map) { - ownMap = newOwnMap; - } - - function getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined) { - if (!redirectedReference) { - return ownMap; - } - const path = redirectedReference.sourceFile.path; - let redirects = redirectsMap.get(path); - if (!redirects) { - // Reuse map if redirected reference map uses same resolution - redirects = !options || optionsHaveModuleResolutionChanges(options, redirectedReference.commandLine.options) ? createMap() : ownMap; - redirectsMap.set(path, redirects); +} +/*@internal*/ +export function createModuleResolutionCacheWithMaps(directoryToModuleNameMap: CacheWithRedirects>, moduleNameToDirectoryMap: CacheWithRedirects, currentDirectory: string, getCanonicalFileName: GetCanonicalFileName): ModuleResolutionCache { + return { getOrCreateCacheForDirectory, getOrCreateCacheForModuleName, directoryToModuleNameMap, moduleNameToDirectoryMap }; + function getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference) { + const path = toPath(directoryName, currentDirectory, getCanonicalFileName); + return getOrCreateCache>(directoryToModuleNameMap, redirectedReference, path, createMap); + } + function getOrCreateCacheForModuleName(nonRelativeModuleName: string, redirectedReference?: ResolvedProjectReference): PerModuleNameCache { + Debug.assert(!isExternalModuleNameRelative(nonRelativeModuleName)); + return getOrCreateCache(moduleNameToDirectoryMap, redirectedReference, nonRelativeModuleName, createPerModuleNameCache); + } + function getOrCreateCache(cacheWithRedirects: CacheWithRedirects, redirectedReference: ResolvedProjectReference | undefined, key: string, create: () => T): T { + const cache = cacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference); + let result = cache.get(key); + if (!result) { + result = create(); + cache.set(key, result); + } + return result; + } + function createPerModuleNameCache(): PerModuleNameCache { + const directoryPathMap = createMap(); + return { get, set }; + function get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined { + return directoryPathMap.get(toPath(directory, currentDirectory, getCanonicalFileName)); + } + /** + * At first this function add entry directory -> module resolution result to the table. + * Then it computes the set of parent folders for 'directory' that should have the same module resolution result + * and for every parent folder in set it adds entry: parent -> module resolution. . + * Lets say we first directory name: /a/b/c/d/e and resolution result is: /a/b/bar.ts. + * Set of parent folders that should have the same result will be: + * [ + * /a/b/c/d, /a/b/c, /a/b + * ] + * this means that request for module resolution from file in any of these folder will be immediately found in cache. + */ + function set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void { + const path = toPath(directory, currentDirectory, getCanonicalFileName); + // if entry is already in cache do nothing + if (directoryPathMap.has(path)) { + return; + } + directoryPathMap.set(path, result); + const resolvedFileName = result.resolvedModule && + (result.resolvedModule.originalPath || result.resolvedModule.resolvedFileName); + // find common prefix between directory and resolved file name + // this common prefix should be the shortest path that has the same resolution + // directory: /a/b/c/d/e + // resolvedFileName: /a/b/foo.d.ts + // commonPrefix: /a/b + // for failed lookups cache the result for every directory up to root + const commonPrefix = resolvedFileName && getCommonPrefix(path, resolvedFileName); + let current = path; + while (current !== commonPrefix) { + const parent = getDirectoryPath(current); + if (parent === current || directoryPathMap.has(parent)) { + break; + } + directoryPathMap.set(parent, result); + current = parent; } - return redirects; - } - - function clear() { - ownMap.clear(); - redirectsMap.clear(); - } - } - - /*@internal*/ - export function createModuleResolutionCacheWithMaps( - directoryToModuleNameMap: CacheWithRedirects>, - moduleNameToDirectoryMap: CacheWithRedirects, - currentDirectory: string, - getCanonicalFileName: GetCanonicalFileName): ModuleResolutionCache { - - return { getOrCreateCacheForDirectory, getOrCreateCacheForModuleName, directoryToModuleNameMap, moduleNameToDirectoryMap }; - - function getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference) { - const path = toPath(directoryName, currentDirectory, getCanonicalFileName); - return getOrCreateCache>(directoryToModuleNameMap, redirectedReference, path, createMap); - } - - function getOrCreateCacheForModuleName(nonRelativeModuleName: string, redirectedReference?: ResolvedProjectReference): PerModuleNameCache { - Debug.assert(!isExternalModuleNameRelative(nonRelativeModuleName)); - return getOrCreateCache(moduleNameToDirectoryMap, redirectedReference, nonRelativeModuleName, createPerModuleNameCache); - } - - function getOrCreateCache(cacheWithRedirects: CacheWithRedirects, redirectedReference: ResolvedProjectReference | undefined, key: string, create: () => T): T { - const cache = cacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference); - let result = cache.get(key); - if (!result) { - result = create(); - cache.set(key, result); + } + function getCommonPrefix(directory: Path, resolution: string) { + const resolutionDirectory = toPath(getDirectoryPath(resolution), currentDirectory, getCanonicalFileName); + // find first position where directory and resolution differs + let i = 0; + const limit = Math.min(directory.length, resolutionDirectory.length); + while (i < limit && directory.charCodeAt(i) === resolutionDirectory.charCodeAt(i)) { + i++; } - return result; - } - - function createPerModuleNameCache(): PerModuleNameCache { - const directoryPathMap = createMap(); - - return { get, set }; - - function get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined { - return directoryPathMap.get(toPath(directory, currentDirectory, getCanonicalFileName)); + if (i === directory.length && (resolutionDirectory.length === i || resolutionDirectory[i] === directorySeparator)) { + return directory; } - - /** - * At first this function add entry directory -> module resolution result to the table. - * Then it computes the set of parent folders for 'directory' that should have the same module resolution result - * and for every parent folder in set it adds entry: parent -> module resolution. . - * Lets say we first directory name: /a/b/c/d/e and resolution result is: /a/b/bar.ts. - * Set of parent folders that should have the same result will be: - * [ - * /a/b/c/d, /a/b/c, /a/b - * ] - * this means that request for module resolution from file in any of these folder will be immediately found in cache. - */ - function set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void { - const path = toPath(directory, currentDirectory, getCanonicalFileName); - // if entry is already in cache do nothing - if (directoryPathMap.has(path)) { - return; - } - directoryPathMap.set(path, result); - - const resolvedFileName = result.resolvedModule && - (result.resolvedModule.originalPath || result.resolvedModule.resolvedFileName); - // find common prefix between directory and resolved file name - // this common prefix should be the shortest path that has the same resolution - // directory: /a/b/c/d/e - // resolvedFileName: /a/b/foo.d.ts - // commonPrefix: /a/b - // for failed lookups cache the result for every directory up to root - const commonPrefix = resolvedFileName && getCommonPrefix(path, resolvedFileName); - let current = path; - while (current !== commonPrefix) { - const parent = getDirectoryPath(current); - if (parent === current || directoryPathMap.has(parent)) { - break; - } - directoryPathMap.set(parent, result); - current = parent; - } + const rootLength = getRootLength(directory); + if (i < rootLength) { + return undefined; } - - function getCommonPrefix(directory: Path, resolution: string) { - const resolutionDirectory = toPath(getDirectoryPath(resolution), currentDirectory, getCanonicalFileName); - - // find first position where directory and resolution differs - let i = 0; - const limit = Math.min(directory.length, resolutionDirectory.length); - while (i < limit && directory.charCodeAt(i) === resolutionDirectory.charCodeAt(i)) { - i++; - } - if (i === directory.length && (resolutionDirectory.length === i || resolutionDirectory[i] === directorySeparator)) { - return directory; - } - const rootLength = getRootLength(directory); - if (i < rootLength) { - return undefined; - } - const sep = directory.lastIndexOf(directorySeparator, i - 1); - if (sep === -1) { - return undefined; - } - return directory.substr(0, Math.max(sep, rootLength)); + const sep = directory.lastIndexOf(directorySeparator, i - 1); + if (sep === -1) { + return undefined; } + return directory.substr(0, Math.max(sep, rootLength)); } } - - export function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations | undefined { - const containingDirectory = getDirectoryPath(containingFile); - const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory); - return perFolderCache && perFolderCache.get(moduleName); +} +export function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations | undefined { + const containingDirectory = getDirectoryPath(containingFile); + const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory); + return perFolderCache && perFolderCache.get(moduleName); +} +export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations { + const traceEnabled = isTraceEnabled(compilerOptions, host); + if (redirectedReference) { + compilerOptions = redirectedReference.commandLine.options; } - - export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations { - const traceEnabled = isTraceEnabled(compilerOptions, host); + if (traceEnabled) { + trace(host, Diagnostics.Resolving_module_0_from_1, moduleName, containingFile); if (redirectedReference) { - compilerOptions = redirectedReference.commandLine.options; + trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); } + } + const containingDirectory = getDirectoryPath(containingFile); + const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory, redirectedReference); + let result = perFolderCache && perFolderCache.get(moduleName); + if (result) { if (traceEnabled) { - trace(host, Diagnostics.Resolving_module_0_from_1, moduleName, containingFile); - if (redirectedReference) { - trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); - } + trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory); } - const containingDirectory = getDirectoryPath(containingFile); - const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory, redirectedReference); - let result = perFolderCache && perFolderCache.get(moduleName); - - if (result) { + } + else { + let moduleResolution = compilerOptions.moduleResolution; + if (moduleResolution === undefined) { + moduleResolution = getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS ? ModuleResolutionKind.NodeJs : ModuleResolutionKind.Classic; if (traceEnabled) { - trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory); + trace(host, Diagnostics.Module_resolution_kind_is_not_specified_using_0, ModuleResolutionKind[moduleResolution]); } } else { - let moduleResolution = compilerOptions.moduleResolution; - if (moduleResolution === undefined) { - moduleResolution = getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS ? ModuleResolutionKind.NodeJs : ModuleResolutionKind.Classic; - if (traceEnabled) { - trace(host, Diagnostics.Module_resolution_kind_is_not_specified_using_0, ModuleResolutionKind[moduleResolution]); - } - } - else { - if (traceEnabled) { - trace(host, Diagnostics.Explicitly_specified_module_resolution_kind_Colon_0, ModuleResolutionKind[moduleResolution]); - } - } - - perfLogger.logStartResolveModule(moduleName /* , containingFile, ModuleResolutionKind[moduleResolution]*/); - switch (moduleResolution) { - case ModuleResolutionKind.NodeJs: - result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference); - break; - case ModuleResolutionKind.Classic: - result = classicNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference); - break; - default: - return Debug.fail(`Unexpected moduleResolution: ${moduleResolution}`); - } - if (result && result.resolvedModule) perfLogger.logInfoEvent(`Module "${moduleName}" resolved to "${result.resolvedModule.resolvedFileName}"`); - perfLogger.logStopResolveModule((result && result.resolvedModule) ? "" + result.resolvedModule.resolvedFileName : "null"); - - if (perFolderCache) { - perFolderCache.set(moduleName, result); - if (!isExternalModuleNameRelative(moduleName)) { - // put result in per-module name cache - cache!.getOrCreateCacheForModuleName(moduleName, redirectedReference).set(containingDirectory, result); - } + if (traceEnabled) { + trace(host, Diagnostics.Explicitly_specified_module_resolution_kind_Colon_0, ModuleResolutionKind[moduleResolution]); + } + } + perfLogger.logStartResolveModule(moduleName /* , containingFile, ModuleResolutionKind[moduleResolution]*/); + switch (moduleResolution) { + case ModuleResolutionKind.NodeJs: + result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference); + break; + case ModuleResolutionKind.Classic: + result = classicNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference); + break; + default: + return Debug.fail(`Unexpected moduleResolution: ${moduleResolution}`); + } + if (result && result.resolvedModule) + perfLogger.logInfoEvent(`Module "${moduleName}" resolved to "${result.resolvedModule.resolvedFileName}"`); + perfLogger.logStopResolveModule((result && result.resolvedModule) ? "" + result.resolvedModule.resolvedFileName : "null"); + if (perFolderCache) { + perFolderCache.set(moduleName, result); + if (!isExternalModuleNameRelative(moduleName)) { + // put result in per-module name cache + cache!.getOrCreateCacheForModuleName(moduleName, redirectedReference).set(containingDirectory, result); } } - - if (traceEnabled) { - if (result.resolvedModule) { - if (result.resolvedModule.packageId) { - trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1_with_Package_ID_2, moduleName, result.resolvedModule.resolvedFileName, packageIdToString(result.resolvedModule.packageId)); - } - else { - trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1, moduleName, result.resolvedModule.resolvedFileName); - } + } + if (traceEnabled) { + if (result.resolvedModule) { + if (result.resolvedModule.packageId) { + trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1_with_Package_ID_2, moduleName, result.resolvedModule.resolvedFileName, packageIdToString(result.resolvedModule.packageId)); } else { - trace(host, Diagnostics.Module_name_0_was_not_resolved, moduleName); + trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1, moduleName, result.resolvedModule.resolvedFileName); } } - - return result; - } - - /* - * Every module resolution kind can has its specific understanding how to load module from a specific path on disk - * I.e. for path '/a/b/c': - * - Node loader will first to try to check if '/a/b/c' points to a file with some supported extension and if this fails - * it will try to load module from directory: directory '/a/b/c' should exist and it should have either 'package.json' with - * 'typings' entry or file 'index' with some supported extension - * - Classic loader will only try to interpret '/a/b/c' as file. - */ - type ResolutionKindSpecificLoader = (extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState) => Resolved | undefined; - - /** - * Any module resolution kind can be augmented with optional settings: 'baseUrl', 'paths' and 'rootDirs' - they are used to - * mitigate differences between design time structure of the project and its runtime counterpart so the same import name - * can be resolved successfully by TypeScript compiler and runtime module loader. - * If these settings are set then loading procedure will try to use them to resolve module name and it can of failure it will - * fallback to standard resolution routine. - * - * - baseUrl - this setting controls how non-relative module names are resolved. If this setting is specified then non-relative - * names will be resolved relative to baseUrl: i.e. if baseUrl is '/a/b' then candidate location to resolve module name 'c/d' will - * be '/a/b/c/d' - * - paths - this setting can only be used when baseUrl is specified. allows to tune how non-relative module names - * will be resolved based on the content of the module name. - * Structure of 'paths' compiler options - * 'paths': { - * pattern-1: [...substitutions], - * pattern-2: [...substitutions], - * ... - * pattern-n: [...substitutions] - * } - * Pattern here is a string that can contain zero or one '*' character. During module resolution module name will be matched against - * all patterns in the list. Matching for patterns that don't contain '*' means that module name must be equal to pattern respecting the case. - * If pattern contains '*' then to match pattern "*" module name must start with the and end with . - * denotes part of the module name between and . - * If module name can be matches with multiple patterns then pattern with the longest prefix will be picked. - * After selecting pattern we'll use list of substitutions to get candidate locations of the module and the try to load module - * from the candidate location. - * Substitution is a string that can contain zero or one '*'. To get candidate location from substitution we'll pick every - * substitution in the list and replace '*' with string. If candidate location is not rooted it - * will be converted to absolute using baseUrl. - * For example: - * baseUrl: /a/b/c - * "paths": { - * // match all module names - * "*": [ - * "*", // use matched name as is, - * // will be looked as /a/b/c/ - * - * "folder1/*" // substitution will convert matched name to 'folder1/', - * // since it is not rooted then final candidate location will be /a/b/c/folder1/ - * ], - * // match module names that start with 'components/' - * "components/*": [ "/root/components/*" ] // substitution will convert /components/folder1/ to '/root/components/folder1/', - * // it is rooted so it will be final candidate location - * } - * - * 'rootDirs' allows the project to be spreaded across multiple locations and resolve modules with relative names as if - * they were in the same location. For example lets say there are two files - * '/local/src/content/file1.ts' - * '/shared/components/contracts/src/content/protocols/file2.ts' - * After bundling content of '/shared/components/contracts/src' will be merged with '/local/src' so - * if file1 has the following import 'import {x} from "./protocols/file2"' it will be resolved successfully in runtime. - * 'rootDirs' provides the way to tell compiler that in order to get the whole project it should behave as if content of all - * root dirs were merged together. - * I.e. for the example above 'rootDirs' will have two entries: [ '/local/src', '/shared/components/contracts/src' ]. - * Compiler will first convert './protocols/file2' into absolute path relative to the location of containing file: - * '/local/src/content/protocols/file2' and try to load it - failure. - * Then it will search 'rootDirs' looking for a longest matching prefix of this absolute path and if such prefix is found - absolute path will - * be converted to a path relative to found rootDir entry './content/protocols/file2' (*). As a last step compiler will check all remaining - * entries in 'rootDirs', use them to build absolute path out of (*) and try to resolve module from this location. - */ - function tryLoadModuleUsingOptionalResolutionSettings(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, - state: ModuleResolutionState): Resolved | undefined { - - const resolved = tryLoadModuleUsingPathsIfEligible(extensions, moduleName, loader, state); - if (resolved) return resolved.value; - - if (!isExternalModuleNameRelative(moduleName)) { - return tryLoadModuleUsingBaseUrl(extensions, moduleName, loader, state); - } else { - return tryLoadModuleUsingRootDirs(extensions, moduleName, containingDirectory, loader, state); + trace(host, Diagnostics.Module_name_0_was_not_resolved, moduleName); } } - - function tryLoadModuleUsingPathsIfEligible(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState) { - const { baseUrl, paths } = state.compilerOptions; - if (baseUrl && paths && !pathIsRelative(moduleName)) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName); - trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName); - } - return tryLoadModuleUsingPaths(extensions, moduleName, baseUrl, paths, loader, /*onlyRecordFailures*/ false, state); + return result; +} +/* + * Every module resolution kind can has its specific understanding how to load module from a specific path on disk + * I.e. for path '/a/b/c': + * - Node loader will first to try to check if '/a/b/c' points to a file with some supported extension and if this fails + * it will try to load module from directory: directory '/a/b/c' should exist and it should have either 'package.json' with + * 'typings' entry or file 'index' with some supported extension + * - Classic loader will only try to interpret '/a/b/c' as file. + */ +type ResolutionKindSpecificLoader = (extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState) => Resolved | undefined; +/** + * Any module resolution kind can be augmented with optional settings: 'baseUrl', 'paths' and 'rootDirs' - they are used to + * mitigate differences between design time structure of the project and its runtime counterpart so the same import name + * can be resolved successfully by TypeScript compiler and runtime module loader. + * If these settings are set then loading procedure will try to use them to resolve module name and it can of failure it will + * fallback to standard resolution routine. + * + * - baseUrl - this setting controls how non-relative module names are resolved. If this setting is specified then non-relative + * names will be resolved relative to baseUrl: i.e. if baseUrl is '/a/b' then candidate location to resolve module name 'c/d' will + * be '/a/b/c/d' + * - paths - this setting can only be used when baseUrl is specified. allows to tune how non-relative module names + * will be resolved based on the content of the module name. + * Structure of 'paths' compiler options + * 'paths': { + * pattern-1: [...substitutions], + * pattern-2: [...substitutions], + * ... + * pattern-n: [...substitutions] + * } + * Pattern here is a string that can contain zero or one '*' character. During module resolution module name will be matched against + * all patterns in the list. Matching for patterns that don't contain '*' means that module name must be equal to pattern respecting the case. + * If pattern contains '*' then to match pattern "*" module name must start with the and end with . + * denotes part of the module name between and . + * If module name can be matches with multiple patterns then pattern with the longest prefix will be picked. + * After selecting pattern we'll use list of substitutions to get candidate locations of the module and the try to load module + * from the candidate location. + * Substitution is a string that can contain zero or one '*'. To get candidate location from substitution we'll pick every + * substitution in the list and replace '*' with string. If candidate location is not rooted it + * will be converted to absolute using baseUrl. + * For example: + * baseUrl: /a/b/c + * "paths": { + * // match all module names + * "*": [ + * "*", // use matched name as is, + * // will be looked as /a/b/c/ + * + * "folder1/*" // substitution will convert matched name to 'folder1/', + * // since it is not rooted then final candidate location will be /a/b/c/folder1/ + * ], + * // match module names that start with 'components/' + * "components/*": [ "/root/components/*" ] // substitution will convert /components/folder1/ to '/root/components/folder1/', + * // it is rooted so it will be final candidate location + * } + * + * 'rootDirs' allows the project to be spreaded across multiple locations and resolve modules with relative names as if + * they were in the same location. For example lets say there are two files + * '/local/src/content/file1.ts' + * '/shared/components/contracts/src/content/protocols/file2.ts' + * After bundling content of '/shared/components/contracts/src' will be merged with '/local/src' so + * if file1 has the following import 'import {x} from "./protocols/file2"' it will be resolved successfully in runtime. + * 'rootDirs' provides the way to tell compiler that in order to get the whole project it should behave as if content of all + * root dirs were merged together. + * I.e. for the example above 'rootDirs' will have two entries: [ '/local/src', '/shared/components/contracts/src' ]. + * Compiler will first convert './protocols/file2' into absolute path relative to the location of containing file: + * '/local/src/content/protocols/file2' and try to load it - failure. + * Then it will search 'rootDirs' looking for a longest matching prefix of this absolute path and if such prefix is found - absolute path will + * be converted to a path relative to found rootDir entry './content/protocols/file2' (*). As a last step compiler will check all remaining + * entries in 'rootDirs', use them to build absolute path out of (*) and try to resolve module from this location. + */ +function tryLoadModuleUsingOptionalResolutionSettings(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState): Resolved | undefined { + const resolved = tryLoadModuleUsingPathsIfEligible(extensions, moduleName, loader, state); + if (resolved) + return resolved.value; + if (!isExternalModuleNameRelative(moduleName)) { + return tryLoadModuleUsingBaseUrl(extensions, moduleName, loader, state); + } + else { + return tryLoadModuleUsingRootDirs(extensions, moduleName, containingDirectory, loader, state); + } +} +function tryLoadModuleUsingPathsIfEligible(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState) { + const { baseUrl, paths } = state.compilerOptions; + if (baseUrl && paths && !pathIsRelative(moduleName)) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName); + trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName); + } + return tryLoadModuleUsingPaths(extensions, moduleName, baseUrl, paths, loader, /*onlyRecordFailures*/ false, state); + } +} +function tryLoadModuleUsingRootDirs(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState): Resolved | undefined { + if (!state.compilerOptions.rootDirs) { + return undefined; + } + if (state.traceEnabled) { + trace(state.host, Diagnostics.rootDirs_option_is_set_using_it_to_resolve_relative_module_name_0, moduleName); + } + const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); + let matchedRootDir: string | undefined; + let matchedNormalizedPrefix: string | undefined; + for (const rootDir of state.compilerOptions.rootDirs) { + // rootDirs are expected to be absolute + // in case of tsconfig.json this will happen automatically - compiler will expand relative names + // using location of tsconfig.json as base location + let normalizedRoot = normalizePath(rootDir); + if (!endsWith(normalizedRoot, directorySeparator)) { + normalizedRoot += directorySeparator; + } + const isLongestMatchingPrefix = startsWith(candidate, normalizedRoot) && + (matchedNormalizedPrefix === undefined || matchedNormalizedPrefix.length < normalizedRoot.length); + if (state.traceEnabled) { + trace(state.host, Diagnostics.Checking_if_0_is_the_longest_matching_prefix_for_1_2, normalizedRoot, candidate, isLongestMatchingPrefix); + } + if (isLongestMatchingPrefix) { + matchedNormalizedPrefix = normalizedRoot; + matchedRootDir = rootDir; } } - - function tryLoadModuleUsingRootDirs(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, - state: ModuleResolutionState): Resolved | undefined { - - if (!state.compilerOptions.rootDirs) { - return undefined; + if (matchedNormalizedPrefix) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Longest_matching_prefix_for_0_is_1, candidate, matchedNormalizedPrefix); } - + const suffix = candidate.substr(matchedNormalizedPrefix.length); + // first - try to load from a initial location if (state.traceEnabled) { - trace(state.host, Diagnostics.rootDirs_option_is_set_using_it_to_resolve_relative_module_name_0, moduleName); + trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, matchedNormalizedPrefix, candidate); } - - const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); - - let matchedRootDir: string | undefined; - let matchedNormalizedPrefix: string | undefined; - for (const rootDir of state.compilerOptions.rootDirs) { - // rootDirs are expected to be absolute - // in case of tsconfig.json this will happen automatically - compiler will expand relative names - // using location of tsconfig.json as base location - let normalizedRoot = normalizePath(rootDir); - if (!endsWith(normalizedRoot, directorySeparator)) { - normalizedRoot += directorySeparator; - } - const isLongestMatchingPrefix = - startsWith(candidate, normalizedRoot) && - (matchedNormalizedPrefix === undefined || matchedNormalizedPrefix.length < normalizedRoot.length); - - if (state.traceEnabled) { - trace(state.host, Diagnostics.Checking_if_0_is_the_longest_matching_prefix_for_1_2, normalizedRoot, candidate, isLongestMatchingPrefix); - } - - if (isLongestMatchingPrefix) { - matchedNormalizedPrefix = normalizedRoot; - matchedRootDir = rootDir; - } + const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(containingDirectory, state.host), state); + if (resolvedFileName) { + return resolvedFileName; } - if (matchedNormalizedPrefix) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Longest_matching_prefix_for_0_is_1, candidate, matchedNormalizedPrefix); + if (state.traceEnabled) { + trace(state.host, Diagnostics.Trying_other_entries_in_rootDirs); + } + // then try to resolve using remaining entries in rootDirs + for (const rootDir of state.compilerOptions.rootDirs) { + if (rootDir === matchedRootDir) { + // skip the initially matched entry + continue; } - const suffix = candidate.substr(matchedNormalizedPrefix.length); - - // first - try to load from a initial location + const candidate = combinePaths(normalizePath(rootDir), suffix); if (state.traceEnabled) { - trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, matchedNormalizedPrefix, candidate); + trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, rootDir, candidate); } - const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(containingDirectory, state.host), state); + const baseDirectory = getDirectoryPath(candidate); + const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(baseDirectory, state.host), state); if (resolvedFileName) { return resolvedFileName; } - - if (state.traceEnabled) { - trace(state.host, Diagnostics.Trying_other_entries_in_rootDirs); - } - // then try to resolve using remaining entries in rootDirs - for (const rootDir of state.compilerOptions.rootDirs) { - if (rootDir === matchedRootDir) { - // skip the initially matched entry - continue; - } - const candidate = combinePaths(normalizePath(rootDir), suffix); - if (state.traceEnabled) { - trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, rootDir, candidate); - } - const baseDirectory = getDirectoryPath(candidate); - const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(baseDirectory, state.host), state); - if (resolvedFileName) { - return resolvedFileName; - } - } - if (state.traceEnabled) { - trace(state.host, Diagnostics.Module_resolution_using_rootDirs_has_failed); - } } - return undefined; - } - - function tryLoadModuleUsingBaseUrl(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState): Resolved | undefined { - const { baseUrl } = state.compilerOptions; - if (!baseUrl) { - return undefined; - } - if (state.traceEnabled) { - trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName); - } - const candidate = normalizePath(combinePaths(baseUrl, moduleName)); if (state.traceEnabled) { - trace(state.host, Diagnostics.Resolving_module_name_0_relative_to_base_url_1_2, moduleName, baseUrl, candidate); + trace(state.host, Diagnostics.Module_resolution_using_rootDirs_has_failed); } - return loader(extensions, candidate, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); } - - /** - * Expose resolution logic to allow us to use Node module resolution logic from arbitrary locations. - * No way to do this with `require()`: https://github.com/nodejs/node/issues/5963 - * Throws an error if the module can't be resolved. - */ - /* @internal */ - export function resolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost): string { - const { resolvedModule, failedLookupLocations } = tryResolveJSModuleWorker(moduleName, initialDir, host); - if (!resolvedModule) { - throw new Error(`Could not resolve JS module '${moduleName}' starting at '${initialDir}'. Looked in: ${failedLookupLocations.join(", ")}`); - } - return resolvedModule.resolvedFileName; - } - - /* @internal */ - export function tryResolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost): string | undefined { - const { resolvedModule } = tryResolveJSModuleWorker(moduleName, initialDir, host); - return resolvedModule && resolvedModule.resolvedFileName; - } - - const jsOnlyExtensions = [Extensions.JavaScript]; - const tsExtensions = [Extensions.TypeScript, Extensions.JavaScript]; - const tsPlusJsonExtensions = [...tsExtensions, Extensions.Json]; - const tsconfigExtensions = [Extensions.TSConfig]; - function tryResolveJSModuleWorker(moduleName: string, initialDir: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { - return nodeModuleNameResolverWorker(moduleName, initialDir, { moduleResolution: ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, jsOnlyExtensions, /*redirectedReferences*/ undefined); - } - - export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; - /* @internal */ export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations; // eslint-disable-line @typescript-eslint/unified-signatures - export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations { - return nodeModuleNameResolverWorker(moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, lookupConfig ? tsconfigExtensions : (compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions), redirectedReference); - } - - function nodeModuleNameResolverWorker(moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, extensions: Extensions[], redirectedReference: ResolvedProjectReference | undefined): ResolvedModuleWithFailedLookupLocations { - const traceEnabled = isTraceEnabled(compilerOptions, host); - - const failedLookupLocations: string[] = []; - const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations }; - - const result = forEach(extensions, ext => tryResolve(ext)); - if (result && result.value) { - const { resolved, isExternalLibraryImport } = result.value; - return createResolvedModuleWithFailedLookupLocations(resolved, isExternalLibraryImport, failedLookupLocations); - } - return { resolvedModule: undefined, failedLookupLocations }; - - function tryResolve(extensions: Extensions): SearchResult<{ resolved: Resolved, isExternalLibraryImport: boolean }> { - const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => nodeLoadModuleByRelativeName(extensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ true); - const resolved = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loader, state); - if (resolved) { - return toSearchResult({ resolved, isExternalLibraryImport: pathContainsNodeModules(resolved.path) }); - } - - if (!isExternalModuleNameRelative(moduleName)) { - if (traceEnabled) { - trace(host, Diagnostics.Loading_module_0_from_node_modules_folder_target_file_type_1, moduleName, Extensions[extensions]); - } - const resolved = loadModuleFromNearestNodeModulesDirectory(extensions, moduleName, containingDirectory, state, cache, redirectedReference); - if (!resolved) return undefined; - - let resolvedValue = resolved.value; - if (!compilerOptions.preserveSymlinks && resolvedValue && !resolvedValue.originalPath) { - const path = realPath(resolvedValue.path, host, traceEnabled); - const originalPath = path === resolvedValue.path ? undefined : resolvedValue.path; - resolvedValue = { ...resolvedValue, path, originalPath }; - } - // For node_modules lookups, get the real path so that multiple accesses to an `npm link`-ed module do not create duplicate files. - return { value: resolvedValue && { resolved: resolvedValue, isExternalLibraryImport: true } }; - } - else { - const { path: candidate, parts } = normalizePathAndParts(combinePaths(containingDirectory, moduleName)); - const resolved = nodeLoadModuleByRelativeName(extensions, candidate, /*onlyRecordFailures*/ false, state, /*considerPackageJson*/ true); - // Treat explicit "node_modules" import as an external library import. - return resolved && toSearchResult({ resolved, isExternalLibraryImport: contains(parts, "node_modules") }); - } - } + return undefined; +} +function tryLoadModuleUsingBaseUrl(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState): Resolved | undefined { + const { baseUrl } = state.compilerOptions; + if (!baseUrl) { + return undefined; } - - function realPath(path: string, host: ModuleResolutionHost, traceEnabled: boolean): string { - if (!host.realpath) { - return path; - } - - const real = normalizePath(host.realpath(path)); - if (traceEnabled) { - trace(host, Diagnostics.Resolving_real_path_for_0_result_1, path, real); - } - Debug.assert(host.fileExists(real), `${path} linked to nonexistent file ${real}`); - return real; + if (state.traceEnabled) { + trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName); } - - function nodeLoadModuleByRelativeName(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson: boolean): Resolved | undefined { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Loading_module_as_file_Slash_folder_candidate_module_location_0_target_file_type_1, candidate, Extensions[extensions]); - } - if (!hasTrailingDirectorySeparator(candidate)) { - if (!onlyRecordFailures) { - const parentOfCandidate = getDirectoryPath(candidate); - if (!directoryProbablyExists(parentOfCandidate, state.host)) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, parentOfCandidate); - } - onlyRecordFailures = true; - } + const candidate = normalizePath(combinePaths(baseUrl, moduleName)); + if (state.traceEnabled) { + trace(state.host, Diagnostics.Resolving_module_name_0_relative_to_base_url_1_2, moduleName, baseUrl, candidate); + } + return loader(extensions, candidate, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); +} +/** + * Expose resolution logic to allow us to use Node module resolution logic from arbitrary locations. + * No way to do this with `require()`: https://github.com/nodejs/node/issues/5963 + * Throws an error if the module can't be resolved. + */ +/* @internal */ +export function resolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost): string { + const { resolvedModule, failedLookupLocations } = tryResolveJSModuleWorker(moduleName, initialDir, host); + if (!resolvedModule) { + throw new Error(`Could not resolve JS module '${moduleName}' starting at '${initialDir}'. Looked in: ${failedLookupLocations.join(", ")}`); + } + return resolvedModule.resolvedFileName; +} +/* @internal */ +export function tryResolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost): string | undefined { + const { resolvedModule } = tryResolveJSModuleWorker(moduleName, initialDir, host); + return resolvedModule && resolvedModule.resolvedFileName; +} +const jsOnlyExtensions = [Extensions.JavaScript]; +const tsExtensions = [Extensions.TypeScript, Extensions.JavaScript]; +const tsPlusJsonExtensions = [...tsExtensions, Extensions.Json]; +const tsconfigExtensions = [Extensions.TSConfig]; +function tryResolveJSModuleWorker(moduleName: string, initialDir: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { + return nodeModuleNameResolverWorker(moduleName, initialDir, { moduleResolution: ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, jsOnlyExtensions, /*redirectedReferences*/ undefined); +} +export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; +/* @internal */ export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations; // eslint-disable-line @typescript-eslint/unified-signatures +export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations { + return nodeModuleNameResolverWorker(moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, lookupConfig ? tsconfigExtensions : (compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions), redirectedReference); +} +function nodeModuleNameResolverWorker(moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, extensions: Extensions[], redirectedReference: ResolvedProjectReference | undefined): ResolvedModuleWithFailedLookupLocations { + const traceEnabled = isTraceEnabled(compilerOptions, host); + const failedLookupLocations: string[] = []; + const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations }; + const result = forEach(extensions, ext => tryResolve(ext)); + if (result && result.value) { + const { resolved, isExternalLibraryImport } = result.value; + return createResolvedModuleWithFailedLookupLocations(resolved, isExternalLibraryImport, failedLookupLocations); + } + return { resolvedModule: undefined, failedLookupLocations }; + function tryResolve(extensions: Extensions): SearchResult<{ + resolved: Resolved; + isExternalLibraryImport: boolean; + }> { + const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => nodeLoadModuleByRelativeName(extensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ true); + const resolved = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loader, state); + if (resolved) { + return toSearchResult({ resolved, isExternalLibraryImport: pathContainsNodeModules(resolved.path) }); + } + if (!isExternalModuleNameRelative(moduleName)) { + if (traceEnabled) { + trace(host, Diagnostics.Loading_module_0_from_node_modules_folder_target_file_type_1, moduleName, Extensions[extensions]); } - const resolvedFromFile = loadModuleFromFile(extensions, candidate, onlyRecordFailures, state); - if (resolvedFromFile) { - const packageDirectory = considerPackageJson ? parseNodeModuleFromPath(resolvedFromFile) : undefined; - const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, state) : undefined; - return withPackageId(packageInfo, resolvedFromFile); + const resolved = loadModuleFromNearestNodeModulesDirectory(extensions, moduleName, containingDirectory, state, cache, redirectedReference); + if (!resolved) + return undefined; + let resolvedValue = resolved.value; + if (!compilerOptions.preserveSymlinks && resolvedValue && !resolvedValue.originalPath) { + const path = realPath(resolvedValue.path, host, traceEnabled); + const originalPath = path === resolvedValue.path ? undefined : resolvedValue.path; + resolvedValue = { ...resolvedValue, path, originalPath }; } + // For node_modules lookups, get the real path so that multiple accesses to an `npm link`-ed module do not create duplicate files. + return { value: resolvedValue && { resolved: resolvedValue, isExternalLibraryImport: true } }; + } + else { + const { path: candidate, parts } = normalizePathAndParts(combinePaths(containingDirectory, moduleName)); + const resolved = nodeLoadModuleByRelativeName(extensions, candidate, /*onlyRecordFailures*/ false, state, /*considerPackageJson*/ true); + // Treat explicit "node_modules" import as an external library import. + return resolved && toSearchResult({ resolved, isExternalLibraryImport: contains(parts, "node_modules") }); } + } +} +function realPath(path: string, host: ModuleResolutionHost, traceEnabled: boolean): string { + if (!host.realpath) { + return path; + } + const real = normalizePath(host.realpath(path)); + if (traceEnabled) { + trace(host, Diagnostics.Resolving_real_path_for_0_result_1, path, real); + } + Debug.assert(host.fileExists(real), `${path} linked to nonexistent file ${real}`); + return real; +} +function nodeLoadModuleByRelativeName(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson: boolean): Resolved | undefined { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Loading_module_as_file_Slash_folder_candidate_module_location_0_target_file_type_1, candidate, Extensions[extensions]); + } + if (!hasTrailingDirectorySeparator(candidate)) { if (!onlyRecordFailures) { - const candidateExists = directoryProbablyExists(candidate, state.host); - if (!candidateExists) { + const parentOfCandidate = getDirectoryPath(candidate); + if (!directoryProbablyExists(parentOfCandidate, state.host)) { if (state.traceEnabled) { - trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, candidate); + trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, parentOfCandidate); } onlyRecordFailures = true; } } - return loadNodeModuleFromDirectory(extensions, candidate, onlyRecordFailures, state, considerPackageJson); - } - - /*@internal*/ - export const nodeModulesPathPart = "/node_modules/"; - /*@internal*/ - export function pathContainsNodeModules(path: string): boolean { - return stringContains(path, nodeModulesPathPart); + const resolvedFromFile = loadModuleFromFile(extensions, candidate, onlyRecordFailures, state); + if (resolvedFromFile) { + const packageDirectory = considerPackageJson ? parseNodeModuleFromPath(resolvedFromFile) : undefined; + const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, state) : undefined; + return withPackageId(packageInfo, resolvedFromFile); + } } - - /** - * This will be called on the successfully resolved path from `loadModuleFromFile`. - * (Not neeeded for `loadModuleFromNodeModules` as that looks up the `package.json` as part of resolution.) - * - * packageDirectory is the directory of the package itself. - * For `blah/node_modules/foo/index.d.ts` this is packageDirectory: "foo" - * For `/node_modules/foo/bar.d.ts` this is packageDirectory: "foo" - * For `/node_modules/@types/foo/bar/index.d.ts` this is packageDirectory: "@types/foo" - * For `/node_modules/foo/bar/index.d.ts` this is packageDirectory: "foo" - */ - function parseNodeModuleFromPath(resolved: PathAndExtension): string | undefined { - const path = normalizePath(resolved.path); - const idx = path.lastIndexOf(nodeModulesPathPart); - if (idx === -1) { - return undefined; - } - - const indexAfterNodeModules = idx + nodeModulesPathPart.length; - let indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterNodeModules); - if (path.charCodeAt(indexAfterNodeModules) === CharacterCodes.at) { - indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterPackageName); - } - return path.slice(0, indexAfterPackageName); - } - - function moveToNextDirectorySeparatorIfAvailable(path: string, prevSeparatorIndex: number): number { - const nextSeparatorIndex = path.indexOf(directorySeparator, prevSeparatorIndex + 1); - return nextSeparatorIndex === -1 ? prevSeparatorIndex : nextSeparatorIndex; - } - - function loadModuleFromFileNoPackageId(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved | undefined { - return noPackageId(loadModuleFromFile(extensions, candidate, onlyRecordFailures, state)); - } - - /** - * @param {boolean} onlyRecordFailures - if true then function won't try to actually load files but instead record all attempts as failures. This flag is necessary - * in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations. - */ - function loadModuleFromFile(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { - if (extensions === Extensions.Json || extensions === Extensions.TSConfig) { - const extensionLess = tryRemoveExtension(candidate, Extension.Json); - return (extensionLess === undefined && extensions === Extensions.Json) ? undefined : tryAddingExtensions(extensionLess || candidate, extensions, onlyRecordFailures, state); - } - - // First, try adding an extension. An import of "foo" could be matched by a file "foo.ts", or "foo.js" by "foo.js.ts" - const resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, onlyRecordFailures, state); - if (resolvedByAddingExtension) { - return resolvedByAddingExtension; - } - - // If that didn't work, try stripping a ".js" or ".jsx" extension and replacing it with a TypeScript one; - // e.g. "./foo.js" can be matched by "./foo.ts" or "./foo.d.ts" - if (hasJSFileExtension(candidate)) { - const extensionless = removeFileExtension(candidate); + if (!onlyRecordFailures) { + const candidateExists = directoryProbablyExists(candidate, state.host); + if (!candidateExists) { if (state.traceEnabled) { - const extension = candidate.substring(extensionless.length); - trace(state.host, Diagnostics.File_name_0_has_a_1_extension_stripping_it, candidate, extension); + trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, candidate); } - return tryAddingExtensions(extensionless, extensions, onlyRecordFailures, state); + onlyRecordFailures = true; } } - - /** Try to return an existing file that adds one of the `extensions` to `candidate`. */ - function tryAddingExtensions(candidate: string, extensions: Extensions, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { - if (!onlyRecordFailures) { - // check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing - const directory = getDirectoryPath(candidate); - if (directory) { - onlyRecordFailures = !directoryProbablyExists(directory, state.host); - } - } - - switch (extensions) { - case Extensions.DtsOnly: - return tryExtension(Extension.Dts); - case Extensions.TypeScript: - return tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || tryExtension(Extension.Dts); - case Extensions.JavaScript: - return tryExtension(Extension.Js) || tryExtension(Extension.Jsx); - case Extensions.TSConfig: - case Extensions.Json: - return tryExtension(Extension.Json); - } - - function tryExtension(ext: Extension): PathAndExtension | undefined { - const path = tryFile(candidate + ext, onlyRecordFailures, state); - return path === undefined ? undefined : { path, ext }; - } + return loadNodeModuleFromDirectory(extensions, candidate, onlyRecordFailures, state, considerPackageJson); +} +/*@internal*/ +export const nodeModulesPathPart = "/node_modules/"; +/*@internal*/ +export function pathContainsNodeModules(path: string): boolean { + return stringContains(path, nodeModulesPathPart); +} +/** + * This will be called on the successfully resolved path from `loadModuleFromFile`. + * (Not neeeded for `loadModuleFromNodeModules` as that looks up the `package.json` as part of resolution.) + * + * packageDirectory is the directory of the package itself. + * For `blah/node_modules/foo/index.d.ts` this is packageDirectory: "foo" + * For `/node_modules/foo/bar.d.ts` this is packageDirectory: "foo" + * For `/node_modules/@types/foo/bar/index.d.ts` this is packageDirectory: "@types/foo" + * For `/node_modules/foo/bar/index.d.ts` this is packageDirectory: "foo" + */ +function parseNodeModuleFromPath(resolved: PathAndExtension): string | undefined { + const path = normalizePath(resolved.path); + const idx = path.lastIndexOf(nodeModulesPathPart); + if (idx === -1) { + return undefined; } - - /** Return the file if it exists. */ - function tryFile(fileName: string, onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined { - if (!onlyRecordFailures) { - if (state.host.fileExists(fileName)) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.File_0_exist_use_it_as_a_name_resolution_result, fileName); - } - return fileName; - } - else { - if (state.traceEnabled) { - trace(state.host, Diagnostics.File_0_does_not_exist, fileName); - } - } + const indexAfterNodeModules = idx + nodeModulesPathPart.length; + let indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterNodeModules); + if (path.charCodeAt(indexAfterNodeModules) === CharacterCodes.at) { + indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterPackageName); + } + return path.slice(0, indexAfterPackageName); +} +function moveToNextDirectorySeparatorIfAvailable(path: string, prevSeparatorIndex: number): number { + const nextSeparatorIndex = path.indexOf(directorySeparator, prevSeparatorIndex + 1); + return nextSeparatorIndex === -1 ? prevSeparatorIndex : nextSeparatorIndex; +} +function loadModuleFromFileNoPackageId(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved | undefined { + return noPackageId(loadModuleFromFile(extensions, candidate, onlyRecordFailures, state)); +} +/** + * @param {boolean} onlyRecordFailures - if true then function won't try to actually load files but instead record all attempts as failures. This flag is necessary + * in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations. + */ +function loadModuleFromFile(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { + if (extensions === Extensions.Json || extensions === Extensions.TSConfig) { + const extensionLess = tryRemoveExtension(candidate, Extension.Json); + return (extensionLess === undefined && extensions === Extensions.Json) ? undefined : tryAddingExtensions(extensionLess || candidate, extensions, onlyRecordFailures, state); + } + // First, try adding an extension. An import of "foo" could be matched by a file "foo.ts", or "foo.js" by "foo.js.ts" + const resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, onlyRecordFailures, state); + if (resolvedByAddingExtension) { + return resolvedByAddingExtension; + } + // If that didn't work, try stripping a ".js" or ".jsx" extension and replacing it with a TypeScript one; + // e.g. "./foo.js" can be matched by "./foo.ts" or "./foo.d.ts" + if (hasJSFileExtension(candidate)) { + const extensionless = removeFileExtension(candidate); + if (state.traceEnabled) { + const extension = candidate.substring(extensionless.length); + trace(state.host, Diagnostics.File_name_0_has_a_1_extension_stripping_it, candidate, extension); } - state.failedLookupLocations.push(fileName); - return undefined; + return tryAddingExtensions(extensionless, extensions, onlyRecordFailures, state); } - - function loadNodeModuleFromDirectory(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson = true) { - const packageInfo = considerPackageJson ? getPackageJsonInfo(candidate, onlyRecordFailures, state) : undefined; - const packageJsonContent = packageInfo && packageInfo.packageJsonContent; - const versionPaths = packageInfo && packageInfo.versionPaths; - return withPackageId(packageInfo, loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths)); - } - - interface PackageJsonInfo { - packageDirectory: string; - packageJsonContent: PackageJsonPathFields; - versionPaths: VersionPaths | undefined; - } - - function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined { - const { host, traceEnabled } = state; - const directoryExists = !onlyRecordFailures && directoryProbablyExists(packageDirectory, host); - const packageJsonPath = combinePaths(packageDirectory, "package.json"); - if (directoryExists && host.fileExists(packageJsonPath)) { - const packageJsonContent = readJson(packageJsonPath, host) as PackageJson; - if (traceEnabled) { - trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath); +} +/** Try to return an existing file that adds one of the `extensions` to `candidate`. */ +function tryAddingExtensions(candidate: string, extensions: Extensions, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { + if (!onlyRecordFailures) { + // check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing + const directory = getDirectoryPath(candidate); + if (directory) { + onlyRecordFailures = !directoryProbablyExists(directory, state.host); + } + } + switch (extensions) { + case Extensions.DtsOnly: + return tryExtension(Extension.Dts); + case Extensions.TypeScript: + return tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || tryExtension(Extension.Dts); + case Extensions.JavaScript: + return tryExtension(Extension.Js) || tryExtension(Extension.Jsx); + case Extensions.TSConfig: + case Extensions.Json: + return tryExtension(Extension.Json); + } + function tryExtension(ext: Extension): PathAndExtension | undefined { + const path = tryFile(candidate + ext, onlyRecordFailures, state); + return path === undefined ? undefined : { path, ext }; + } +} +/** Return the file if it exists. */ +function tryFile(fileName: string, onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined { + if (!onlyRecordFailures) { + if (state.host.fileExists(fileName)) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.File_0_exist_use_it_as_a_name_resolution_result, fileName); } - const versionPaths = readPackageJsonTypesVersionPaths(packageJsonContent, state); - return { packageDirectory, packageJsonContent, versionPaths }; + return fileName; } else { - if (directoryExists && traceEnabled) { - trace(host, Diagnostics.File_0_does_not_exist, packageJsonPath); - } - - // record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results - state.failedLookupLocations.push(packageJsonPath); - } - } - - function loadNodeModuleFromDirectoryWorker(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, jsonContent: PackageJsonPathFields | undefined, versionPaths: VersionPaths | undefined): PathAndExtension | undefined { - let packageFile: string | undefined; - if (jsonContent) { - switch (extensions) { - case Extensions.JavaScript: - case Extensions.Json: - packageFile = readPackageJsonMainField(jsonContent, candidate, state); - break; - case Extensions.TypeScript: - // When resolving typescript modules, try resolving using main field as well - packageFile = readPackageJsonTypesFields(jsonContent, candidate, state) || readPackageJsonMainField(jsonContent, candidate, state); - break; - case Extensions.DtsOnly: - packageFile = readPackageJsonTypesFields(jsonContent, candidate, state); - break; - case Extensions.TSConfig: - packageFile = readPackageJsonTSConfigField(jsonContent, candidate, state); - break; - default: - return Debug.assertNever(extensions); - } - } - - const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { - const fromFile = tryFile(candidate, onlyRecordFailures, state); - if (fromFile) { - const resolved = resolvedIfExtensionMatches(extensions, fromFile); - if (resolved) { - return noPackageId(resolved); - } - if (state.traceEnabled) { - trace(state.host, Diagnostics.File_0_has_an_unsupported_extension_so_skipping_it, fromFile); - } - } - - // Even if extensions is DtsOnly, we can still look up a .ts file as a result of package.json "types" - const nextExtensions = extensions === Extensions.DtsOnly ? Extensions.TypeScript : extensions; - // Don't do package.json lookup recursively, because Node.js' package lookup doesn't. - return nodeLoadModuleByRelativeName(nextExtensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ false); - }; - - const onlyRecordFailuresForPackageFile = packageFile ? !directoryProbablyExists(getDirectoryPath(packageFile), state.host) : undefined; - const onlyRecordFailuresForIndex = onlyRecordFailures || !directoryProbablyExists(candidate, state.host); - const indexPath = combinePaths(candidate, extensions === Extensions.TSConfig ? "tsconfig" : "index"); - - if (versionPaths && (!packageFile || containsPath(candidate, packageFile))) { - const moduleName = getRelativePathFromDirectory(candidate, packageFile || indexPath, /*ignoreCase*/ false); if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2, versionPaths.version, version, moduleName); - } - const result = tryLoadModuleUsingPaths(extensions, moduleName, candidate, versionPaths.paths, loader, onlyRecordFailuresForPackageFile || onlyRecordFailuresForIndex, state); - if (result) { - return removeIgnoredPackageId(result.value); + trace(state.host, Diagnostics.File_0_does_not_exist, fileName); } } - - // It won't have a `packageId` set, because we disabled `considerPackageJson`. - const packageFileResult = packageFile && removeIgnoredPackageId(loader(extensions, packageFile, onlyRecordFailuresForPackageFile!, state)); - if (packageFileResult) return packageFileResult; - - return loadModuleFromFile(extensions, indexPath, onlyRecordFailuresForIndex, state); - } - - /** Resolve from an arbitrarily specified file. Return `undefined` if it has an unsupported extension. */ - function resolvedIfExtensionMatches(extensions: Extensions, path: string): PathAndExtension | undefined { - const ext = tryGetExtensionFromPath(path); - return ext !== undefined && extensionIsOk(extensions, ext) ? { path, ext } : undefined; - } - - /** True if `extension` is one of the supported `extensions`. */ - function extensionIsOk(extensions: Extensions, extension: Extension): boolean { + } + state.failedLookupLocations.push(fileName); + return undefined; +} +function loadNodeModuleFromDirectory(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson = true) { + const packageInfo = considerPackageJson ? getPackageJsonInfo(candidate, onlyRecordFailures, state) : undefined; + const packageJsonContent = packageInfo && packageInfo.packageJsonContent; + const versionPaths = packageInfo && packageInfo.versionPaths; + return withPackageId(packageInfo, loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths)); +} +interface PackageJsonInfo { + packageDirectory: string; + packageJsonContent: PackageJsonPathFields; + versionPaths: VersionPaths | undefined; +} +function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined { + const { host, traceEnabled } = state; + const directoryExists = !onlyRecordFailures && directoryProbablyExists(packageDirectory, host); + const packageJsonPath = combinePaths(packageDirectory, "package.json"); + if (directoryExists && host.fileExists(packageJsonPath)) { + const packageJsonContent = (readJson(packageJsonPath, host) as PackageJson); + if (traceEnabled) { + trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath); + } + const versionPaths = readPackageJsonTypesVersionPaths(packageJsonContent, state); + return { packageDirectory, packageJsonContent, versionPaths }; + } + else { + if (directoryExists && traceEnabled) { + trace(host, Diagnostics.File_0_does_not_exist, packageJsonPath); + } + // record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results + state.failedLookupLocations.push(packageJsonPath); + } +} +function loadNodeModuleFromDirectoryWorker(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, jsonContent: PackageJsonPathFields | undefined, versionPaths: VersionPaths | undefined): PathAndExtension | undefined { + let packageFile: string | undefined; + if (jsonContent) { switch (extensions) { case Extensions.JavaScript: - return extension === Extension.Js || extension === Extension.Jsx; - case Extensions.TSConfig: case Extensions.Json: - return extension === Extension.Json; + packageFile = readPackageJsonMainField(jsonContent, candidate, state); + break; case Extensions.TypeScript: - return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Dts; + // When resolving typescript modules, try resolving using main field as well + packageFile = readPackageJsonTypesFields(jsonContent, candidate, state) || readPackageJsonMainField(jsonContent, candidate, state); + break; case Extensions.DtsOnly: - return extension === Extension.Dts; + packageFile = readPackageJsonTypesFields(jsonContent, candidate, state); + break; + case Extensions.TSConfig: + packageFile = readPackageJsonTSConfigField(jsonContent, candidate, state); + break; + default: + return Debug.assertNever(extensions); } } - - /* @internal */ - export function parsePackageName(moduleName: string): { packageName: string, rest: string } { - let idx = moduleName.indexOf(directorySeparator); - if (moduleName[0] === "@") { - idx = moduleName.indexOf(directorySeparator, idx + 1); + const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { + const fromFile = tryFile(candidate, onlyRecordFailures, state); + if (fromFile) { + const resolved = resolvedIfExtensionMatches(extensions, fromFile); + if (resolved) { + return noPackageId(resolved); + } + if (state.traceEnabled) { + trace(state.host, Diagnostics.File_0_has_an_unsupported_extension_so_skipping_it, fromFile); + } + } + // Even if extensions is DtsOnly, we can still look up a .ts file as a result of package.json "types" + const nextExtensions = extensions === Extensions.DtsOnly ? Extensions.TypeScript : extensions; + // Don't do package.json lookup recursively, because Node.js' package lookup doesn't. + return nodeLoadModuleByRelativeName(nextExtensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ false); + }; + const onlyRecordFailuresForPackageFile = packageFile ? !directoryProbablyExists(getDirectoryPath(packageFile), state.host) : undefined; + const onlyRecordFailuresForIndex = onlyRecordFailures || !directoryProbablyExists(candidate, state.host); + const indexPath = combinePaths(candidate, extensions === Extensions.TSConfig ? "tsconfig" : "index"); + if (versionPaths && (!packageFile || containsPath(candidate, packageFile))) { + const moduleName = getRelativePathFromDirectory(candidate, packageFile || indexPath, /*ignoreCase*/ false); + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2, versionPaths.version, version, moduleName); + } + const result = tryLoadModuleUsingPaths(extensions, moduleName, candidate, versionPaths.paths, loader, onlyRecordFailuresForPackageFile || onlyRecordFailuresForIndex, state); + if (result) { + return removeIgnoredPackageId(result.value); } - return idx === -1 ? { packageName: moduleName, rest: "" } : { packageName: moduleName.slice(0, idx), rest: moduleName.slice(idx + 1) }; - } - - function loadModuleFromNearestNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: NonRelativeModuleNameResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { - return loadModuleFromNearestNodeModulesDirectoryWorker(extensions, moduleName, directory, state, /*typesScopeOnly*/ false, cache, redirectedReference); } - - function loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName: string, directory: string, state: ModuleResolutionState): SearchResult { - // Extensions parameter here doesn't actually matter, because typesOnly ensures we're just doing @types lookup, which is always DtsOnly. - return loadModuleFromNearestNodeModulesDirectoryWorker(Extensions.DtsOnly, moduleName, directory, state, /*typesScopeOnly*/ true, /*cache*/ undefined, /*redirectedReference*/ undefined); + // It won't have a `packageId` set, because we disabled `considerPackageJson`. + const packageFileResult = packageFile && removeIgnoredPackageId(loader(extensions, packageFile, onlyRecordFailuresForPackageFile!, state)); + if (packageFileResult) + return packageFileResult; + return loadModuleFromFile(extensions, indexPath, onlyRecordFailuresForIndex, state); +} +/** Resolve from an arbitrarily specified file. Return `undefined` if it has an unsupported extension. */ +function resolvedIfExtensionMatches(extensions: Extensions, path: string): PathAndExtension | undefined { + const ext = tryGetExtensionFromPath(path); + return ext !== undefined && extensionIsOk(extensions, ext) ? { path, ext } : undefined; +} +/** True if `extension` is one of the supported `extensions`. */ +function extensionIsOk(extensions: Extensions, extension: Extension): boolean { + switch (extensions) { + case Extensions.JavaScript: + return extension === Extension.Js || extension === Extension.Jsx; + case Extensions.TSConfig: + case Extensions.Json: + return extension === Extension.Json; + case Extensions.TypeScript: + return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Dts; + case Extensions.DtsOnly: + return extension === Extension.Dts; } - - function loadModuleFromNearestNodeModulesDirectoryWorker(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: NonRelativeModuleNameResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { - const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, redirectedReference); - return forEachAncestorDirectory(normalizeSlashes(directory), ancestorDirectory => { - if (getBaseFileName(ancestorDirectory) !== "node_modules") { - const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, ancestorDirectory, state); - if (resolutionFromCache) { - return resolutionFromCache; - } - return toSearchResult(loadModuleFromImmediateNodeModulesDirectory(extensions, moduleName, ancestorDirectory, state, typesScopeOnly)); +} +/* @internal */ +export function parsePackageName(moduleName: string): { + packageName: string; + rest: string; +} { + let idx = moduleName.indexOf(directorySeparator); + if (moduleName[0] === "@") { + idx = moduleName.indexOf(directorySeparator, idx + 1); + } + return idx === -1 ? { packageName: moduleName, rest: "" } : { packageName: moduleName.slice(0, idx), rest: moduleName.slice(idx + 1) }; +} +function loadModuleFromNearestNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: NonRelativeModuleNameResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { + return loadModuleFromNearestNodeModulesDirectoryWorker(extensions, moduleName, directory, state, /*typesScopeOnly*/ false, cache, redirectedReference); +} +function loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName: string, directory: string, state: ModuleResolutionState): SearchResult { + // Extensions parameter here doesn't actually matter, because typesOnly ensures we're just doing @types lookup, which is always DtsOnly. + return loadModuleFromNearestNodeModulesDirectoryWorker(Extensions.DtsOnly, moduleName, directory, state, /*typesScopeOnly*/ true, /*cache*/ undefined, /*redirectedReference*/ undefined); +} +function loadModuleFromNearestNodeModulesDirectoryWorker(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: NonRelativeModuleNameResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { + const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, redirectedReference); + return forEachAncestorDirectory(normalizeSlashes(directory), ancestorDirectory => { + if (getBaseFileName(ancestorDirectory) !== "node_modules") { + const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, ancestorDirectory, state); + if (resolutionFromCache) { + return resolutionFromCache; } - }); + return toSearchResult(loadModuleFromImmediateNodeModulesDirectory(extensions, moduleName, ancestorDirectory, state, typesScopeOnly)); + } + }); +} +function loadModuleFromImmediateNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean): Resolved | undefined { + const nodeModulesFolder = combinePaths(directory, "node_modules"); + const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host); + if (!nodeModulesFolderExists && state.traceEnabled) { + trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, nodeModulesFolder); + } + const packageResult = typesScopeOnly ? undefined : loadModuleFromSpecificNodeModulesDirectory(extensions, moduleName, nodeModulesFolder, nodeModulesFolderExists, state); + if (packageResult) { + return packageResult; + } + if (extensions === Extensions.TypeScript || extensions === Extensions.DtsOnly) { + const nodeModulesAtTypes = combinePaths(nodeModulesFolder, "@types"); + let nodeModulesAtTypesExists = nodeModulesFolderExists; + if (nodeModulesFolderExists && !directoryProbablyExists(nodeModulesAtTypes, state.host)) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, nodeModulesAtTypes); + } + nodeModulesAtTypesExists = false; + } + return loadModuleFromSpecificNodeModulesDirectory(Extensions.DtsOnly, mangleScopedPackageNameWithTrace(moduleName, state), nodeModulesAtTypes, nodeModulesAtTypesExists, state); } - - function loadModuleFromImmediateNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean): Resolved | undefined { - const nodeModulesFolder = combinePaths(directory, "node_modules"); - const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host); - if (!nodeModulesFolderExists && state.traceEnabled) { - trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, nodeModulesFolder); - } - - const packageResult = typesScopeOnly ? undefined : loadModuleFromSpecificNodeModulesDirectory(extensions, moduleName, nodeModulesFolder, nodeModulesFolderExists, state); - if (packageResult) { - return packageResult; - } - if (extensions === Extensions.TypeScript || extensions === Extensions.DtsOnly) { - const nodeModulesAtTypes = combinePaths(nodeModulesFolder, "@types"); - let nodeModulesAtTypesExists = nodeModulesFolderExists; - if (nodeModulesFolderExists && !directoryProbablyExists(nodeModulesAtTypes, state.host)) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, nodeModulesAtTypes); - } - nodeModulesAtTypesExists = false; +} +function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, moduleName: string, nodeModulesDirectory: string, nodeModulesDirectoryExists: boolean, state: ModuleResolutionState): Resolved | undefined { + const candidate = normalizePath(combinePaths(nodeModulesDirectory, moduleName)); + // First look for a nested package.json, as in `node_modules/foo/bar/package.json`. + let packageInfo = getPackageJsonInfo(candidate, !nodeModulesDirectoryExists, state); + if (packageInfo) { + const fromFile = loadModuleFromFile(extensions, candidate, !nodeModulesDirectoryExists, state); + if (fromFile) { + return noPackageId(fromFile); + } + const fromDirectory = loadNodeModuleFromDirectoryWorker(extensions, candidate, !nodeModulesDirectoryExists, state, packageInfo.packageJsonContent, packageInfo.versionPaths); + return withPackageId(packageInfo, fromDirectory); + } + const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { + const pathAndExtension = loadModuleFromFile(extensions, candidate, onlyRecordFailures, state) || + loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageInfo && packageInfo.packageJsonContent, packageInfo && packageInfo.versionPaths); + return withPackageId(packageInfo, pathAndExtension); + }; + const { packageName, rest } = parsePackageName(moduleName); + if (rest !== "") { // If "rest" is empty, we just did this search above. + const packageDirectory = combinePaths(nodeModulesDirectory, packageName); + // Don't use a "types" or "main" from here because we're not loading the root, but a subdirectory -- just here for the packageId and path mappings. + packageInfo = getPackageJsonInfo(packageDirectory, !nodeModulesDirectoryExists, state); + if (packageInfo && packageInfo.versionPaths) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2, packageInfo.versionPaths.version, version, rest); } - return loadModuleFromSpecificNodeModulesDirectory(Extensions.DtsOnly, mangleScopedPackageNameWithTrace(moduleName, state), nodeModulesAtTypes, nodeModulesAtTypesExists, state); - } - } - - function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, moduleName: string, nodeModulesDirectory: string, nodeModulesDirectoryExists: boolean, state: ModuleResolutionState): Resolved | undefined { - const candidate = normalizePath(combinePaths(nodeModulesDirectory, moduleName)); - - // First look for a nested package.json, as in `node_modules/foo/bar/package.json`. - let packageInfo = getPackageJsonInfo(candidate, !nodeModulesDirectoryExists, state); - if (packageInfo) { - const fromFile = loadModuleFromFile(extensions, candidate, !nodeModulesDirectoryExists, state); - if (fromFile) { - return noPackageId(fromFile); - } - - const fromDirectory = loadNodeModuleFromDirectoryWorker( - extensions, - candidate, - !nodeModulesDirectoryExists, - state, - packageInfo.packageJsonContent, - packageInfo.versionPaths - ); - return withPackageId(packageInfo, fromDirectory); - } - - const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { - const pathAndExtension = - loadModuleFromFile(extensions, candidate, onlyRecordFailures, state) || - loadNodeModuleFromDirectoryWorker( - extensions, - candidate, - onlyRecordFailures, - state, - packageInfo && packageInfo.packageJsonContent, - packageInfo && packageInfo.versionPaths - ); - return withPackageId(packageInfo, pathAndExtension); - }; - - const { packageName, rest } = parsePackageName(moduleName); - if (rest !== "") { // If "rest" is empty, we just did this search above. - const packageDirectory = combinePaths(nodeModulesDirectory, packageName); - - // Don't use a "types" or "main" from here because we're not loading the root, but a subdirectory -- just here for the packageId and path mappings. - packageInfo = getPackageJsonInfo(packageDirectory, !nodeModulesDirectoryExists, state); - if (packageInfo && packageInfo.versionPaths) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2, packageInfo.versionPaths.version, version, rest); - } - const packageDirectoryExists = nodeModulesDirectoryExists && directoryProbablyExists(packageDirectory, state.host); - const fromPaths = tryLoadModuleUsingPaths(extensions, rest, packageDirectory, packageInfo.versionPaths.paths, loader, !packageDirectoryExists, state); - if (fromPaths) { - return fromPaths.value; - } + const packageDirectoryExists = nodeModulesDirectoryExists && directoryProbablyExists(packageDirectory, state.host); + const fromPaths = tryLoadModuleUsingPaths(extensions, rest, packageDirectory, packageInfo.versionPaths.paths, loader, !packageDirectoryExists, state); + if (fromPaths) { + return fromPaths.value; } } - - return loader(extensions, candidate, !nodeModulesDirectoryExists, state); } - - function tryLoadModuleUsingPaths(extensions: Extensions, moduleName: string, baseDirectory: string, paths: MapLike, loader: ResolutionKindSpecificLoader, onlyRecordFailures: boolean, state: ModuleResolutionState): SearchResult { - const matchedPattern = matchPatternOrExact(getOwnKeys(paths), moduleName); - if (matchedPattern) { - const matchedStar = isString(matchedPattern) ? undefined : matchedText(matchedPattern, moduleName); - const matchedPatternText = isString(matchedPattern) ? matchedPattern : patternText(matchedPattern); + return loader(extensions, candidate, !nodeModulesDirectoryExists, state); +} +function tryLoadModuleUsingPaths(extensions: Extensions, moduleName: string, baseDirectory: string, paths: MapLike, loader: ResolutionKindSpecificLoader, onlyRecordFailures: boolean, state: ModuleResolutionState): SearchResult { + const matchedPattern = matchPatternOrExact(getOwnKeys(paths), moduleName); + if (matchedPattern) { + const matchedStar = isString(matchedPattern) ? undefined : matchedText(matchedPattern, moduleName); + const matchedPatternText = isString(matchedPattern) ? matchedPattern : patternText(matchedPattern); + if (state.traceEnabled) { + trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText); + } + const resolved = forEach(paths[matchedPatternText], subst => { + const path = matchedStar ? subst.replace("*", matchedStar) : subst; + const candidate = normalizePath(combinePaths(baseDirectory, path)); if (state.traceEnabled) { - trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText); - } - const resolved = forEach(paths[matchedPatternText], subst => { - const path = matchedStar ? subst.replace("*", matchedStar) : subst; - const candidate = normalizePath(combinePaths(baseDirectory, path)); - if (state.traceEnabled) { - trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path); - } - // A path mapping may have an extension, in contrast to an import, which should omit it. - const extension = tryGetExtensionFromPath(candidate); - if (extension !== undefined) { - const path = tryFile(candidate, onlyRecordFailures, state); - if (path !== undefined) { - return noPackageId({ path, ext: extension }); - } + trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path); + } + // A path mapping may have an extension, in contrast to an import, which should omit it. + const extension = tryGetExtensionFromPath(candidate); + if (extension !== undefined) { + const path = tryFile(candidate, onlyRecordFailures, state); + if (path !== undefined) { + return noPackageId({ path, ext: extension }); } - return loader(extensions, candidate, onlyRecordFailures || !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); - }); - return { value: resolved }; - } - } - - /** Double underscores are used in DefinitelyTyped to delimit scoped packages. */ - const mangledScopedPackageSeparator = "__"; - - /** For a scoped package, we must look in `@types/foo__bar` instead of `@types/@foo/bar`. */ - function mangleScopedPackageNameWithTrace(packageName: string, state: ModuleResolutionState): string { - const mangled = mangleScopedPackageName(packageName); - if (state.traceEnabled && mangled !== packageName) { - trace(state.host, Diagnostics.Scoped_package_detected_looking_in_0, mangled); - } - return mangled; - } - - /* @internal */ - export function getTypesPackageName(packageName: string): string { - return `@types/${mangleScopedPackageName(packageName)}`; - } - - /* @internal */ - export function mangleScopedPackageName(packageName: string): string { - if (startsWith(packageName, "@")) { - const replaceSlash = packageName.replace(directorySeparator, mangledScopedPackageSeparator); - if (replaceSlash !== packageName) { - return replaceSlash.slice(1); // Take off the "@" } - } - return packageName; + return loader(extensions, candidate, onlyRecordFailures || !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); + }); + return { value: resolved }; } - - /* @internal */ - export function getPackageNameFromTypesPackageName(mangledName: string): string { - const withoutAtTypePrefix = removePrefix(mangledName, "@types/"); - if (withoutAtTypePrefix !== mangledName) { - return unmangleScopedPackageName(withoutAtTypePrefix); +} +/** Double underscores are used in DefinitelyTyped to delimit scoped packages. */ +const mangledScopedPackageSeparator = "__"; +/** For a scoped package, we must look in `@types/foo__bar` instead of `@types/@foo/bar`. */ +function mangleScopedPackageNameWithTrace(packageName: string, state: ModuleResolutionState): string { + const mangled = mangleScopedPackageName(packageName); + if (state.traceEnabled && mangled !== packageName) { + trace(state.host, Diagnostics.Scoped_package_detected_looking_in_0, mangled); + } + return mangled; +} +/* @internal */ +export function getTypesPackageName(packageName: string): string { + return `@types/${mangleScopedPackageName(packageName)}`; +} +/* @internal */ +export function mangleScopedPackageName(packageName: string): string { + if (startsWith(packageName, "@")) { + const replaceSlash = packageName.replace(directorySeparator, mangledScopedPackageSeparator); + if (replaceSlash !== packageName) { + return replaceSlash.slice(1); // Take off the "@" } - return mangledName; } - - /* @internal */ - export function unmangleScopedPackageName(typesPackageName: string): string { - return stringContains(typesPackageName, mangledScopedPackageSeparator) ? - "@" + typesPackageName.replace(mangledScopedPackageSeparator, directorySeparator) : - typesPackageName; + return packageName; +} +/* @internal */ +export function getPackageNameFromTypesPackageName(mangledName: string): string { + const withoutAtTypePrefix = removePrefix(mangledName, "@types/"); + if (withoutAtTypePrefix !== mangledName) { + return unmangleScopedPackageName(withoutAtTypePrefix); } - - function tryFindNonRelativeModuleNameInCache(cache: PerModuleNameCache | undefined, moduleName: string, containingDirectory: string, state: ModuleResolutionState): SearchResult { - const result = cache && cache.get(containingDirectory); - if (result) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory); - } - state.failedLookupLocations.push(...result.failedLookupLocations); - return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, originalPath: result.resolvedModule.originalPath || true, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId } }; - } - } - - export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations { - const traceEnabled = isTraceEnabled(compilerOptions, host); - const failedLookupLocations: string[] = []; - const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations }; - const containingDirectory = getDirectoryPath(containingFile); - - const resolved = tryResolve(Extensions.TypeScript) || tryResolve(Extensions.JavaScript); - // No originalPath because classic resolution doesn't resolve realPath - return createResolvedModuleWithFailedLookupLocations(resolved && resolved.value, /*isExternalLibraryImport*/ false, failedLookupLocations); - - function tryResolve(extensions: Extensions): SearchResult { - const resolvedUsingSettings = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loadModuleFromFileNoPackageId, state); - if (resolvedUsingSettings) { - return { value: resolvedUsingSettings }; - } - - if (!isExternalModuleNameRelative(moduleName)) { - const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, redirectedReference); - // Climb up parent directories looking for a module. - const resolved = forEachAncestorDirectory(containingDirectory, directory => { - const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, directory, state); - if (resolutionFromCache) { - return resolutionFromCache; - } - const searchName = normalizePath(combinePaths(directory, moduleName)); - return toSearchResult(loadModuleFromFileNoPackageId(extensions, searchName, /*onlyRecordFailures*/ false, state)); - }); - if (resolved) { - return resolved; - } - if (extensions === Extensions.TypeScript) { - // If we didn't find the file normally, look it up in @types. - return loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName, containingDirectory, state); + return mangledName; +} +/* @internal */ +export function unmangleScopedPackageName(typesPackageName: string): string { + return stringContains(typesPackageName, mangledScopedPackageSeparator) ? + "@" + typesPackageName.replace(mangledScopedPackageSeparator, directorySeparator) : + typesPackageName; +} +function tryFindNonRelativeModuleNameInCache(cache: PerModuleNameCache | undefined, moduleName: string, containingDirectory: string, state: ModuleResolutionState): SearchResult { + const result = cache && cache.get(containingDirectory); + if (result) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory); + } + state.failedLookupLocations.push(...result.failedLookupLocations); + return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, originalPath: result.resolvedModule.originalPath || true, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId } }; + } +} +export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations { + const traceEnabled = isTraceEnabled(compilerOptions, host); + const failedLookupLocations: string[] = []; + const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations }; + const containingDirectory = getDirectoryPath(containingFile); + const resolved = tryResolve(Extensions.TypeScript) || tryResolve(Extensions.JavaScript); + // No originalPath because classic resolution doesn't resolve realPath + return createResolvedModuleWithFailedLookupLocations(resolved && resolved.value, /*isExternalLibraryImport*/ false, failedLookupLocations); + function tryResolve(extensions: Extensions): SearchResult { + const resolvedUsingSettings = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loadModuleFromFileNoPackageId, state); + if (resolvedUsingSettings) { + return { value: resolvedUsingSettings }; + } + if (!isExternalModuleNameRelative(moduleName)) { + const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, redirectedReference); + // Climb up parent directories looking for a module. + const resolved = forEachAncestorDirectory(containingDirectory, directory => { + const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, directory, state); + if (resolutionFromCache) { + return resolutionFromCache; } + const searchName = normalizePath(combinePaths(directory, moduleName)); + return toSearchResult(loadModuleFromFileNoPackageId(extensions, searchName, /*onlyRecordFailures*/ false, state)); + }); + if (resolved) { + return resolved; } - else { - const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); - return toSearchResult(loadModuleFromFileNoPackageId(extensions, candidate, /*onlyRecordFailures*/ false, state)); + if (extensions === Extensions.TypeScript) { + // If we didn't find the file normally, look it up in @types. + return loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName, containingDirectory, state); } } - } - - /** - * A host may load a module from a global cache of typings. - * This is the minumum code needed to expose that functionality; the rest is in the host. - */ - /* @internal */ - export function loadModuleFromGlobalCache(moduleName: string, projectName: string | undefined, compilerOptions: CompilerOptions, host: ModuleResolutionHost, globalCache: string): ResolvedModuleWithFailedLookupLocations { - const traceEnabled = isTraceEnabled(compilerOptions, host); - if (traceEnabled) { - trace(host, Diagnostics.Auto_discovery_for_typings_is_enabled_in_project_0_Running_extra_resolution_pass_for_module_1_using_cache_location_2, projectName, moduleName, globalCache); + else { + const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); + return toSearchResult(loadModuleFromFileNoPackageId(extensions, candidate, /*onlyRecordFailures*/ false, state)); } - const failedLookupLocations: string[] = []; - const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations }; - const resolved = loadModuleFromImmediateNodeModulesDirectory(Extensions.DtsOnly, moduleName, globalCache, state, /*typesScopeOnly*/ false); - return createResolvedModuleWithFailedLookupLocations(resolved, /*isExternalLibraryImport*/ true, failedLookupLocations); - } - - /** - * Represents result of search. Normally when searching among several alternatives we treat value `undefined` as indicator - * that search fails and we should try another option. - * However this does not allow us to represent final result that should be used instead of further searching (i.e. a final result that was found in cache). - * SearchResult is used to deal with this issue, its values represents following outcomes: - * - undefined - not found, continue searching - * - { value: undefined } - not found - stop searching - * - { value: } - found - stop searching - */ - type SearchResult = { value: T | undefined } | undefined; - - /** - * Wraps value to SearchResult. - * @returns undefined if value is undefined or { value } otherwise - */ - function toSearchResult(value: T | undefined): SearchResult { - return value !== undefined ? { value } : undefined; } } +/** + * A host may load a module from a global cache of typings. + * This is the minumum code needed to expose that functionality; the rest is in the host. + */ +/* @internal */ +export function loadModuleFromGlobalCache(moduleName: string, projectName: string | undefined, compilerOptions: CompilerOptions, host: ModuleResolutionHost, globalCache: string): ResolvedModuleWithFailedLookupLocations { + const traceEnabled = isTraceEnabled(compilerOptions, host); + if (traceEnabled) { + trace(host, Diagnostics.Auto_discovery_for_typings_is_enabled_in_project_0_Running_extra_resolution_pass_for_module_1_using_cache_location_2, projectName, moduleName, globalCache); + } + const failedLookupLocations: string[] = []; + const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations }; + const resolved = loadModuleFromImmediateNodeModulesDirectory(Extensions.DtsOnly, moduleName, globalCache, state, /*typesScopeOnly*/ false); + return createResolvedModuleWithFailedLookupLocations(resolved, /*isExternalLibraryImport*/ true, failedLookupLocations); +} +/** + * Represents result of search. Normally when searching among several alternatives we treat value `undefined` as indicator + * that search fails and we should try another option. + * However this does not allow us to represent final result that should be used instead of further searching (i.e. a final result that was found in cache). + * SearchResult is used to deal with this issue, its values represents following outcomes: + * - undefined - not found, continue searching + * - { value: undefined } - not found - stop searching + * - { value: } - found - stop searching + */ +type SearchResult = { + value: T | undefined; +} | undefined; +/** + * Wraps value to SearchResult. + * @returns undefined if value is undefined or { value } otherwise + */ +function toSearchResult(value: T | undefined): SearchResult { + return value !== undefined ? { value } : undefined; +} diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 1850220a411c1..3d6783573c24d 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -1,484 +1,423 @@ +import { UserPreferences, CompilerOptions, SourceFile, getEmitModuleResolutionKind, ModuleResolutionKind, isExternalModuleNameRelative, hasJSFileExtension, endsWith, Path, ModuleSpecifierResolutionHost, RedirectTargetsMap, firstDefined, Symbol, getSourceFileOfNode, getNonAugmentationDeclaration, mapDefined, GetCanonicalFileName, createGetCanonicalFileName, getDirectoryPath, ensurePathIsNonModuleName, getRelativePathFromDirectory, removeFileExtension, Debug, startsWith, CharacterCodes, pathIsRelative, compareValues, getNormalizedAbsolutePath, discoverProbableSymlinks, compareStringsCaseSensitive, compareStringsCaseInsensitive, startsWithDirectory, find, Comparison, resolvePath, arrayToMap, identity, toPath, ensureTrailingDirectorySeparator, isNonGlobalAmbientModule, isExternalModuleAugmentation, getTextOfIdentifierOrLiteral, ModuleDeclaration, StringLiteral, MapLike, normalizePath, removeTrailingDirectorySeparator, combinePaths, getPackageJsonTypesVersionsPaths, getPackageNameFromTypesPackageName, getSupportedExtensions, ScriptKind, nodeModulesPathPart, fileExtensionIs, Extension, removeSuffix, extensionFromPath, JsxEmit, getRelativePathToDirectoryOrUrl, isRootedDiskPath } from "./ts"; // Used by importFixes, getEditsForFileRename, and declaration emit to synthesize import module specifiers. /* @internal */ -namespace ts.moduleSpecifiers { - const enum RelativePreference { Relative, NonRelative, Auto } - // See UserPreferences#importPathEnding - const enum Ending { Minimal, Index, JsExtension } - - // Processed preferences - interface Preferences { - readonly relativePreference: RelativePreference; - readonly ending: Ending; - } - - function getPreferences({ importModuleSpecifierPreference, importModuleSpecifierEnding }: UserPreferences, compilerOptions: CompilerOptions, importingSourceFile: SourceFile): Preferences { - return { - relativePreference: importModuleSpecifierPreference === "relative" ? RelativePreference.Relative : importModuleSpecifierPreference === "non-relative" ? RelativePreference.NonRelative : RelativePreference.Auto, - ending: getEnding(), - }; - function getEnding(): Ending { - switch (importModuleSpecifierEnding) { - case "minimal": return Ending.Minimal; - case "index": return Ending.Index; - case "js": return Ending.JsExtension; - default: return usesJsExtensionOnImports(importingSourceFile) ? Ending.JsExtension - : getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs ? Ending.Index : Ending.Minimal; - } +const enum RelativePreference { + Relative, + NonRelative, + Auto +} +// See UserPreferences#importPathEnding +/* @internal */ +const enum Ending { + Minimal, + Index, + JsExtension +} +// Processed preferences +/* @internal */ +interface Preferences { + readonly relativePreference: RelativePreference; + readonly ending: Ending; +} +/* @internal */ +function getPreferences({ importModuleSpecifierPreference, importModuleSpecifierEnding }: UserPreferences, compilerOptions: CompilerOptions, importingSourceFile: SourceFile): Preferences { + return { + relativePreference: importModuleSpecifierPreference === "relative" ? RelativePreference.Relative : importModuleSpecifierPreference === "non-relative" ? RelativePreference.NonRelative : RelativePreference.Auto, + ending: getEnding(), + }; + function getEnding(): Ending { + switch (importModuleSpecifierEnding) { + case "minimal": return Ending.Minimal; + case "index": return Ending.Index; + case "js": return Ending.JsExtension; + default: return usesJsExtensionOnImports(importingSourceFile) ? Ending.JsExtension + : getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs ? Ending.Index : Ending.Minimal; } } - - function getPreferencesForUpdate(compilerOptions: CompilerOptions, oldImportSpecifier: string): Preferences { - return { - relativePreference: isExternalModuleNameRelative(oldImportSpecifier) ? RelativePreference.Relative : RelativePreference.NonRelative, - ending: hasJSFileExtension(oldImportSpecifier) ? - Ending.JsExtension : - getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs || endsWith(oldImportSpecifier, "index") ? Ending.Index : Ending.Minimal, - }; - } - - export function updateModuleSpecifier( - compilerOptions: CompilerOptions, - importingSourceFileName: Path, - toFileName: string, - host: ModuleSpecifierResolutionHost, - files: readonly SourceFile[], - redirectTargetsMap: RedirectTargetsMap, - oldImportSpecifier: string, - ): string | undefined { - const res = getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, files, redirectTargetsMap, getPreferencesForUpdate(compilerOptions, oldImportSpecifier)); - if (res === oldImportSpecifier) return undefined; - return res; - } - - // Note: importingSourceFile is just for usesJsExtensionOnImports - export function getModuleSpecifier( - compilerOptions: CompilerOptions, - importingSourceFile: SourceFile, - importingSourceFileName: Path, - toFileName: string, - host: ModuleSpecifierResolutionHost, - files: readonly SourceFile[], - preferences: UserPreferences = {}, - redirectTargetsMap: RedirectTargetsMap, - ): string { - return getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, files, redirectTargetsMap, getPreferences(preferences, compilerOptions, importingSourceFile)); - } - - export function getNodeModulesPackageName( - compilerOptions: CompilerOptions, - importingSourceFileName: Path, - nodeModulesFileName: string, - host: ModuleSpecifierResolutionHost, - files: readonly SourceFile[], - redirectTargetsMap: RedirectTargetsMap, - ): string | undefined { - const info = getInfo(importingSourceFileName, host); - const modulePaths = getAllModulePaths(files, importingSourceFileName, nodeModulesFileName, info.getCanonicalFileName, host, redirectTargetsMap); - return firstDefined(modulePaths, - moduleFileName => tryGetModuleNameAsNodeModule(moduleFileName, info, host, compilerOptions, /*packageNameOnly*/ true)); - } - - function getModuleSpecifierWorker( - compilerOptions: CompilerOptions, - importingSourceFileName: Path, - toFileName: string, - host: ModuleSpecifierResolutionHost, - files: readonly SourceFile[], - redirectTargetsMap: RedirectTargetsMap, - preferences: Preferences - ): string { - const info = getInfo(importingSourceFileName, host); - const modulePaths = getAllModulePaths(files, importingSourceFileName, toFileName, info.getCanonicalFileName, host, redirectTargetsMap); - return firstDefined(modulePaths, moduleFileName => tryGetModuleNameAsNodeModule(moduleFileName, info, host, compilerOptions)) || - getLocalModuleSpecifier(toFileName, info, compilerOptions, preferences); +} +/* @internal */ +function getPreferencesForUpdate(compilerOptions: CompilerOptions, oldImportSpecifier: string): Preferences { + return { + relativePreference: isExternalModuleNameRelative(oldImportSpecifier) ? RelativePreference.Relative : RelativePreference.NonRelative, + ending: hasJSFileExtension(oldImportSpecifier) ? + Ending.JsExtension : + getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs || endsWith(oldImportSpecifier, "index") ? Ending.Index : Ending.Minimal, + }; +} +/* @internal */ +export function updateModuleSpecifier(compilerOptions: CompilerOptions, importingSourceFileName: Path, toFileName: string, host: ModuleSpecifierResolutionHost, files: readonly SourceFile[], redirectTargetsMap: RedirectTargetsMap, oldImportSpecifier: string): string | undefined { + const res = getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, files, redirectTargetsMap, getPreferencesForUpdate(compilerOptions, oldImportSpecifier)); + if (res === oldImportSpecifier) + return undefined; + return res; +} +// Note: importingSourceFile is just for usesJsExtensionOnImports +/* @internal */ +export function getModuleSpecifier(compilerOptions: CompilerOptions, importingSourceFile: SourceFile, importingSourceFileName: Path, toFileName: string, host: ModuleSpecifierResolutionHost, files: readonly SourceFile[], preferences: UserPreferences = {}, redirectTargetsMap: RedirectTargetsMap): string { + return getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, files, redirectTargetsMap, getPreferences(preferences, compilerOptions, importingSourceFile)); +} +/* @internal */ +export function getNodeModulesPackageName(compilerOptions: CompilerOptions, importingSourceFileName: Path, nodeModulesFileName: string, host: ModuleSpecifierResolutionHost, files: readonly SourceFile[], redirectTargetsMap: RedirectTargetsMap): string | undefined { + const info = getInfo(importingSourceFileName, host); + const modulePaths = getAllModulePaths(files, importingSourceFileName, nodeModulesFileName, info.getCanonicalFileName, host, redirectTargetsMap); + return firstDefined(modulePaths, moduleFileName => tryGetModuleNameAsNodeModule(moduleFileName, info, host, compilerOptions, /*packageNameOnly*/ true)); +} +/* @internal */ +function getModuleSpecifierWorker(compilerOptions: CompilerOptions, importingSourceFileName: Path, toFileName: string, host: ModuleSpecifierResolutionHost, files: readonly SourceFile[], redirectTargetsMap: RedirectTargetsMap, preferences: Preferences): string { + const info = getInfo(importingSourceFileName, host); + const modulePaths = getAllModulePaths(files, importingSourceFileName, toFileName, info.getCanonicalFileName, host, redirectTargetsMap); + return firstDefined(modulePaths, moduleFileName => tryGetModuleNameAsNodeModule(moduleFileName, info, host, compilerOptions)) || + getLocalModuleSpecifier(toFileName, info, compilerOptions, preferences); +} +/** Returns an import for each symlink and for the realpath. */ +/* @internal */ +export function getModuleSpecifiers(moduleSymbol: Symbol, compilerOptions: CompilerOptions, importingSourceFile: SourceFile, host: ModuleSpecifierResolutionHost, files: readonly SourceFile[], userPreferences: UserPreferences, redirectTargetsMap: RedirectTargetsMap): readonly string[] { + const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol); + if (ambient) + return [ambient]; + const info = getInfo(importingSourceFile.path, host); + const moduleSourceFile = getSourceFileOfNode(moduleSymbol.valueDeclaration || getNonAugmentationDeclaration(moduleSymbol)); + const modulePaths = getAllModulePaths(files, importingSourceFile.path, moduleSourceFile.originalFileName, info.getCanonicalFileName, host, redirectTargetsMap); + const preferences = getPreferences(userPreferences, compilerOptions, importingSourceFile); + const global = mapDefined(modulePaths, moduleFileName => tryGetModuleNameAsNodeModule(moduleFileName, info, host, compilerOptions)); + return global.length ? global : modulePaths.map(moduleFileName => getLocalModuleSpecifier(moduleFileName, info, compilerOptions, preferences)); +} +/* @internal */ +interface Info { + readonly getCanonicalFileName: GetCanonicalFileName; + readonly sourceDirectory: Path; +} +// importingSourceFileName is separate because getEditsForFileRename may need to specify an updated path +/* @internal */ +function getInfo(importingSourceFileName: Path, host: ModuleSpecifierResolutionHost): Info { + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : true); + const sourceDirectory = getDirectoryPath(importingSourceFileName); + return { getCanonicalFileName, sourceDirectory }; +} +/* @internal */ +function getLocalModuleSpecifier(moduleFileName: string, { getCanonicalFileName, sourceDirectory }: Info, compilerOptions: CompilerOptions, { ending, relativePreference }: Preferences): string { + const { baseUrl, paths, rootDirs } = compilerOptions; + const relativePath = rootDirs && tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName, ending, compilerOptions) || + removeExtensionAndIndexPostFix(ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, moduleFileName, getCanonicalFileName)), ending, compilerOptions); + if (!baseUrl || relativePreference === RelativePreference.Relative) { + return relativePath; } - - /** Returns an import for each symlink and for the realpath. */ - export function getModuleSpecifiers( - moduleSymbol: Symbol, - compilerOptions: CompilerOptions, - importingSourceFile: SourceFile, - host: ModuleSpecifierResolutionHost, - files: readonly SourceFile[], - userPreferences: UserPreferences, - redirectTargetsMap: RedirectTargetsMap, - ): readonly string[] { - const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol); - if (ambient) return [ambient]; - - const info = getInfo(importingSourceFile.path, host); - const moduleSourceFile = getSourceFileOfNode(moduleSymbol.valueDeclaration || getNonAugmentationDeclaration(moduleSymbol)); - const modulePaths = getAllModulePaths(files, importingSourceFile.path, moduleSourceFile.originalFileName, info.getCanonicalFileName, host, redirectTargetsMap); - - const preferences = getPreferences(userPreferences, compilerOptions, importingSourceFile); - const global = mapDefined(modulePaths, moduleFileName => tryGetModuleNameAsNodeModule(moduleFileName, info, host, compilerOptions)); - return global.length ? global : modulePaths.map(moduleFileName => getLocalModuleSpecifier(moduleFileName, info, compilerOptions, preferences)); + const relativeToBaseUrl = getRelativePathIfInDirectory(moduleFileName, baseUrl, getCanonicalFileName); + if (!relativeToBaseUrl) { + return relativePath; } - - interface Info { - readonly getCanonicalFileName: GetCanonicalFileName; - readonly sourceDirectory: Path; + const importRelativeToBaseUrl = removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions); + const fromPaths = paths && tryGetModuleNameFromPaths(removeFileExtension(relativeToBaseUrl), importRelativeToBaseUrl, paths); + const nonRelative = fromPaths === undefined ? importRelativeToBaseUrl : fromPaths; + if (relativePreference === RelativePreference.NonRelative) { + return nonRelative; } - // importingSourceFileName is separate because getEditsForFileRename may need to specify an updated path - function getInfo(importingSourceFileName: Path, host: ModuleSpecifierResolutionHost): Info { - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : true); - const sourceDirectory = getDirectoryPath(importingSourceFileName); - return { getCanonicalFileName, sourceDirectory }; + if (relativePreference !== RelativePreference.Auto) + Debug.assertNever(relativePreference); + // Prefer a relative import over a baseUrl import if it has fewer components. + return isPathRelativeToParent(nonRelative) || countPathComponents(relativePath) < countPathComponents(nonRelative) ? relativePath : nonRelative; +} +/* @internal */ +export function countPathComponents(path: string): number { + let count = 0; + for (let i = startsWith(path, "./") ? 2 : 0; i < path.length; i++) { + if (path.charCodeAt(i) === CharacterCodes.slash) + count++; } - - function getLocalModuleSpecifier(moduleFileName: string, { getCanonicalFileName, sourceDirectory }: Info, compilerOptions: CompilerOptions, { ending, relativePreference }: Preferences): string { - const { baseUrl, paths, rootDirs } = compilerOptions; - - const relativePath = rootDirs && tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName, ending, compilerOptions) || - removeExtensionAndIndexPostFix(ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, moduleFileName, getCanonicalFileName)), ending, compilerOptions); - if (!baseUrl || relativePreference === RelativePreference.Relative) { - return relativePath; - } - - const relativeToBaseUrl = getRelativePathIfInDirectory(moduleFileName, baseUrl, getCanonicalFileName); - if (!relativeToBaseUrl) { - return relativePath; - } - - const importRelativeToBaseUrl = removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions); - const fromPaths = paths && tryGetModuleNameFromPaths(removeFileExtension(relativeToBaseUrl), importRelativeToBaseUrl, paths); - const nonRelative = fromPaths === undefined ? importRelativeToBaseUrl : fromPaths; - - if (relativePreference === RelativePreference.NonRelative) { - return nonRelative; + return count; +} +/* @internal */ +function usesJsExtensionOnImports({ imports }: SourceFile): boolean { + return firstDefined(imports, ({ text }) => pathIsRelative(text) ? hasJSFileExtension(text) : undefined) || false; +} +/* @internal */ +function numberOfDirectorySeparators(str: string) { + const match = str.match(/\//g); + return match ? match.length : 0; +} +/* @internal */ +function comparePathsByNumberOfDirectrorySeparators(a: string, b: string) { + return compareValues(numberOfDirectorySeparators(a), numberOfDirectorySeparators(b)); +} +/** + * Looks for existing imports that use symlinks to this module. + * Symlinks will be returned first so they are preferred over the real path. + */ +/* @internal */ +function getAllModulePaths(files: readonly SourceFile[], importingFileName: string, importedFileName: string, getCanonicalFileName: GetCanonicalFileName, host: ModuleSpecifierResolutionHost, redirectTargetsMap: RedirectTargetsMap): readonly string[] { + const redirects = redirectTargetsMap.get(importedFileName); + const importedFileNames = redirects ? [...redirects, importedFileName] : [importedFileName]; + const cwd = host.getCurrentDirectory ? host.getCurrentDirectory() : ""; + const targets = importedFileNames.map(f => getNormalizedAbsolutePath(f, cwd)); + const links = host.getProbableSymlinks + ? host.getProbableSymlinks(files) + : discoverProbableSymlinks(files, getCanonicalFileName, cwd); + const result: string[] = []; + const compareStrings = (!host.useCaseSensitiveFileNames || host.useCaseSensitiveFileNames()) ? compareStringsCaseSensitive : compareStringsCaseInsensitive; + links.forEach((resolved, path) => { + if (startsWithDirectory(importingFileName, resolved, getCanonicalFileName)) { + return; // Don't want to a package to globally import from itself } - - if (relativePreference !== RelativePreference.Auto) Debug.assertNever(relativePreference); - - // Prefer a relative import over a baseUrl import if it has fewer components. - return isPathRelativeToParent(nonRelative) || countPathComponents(relativePath) < countPathComponents(nonRelative) ? relativePath : nonRelative; - } - - export function countPathComponents(path: string): number { - let count = 0; - for (let i = startsWith(path, "./") ? 2 : 0; i < path.length; i++) { - if (path.charCodeAt(i) === CharacterCodes.slash) count++; + const target = find(targets, t => compareStrings(t.slice(0, resolved.length + 1), resolved + "/") === Comparison.EqualTo); + if (target === undefined) + return; + const relative = getRelativePathFromDirectory(resolved, target, getCanonicalFileName); + const option = resolvePath(path, relative); + if (!host.fileExists || host.fileExists(option)) { + result.push(option); } - return count; - } - - function usesJsExtensionOnImports({ imports }: SourceFile): boolean { - return firstDefined(imports, ({ text }) => pathIsRelative(text) ? hasJSFileExtension(text) : undefined) || false; - } - - function numberOfDirectorySeparators(str: string) { - const match = str.match(/\//g); - return match ? match.length : 0; - } - - function comparePathsByNumberOfDirectrorySeparators(a: string, b: string) { - return compareValues( - numberOfDirectorySeparators(a), - numberOfDirectorySeparators(b) - ); - } - - /** - * Looks for existing imports that use symlinks to this module. - * Symlinks will be returned first so they are preferred over the real path. - */ - function getAllModulePaths(files: readonly SourceFile[], importingFileName: string, importedFileName: string, getCanonicalFileName: GetCanonicalFileName, host: ModuleSpecifierResolutionHost, redirectTargetsMap: RedirectTargetsMap): readonly string[] { - const redirects = redirectTargetsMap.get(importedFileName); - const importedFileNames = redirects ? [...redirects, importedFileName] : [importedFileName]; - const cwd = host.getCurrentDirectory ? host.getCurrentDirectory() : ""; - const targets = importedFileNames.map(f => getNormalizedAbsolutePath(f, cwd)); - const links = host.getProbableSymlinks - ? host.getProbableSymlinks(files) - : discoverProbableSymlinks(files, getCanonicalFileName, cwd); - - const result: string[] = []; - const compareStrings = (!host.useCaseSensitiveFileNames || host.useCaseSensitiveFileNames()) ? compareStringsCaseSensitive : compareStringsCaseInsensitive; - links.forEach((resolved, path) => { - if (startsWithDirectory(importingFileName, resolved, getCanonicalFileName)) { - return; // Don't want to a package to globally import from itself - } - - const target = find(targets, t => compareStrings(t.slice(0, resolved.length + 1), resolved + "/") === Comparison.EqualTo); - if (target === undefined) return; - - const relative = getRelativePathFromDirectory(resolved, target, getCanonicalFileName); - const option = resolvePath(path, relative); - if (!host.fileExists || host.fileExists(option)) { - result.push(option); + }); + result.push(...targets); + if (result.length < 2) + return result; + // Sort by paths closest to importing file Name directory + const allFileNames = arrayToMap(result, identity, getCanonicalFileName); + const sortedPaths: string[] = []; + for (let directory = getDirectoryPath(toPath(importingFileName, cwd, getCanonicalFileName)); allFileNames.size !== 0; directory = getDirectoryPath(directory)) { + const directoryStart = ensureTrailingDirectorySeparator(directory); + let pathsInDirectory: string[] | undefined; + allFileNames.forEach((canonicalFileName, fileName) => { + if (startsWith(canonicalFileName, directoryStart)) { + (pathsInDirectory || (pathsInDirectory = [])).push(fileName); + allFileNames.delete(fileName); } }); - result.push(...targets); - if (result.length < 2) return result; - - // Sort by paths closest to importing file Name directory - const allFileNames = arrayToMap(result, identity, getCanonicalFileName); - const sortedPaths: string[] = []; - for ( - let directory = getDirectoryPath(toPath(importingFileName, cwd, getCanonicalFileName)); - allFileNames.size !== 0; - directory = getDirectoryPath(directory) - ) { - const directoryStart = ensureTrailingDirectorySeparator(directory); - let pathsInDirectory: string[] | undefined; - allFileNames.forEach((canonicalFileName, fileName) => { - if (startsWith(canonicalFileName, directoryStart)) { - (pathsInDirectory || (pathsInDirectory = [])).push(fileName); - allFileNames.delete(fileName); - } - }); - if (pathsInDirectory) { - if (pathsInDirectory.length > 1) { - pathsInDirectory.sort(comparePathsByNumberOfDirectrorySeparators); - } - sortedPaths.push(...pathsInDirectory); + if (pathsInDirectory) { + if (pathsInDirectory.length > 1) { + pathsInDirectory.sort(comparePathsByNumberOfDirectrorySeparators); } + sortedPaths.push(...pathsInDirectory); } - return sortedPaths; } - - function tryGetModuleNameFromAmbientModule(moduleSymbol: Symbol): string | undefined { - const decl = find(moduleSymbol.declarations, - d => isNonGlobalAmbientModule(d) && (!isExternalModuleAugmentation(d) || !isExternalModuleNameRelative(getTextOfIdentifierOrLiteral(d.name))) - ) as (ModuleDeclaration & { name: StringLiteral }) | undefined; - if (decl) { - return decl.name.text; - } + return sortedPaths; +} +/* @internal */ +function tryGetModuleNameFromAmbientModule(moduleSymbol: Symbol): string | undefined { + const decl = (find(moduleSymbol.declarations, d => isNonGlobalAmbientModule(d) && (!isExternalModuleAugmentation(d) || !isExternalModuleNameRelative(getTextOfIdentifierOrLiteral(d.name)))) as (ModuleDeclaration & { + name: StringLiteral; + }) | undefined); + if (decl) { + return decl.name.text; } - - function tryGetModuleNameFromPaths(relativeToBaseUrlWithIndex: string, relativeToBaseUrl: string, paths: MapLike): string | undefined { - for (const key in paths) { - for (const patternText of paths[key]) { - const pattern = removeFileExtension(normalizePath(patternText)); - const indexOfStar = pattern.indexOf("*"); - if (indexOfStar !== -1) { - const prefix = pattern.substr(0, indexOfStar); - const suffix = pattern.substr(indexOfStar + 1); - if (relativeToBaseUrl.length >= prefix.length + suffix.length && - startsWith(relativeToBaseUrl, prefix) && - endsWith(relativeToBaseUrl, suffix) || - !suffix && relativeToBaseUrl === removeTrailingDirectorySeparator(prefix)) { - const matchedStar = relativeToBaseUrl.substr(prefix.length, relativeToBaseUrl.length - suffix.length); - return key.replace("*", matchedStar); - } - } - else if (pattern === relativeToBaseUrl || pattern === relativeToBaseUrlWithIndex) { - return key; +} +/* @internal */ +function tryGetModuleNameFromPaths(relativeToBaseUrlWithIndex: string, relativeToBaseUrl: string, paths: MapLike): string | undefined { + for (const key in paths) { + for (const patternText of paths[key]) { + const pattern = removeFileExtension(normalizePath(patternText)); + const indexOfStar = pattern.indexOf("*"); + if (indexOfStar !== -1) { + const prefix = pattern.substr(0, indexOfStar); + const suffix = pattern.substr(indexOfStar + 1); + if (relativeToBaseUrl.length >= prefix.length + suffix.length && + startsWith(relativeToBaseUrl, prefix) && + endsWith(relativeToBaseUrl, suffix) || + !suffix && relativeToBaseUrl === removeTrailingDirectorySeparator(prefix)) { + const matchedStar = relativeToBaseUrl.substr(prefix.length, relativeToBaseUrl.length - suffix.length); + return key.replace("*", matchedStar); } } + else if (pattern === relativeToBaseUrl || pattern === relativeToBaseUrlWithIndex) { + return key; + } } } - - function tryGetModuleNameFromRootDirs(rootDirs: readonly string[], moduleFileName: string, sourceDirectory: string, getCanonicalFileName: (file: string) => string, ending: Ending, compilerOptions: CompilerOptions): string | undefined { - const normalizedTargetPath = getPathRelativeToRootDirs(moduleFileName, rootDirs, getCanonicalFileName); - if (normalizedTargetPath === undefined) { - return undefined; - } - - const normalizedSourcePath = getPathRelativeToRootDirs(sourceDirectory, rootDirs, getCanonicalFileName); - const relativePath = normalizedSourcePath !== undefined ? ensurePathIsNonModuleName(getRelativePathFromDirectory(normalizedSourcePath, normalizedTargetPath, getCanonicalFileName)) : normalizedTargetPath; - return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs - ? removeExtensionAndIndexPostFix(relativePath, ending, compilerOptions) - : removeFileExtension(relativePath); +} +/* @internal */ +function tryGetModuleNameFromRootDirs(rootDirs: readonly string[], moduleFileName: string, sourceDirectory: string, getCanonicalFileName: (file: string) => string, ending: Ending, compilerOptions: CompilerOptions): string | undefined { + const normalizedTargetPath = getPathRelativeToRootDirs(moduleFileName, rootDirs, getCanonicalFileName); + if (normalizedTargetPath === undefined) { + return undefined; } - - function tryGetModuleNameAsNodeModule(moduleFileName: string, { getCanonicalFileName, sourceDirectory }: Info, host: ModuleSpecifierResolutionHost, options: CompilerOptions, packageNameOnly?: boolean): string | undefined { - if (!host.fileExists || !host.readFile) { - return undefined; - } - const parts: NodeModulePathParts = getNodeModulePathParts(moduleFileName)!; - if (!parts) { - return undefined; - } - - let packageJsonContent: any | undefined; - const packageRootPath = moduleFileName.substring(0, parts.packageRootIndex); - if (!packageNameOnly) { - const packageJsonPath = combinePaths(packageRootPath, "package.json"); - packageJsonContent = host.fileExists(packageJsonPath) - ? JSON.parse(host.readFile(packageJsonPath)!) - : undefined; - const versionPaths = packageJsonContent && packageJsonContent.typesVersions - ? getPackageJsonTypesVersionsPaths(packageJsonContent.typesVersions) - : undefined; - if (versionPaths) { - const subModuleName = moduleFileName.slice(parts.packageRootIndex + 1); - const fromPaths = tryGetModuleNameFromPaths( - removeFileExtension(subModuleName), - removeExtensionAndIndexPostFix(subModuleName, Ending.Minimal, options), - versionPaths.paths - ); - if (fromPaths !== undefined) { - moduleFileName = combinePaths(moduleFileName.slice(0, parts.packageRootIndex), fromPaths); - } - } - } - - // Simplify the full file path to something that can be resolved by Node. - - // If the module could be imported by a directory name, use that directory's name - const moduleSpecifier = packageNameOnly ? moduleFileName : getDirectoryOrExtensionlessFileName(moduleFileName); - const globalTypingsCacheLocation = host.getGlobalTypingsCacheLocation && host.getGlobalTypingsCacheLocation(); - // Get a path that's relative to node_modules or the importing file's path - // if node_modules folder is in this folder or any of its parent folders, no need to keep it. - const pathToTopLevelNodeModules = getCanonicalFileName(moduleSpecifier.substring(0, parts.topLevelNodeModulesIndex)); - if (!(startsWith(sourceDirectory, pathToTopLevelNodeModules) || globalTypingsCacheLocation && startsWith(getCanonicalFileName(globalTypingsCacheLocation), pathToTopLevelNodeModules))) { - return undefined; - } - - // If the module was found in @types, get the actual Node package name - const nodeModulesDirectoryName = moduleSpecifier.substring(parts.topLevelPackageNameIndex + 1); - const packageName = getPackageNameFromTypesPackageName(nodeModulesDirectoryName); - // For classic resolution, only allow importing from node_modules/@types, not other node_modules - return getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs && packageName === nodeModulesDirectoryName ? undefined : packageName; - - function getDirectoryOrExtensionlessFileName(path: string): string { - // If the file is the main module, it can be imported by the package name - if (packageJsonContent) { - const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main; - if (mainFileRelative) { - const mainExportFile = toPath(mainFileRelative, packageRootPath, getCanonicalFileName); - if (removeFileExtension(mainExportFile) === removeFileExtension(getCanonicalFileName(path))) { - return packageRootPath; - } - } - } - - // We still have a file name - remove the extension - const fullModulePathWithoutExtension = removeFileExtension(path); - - // If the file is /index, it can be imported by its directory name - // IFF there is not _also_ a file by the same name - if (getCanonicalFileName(fullModulePathWithoutExtension.substring(parts.fileNameIndex)) === "/index" && !tryGetAnyFileFromPath(host, fullModulePathWithoutExtension.substring(0, parts.fileNameIndex))) { - return fullModulePathWithoutExtension.substring(0, parts.fileNameIndex); - } - - return fullModulePathWithoutExtension; - } + const normalizedSourcePath = getPathRelativeToRootDirs(sourceDirectory, rootDirs, getCanonicalFileName); + const relativePath = normalizedSourcePath !== undefined ? ensurePathIsNonModuleName(getRelativePathFromDirectory(normalizedSourcePath, normalizedTargetPath, getCanonicalFileName)) : normalizedTargetPath; + return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs + ? removeExtensionAndIndexPostFix(relativePath, ending, compilerOptions) + : removeFileExtension(relativePath); +} +/* @internal */ +function tryGetModuleNameAsNodeModule(moduleFileName: string, { getCanonicalFileName, sourceDirectory }: Info, host: ModuleSpecifierResolutionHost, options: CompilerOptions, packageNameOnly?: boolean): string | undefined { + if (!host.fileExists || !host.readFile) { + return undefined; } - - function tryGetAnyFileFromPath(host: ModuleSpecifierResolutionHost, path: string) { - if (!host.fileExists) return; - // We check all js, `node` and `json` extensions in addition to TS, since node module resolution would also choose those over the directory - const extensions = getSupportedExtensions({ allowJs: true }, [{ extension: "node", isMixedContent: false }, { extension: "json", isMixedContent: false, scriptKind: ScriptKind.JSON }]); - for (const e of extensions) { - const fullPath = path + e; - if (host.fileExists(fullPath)) { - return fullPath; + const parts: NodeModulePathParts = getNodeModulePathParts(moduleFileName)!; + if (!parts) { + return undefined; + } + let packageJsonContent: any | undefined; + const packageRootPath = moduleFileName.substring(0, parts.packageRootIndex); + if (!packageNameOnly) { + const packageJsonPath = combinePaths(packageRootPath, "package.json"); + packageJsonContent = host.fileExists(packageJsonPath) + ? JSON.parse(host.readFile(packageJsonPath)!) + : undefined; + const versionPaths = packageJsonContent && packageJsonContent.typesVersions + ? getPackageJsonTypesVersionsPaths(packageJsonContent.typesVersions) + : undefined; + if (versionPaths) { + const subModuleName = moduleFileName.slice(parts.packageRootIndex + 1); + const fromPaths = tryGetModuleNameFromPaths(removeFileExtension(subModuleName), removeExtensionAndIndexPostFix(subModuleName, Ending.Minimal, options), versionPaths.paths); + if (fromPaths !== undefined) { + moduleFileName = combinePaths(moduleFileName.slice(0, parts.packageRootIndex), fromPaths); } } } - - interface NodeModulePathParts { - readonly topLevelNodeModulesIndex: number; - readonly topLevelPackageNameIndex: number; - readonly packageRootIndex: number; - readonly fileNameIndex: number; + // Simplify the full file path to something that can be resolved by Node. + // If the module could be imported by a directory name, use that directory's name + const moduleSpecifier = packageNameOnly ? moduleFileName : getDirectoryOrExtensionlessFileName(moduleFileName); + const globalTypingsCacheLocation = host.getGlobalTypingsCacheLocation && host.getGlobalTypingsCacheLocation(); + // Get a path that's relative to node_modules or the importing file's path + // if node_modules folder is in this folder or any of its parent folders, no need to keep it. + const pathToTopLevelNodeModules = getCanonicalFileName(moduleSpecifier.substring(0, parts.topLevelNodeModulesIndex)); + if (!(startsWith(sourceDirectory, pathToTopLevelNodeModules) || globalTypingsCacheLocation && startsWith(getCanonicalFileName(globalTypingsCacheLocation), pathToTopLevelNodeModules))) { + return undefined; } - function getNodeModulePathParts(fullPath: string): NodeModulePathParts | undefined { - // If fullPath can't be valid module file within node_modules, returns undefined. - // Example of expected pattern: /base/path/node_modules/[@scope/otherpackage/@otherscope/node_modules/]package/[subdirectory/]file.js - // Returns indices: ^ ^ ^ ^ - - let topLevelNodeModulesIndex = 0; - let topLevelPackageNameIndex = 0; - let packageRootIndex = 0; - let fileNameIndex = 0; - - const enum States { - BeforeNodeModules, - NodeModules, - Scope, - PackageContent - } - - let partStart = 0; - let partEnd = 0; - let state = States.BeforeNodeModules; - - while (partEnd >= 0) { - partStart = partEnd; - partEnd = fullPath.indexOf("/", partStart + 1); - switch (state) { - case States.BeforeNodeModules: - if (fullPath.indexOf(nodeModulesPathPart, partStart) === partStart) { - topLevelNodeModulesIndex = partStart; - topLevelPackageNameIndex = partEnd; - state = States.NodeModules; - } - break; - case States.NodeModules: - case States.Scope: - if (state === States.NodeModules && fullPath.charAt(partStart + 1) === "@") { - state = States.Scope; - } - else { - packageRootIndex = partEnd; - state = States.PackageContent; - } - break; - case States.PackageContent: - if (fullPath.indexOf(nodeModulesPathPart, partStart) === partStart) { - state = States.NodeModules; - } - else { - state = States.PackageContent; - } - break; + // If the module was found in @types, get the actual Node package name + const nodeModulesDirectoryName = moduleSpecifier.substring(parts.topLevelPackageNameIndex + 1); + const packageName = getPackageNameFromTypesPackageName(nodeModulesDirectoryName); + // For classic resolution, only allow importing from node_modules/@types, not other node_modules + return getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs && packageName === nodeModulesDirectoryName ? undefined : packageName; + function getDirectoryOrExtensionlessFileName(path: string): string { + // If the file is the main module, it can be imported by the package name + if (packageJsonContent) { + const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main; + if (mainFileRelative) { + const mainExportFile = toPath(mainFileRelative, packageRootPath, getCanonicalFileName); + if (removeFileExtension(mainExportFile) === removeFileExtension(getCanonicalFileName(path))) { + return packageRootPath; + } } } - - fileNameIndex = partStart; - - return state > States.NodeModules ? { topLevelNodeModulesIndex, topLevelPackageNameIndex, packageRootIndex, fileNameIndex } : undefined; - } - - function getPathRelativeToRootDirs(path: string, rootDirs: readonly string[], getCanonicalFileName: GetCanonicalFileName): string | undefined { - return firstDefined(rootDirs, rootDir => { - const relativePath = getRelativePathIfInDirectory(path, rootDir, getCanonicalFileName)!; // TODO: GH#18217 - return isPathRelativeToParent(relativePath) ? undefined : relativePath; - }); + // We still have a file name - remove the extension + const fullModulePathWithoutExtension = removeFileExtension(path); + // If the file is /index, it can be imported by its directory name + // IFF there is not _also_ a file by the same name + if (getCanonicalFileName(fullModulePathWithoutExtension.substring(parts.fileNameIndex)) === "/index" && !tryGetAnyFileFromPath(host, fullModulePathWithoutExtension.substring(0, parts.fileNameIndex))) { + return fullModulePathWithoutExtension.substring(0, parts.fileNameIndex); + } + return fullModulePathWithoutExtension; } - - function removeExtensionAndIndexPostFix(fileName: string, ending: Ending, options: CompilerOptions): string { - if (fileExtensionIs(fileName, Extension.Json)) return fileName; - const noExtension = removeFileExtension(fileName); - switch (ending) { - case Ending.Minimal: - return removeSuffix(noExtension, "/index"); - case Ending.Index: - return noExtension; - case Ending.JsExtension: - return noExtension + getJSExtensionForFile(fileName, options); - default: - return Debug.assertNever(ending); +} +/* @internal */ +function tryGetAnyFileFromPath(host: ModuleSpecifierResolutionHost, path: string) { + if (!host.fileExists) + return; + // We check all js, `node` and `json` extensions in addition to TS, since node module resolution would also choose those over the directory + const extensions = getSupportedExtensions({ allowJs: true }, [{ extension: "node", isMixedContent: false }, { extension: "json", isMixedContent: false, scriptKind: ScriptKind.JSON }]); + for (const e of extensions) { + const fullPath = path + e; + if (host.fileExists(fullPath)) { + return fullPath; } } - - function getJSExtensionForFile(fileName: string, options: CompilerOptions): Extension { - const ext = extensionFromPath(fileName); - switch (ext) { - case Extension.Ts: - case Extension.Dts: - return Extension.Js; - case Extension.Tsx: - return options.jsx === JsxEmit.Preserve ? Extension.Jsx : Extension.Js; - case Extension.Js: - case Extension.Jsx: - case Extension.Json: - return ext; - case Extension.TsBuildInfo: - return Debug.fail(`Extension ${Extension.TsBuildInfo} is unsupported:: FileName:: ${fileName}`); - default: - return Debug.assertNever(ext); +} +/* @internal */ +interface NodeModulePathParts { + readonly topLevelNodeModulesIndex: number; + readonly topLevelPackageNameIndex: number; + readonly packageRootIndex: number; + readonly fileNameIndex: number; +} +/* @internal */ +function getNodeModulePathParts(fullPath: string): NodeModulePathParts | undefined { + // If fullPath can't be valid module file within node_modules, returns undefined. + // Example of expected pattern: /base/path/node_modules/[@scope/otherpackage/@otherscope/node_modules/]package/[subdirectory/]file.js + // Returns indices: ^ ^ ^ ^ + let topLevelNodeModulesIndex = 0; + let topLevelPackageNameIndex = 0; + let packageRootIndex = 0; + let fileNameIndex = 0; + const enum States { + BeforeNodeModules, + NodeModules, + Scope, + PackageContent + } + let partStart = 0; + let partEnd = 0; + let state = States.BeforeNodeModules; + while (partEnd >= 0) { + partStart = partEnd; + partEnd = fullPath.indexOf("/", partStart + 1); + switch (state) { + case States.BeforeNodeModules: + if (fullPath.indexOf(nodeModulesPathPart, partStart) === partStart) { + topLevelNodeModulesIndex = partStart; + topLevelPackageNameIndex = partEnd; + state = States.NodeModules; + } + break; + case States.NodeModules: + case States.Scope: + if (state === States.NodeModules && fullPath.charAt(partStart + 1) === "@") { + state = States.Scope; + } + else { + packageRootIndex = partEnd; + state = States.PackageContent; + } + break; + case States.PackageContent: + if (fullPath.indexOf(nodeModulesPathPart, partStart) === partStart) { + state = States.NodeModules; + } + else { + state = States.PackageContent; + } + break; } } - - function getRelativePathIfInDirectory(path: string, directoryPath: string, getCanonicalFileName: GetCanonicalFileName): string | undefined { - const relativePath = getRelativePathToDirectoryOrUrl(directoryPath, path, directoryPath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); - return isRootedDiskPath(relativePath) ? undefined : relativePath; + fileNameIndex = partStart; + return state > States.NodeModules ? { topLevelNodeModulesIndex, topLevelPackageNameIndex, packageRootIndex, fileNameIndex } : undefined; +} +/* @internal */ +function getPathRelativeToRootDirs(path: string, rootDirs: readonly string[], getCanonicalFileName: GetCanonicalFileName): string | undefined { + return firstDefined(rootDirs, rootDir => { + const relativePath = getRelativePathIfInDirectory(path, rootDir, getCanonicalFileName)!; // TODO: GH#18217 + return isPathRelativeToParent(relativePath) ? undefined : relativePath; + }); +} +/* @internal */ +function removeExtensionAndIndexPostFix(fileName: string, ending: Ending, options: CompilerOptions): string { + if (fileExtensionIs(fileName, Extension.Json)) + return fileName; + const noExtension = removeFileExtension(fileName); + switch (ending) { + case Ending.Minimal: + return removeSuffix(noExtension, "/index"); + case Ending.Index: + return noExtension; + case Ending.JsExtension: + return noExtension + getJSExtensionForFile(fileName, options); + default: + return Debug.assertNever(ending); } - - function isPathRelativeToParent(path: string): boolean { - return startsWith(path, ".."); +} +/* @internal */ +function getJSExtensionForFile(fileName: string, options: CompilerOptions): Extension { + const ext = extensionFromPath(fileName); + switch (ext) { + case Extension.Ts: + case Extension.Dts: + return Extension.Js; + case Extension.Tsx: + return options.jsx === JsxEmit.Preserve ? Extension.Jsx : Extension.Js; + case Extension.Js: + case Extension.Jsx: + case Extension.Json: + return ext; + case Extension.TsBuildInfo: + return Debug.fail(`Extension ${Extension.TsBuildInfo} is unsupported:: FileName:: ${fileName}`); + default: + return Debug.assertNever(ext); } } +/* @internal */ +function getRelativePathIfInDirectory(path: string, directoryPath: string, getCanonicalFileName: GetCanonicalFileName): string | undefined { + const relativePath = getRelativePathToDirectoryOrUrl(directoryPath, path, directoryPath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); + return isRootedDiskPath(relativePath) ? undefined : relativePath; +} +/* @internal */ +function isPathRelativeToParent(path: string): boolean { + return startsWith(path, ".."); +} diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 0cda1e4d1d8c2..f1d5a50a772eb 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1,8294 +1,7411 @@ -namespace ts { - const enum SignatureFlags { - None = 0, - Yield = 1 << 0, - Await = 1 << 1, - Type = 1 << 2, - IgnoreMissingOpenBrace = 1 << 4, - JSDoc = 1 << 5, - } - - 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 SourceFileConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; - - export function createNode(kind: SyntaxKind, pos?: number, end?: number): Node { - if (kind === SyntaxKind.SourceFile) { - return new (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor()))(kind, pos, end); - } - else if (kind === SyntaxKind.Identifier) { - return new (IdentifierConstructor || (IdentifierConstructor = objectAllocator.getIdentifierConstructor()))(kind, pos, end); - } - else if (!isNodeKind(kind)) { - return new (TokenConstructor || (TokenConstructor = objectAllocator.getTokenConstructor()))(kind, pos, end); - } - else { - return new (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor()))(kind, pos, end); - } +import { SyntaxKind, Node, objectAllocator, isNodeKind, NodeArray, CharacterCodes, QualifiedName, TypeParameterDeclaration, ShorthandPropertyAssignment, SpreadAssignment, ParameterDeclaration, PropertyDeclaration, PropertySignature, PropertyAssignment, VariableDeclaration, BindingElement, SignatureDeclaration, FunctionLikeDeclaration, ArrowFunction, TypeReferenceNode, TypePredicateNode, TypeQueryNode, TypeLiteralNode, ArrayTypeNode, TupleTypeNode, UnionOrIntersectionTypeNode, ConditionalTypeNode, InferTypeNode, ImportTypeNode, ParenthesizedTypeNode, TypeOperatorNode, IndexedAccessTypeNode, MappedTypeNode, LiteralTypeNode, BindingPattern, ArrayLiteralExpression, ObjectLiteralExpression, PropertyAccessExpression, ElementAccessExpression, CallExpression, TaggedTemplateExpression, TypeAssertion, ParenthesizedExpression, DeleteExpression, TypeOfExpression, VoidExpression, PrefixUnaryExpression, YieldExpression, AwaitExpression, PostfixUnaryExpression, BinaryExpression, AsExpression, NonNullExpression, MetaProperty, ConditionalExpression, SpreadElement, Block, SourceFile, VariableStatement, VariableDeclarationList, ExpressionStatement, IfStatement, DoStatement, WhileStatement, ForStatement, ForInStatement, ForOfStatement, BreakOrContinueStatement, ReturnStatement, WithStatement, SwitchStatement, CaseBlock, CaseClause, DefaultClause, LabeledStatement, ThrowStatement, TryStatement, CatchClause, Decorator, ClassLikeDeclaration, InterfaceDeclaration, ClassDeclaration, TypeAliasDeclaration, EnumDeclaration, EnumMember, ModuleDeclaration, ImportEqualsDeclaration, ImportDeclaration, ImportClause, NamespaceExportDeclaration, NamespaceImport, NamedImportsOrExports, ExportDeclaration, ImportOrExportSpecifier, ExportAssignment, TemplateExpression, TemplateSpan, ComputedPropertyName, HeritageClause, ExpressionWithTypeArguments, ExternalModuleReference, CommaListExpression, JsxElement, JsxFragment, JsxOpeningLikeElement, JsxAttributes, JsxAttribute, JsxSpreadAttribute, JsxExpression, JsxClosingElement, OptionalTypeNode, RestTypeNode, JSDocTypeExpression, JSDocTypeReferencingNode, JSDocFunctionType, JSDoc, JSDocTag, JSDocPropertyLikeTag, JSDocAugmentsTag, JSDocTemplateTag, JSDocTypedefTag, JSDocCallbackTag, JSDocReturnTag, JSDocTypeTag, JSDocThisTag, JSDocEnumTag, forEach, JSDocSignature, JSDocTypeLiteral, PartiallyEmittedExpression, ScriptTarget, ScriptKind, perfLogger, EntityName, JsonSourceFile, TextChangeRange, NodeFlags, createScanner, DiagnosticWithLocation, ensureScriptKind, convertToObjectWorker, emptyArray, emptyMap, EndOfFileToken, JsonObjectExpressionStatement, BooleanLiteral, NullLiteral, JsonMinusNumericLiteral, StringLiteral, NumericLiteral, Diagnostics, LanguageVariant, createMap, Debug, DiagnosticMessage, createFileDiagnostic, HasJSDoc, mapDefined, getJSDocCommentRanges, hasJSDocNodes, normalizePath, lastOrUndefined, TextRange, isKeyword, JSDocSyntaxKind, tokenToString, Token, TokenFlags, MutableNodeArray, Identifier, __String, isLiteralKind, isTemplateLiteralKind, LiteralLikeNode, escapeLeadingUnderscores, tokenIsIdentifierOrKeyword, PropertyName, isModifierKind, tokenIsIdentifierOrKeywordOrGreaterThan, nodeIsMissing, containsParseError, JSDocContainer, MethodDeclaration, last, TemplateMiddle, TemplateTail, LiteralExpression, TemplateHead, TemplateLiteralLikeNode, TypeNode, FunctionOrConstructorTypeNode, ThisTypeNode, JSDocAllType, JSDocOptionalType, JSDocNonNullableType, JSDocUnknownType, JSDocNullableType, JSDocNamepathType, JSDocVariadicType, getFullWidth, hasModifiers, CallSignatureDeclaration, ConstructSignatureDeclaration, IndexSignatureDeclaration, MethodSignature, TypeElement, ReadonlyToken, PlusToken, MinusToken, QuestionToken, Expression, BinaryOperatorToken, isLeftHandSideExpression, isAssignmentOperator, Modifier, hasModifier, ModifierFlags, isJSDocFunctionType, nodeIsPresent, getBinaryOperatorPrecedence, PrefixUnaryOperator, UnaryExpression, skipTrivia, UpdateExpression, PostfixUnaryOperator, LeftHandSideExpression, MemberExpression, PrimaryExpression, JsxSelfClosingElement, getTextOfNodeFromSourceText, JsxText, JsxOpeningElement, JsxOpeningFragment, JsxTokenSyntaxKind, JsxChild, isJsxOpeningFragment, JsxTagNameExpression, ThisExpression, JsxTagNamePropertyAccess, JsxClosingFragment, QuestionDotToken, isStringOrNumericLiteralLike, NoSubstitutionTemplateLiteral, ObjectLiteralElementLike, AccessorDeclaration, FunctionExpression, NewExpression, addRelatedInfo, Statement, IterationStatement, CaseOrDefaultClause, FunctionDeclaration, some, ArrayBindingElement, OmittedExpression, ObjectBindingPattern, ArrayBindingPattern, ConstructorDeclaration, AsteriskToken, isClassMemberModifier, ClassElement, SemicolonClassElement, ClassExpression, ModuleBlock, NamespaceDeclaration, NamedImports, NamedExports, ImportSpecifier, ExportSpecifier, isMetaProperty, Diagnostic, isTypeReferenceNode, JSDocParameterTag, JSDocPropertyTag, append, isJSDocReturnTag, isJSDocTypeTag, JSDocAuthorTag, PropertyAccessEntityNameExpression, JSDocClassTag, JSDocNamespaceDeclaration, JSDocNamespaceBody, AssertionLevel, textChangeRangeIsUnchanged, textSpanEnd, textChangeRangeNewSpan, createTextSpanFromBounds, createTextChangeRange, getLastChild, fileExtensionIs, Extension, PragmaMap, CheckJsDirective, FileReference, AmdDependency, PragmaPseudoMapEntry, getLeadingCommentRanges, toArray, PragmaPseudoMap, map, CommentRange, commentPragmas, PragmaDefinition, PragmaKindFlags } from "./ts"; +import { mark, measure } from "./ts.performance"; +import * as ts from "./ts"; +const enum SignatureFlags { + None = 0, + Yield = 1 << 0, + Await = 1 << 1, + Type = 1 << 2, + IgnoreMissingOpenBrace = 1 << 4, + JSDoc = 1 << 5 +} +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 SourceFileConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; +export function createNode(kind: SyntaxKind, pos?: number, end?: number): Node { + if (kind === SyntaxKind.SourceFile) { + return new (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor()))(kind, pos, end); } - - function visitNode(cbNode: (node: Node) => T, node: Node | undefined): T | undefined { - return node && cbNode(node); + else if (kind === SyntaxKind.Identifier) { + return new (IdentifierConstructor || (IdentifierConstructor = objectAllocator.getIdentifierConstructor()))(kind, pos, end); } - - 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; - } + else if (!isNodeKind(kind)) { + return new (TokenConstructor || (TokenConstructor = objectAllocator.getTokenConstructor()))(kind, pos, end); + } + else { + return new (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor()))(kind, pos, end); + } +} +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 isJSDocLikeText(text: string, start: number) { + return text.charCodeAt(start + 1) === CharacterCodes.asterisk && + text.charCodeAt(start + 2) === CharacterCodes.asterisk && + text.charCodeAt(start + 3) !== CharacterCodes.slash; +} +/** + * 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 || node.kind <= SyntaxKind.LastToken) { + return; } - - /** - * 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 || node.kind <= SyntaxKind.LastToken) { - return; - } - switch (node.kind) { - case SyntaxKind.QualifiedName: - return visitNode(cbNode, (node).left) || - visitNode(cbNode, (node).right); - case SyntaxKind.TypeParameter: - return visitNode(cbNode, (node).name) || - visitNode(cbNode, (node).constraint) || - visitNode(cbNode, (node).default) || - visitNode(cbNode, (node).expression); - case SyntaxKind.ShorthandPropertyAssignment: - return visitNodes(cbNode, cbNodes, node.decorators) || - 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); - case SyntaxKind.SpreadAssignment: - return visitNode(cbNode, (node).expression); - case SyntaxKind.Parameter: - return visitNodes(cbNode, cbNodes, node.decorators) || - 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); - case SyntaxKind.PropertyDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators) || - 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); - case SyntaxKind.PropertySignature: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node).name) || - visitNode(cbNode, (node).questionToken) || - visitNode(cbNode, (node).type) || - visitNode(cbNode, (node).initializer); - case SyntaxKind.PropertyAssignment: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node).name) || - visitNode(cbNode, (node).questionToken) || - visitNode(cbNode, (node).initializer); - case SyntaxKind.VariableDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node).name) || - visitNode(cbNode, (node).exclamationToken) || - visitNode(cbNode, (node).type) || - visitNode(cbNode, (node).initializer); - case SyntaxKind.BindingElement: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node).dotDotDotToken) || - visitNode(cbNode, (node).propertyName) || - visitNode(cbNode, (node).name) || - visitNode(cbNode, (node).initializer); - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNodes(cbNode, cbNodes, (node).typeParameters) || - visitNodes(cbNode, cbNodes, (node).parameters) || - visitNode(cbNode, (node).type); - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ArrowFunction: - return visitNodes(cbNode, cbNodes, node.decorators) || - 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).equalsGreaterThanToken) || - visitNode(cbNode, (node).body); - case SyntaxKind.TypeReference: - return visitNode(cbNode, (node).typeName) || - visitNodes(cbNode, cbNodes, (node).typeArguments); - case SyntaxKind.TypePredicate: - return visitNode(cbNode, (node).assertsModifier) || - visitNode(cbNode, (node).parameterName) || - visitNode(cbNode, (node).type); - case SyntaxKind.TypeQuery: - return visitNode(cbNode, (node).exprName); - case SyntaxKind.TypeLiteral: - return visitNodes(cbNode, cbNodes, (node).members); - case SyntaxKind.ArrayType: - return visitNode(cbNode, (node).elementType); - case SyntaxKind.TupleType: - return visitNodes(cbNode, cbNodes, (node).elementTypes); - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: - return visitNodes(cbNode, cbNodes, (node).types); - case SyntaxKind.ConditionalType: - return visitNode(cbNode, (node).checkType) || - visitNode(cbNode, (node).extendsType) || - visitNode(cbNode, (node).trueType) || - visitNode(cbNode, (node).falseType); - case SyntaxKind.InferType: - return visitNode(cbNode, (node).typeParameter); - case SyntaxKind.ImportType: - return visitNode(cbNode, (node).argument) || - visitNode(cbNode, (node).qualifier) || - visitNodes(cbNode, cbNodes, (node).typeArguments); - case SyntaxKind.ParenthesizedType: - case SyntaxKind.TypeOperator: - return visitNode(cbNode, (node).type); - case SyntaxKind.IndexedAccessType: - return visitNode(cbNode, (node).objectType) || - visitNode(cbNode, (node).indexType); - case SyntaxKind.MappedType: - return visitNode(cbNode, (node).readonlyToken) || - visitNode(cbNode, (node).typeParameter) || - visitNode(cbNode, (node).questionToken) || - visitNode(cbNode, (node).type); - case SyntaxKind.LiteralType: - return visitNode(cbNode, (node).literal); - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ArrayBindingPattern: - return visitNodes(cbNode, cbNodes, (node).elements); - case SyntaxKind.ArrayLiteralExpression: - return visitNodes(cbNode, cbNodes, (node).elements); - case SyntaxKind.ObjectLiteralExpression: - return visitNodes(cbNode, cbNodes, (node).properties); - case SyntaxKind.PropertyAccessExpression: - return visitNode(cbNode, (node).expression) || - visitNode(cbNode, (node).questionDotToken) || - visitNode(cbNode, (node).name); - case SyntaxKind.ElementAccessExpression: - return visitNode(cbNode, (node).expression) || - visitNode(cbNode, (node).questionDotToken) || - visitNode(cbNode, (node).argumentExpression); - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - return visitNode(cbNode, (node).expression) || - visitNode(cbNode, (node).questionDotToken) || - visitNodes(cbNode, cbNodes, (node).typeArguments) || - visitNodes(cbNode, cbNodes, (node).arguments); - case SyntaxKind.TaggedTemplateExpression: - return visitNode(cbNode, (node).tag) || - visitNode(cbNode, (node).questionDotToken) || - visitNodes(cbNode, cbNodes, (node).typeArguments) || - visitNode(cbNode, (node).template); - case SyntaxKind.TypeAssertionExpression: - return visitNode(cbNode, (node).type) || - visitNode(cbNode, (node).expression); - case SyntaxKind.ParenthesizedExpression: - return visitNode(cbNode, (node).expression); - case SyntaxKind.DeleteExpression: - return visitNode(cbNode, (node).expression); - case SyntaxKind.TypeOfExpression: - return visitNode(cbNode, (node).expression); - case SyntaxKind.VoidExpression: - return visitNode(cbNode, (node).expression); - case SyntaxKind.PrefixUnaryExpression: - return visitNode(cbNode, (node).operand); - case SyntaxKind.YieldExpression: - return visitNode(cbNode, (node).asteriskToken) || - visitNode(cbNode, (node).expression); - case SyntaxKind.AwaitExpression: - return visitNode(cbNode, (node).expression); - case SyntaxKind.PostfixUnaryExpression: - return visitNode(cbNode, (node).operand); - case SyntaxKind.BinaryExpression: - return visitNode(cbNode, (node).left) || - visitNode(cbNode, (node).operatorToken) || - visitNode(cbNode, (node).right); - case SyntaxKind.AsExpression: - return visitNode(cbNode, (node).expression) || - visitNode(cbNode, (node).type); - case SyntaxKind.NonNullExpression: - return visitNode(cbNode, (node).expression); - case SyntaxKind.MetaProperty: - return visitNode(cbNode, (node).name); - case SyntaxKind.ConditionalExpression: - return visitNode(cbNode, (node).condition) || - visitNode(cbNode, (node).questionToken) || - visitNode(cbNode, (node).whenTrue) || - visitNode(cbNode, (node).colonToken) || - visitNode(cbNode, (node).whenFalse); - case SyntaxKind.SpreadElement: - return visitNode(cbNode, (node).expression); - case SyntaxKind.Block: - case SyntaxKind.ModuleBlock: - return visitNodes(cbNode, cbNodes, (node).statements); - case SyntaxKind.SourceFile: - return visitNodes(cbNode, cbNodes, (node).statements) || - visitNode(cbNode, (node).endOfFileToken); - case SyntaxKind.VariableStatement: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node).declarationList); - case SyntaxKind.VariableDeclarationList: - return visitNodes(cbNode, cbNodes, (node).declarations); - case SyntaxKind.ExpressionStatement: - return visitNode(cbNode, (node).expression); - case SyntaxKind.IfStatement: - return visitNode(cbNode, (node).expression) || - visitNode(cbNode, (node).thenStatement) || - visitNode(cbNode, (node).elseStatement); - case SyntaxKind.DoStatement: - return visitNode(cbNode, (node).statement) || - visitNode(cbNode, (node).expression); - case SyntaxKind.WhileStatement: - return visitNode(cbNode, (node).expression) || - visitNode(cbNode, (node).statement); - case SyntaxKind.ForStatement: - return visitNode(cbNode, (node).initializer) || - visitNode(cbNode, (node).condition) || - visitNode(cbNode, (node).incrementor) || - visitNode(cbNode, (node).statement); - case SyntaxKind.ForInStatement: - return visitNode(cbNode, (node).initializer) || - visitNode(cbNode, (node).expression) || - visitNode(cbNode, (node).statement); - case SyntaxKind.ForOfStatement: - return visitNode(cbNode, (node).awaitModifier) || - visitNode(cbNode, (node).initializer) || - visitNode(cbNode, (node).expression) || - visitNode(cbNode, (node).statement); - case SyntaxKind.ContinueStatement: - case SyntaxKind.BreakStatement: - return visitNode(cbNode, (node).label); - case SyntaxKind.ReturnStatement: - return visitNode(cbNode, (node).expression); - case SyntaxKind.WithStatement: - return visitNode(cbNode, (node).expression) || - visitNode(cbNode, (node).statement); - case SyntaxKind.SwitchStatement: - return visitNode(cbNode, (node).expression) || - visitNode(cbNode, (node).caseBlock); - case SyntaxKind.CaseBlock: - return visitNodes(cbNode, cbNodes, (node).clauses); - case SyntaxKind.CaseClause: - return visitNode(cbNode, (node).expression) || - visitNodes(cbNode, cbNodes, (node).statements); - case SyntaxKind.DefaultClause: - return visitNodes(cbNode, cbNodes, (node).statements); - case SyntaxKind.LabeledStatement: - return visitNode(cbNode, (node).label) || - visitNode(cbNode, (node).statement); - case SyntaxKind.ThrowStatement: - return visitNode(cbNode, (node).expression); - case SyntaxKind.TryStatement: - return visitNode(cbNode, (node).tryBlock) || - visitNode(cbNode, (node).catchClause) || - visitNode(cbNode, (node).finallyBlock); - case SyntaxKind.CatchClause: - return visitNode(cbNode, (node).variableDeclaration) || - visitNode(cbNode, (node).block); - case SyntaxKind.Decorator: - return visitNode(cbNode, (node).expression); - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node).name) || - visitNodes(cbNode, cbNodes, (node).typeParameters) || - visitNodes(cbNode, cbNodes, (node).heritageClauses) || - visitNodes(cbNode, cbNodes, (node).members); - case SyntaxKind.InterfaceDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node).name) || - visitNodes(cbNode, cbNodes, (node).typeParameters) || - visitNodes(cbNode, cbNodes, (node).heritageClauses) || - visitNodes(cbNode, cbNodes, (node).members); - case SyntaxKind.TypeAliasDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node).name) || - visitNodes(cbNode, cbNodes, (node).typeParameters) || - visitNode(cbNode, (node).type); - case SyntaxKind.EnumDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node).name) || - visitNodes(cbNode, cbNodes, (node).members); - case SyntaxKind.EnumMember: - return visitNode(cbNode, (node).name) || - visitNode(cbNode, (node).initializer); - case SyntaxKind.ModuleDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node).name) || - visitNode(cbNode, (node).body); - case SyntaxKind.ImportEqualsDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node).name) || - visitNode(cbNode, (node).moduleReference); - case SyntaxKind.ImportDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node).importClause) || - visitNode(cbNode, (node).moduleSpecifier); - case SyntaxKind.ImportClause: - return visitNode(cbNode, (node).name) || - visitNode(cbNode, (node).namedBindings); - case SyntaxKind.NamespaceExportDeclaration: - return visitNode(cbNode, (node).name); - - case SyntaxKind.NamespaceImport: - return visitNode(cbNode, (node).name); - case SyntaxKind.NamedImports: - case SyntaxKind.NamedExports: - return visitNodes(cbNode, cbNodes, (node).elements); - case SyntaxKind.ExportDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node).exportClause) || - visitNode(cbNode, (node).moduleSpecifier); - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: - return visitNode(cbNode, (node).propertyName) || - visitNode(cbNode, (node).name); - case SyntaxKind.ExportAssignment: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node).expression); - case SyntaxKind.TemplateExpression: - return visitNode(cbNode, (node).head) || visitNodes(cbNode, cbNodes, (node).templateSpans); - case SyntaxKind.TemplateSpan: - return visitNode(cbNode, (node).expression) || visitNode(cbNode, (node).literal); - case SyntaxKind.ComputedPropertyName: - return visitNode(cbNode, (node).expression); - case SyntaxKind.HeritageClause: - return visitNodes(cbNode, cbNodes, (node).types); - case SyntaxKind.ExpressionWithTypeArguments: - return visitNode(cbNode, (node).expression) || - visitNodes(cbNode, cbNodes, (node).typeArguments); - case SyntaxKind.ExternalModuleReference: - return visitNode(cbNode, (node).expression); - case SyntaxKind.MissingDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators); - case SyntaxKind.CommaListExpression: - return visitNodes(cbNode, cbNodes, (node).elements); - - case SyntaxKind.JsxElement: - return visitNode(cbNode, (node).openingElement) || - visitNodes(cbNode, cbNodes, (node).children) || - visitNode(cbNode, (node).closingElement); - case SyntaxKind.JsxFragment: - return visitNode(cbNode, (node).openingFragment) || - visitNodes(cbNode, cbNodes, (node).children) || - visitNode(cbNode, (node).closingFragment); - case SyntaxKind.JsxSelfClosingElement: - case SyntaxKind.JsxOpeningElement: - return visitNode(cbNode, (node).tagName) || - visitNodes(cbNode, cbNodes, (node).typeArguments) || - visitNode(cbNode, (node).attributes); - case SyntaxKind.JsxAttributes: - return visitNodes(cbNode, cbNodes, (node).properties); - case SyntaxKind.JsxAttribute: - return visitNode(cbNode, (node).name) || - visitNode(cbNode, (node).initializer); - case SyntaxKind.JsxSpreadAttribute: - return visitNode(cbNode, (node).expression); - case SyntaxKind.JsxExpression: - return visitNode(cbNode, (node as JsxExpression).dotDotDotToken) || - visitNode(cbNode, (node as JsxExpression).expression); - case SyntaxKind.JsxClosingElement: - return visitNode(cbNode, (node).tagName); - - case SyntaxKind.OptionalType: - case SyntaxKind.RestType: - case SyntaxKind.JSDocTypeExpression: - case SyntaxKind.JSDocNonNullableType: - case SyntaxKind.JSDocNullableType: - case SyntaxKind.JSDocOptionalType: - case SyntaxKind.JSDocVariadicType: - return visitNode(cbNode, (node).type); - case SyntaxKind.JSDocFunctionType: - return visitNodes(cbNode, cbNodes, (node).parameters) || - visitNode(cbNode, (node).type); - case SyntaxKind.JSDocComment: - return visitNodes(cbNode, cbNodes, (node).tags); - case SyntaxKind.JSDocParameterTag: - case SyntaxKind.JSDocPropertyTag: - return visitNode(cbNode, (node as JSDocTag).tagName) || - ((node as JSDocPropertyLikeTag).isNameFirst - ? visitNode(cbNode, (node).name) || - visitNode(cbNode, (node).typeExpression) - : visitNode(cbNode, (node).typeExpression) || - visitNode(cbNode, (node).name)); - case SyntaxKind.JSDocAuthorTag: - return visitNode(cbNode, (node as JSDocTag).tagName); - case SyntaxKind.JSDocAugmentsTag: - return visitNode(cbNode, (node as JSDocTag).tagName) || - visitNode(cbNode, (node).class); - case SyntaxKind.JSDocTemplateTag: - return visitNode(cbNode, (node as JSDocTag).tagName) || - visitNode(cbNode, (node).constraint) || - visitNodes(cbNode, cbNodes, (node).typeParameters); - case SyntaxKind.JSDocTypedefTag: - return visitNode(cbNode, (node as JSDocTag).tagName) || - ((node as JSDocTypedefTag).typeExpression && - (node as JSDocTypedefTag).typeExpression!.kind === SyntaxKind.JSDocTypeExpression - ? visitNode(cbNode, (node).typeExpression) || - visitNode(cbNode, (node).fullName) - : visitNode(cbNode, (node).fullName) || - visitNode(cbNode, (node).typeExpression)); - case SyntaxKind.JSDocCallbackTag: - return visitNode(cbNode, (node as JSDocTag).tagName) || - visitNode(cbNode, (node as JSDocCallbackTag).fullName) || - visitNode(cbNode, (node as JSDocCallbackTag).typeExpression); - case SyntaxKind.JSDocReturnTag: - case SyntaxKind.JSDocTypeTag: - case SyntaxKind.JSDocThisTag: - case SyntaxKind.JSDocEnumTag: - return visitNode(cbNode, (node as JSDocTag).tagName) || - visitNode(cbNode, (node as JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag).typeExpression); - case SyntaxKind.JSDocSignature: - return forEach((node).typeParameters, cbNode) || - forEach((node).parameters, cbNode) || - visitNode(cbNode, (node).type); - case SyntaxKind.JSDocTypeLiteral: - return forEach((node as JSDocTypeLiteral).jsDocPropertyTags, cbNode); - case SyntaxKind.JSDocTag: - case SyntaxKind.JSDocClassTag: - return visitNode(cbNode, (node as JSDocTag).tagName); - case SyntaxKind.PartiallyEmittedExpression: - return visitNode(cbNode, (node).expression); - } - } - - export function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes = false, scriptKind?: ScriptKind): SourceFile { - performance.mark("beforeParse"); - let result: SourceFile; - - perfLogger.logStartParseSourceFile(fileName); - if (languageVersion === ScriptTarget.JSON) { - result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, ScriptKind.JSON); - } - else { - result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, scriptKind); - } - perfLogger.logStopParseSourceFile(); - - performance.mark("afterParse"); - performance.measure("Parse", "beforeParse", "afterParse"); - return result; + switch (node.kind) { + case SyntaxKind.QualifiedName: + return visitNode(cbNode, (node).left) || + visitNode(cbNode, (node).right); + case SyntaxKind.TypeParameter: + return visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).constraint) || + visitNode(cbNode, (node).default) || + visitNode(cbNode, (node).expression); + case SyntaxKind.ShorthandPropertyAssignment: + return visitNodes(cbNode, cbNodes, node.decorators) || + 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); + case SyntaxKind.SpreadAssignment: + return visitNode(cbNode, (node).expression); + case SyntaxKind.Parameter: + return visitNodes(cbNode, cbNodes, node.decorators) || + 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); + case SyntaxKind.PropertyDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + 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); + case SyntaxKind.PropertySignature: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).questionToken) || + visitNode(cbNode, (node).type) || + visitNode(cbNode, (node).initializer); + case SyntaxKind.PropertyAssignment: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).questionToken) || + visitNode(cbNode, (node).initializer); + case SyntaxKind.VariableDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).exclamationToken) || + visitNode(cbNode, (node).type) || + visitNode(cbNode, (node).initializer); + case SyntaxKind.BindingElement: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node).dotDotDotToken) || + visitNode(cbNode, (node).propertyName) || + visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).initializer); + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNodes(cbNode, cbNodes, (node).typeParameters) || + visitNodes(cbNode, cbNodes, (node).parameters) || + visitNode(cbNode, (node).type); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ArrowFunction: + return visitNodes(cbNode, cbNodes, node.decorators) || + 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).equalsGreaterThanToken) || + visitNode(cbNode, (node).body); + case SyntaxKind.TypeReference: + return visitNode(cbNode, (node).typeName) || + visitNodes(cbNode, cbNodes, (node).typeArguments); + case SyntaxKind.TypePredicate: + return visitNode(cbNode, (node).assertsModifier) || + visitNode(cbNode, (node).parameterName) || + visitNode(cbNode, (node).type); + case SyntaxKind.TypeQuery: + return visitNode(cbNode, (node).exprName); + case SyntaxKind.TypeLiteral: + return visitNodes(cbNode, cbNodes, (node).members); + case SyntaxKind.ArrayType: + return visitNode(cbNode, (node).elementType); + case SyntaxKind.TupleType: + return visitNodes(cbNode, cbNodes, (node).elementTypes); + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + return visitNodes(cbNode, cbNodes, (node).types); + case SyntaxKind.ConditionalType: + return visitNode(cbNode, (node).checkType) || + visitNode(cbNode, (node).extendsType) || + visitNode(cbNode, (node).trueType) || + visitNode(cbNode, (node).falseType); + case SyntaxKind.InferType: + return visitNode(cbNode, (node).typeParameter); + case SyntaxKind.ImportType: + return visitNode(cbNode, (node).argument) || + visitNode(cbNode, (node).qualifier) || + visitNodes(cbNode, cbNodes, (node).typeArguments); + case SyntaxKind.ParenthesizedType: + case SyntaxKind.TypeOperator: + return visitNode(cbNode, (node).type); + case SyntaxKind.IndexedAccessType: + return visitNode(cbNode, (node).objectType) || + visitNode(cbNode, (node).indexType); + case SyntaxKind.MappedType: + return visitNode(cbNode, (node).readonlyToken) || + visitNode(cbNode, (node).typeParameter) || + visitNode(cbNode, (node).questionToken) || + visitNode(cbNode, (node).type); + case SyntaxKind.LiteralType: + return visitNode(cbNode, (node).literal); + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + return visitNodes(cbNode, cbNodes, (node).elements); + case SyntaxKind.ArrayLiteralExpression: + return visitNodes(cbNode, cbNodes, (node).elements); + case SyntaxKind.ObjectLiteralExpression: + return visitNodes(cbNode, cbNodes, (node).properties); + case SyntaxKind.PropertyAccessExpression: + return visitNode(cbNode, (node).expression) || + visitNode(cbNode, (node).questionDotToken) || + visitNode(cbNode, (node).name); + case SyntaxKind.ElementAccessExpression: + return visitNode(cbNode, (node).expression) || + visitNode(cbNode, (node).questionDotToken) || + visitNode(cbNode, (node).argumentExpression); + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + return visitNode(cbNode, (node).expression) || + visitNode(cbNode, (node).questionDotToken) || + visitNodes(cbNode, cbNodes, (node).typeArguments) || + visitNodes(cbNode, cbNodes, (node).arguments); + case SyntaxKind.TaggedTemplateExpression: + return visitNode(cbNode, (node).tag) || + visitNode(cbNode, (node).questionDotToken) || + visitNodes(cbNode, cbNodes, (node).typeArguments) || + visitNode(cbNode, (node).template); + case SyntaxKind.TypeAssertionExpression: + return visitNode(cbNode, (node).type) || + visitNode(cbNode, (node).expression); + case SyntaxKind.ParenthesizedExpression: + return visitNode(cbNode, (node).expression); + case SyntaxKind.DeleteExpression: + return visitNode(cbNode, (node).expression); + case SyntaxKind.TypeOfExpression: + return visitNode(cbNode, (node).expression); + case SyntaxKind.VoidExpression: + return visitNode(cbNode, (node).expression); + case SyntaxKind.PrefixUnaryExpression: + return visitNode(cbNode, (node).operand); + case SyntaxKind.YieldExpression: + return visitNode(cbNode, (node).asteriskToken) || + visitNode(cbNode, (node).expression); + case SyntaxKind.AwaitExpression: + return visitNode(cbNode, (node).expression); + case SyntaxKind.PostfixUnaryExpression: + return visitNode(cbNode, (node).operand); + case SyntaxKind.BinaryExpression: + return visitNode(cbNode, (node).left) || + visitNode(cbNode, (node).operatorToken) || + visitNode(cbNode, (node).right); + case SyntaxKind.AsExpression: + return visitNode(cbNode, (node).expression) || + visitNode(cbNode, (node).type); + case SyntaxKind.NonNullExpression: + return visitNode(cbNode, (node).expression); + case SyntaxKind.MetaProperty: + return visitNode(cbNode, (node).name); + case SyntaxKind.ConditionalExpression: + return visitNode(cbNode, (node).condition) || + visitNode(cbNode, (node).questionToken) || + visitNode(cbNode, (node).whenTrue) || + visitNode(cbNode, (node).colonToken) || + visitNode(cbNode, (node).whenFalse); + case SyntaxKind.SpreadElement: + return visitNode(cbNode, (node).expression); + case SyntaxKind.Block: + case SyntaxKind.ModuleBlock: + return visitNodes(cbNode, cbNodes, (node).statements); + case SyntaxKind.SourceFile: + return visitNodes(cbNode, cbNodes, (node).statements) || + visitNode(cbNode, (node).endOfFileToken); + case SyntaxKind.VariableStatement: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node).declarationList); + case SyntaxKind.VariableDeclarationList: + return visitNodes(cbNode, cbNodes, (node).declarations); + case SyntaxKind.ExpressionStatement: + return visitNode(cbNode, (node).expression); + case SyntaxKind.IfStatement: + return visitNode(cbNode, (node).expression) || + visitNode(cbNode, (node).thenStatement) || + visitNode(cbNode, (node).elseStatement); + case SyntaxKind.DoStatement: + return visitNode(cbNode, (node).statement) || + visitNode(cbNode, (node).expression); + case SyntaxKind.WhileStatement: + return visitNode(cbNode, (node).expression) || + visitNode(cbNode, (node).statement); + case SyntaxKind.ForStatement: + return visitNode(cbNode, (node).initializer) || + visitNode(cbNode, (node).condition) || + visitNode(cbNode, (node).incrementor) || + visitNode(cbNode, (node).statement); + case SyntaxKind.ForInStatement: + return visitNode(cbNode, (node).initializer) || + visitNode(cbNode, (node).expression) || + visitNode(cbNode, (node).statement); + case SyntaxKind.ForOfStatement: + return visitNode(cbNode, (node).awaitModifier) || + visitNode(cbNode, (node).initializer) || + visitNode(cbNode, (node).expression) || + visitNode(cbNode, (node).statement); + case SyntaxKind.ContinueStatement: + case SyntaxKind.BreakStatement: + return visitNode(cbNode, (node).label); + case SyntaxKind.ReturnStatement: + return visitNode(cbNode, (node).expression); + case SyntaxKind.WithStatement: + return visitNode(cbNode, (node).expression) || + visitNode(cbNode, (node).statement); + case SyntaxKind.SwitchStatement: + return visitNode(cbNode, (node).expression) || + visitNode(cbNode, (node).caseBlock); + case SyntaxKind.CaseBlock: + return visitNodes(cbNode, cbNodes, (node).clauses); + case SyntaxKind.CaseClause: + return visitNode(cbNode, (node).expression) || + visitNodes(cbNode, cbNodes, (node).statements); + case SyntaxKind.DefaultClause: + return visitNodes(cbNode, cbNodes, (node).statements); + case SyntaxKind.LabeledStatement: + return visitNode(cbNode, (node).label) || + visitNode(cbNode, (node).statement); + case SyntaxKind.ThrowStatement: + return visitNode(cbNode, (node).expression); + case SyntaxKind.TryStatement: + return visitNode(cbNode, (node).tryBlock) || + visitNode(cbNode, (node).catchClause) || + visitNode(cbNode, (node).finallyBlock); + case SyntaxKind.CatchClause: + return visitNode(cbNode, (node).variableDeclaration) || + visitNode(cbNode, (node).block); + case SyntaxKind.Decorator: + return visitNode(cbNode, (node).expression); + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node).name) || + visitNodes(cbNode, cbNodes, (node).typeParameters) || + visitNodes(cbNode, cbNodes, (node).heritageClauses) || + visitNodes(cbNode, cbNodes, (node).members); + case SyntaxKind.InterfaceDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node).name) || + visitNodes(cbNode, cbNodes, (node).typeParameters) || + visitNodes(cbNode, cbNodes, (node).heritageClauses) || + visitNodes(cbNode, cbNodes, (node).members); + case SyntaxKind.TypeAliasDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node).name) || + visitNodes(cbNode, cbNodes, (node).typeParameters) || + visitNode(cbNode, (node).type); + case SyntaxKind.EnumDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node).name) || + visitNodes(cbNode, cbNodes, (node).members); + case SyntaxKind.EnumMember: + return visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).initializer); + case SyntaxKind.ModuleDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).body); + case SyntaxKind.ImportEqualsDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).moduleReference); + case SyntaxKind.ImportDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node).importClause) || + visitNode(cbNode, (node).moduleSpecifier); + case SyntaxKind.ImportClause: + return visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).namedBindings); + case SyntaxKind.NamespaceExportDeclaration: + return visitNode(cbNode, (node).name); + case SyntaxKind.NamespaceImport: + return visitNode(cbNode, (node).name); + case SyntaxKind.NamedImports: + case SyntaxKind.NamedExports: + return visitNodes(cbNode, cbNodes, (node).elements); + case SyntaxKind.ExportDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node).exportClause) || + visitNode(cbNode, (node).moduleSpecifier); + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: + return visitNode(cbNode, (node).propertyName) || + visitNode(cbNode, (node).name); + case SyntaxKind.ExportAssignment: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node).expression); + case SyntaxKind.TemplateExpression: + return visitNode(cbNode, (node).head) || visitNodes(cbNode, cbNodes, (node).templateSpans); + case SyntaxKind.TemplateSpan: + return visitNode(cbNode, (node).expression) || visitNode(cbNode, (node).literal); + case SyntaxKind.ComputedPropertyName: + return visitNode(cbNode, (node).expression); + case SyntaxKind.HeritageClause: + return visitNodes(cbNode, cbNodes, (node).types); + case SyntaxKind.ExpressionWithTypeArguments: + return visitNode(cbNode, (node).expression) || + visitNodes(cbNode, cbNodes, (node).typeArguments); + case SyntaxKind.ExternalModuleReference: + return visitNode(cbNode, (node).expression); + case SyntaxKind.MissingDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators); + case SyntaxKind.CommaListExpression: + return visitNodes(cbNode, cbNodes, (node).elements); + case SyntaxKind.JsxElement: + return visitNode(cbNode, (node).openingElement) || + visitNodes(cbNode, cbNodes, (node).children) || + visitNode(cbNode, (node).closingElement); + case SyntaxKind.JsxFragment: + return visitNode(cbNode, (node).openingFragment) || + visitNodes(cbNode, cbNodes, (node).children) || + visitNode(cbNode, (node).closingFragment); + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxOpeningElement: + return visitNode(cbNode, (node).tagName) || + visitNodes(cbNode, cbNodes, (node).typeArguments) || + visitNode(cbNode, (node).attributes); + case SyntaxKind.JsxAttributes: + return visitNodes(cbNode, cbNodes, (node).properties); + case SyntaxKind.JsxAttribute: + return visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).initializer); + case SyntaxKind.JsxSpreadAttribute: + return visitNode(cbNode, (node).expression); + case SyntaxKind.JsxExpression: + return visitNode(cbNode, (node as JsxExpression).dotDotDotToken) || + visitNode(cbNode, (node as JsxExpression).expression); + case SyntaxKind.JsxClosingElement: + return visitNode(cbNode, (node).tagName); + case SyntaxKind.OptionalType: + case SyntaxKind.RestType: + case SyntaxKind.JSDocTypeExpression: + case SyntaxKind.JSDocNonNullableType: + case SyntaxKind.JSDocNullableType: + case SyntaxKind.JSDocOptionalType: + case SyntaxKind.JSDocVariadicType: + return visitNode(cbNode, (node).type); + case SyntaxKind.JSDocFunctionType: + return visitNodes(cbNode, cbNodes, (node).parameters) || + visitNode(cbNode, (node).type); + case SyntaxKind.JSDocComment: + return visitNodes(cbNode, cbNodes, (node).tags); + case SyntaxKind.JSDocParameterTag: + case SyntaxKind.JSDocPropertyTag: + return visitNode(cbNode, (node as JSDocTag).tagName) || + ((node as JSDocPropertyLikeTag).isNameFirst + ? visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).typeExpression) + : visitNode(cbNode, (node).typeExpression) || + visitNode(cbNode, (node).name)); + case SyntaxKind.JSDocAuthorTag: + return visitNode(cbNode, (node as JSDocTag).tagName); + case SyntaxKind.JSDocAugmentsTag: + return visitNode(cbNode, (node as JSDocTag).tagName) || + visitNode(cbNode, (node).class); + case SyntaxKind.JSDocTemplateTag: + return visitNode(cbNode, (node as JSDocTag).tagName) || + visitNode(cbNode, (node).constraint) || + visitNodes(cbNode, cbNodes, (node).typeParameters); + case SyntaxKind.JSDocTypedefTag: + return visitNode(cbNode, (node as JSDocTag).tagName) || + ((node as JSDocTypedefTag).typeExpression && + (node as JSDocTypedefTag).typeExpression!.kind === SyntaxKind.JSDocTypeExpression + ? visitNode(cbNode, (node).typeExpression) || + visitNode(cbNode, (node).fullName) + : visitNode(cbNode, (node).fullName) || + visitNode(cbNode, (node).typeExpression)); + case SyntaxKind.JSDocCallbackTag: + return visitNode(cbNode, (node as JSDocTag).tagName) || + visitNode(cbNode, (node as JSDocCallbackTag).fullName) || + visitNode(cbNode, (node as JSDocCallbackTag).typeExpression); + case SyntaxKind.JSDocReturnTag: + case SyntaxKind.JSDocTypeTag: + case SyntaxKind.JSDocThisTag: + case SyntaxKind.JSDocEnumTag: + return visitNode(cbNode, (node as JSDocTag).tagName) || + visitNode(cbNode, (node as JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag).typeExpression); + case SyntaxKind.JSDocSignature: + return forEach((node).typeParameters, cbNode) || + forEach((node).parameters, cbNode) || + visitNode(cbNode, (node).type); + case SyntaxKind.JSDocTypeLiteral: + return forEach((node as JSDocTypeLiteral).jsDocPropertyTags, cbNode); + case SyntaxKind.JSDocTag: + case SyntaxKind.JSDocClassTag: + return visitNode(cbNode, (node as JSDocTag).tagName); + case SyntaxKind.PartiallyEmittedExpression: + return visitNode(cbNode, (node).expression); } - - export function parseIsolatedEntityName(text: string, languageVersion: ScriptTarget): EntityName | undefined { - return Parser.parseIsolatedEntityName(text, languageVersion); +} +export function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes = false, scriptKind?: ScriptKind): SourceFile { + mark("beforeParse"); + let result: SourceFile; + perfLogger.logStartParseSourceFile(fileName); + if (languageVersion === ScriptTarget.JSON) { + result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, ScriptKind.JSON); } - - /** - * 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): SourceFile { - 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.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; + else { + result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, scriptKind); } - - /* @internal */ - // Exposed only for testing. - export function parseJSDocTypeExpressionForTests(content: string, start?: number, length?: number) { - return Parser.JSDocParser.parseJSDocTypeExpressionForTests(content, start, length); - } - - // Implement the parser as a singleton module. We do this for perf reasons because creating - // parser instances can actually be expensive enough to impact us on projects with many source - // files. - namespace Parser { - // Share a single scanner across all calls to parse a source file. This helps speed things - // up by avoiding the cost of creating/compiling scanners over and over again. - const scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true); - const disallowInAndDecoratorContext = NodeFlags.DisallowInContext | NodeFlags.DecoratorContext; - - // capture constructors in 'initializeState' to avoid null checks - 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 SourceFileConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; - - let sourceFile: SourceFile; - let parseDiagnostics: DiagnosticWithLocation[]; - let syntaxCursor: IncrementalParser.SyntaxCursor | undefined; - - let currentToken: SyntaxKind; - let sourceText: string; - let nodeCount: number; - let identifiers: Map; - let identifierCount: number; - - let parsingContext: ParsingContext; - - let notParenthesizedArrow: Map | 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. - let contextFlags: NodeFlags; - - // Whether or not we've had a parse error since creating the last AST node. If we have - // encountered an error, it will be stored on the next AST node we create. Parse errors - // can be broken down into three categories: - // - // 1) An error that occurred during scanning. For example, an unterminated literal, or a - // character that was completely not understood. - // - // 2) A token was expected, but was not present. This type of error is commonly produced - // by the 'parseExpected' function. - // - // 3) A token was present that no parsing function was able to consume. This type of error - // only occurs in the 'abortParsingListOrMoveToNextToken' function when the parser - // decides to skip the token. - // - // In all of these cases, we want to mark the next node as having had an error before it. - // With this mark, we can know in incremental settings if this node can be reused, or if - // we have to reparse it. If we don't keep this information around, we may just reuse the - // node. in that event we would then not produce the same errors as we did before, causing - // significant confusion problems. - // - // Note: it is necessary that this value be saved/restored during speculative/lookahead - // parsing. During lookahead parsing, we will often create a node. That node will have - // this value attached, and then this value will be set back to 'false'. If we decide to - // rewind, we must get back to the same value we had prior to the lookahead. - // - // Note: any errors at the end of the file that do not precede a regular node, should get - // attached to the EOF token. - let parseErrorBeforeNextFinishedNode = false; - - export function parseSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, syntaxCursor: IncrementalParser.SyntaxCursor | undefined, setParentNodes = false, scriptKind?: ScriptKind): SourceFile { - scriptKind = ensureScriptKind(fileName, scriptKind); - if (scriptKind === ScriptKind.JSON) { - const result = parseJsonText(fileName, sourceText, languageVersion, syntaxCursor, setParentNodes); - convertToObjectWorker(result, 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; - return result; - } - - initializeState(sourceText, languageVersion, syntaxCursor, scriptKind); - - const result = parseSourceFileWorker(fileName, languageVersion, setParentNodes, scriptKind); - - clearState(); - + perfLogger.logStopParseSourceFile(); + mark("afterParse"); + measure("Parse", "beforeParse", "afterParse"); + 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): SourceFile { + 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.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); +} +// 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 { + // Share a single scanner across all calls to parse a source file. This helps speed things + // up by avoiding the cost of creating/compiling scanners over and over again. + const scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true); + const disallowInAndDecoratorContext = NodeFlags.DisallowInContext | NodeFlags.DecoratorContext; + // capture constructors in 'initializeState' to avoid null checks + 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 SourceFileConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + let sourceFile: SourceFile; + let parseDiagnostics: DiagnosticWithLocation[]; + let syntaxCursor: IncrementalParser.SyntaxCursor | undefined; + let currentToken: SyntaxKind; + let sourceText: string; + let nodeCount: number; + let identifiers: ts.Map; + let identifierCount: number; + let parsingContext: ParsingContext; + let notParenthesizedArrow: ts.Map | 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. + let contextFlags: NodeFlags; + // Whether or not we've had a parse error since creating the last AST node. If we have + // encountered an error, it will be stored on the next AST node we create. Parse errors + // can be broken down into three categories: + // + // 1) An error that occurred during scanning. For example, an unterminated literal, or a + // character that was completely not understood. + // + // 2) A token was expected, but was not present. This type of error is commonly produced + // by the 'parseExpected' function. + // + // 3) A token was present that no parsing function was able to consume. This type of error + // only occurs in the 'abortParsingListOrMoveToNextToken' function when the parser + // decides to skip the token. + // + // In all of these cases, we want to mark the next node as having had an error before it. + // With this mark, we can know in incremental settings if this node can be reused, or if + // we have to reparse it. If we don't keep this information around, we may just reuse the + // node. in that event we would then not produce the same errors as we did before, causing + // significant confusion problems. + // + // Note: it is necessary that this value be saved/restored during speculative/lookahead + // parsing. During lookahead parsing, we will often create a node. That node will have + // this value attached, and then this value will be set back to 'false'. If we decide to + // rewind, we must get back to the same value we had prior to the lookahead. + // + // Note: any errors at the end of the file that do not precede a regular node, should get + // attached to the EOF token. + let parseErrorBeforeNextFinishedNode = false; + export function parseSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, syntaxCursor: IncrementalParser.SyntaxCursor | undefined, setParentNodes = false, scriptKind?: ScriptKind): SourceFile { + scriptKind = ensureScriptKind(fileName, scriptKind); + if (scriptKind === ScriptKind.JSON) { + const result = parseJsonText(fileName, sourceText, languageVersion, syntaxCursor, setParentNodes); + convertToObjectWorker(result, 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; 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?: boolean): JsonSourceFile { - initializeState(sourceText, languageVersion, syntaxCursor, ScriptKind.JSON); - // Set source file so that errors will be reported with this file name - sourceFile = createSourceFile(fileName, ScriptTarget.ES2015, ScriptKind.JSON, /*isDeclaration*/ false); - sourceFile.flags = contextFlags; - - // Prime the scanner. - nextToken(); - const pos = getNodePos(); - if (token() === SyntaxKind.EndOfFileToken) { - sourceFile.statements = createNodeArray([], pos, pos); - sourceFile.endOfFileToken = parseTokenNode(); - } - else { - const statement = createNode(SyntaxKind.ExpressionStatement) as JsonObjectExpressionStatement; - switch (token()) { - case SyntaxKind.OpenBracketToken: - statement.expression = parseArrayLiteralExpression(); - break; - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.NullKeyword: - statement.expression = parseTokenNode(); - break; - case SyntaxKind.MinusToken: - if (lookAhead(() => nextToken() === SyntaxKind.NumericLiteral && nextToken() !== SyntaxKind.ColonToken)) { - statement.expression = parsePrefixUnaryExpression() as JsonMinusNumericLiteral; - } - else { - statement.expression = parseObjectLiteralExpression(); - } - break; - case SyntaxKind.NumericLiteral: - case SyntaxKind.StringLiteral: - if (lookAhead(() => nextToken() !== SyntaxKind.ColonToken)) { - statement.expression = parseLiteralNode() as StringLiteral | NumericLiteral; - break; - } - // falls through - default: - statement.expression = parseObjectLiteralExpression(); - break; - } - finishNode(statement); - sourceFile.statements = createNodeArray([statement], pos); - sourceFile.endOfFileToken = parseExpectedToken(SyntaxKind.EndOfFileToken, Diagnostics.Unexpected_token); - } - - if (setParentNodes) { - fixupParentReferences(sourceFile); - } - - sourceFile.nodeCount = nodeCount; - sourceFile.identifierCount = identifierCount; - sourceFile.identifiers = identifiers; - sourceFile.parseDiagnostics = parseDiagnostics; - - const result = sourceFile as JsonSourceFile; - clearState(); - return result; + initializeState(sourceText, languageVersion, syntaxCursor, scriptKind); + const result = parseSourceFileWorker(fileName, languageVersion, setParentNodes, scriptKind); + 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?: boolean): JsonSourceFile { + initializeState(sourceText, languageVersion, syntaxCursor, ScriptKind.JSON); + // Set source file so that errors will be reported with this file name + sourceFile = createSourceFile(fileName, ScriptTarget.ES2015, ScriptKind.JSON, /*isDeclaration*/ false); + sourceFile.flags = contextFlags; + // Prime the scanner. + nextToken(); + const pos = getNodePos(); + if (token() === SyntaxKind.EndOfFileToken) { + sourceFile.statements = createNodeArray([], pos, pos); + sourceFile.endOfFileToken = parseTokenNode(); } - - function getLanguageVariant(scriptKind: ScriptKind) { - // .tsx and .jsx files are treated as jsx language variant. - return scriptKind === ScriptKind.TSX || scriptKind === ScriptKind.JSX || scriptKind === ScriptKind.JS || scriptKind === ScriptKind.JSON ? LanguageVariant.JSX : LanguageVariant.Standard; - } - - function initializeState(_sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor | undefined, scriptKind: ScriptKind) { - NodeConstructor = objectAllocator.getNodeConstructor(); - TokenConstructor = objectAllocator.getTokenConstructor(); - IdentifierConstructor = objectAllocator.getIdentifierConstructor(); - SourceFileConstructor = objectAllocator.getSourceFileConstructor(); - - sourceText = _sourceText; - syntaxCursor = _syntaxCursor; - - parseDiagnostics = []; - parsingContext = 0; - identifiers = createMap(); - identifierCount = 0; - nodeCount = 0; - - switch (scriptKind) { - case ScriptKind.JS: - case ScriptKind.JSX: - contextFlags = NodeFlags.JavaScriptFile; + else { + const statement = (createNode(SyntaxKind.ExpressionStatement) as JsonObjectExpressionStatement); + switch (token()) { + case SyntaxKind.OpenBracketToken: + statement.expression = parseArrayLiteralExpression(); + break; + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.NullKeyword: + statement.expression = parseTokenNode(); break; - case ScriptKind.JSON: - contextFlags = NodeFlags.JavaScriptFile | NodeFlags.JsonFile; + case SyntaxKind.MinusToken: + if (lookAhead(() => nextToken() === SyntaxKind.NumericLiteral && nextToken() !== SyntaxKind.ColonToken)) { + statement.expression = (parsePrefixUnaryExpression() as JsonMinusNumericLiteral); + } + else { + statement.expression = parseObjectLiteralExpression(); + } break; + case SyntaxKind.NumericLiteral: + case SyntaxKind.StringLiteral: + if (lookAhead(() => nextToken() !== SyntaxKind.ColonToken)) { + statement.expression = (parseLiteralNode() as StringLiteral | NumericLiteral); + break; + } + // falls through default: - contextFlags = NodeFlags.None; + statement.expression = parseObjectLiteralExpression(); break; } - parseErrorBeforeNextFinishedNode = false; - - // Initialize and prime the scanner before parsing the source elements. - scanner.setText(sourceText); - scanner.setOnError(scanError); - scanner.setScriptTarget(languageVersion); - scanner.setLanguageVariant(getLanguageVariant(scriptKind)); - } - - function clearState() { - // Clear out the text the scanner is pointing at, so it doesn't keep anything alive unnecessarily. - scanner.setText(""); - scanner.setOnError(undefined); - - // Clear any data. We don't want to accidentally hold onto it for too long. - parseDiagnostics = undefined!; - sourceFile = undefined!; - identifiers = undefined!; - syntaxCursor = undefined; - sourceText = undefined!; - notParenthesizedArrow = undefined!; - } - - function parseSourceFileWorker(fileName: string, languageVersion: ScriptTarget, setParentNodes: boolean, scriptKind: ScriptKind): SourceFile { - const isDeclarationFile = isDeclarationFileName(fileName); - if (isDeclarationFile) { - contextFlags |= NodeFlags.Ambient; - } - - sourceFile = createSourceFile(fileName, languageVersion, scriptKind, isDeclarationFile); - sourceFile.flags = contextFlags; - - // Prime the scanner. - nextToken(); - // 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.statements = parseList(ParsingContext.SourceElements, parseStatement); - Debug.assert(token() === SyntaxKind.EndOfFileToken); - sourceFile.endOfFileToken = addJSDocComment(parseTokenNode()); - - setExternalModuleIndicator(sourceFile); - - sourceFile.nodeCount = nodeCount; - sourceFile.identifierCount = identifierCount; - sourceFile.identifiers = identifiers; - sourceFile.parseDiagnostics = parseDiagnostics; - - if (setParentNodes) { - fixupParentReferences(sourceFile); - } - - return sourceFile; - - function reportPragmaDiagnostic(pos: number, end: number, diagnostic: DiagnosticMessage) { - parseDiagnostics.push(createFileDiagnostic(sourceFile, pos, end, diagnostic)); - } + finishNode(statement); + sourceFile.statements = createNodeArray([statement], pos); + sourceFile.endOfFileToken = parseExpectedToken(SyntaxKind.EndOfFileToken, Diagnostics.Unexpected_token); } - - function addJSDocComment(node: T): T { - Debug.assert(!node.jsDoc); // Should only be called once per node - const jsDoc = mapDefined(getJSDocCommentRanges(node, sourceFile.text), comment => JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos)); - if (jsDoc.length) node.jsDoc = jsDoc; - return node; + if (setParentNodes) { + fixupParentReferences(sourceFile); } - - 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. - - let parent: Node = rootNode; - forEachChild(rootNode, visitNode); - return; - - function visitNode(n: Node): void { - // walk down setting parents that differ from the parent we think it should be. This - // allows us to quickly bail out of setting parents for subtrees during incremental - // parsing - if (n.parent !== parent) { - n.parent = parent; - - const saveParent = parent; - parent = n; - forEachChild(n, visitNode); - if (hasJSDocNodes(n)) { - for (const jsDoc of n.jsDoc!) { - jsDoc.parent = n; - parent = jsDoc; - forEachChild(jsDoc, visitNode); - } + sourceFile.nodeCount = nodeCount; + sourceFile.identifierCount = identifierCount; + sourceFile.identifiers = identifiers; + sourceFile.parseDiagnostics = parseDiagnostics; + const result = (sourceFile as JsonSourceFile); + clearState(); + return result; + } + function getLanguageVariant(scriptKind: ScriptKind) { + // .tsx and .jsx files are treated as jsx language variant. + return scriptKind === ScriptKind.TSX || scriptKind === ScriptKind.JSX || scriptKind === ScriptKind.JS || scriptKind === ScriptKind.JSON ? LanguageVariant.JSX : LanguageVariant.Standard; + } + function initializeState(_sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor | undefined, scriptKind: ScriptKind) { + NodeConstructor = objectAllocator.getNodeConstructor(); + TokenConstructor = objectAllocator.getTokenConstructor(); + IdentifierConstructor = objectAllocator.getIdentifierConstructor(); + SourceFileConstructor = objectAllocator.getSourceFileConstructor(); + sourceText = _sourceText; + syntaxCursor = _syntaxCursor; + parseDiagnostics = []; + parsingContext = 0; + identifiers = createMap(); + identifierCount = 0; + nodeCount = 0; + switch (scriptKind) { + case ScriptKind.JS: + case ScriptKind.JSX: + contextFlags = NodeFlags.JavaScriptFile; + break; + case ScriptKind.JSON: + contextFlags = NodeFlags.JavaScriptFile | NodeFlags.JsonFile; + break; + default: + contextFlags = NodeFlags.None; + break; + } + parseErrorBeforeNextFinishedNode = false; + // Initialize and prime the scanner before parsing the source elements. + scanner.setText(sourceText); + scanner.setOnError(scanError); + scanner.setScriptTarget(languageVersion); + scanner.setLanguageVariant(getLanguageVariant(scriptKind)); + } + function clearState() { + // Clear out the text the scanner is pointing at, so it doesn't keep anything alive unnecessarily. + scanner.setText(""); + scanner.setOnError(undefined); + // Clear any data. We don't want to accidentally hold onto it for too long. + parseDiagnostics = undefined!; + sourceFile = undefined!; + identifiers = undefined!; + syntaxCursor = undefined; + sourceText = undefined!; + notParenthesizedArrow = undefined!; + } + function parseSourceFileWorker(fileName: string, languageVersion: ScriptTarget, setParentNodes: boolean, scriptKind: ScriptKind): SourceFile { + const isDeclarationFile = isDeclarationFileName(fileName); + if (isDeclarationFile) { + contextFlags |= NodeFlags.Ambient; + } + sourceFile = createSourceFile(fileName, languageVersion, scriptKind, isDeclarationFile); + sourceFile.flags = contextFlags; + // Prime the scanner. + nextToken(); + // 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.statements = parseList(ParsingContext.SourceElements, parseStatement); + Debug.assert(token() === SyntaxKind.EndOfFileToken); + sourceFile.endOfFileToken = addJSDocComment(parseTokenNode()); + setExternalModuleIndicator(sourceFile); + sourceFile.nodeCount = nodeCount; + sourceFile.identifierCount = identifierCount; + sourceFile.identifiers = identifiers; + sourceFile.parseDiagnostics = parseDiagnostics; + if (setParentNodes) { + fixupParentReferences(sourceFile); + } + return sourceFile; + function reportPragmaDiagnostic(pos: number, end: number, diagnostic: DiagnosticMessage) { + parseDiagnostics.push(createFileDiagnostic(sourceFile, pos, end, diagnostic)); + } + } + function addJSDocComment(node: T): T { + Debug.assert(!node.jsDoc); // Should only be called once per node + const jsDoc = mapDefined(getJSDocCommentRanges(node, sourceFile.text), comment => JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos)); + if (jsDoc.length) + node.jsDoc = jsDoc; + 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. + let parent: Node = rootNode; + forEachChild(rootNode, visitNode); + return; + function visitNode(n: Node): void { + // walk down setting parents that differ from the parent we think it should be. This + // allows us to quickly bail out of setting parents for subtrees during incremental + // parsing + if (n.parent !== parent) { + n.parent = parent; + const saveParent = parent; + parent = n; + forEachChild(n, visitNode); + if (hasJSDocNodes(n)) { + for (const jsDoc of n.jsDoc!) { + jsDoc.parent = n; + parent = jsDoc; + forEachChild(jsDoc, visitNode); } - parent = saveParent; } + parent = saveParent; } } - - function createSourceFile(fileName: string, languageVersion: ScriptTarget, scriptKind: ScriptKind, isDeclarationFile: boolean): 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 - const sourceFile = new SourceFileConstructor(SyntaxKind.SourceFile, /*pos*/ 0, /* end */ sourceText.length); - nodeCount++; - - sourceFile.text = sourceText; - sourceFile.bindDiagnostics = []; - sourceFile.bindSuggestionDiagnostics = undefined; - sourceFile.languageVersion = languageVersion; - sourceFile.fileName = normalizePath(fileName); - sourceFile.languageVariant = getLanguageVariant(scriptKind); - sourceFile.isDeclarationFile = isDeclarationFile; - sourceFile.scriptKind = scriptKind; - - return sourceFile; + } + function createSourceFile(fileName: string, languageVersion: ScriptTarget, scriptKind: ScriptKind, isDeclarationFile: boolean): 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 + const sourceFile = (new SourceFileConstructor(SyntaxKind.SourceFile, /*pos*/ 0, /* end */ sourceText.length)); + nodeCount++; + sourceFile.text = sourceText; + sourceFile.bindDiagnostics = []; + sourceFile.bindSuggestionDiagnostics = undefined; + sourceFile.languageVersion = languageVersion; + sourceFile.fileName = normalizePath(fileName); + sourceFile.languageVariant = getLanguageVariant(scriptKind); + sourceFile.isDeclarationFile = isDeclarationFile; + sourceFile.scriptKind = scriptKind; + return sourceFile; + } + function setContextFlag(val: boolean, flag: NodeFlags) { + if (val) { + contextFlags |= flag; } - - function setContextFlag(val: boolean, flag: NodeFlags) { - if (val) { - contextFlags |= flag; - } - else { - contextFlags &= ~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 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 doInYieldContext(func: () => T): T { - return doInsideOfContext(NodeFlags.YieldContext, func); - } - - function doInDecoratorContext(func: () => T): T { - 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 inYieldContext() { - return inContext(NodeFlags.YieldContext); - } - - function inDisallowInContext() { - return inContext(NodeFlags.DisallowInContext); - } - - function inDecoratorContext() { - return inContext(NodeFlags.DecoratorContext); - } - - function inAwaitContext() { - return inContext(NodeFlags.AwaitContext); - } - - function parseErrorAtCurrentToken(message: DiagnosticMessage, arg0?: any): void { - parseErrorAt(scanner.getTokenPos(), scanner.getTextPos(), message, arg0); - } - - function parseErrorAtPosition(start: number, length: number, message: DiagnosticMessage, arg0?: any): void { - // Don't report another error if it would just be at the same position as the last error. - const lastError = lastOrUndefined(parseDiagnostics); - if (!lastError || start !== lastError.start) { - parseDiagnostics.push(createFileDiagnostic(sourceFile, start, length, message, arg0)); - } - - // Mark that we've encountered an error. We'll set an appropriate bit on the next - // node we finish so that it can't be reused incrementally. - parseErrorBeforeNextFinishedNode = true; - } - - function parseErrorAt(start: number, end: number, message: DiagnosticMessage, arg0?: any): void { - 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(); - } - - // 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 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(): SyntaxKind { - return currentToken = scanner.reScanTemplateToken(); - } - - function reScanLessThanToken(): SyntaxKind { - return currentToken = scanner.reScanLessThanToken(); - } - - 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, isLookAhead: boolean): T { - // Keep track of the state we'll need to rollback to if lookahead fails (or if the - // caller asked us to always reset our state). - 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 = isLookAhead - ? scanner.lookAhead(callback) - : scanner.tryScan(callback); - - Debug.assert(saveContextFlags === contextFlags); - - // If our callback returned something 'falsy' or we're just looking ahead, - // then unconditionally restore us to where we were. - if (!result || isLookAhead) { - currentToken = saveToken; - parseDiagnostics.length = saveParseDiagnosticsLength; - parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode; - } - + else { + contextFlags &= ~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 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; } - - /** 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, /*isLookAhead*/ true); - } - - /** Invokes the provided callback. If the callback returns something falsy, then it restores - * the parser to the state it was in immediately prior to invoking the callback. If the - * callback returns something truthy, then the parser state is not rolled back. The result - * of invoking the callback is returned from this function. - */ - function tryParse(callback: () => T): T { - return speculationHelper(callback, /*isLookAhead*/ false); + // 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; } - - // 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; + // 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 doInYieldContext(func: () => T): T { + return doInsideOfContext(NodeFlags.YieldContext, func); + } + function doInDecoratorContext(func: () => T): T { + 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 inYieldContext() { + return inContext(NodeFlags.YieldContext); + } + function inDisallowInContext() { + return inContext(NodeFlags.DisallowInContext); + } + function inDecoratorContext() { + return inContext(NodeFlags.DecoratorContext); + } + function inAwaitContext() { + return inContext(NodeFlags.AwaitContext); + } + function parseErrorAtCurrentToken(message: DiagnosticMessage, arg0?: any): void { + parseErrorAt(scanner.getTokenPos(), scanner.getTextPos(), message, arg0); + } + function parseErrorAtPosition(start: number, length: number, message: DiagnosticMessage, arg0?: any): void { + // Don't report another error if it would just be at the same position as the last error. + const lastError = lastOrUndefined(parseDiagnostics); + if (!lastError || start !== lastError.start) { + parseDiagnostics.push(createFileDiagnostic(sourceFile, start, length, message, arg0)); + } + // Mark that we've encountered an error. We'll set an appropriate bit on the next + // node we finish so that it can't be reused incrementally. + parseErrorBeforeNextFinishedNode = true; + } + function parseErrorAt(start: number, end: number, message: DiagnosticMessage, arg0?: any): void { + 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(); + } + // 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 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); } - - function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage, shouldAdvance = true): boolean { - if (token() === kind) { - if (shouldAdvance) { - nextToken(); - } - return true; - } - - // Report specific message if provided with one. Otherwise, report generic fallback message. - if (diagnosticMessage) { - parseErrorAtCurrentToken(diagnosticMessage); - } - else { - parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(kind)); - } + return nextTokenWithoutCheck(); + } + function nextTokenJSDoc(): JSDocSyntaxKind { + return currentToken = scanner.scanJsDocToken(); + } + function reScanGreaterToken(): SyntaxKind { + return currentToken = scanner.reScanGreaterToken(); + } + function reScanSlashToken(): SyntaxKind { + return currentToken = scanner.reScanSlashToken(); + } + function reScanTemplateToken(): SyntaxKind { + return currentToken = scanner.reScanTemplateToken(); + } + function reScanLessThanToken(): SyntaxKind { + return currentToken = scanner.reScanLessThanToken(); + } + 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, isLookAhead: boolean): T { + // Keep track of the state we'll need to rollback to if lookahead fails (or if the + // caller asked us to always reset our state). + 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 = isLookAhead + ? scanner.lookAhead(callback) + : scanner.tryScan(callback); + Debug.assert(saveContextFlags === contextFlags); + // If our callback returned something 'falsy' or we're just looking ahead, + // then unconditionally restore us to where we were. + if (!result || isLookAhead) { + currentToken = saveToken; + parseDiagnostics.length = saveParseDiagnosticsLength; + parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode; + } + return result; + } + /** Invokes the provided callback then unconditionally restores the parser to the state it + * was in immediately prior to invoking the callback. The result of invoking the callback + * is returned from this function. + */ + function lookAhead(callback: () => T): T { + return speculationHelper(callback, /*isLookAhead*/ true); + } + /** Invokes the provided callback. If the callback returns something falsy, then it restores + * the parser to the state it was in immediately prior to invoking the callback. If the + * callback returns something truthy, then the parser state is not rolled back. The result + * of invoking the callback is returned from this function. + */ + function tryParse(callback: () => T): T { + return speculationHelper(callback, /*isLookAhead*/ false); + } + // Ignore strict mode flag because we will report an error in type checker instead. + function isIdentifier(): boolean { + if (token() === SyntaxKind.Identifier) { + return true; + } + // If we have a 'yield' keyword, and we're in the [yield] context, then 'yield' is + // considered a keyword and is not an identifier. + if (token() === SyntaxKind.YieldKeyword && inYieldContext()) { return false; } - - function parseExpectedJSDoc(kind: JSDocSyntaxKind) { - if (token() === kind) { - nextTokenJSDoc(); - return true; - } - parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(kind)); + // 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; } - - function parseOptional(t: SyntaxKind): boolean { - if (token() === t) { + return token() > SyntaxKind.LastReservedWord; + } + function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage, shouldAdvance = true): boolean { + if (token() === kind) { + if (shouldAdvance) { nextToken(); - return true; } - return false; + return true; } - - function parseOptionalToken(t: TKind): Token; - function parseOptionalToken(t: SyntaxKind): Node | undefined { - if (token() === t) { - return parseTokenNode(); - } - return undefined; + // Report specific message if provided with one. Otherwise, report generic fallback message. + if (diagnosticMessage) { + parseErrorAtCurrentToken(diagnosticMessage); } - - function parseOptionalTokenJSDoc(t: TKind): Token; - function parseOptionalTokenJSDoc(t: JSDocSyntaxKind): Node | undefined { - if (token() === t) { - return parseTokenNodeJSDoc(); - } - return undefined; + else { + parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(kind)); + } + return false; + } + function parseExpectedJSDoc(kind: JSDocSyntaxKind) { + if (token() === kind) { + nextTokenJSDoc(); + return true; } - - 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 node = createNode(token()); + parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(kind)); + return false; + } + function parseOptional(t: SyntaxKind): boolean { + if (token() === t) { nextToken(); - return finishNode(node); + return true; } - - function parseTokenNodeJSDoc(): T { - const node = createNode(token()); - nextTokenJSDoc(); - return finishNode(node); + 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 node = createNode(token()); + nextToken(); + return finishNode(node); + } + function parseTokenNodeJSDoc(): T { + const node = createNode(token()); + nextTokenJSDoc(); + return finishNode(node); + } + function canParseSemicolon() { + // If there's a real semicolon, then we can always parse it out. + if (token() === SyntaxKind.SemicolonToken) { + return true; } - - function canParseSemicolon() { - // If there's a real semicolon, then we can always parse it out. + // We can parse out an optional semicolon in ASI cases in the following cases. + return token() === SyntaxKind.CloseBraceToken || token() === SyntaxKind.EndOfFileToken || scanner.hasPrecedingLineBreak(); + } + function parseSemicolon(): boolean { + if (canParseSemicolon()) { if (token() === SyntaxKind.SemicolonToken) { - return true; + // consume the semicolon if it was explicitly provided. + nextToken(); } - - // We can parse out an optional semicolon in ASI cases in the following cases. - return token() === SyntaxKind.CloseBraceToken || token() === SyntaxKind.EndOfFileToken || scanner.hasPrecedingLineBreak(); + return true; } - - function parseSemicolon(): boolean { - if (canParseSemicolon()) { - if (token() === SyntaxKind.SemicolonToken) { - // consume the semicolon if it was explicitly provided. - nextToken(); - } - - return true; - } - else { - return parseExpected(SyntaxKind.SemicolonToken); - } + else { + return parseExpected(SyntaxKind.SemicolonToken); } - - function createNode(kind: SyntaxKind, pos?: number): Node { - nodeCount++; - const p = pos! >= 0 ? pos! : scanner.getStartPos(); - return isNodeKind(kind) || kind === SyntaxKind.Unknown ? new NodeConstructor(kind, p, p) : - kind === SyntaxKind.Identifier ? new IdentifierConstructor(kind, p, p) : + } + function createNode(kind: SyntaxKind, pos?: number): Node { + nodeCount++; + const p = pos! >= 0 ? pos! : scanner.getStartPos(); + return isNodeKind(kind) || kind === SyntaxKind.Unknown ? new NodeConstructor(kind, p, p) : + kind === SyntaxKind.Identifier ? new IdentifierConstructor(kind, p, p) : new TokenConstructor(kind, p, p); + } + function createNodeWithJSDoc(kind: SyntaxKind, pos?: number): Node { + const node = createNode(kind, pos); + if (scanner.getTokenFlags() & TokenFlags.PrecedingJSDocComment) { + addJSDocComment((node)); } - - function createNodeWithJSDoc(kind: SyntaxKind, pos?: number): Node { - const node = createNode(kind, pos); - if (scanner.getTokenFlags() & TokenFlags.PrecedingJSDocComment) { - addJSDocComment(node); - } - return node; + return node; + } + function createNodeArray(elements: T[], pos: number, end?: number): NodeArray { + // Since the element list of a node array is typically created by starting with an empty array and + // repeatedly calling push(), the list may not have the optimal memory layout. We invoke slice() for + // small arrays (1 to 4 elements) to give the VM a chance to allocate an optimal representation. + const length = elements.length; + const array = (>(length >= 1 && length <= 4 ? elements.slice() : elements)); + array.pos = pos; + array.end = end === undefined ? scanner.getStartPos() : end; + return array; + } + function finishNode(node: T, end?: number): T { + node.end = end === undefined ? scanner.getStartPos() : end; + if (contextFlags) { + node.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.flags |= NodeFlags.ThisNodeHasError; } - - function createNodeArray(elements: T[], pos: number, end?: number): NodeArray { - // Since the element list of a node array is typically created by starting with an empty array and - // repeatedly calling push(), the list may not have the optimal memory layout. We invoke slice() for - // small arrays (1 to 4 elements) to give the VM a chance to allocate an optimal representation. - const length = elements.length; - const array = >(length >= 1 && length <= 4 ? elements.slice() : elements); - array.pos = pos; - array.end = end === undefined ? scanner.getStartPos() : end; - return array; - } - - function finishNode(node: T, end?: number): T { - node.end = end === undefined ? scanner.getStartPos() : end; - - if (contextFlags) { - node.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.flags |= NodeFlags.ThisNodeHasError; - } - - return node; + 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); } - - 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 result = createNode(kind); - - if (kind === SyntaxKind.Identifier) { - (result as Identifier).escapedText = "" as __String; - } - else if (isLiteralKind(kind) || isTemplateLiteralKind(kind)) { - (result as LiteralLikeNode).text = ""; - } - - return finishNode(result) 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): Identifier { - identifierCount++; - if (isIdentifier) { - const node = createNode(SyntaxKind.Identifier); - - // Store original token kind if it is not just an Identifier so we can report appropriate error later in type checker - if (token() !== SyntaxKind.Identifier) { - node.originalKeywordKind = token(); - } - node.escapedText = escapeLeadingUnderscores(internIdentifier(scanner.getTokenValue())); - nextTokenWithoutCheck(); - return finishNode(node); - } - - // 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 parseIdentifier(diagnosticMessage?: DiagnosticMessage): Identifier { - return createIdentifier(isIdentifier(), diagnosticMessage); - } - - function parseIdentifierName(diagnosticMessage?: DiagnosticMessage): Identifier { - return createIdentifier(tokenIsIdentifierOrKeyword(token()), diagnosticMessage); - } - - function isLiteralPropertyName(): boolean { - return tokenIsIdentifierOrKeyword(token()) || - token() === SyntaxKind.StringLiteral || - token() === SyntaxKind.NumericLiteral; - } - - function parsePropertyNameWorker(allowComputedPropertyNames: boolean): PropertyName { - if (token() === SyntaxKind.StringLiteral || token() === SyntaxKind.NumericLiteral) { - const node = parseLiteralNode(); - node.text = internIdentifier(node.text); - return node; - } - if (allowComputedPropertyNames && token() === SyntaxKind.OpenBracketToken) { - return parseComputedPropertyName(); - } - return parseIdentifierName(); - } - - function parsePropertyName(): PropertyName { - return parsePropertyNameWorker(/*allowComputedPropertyNames*/ true); - } - - function parseComputedPropertyName(): ComputedPropertyName { - // PropertyName [Yield]: - // LiteralPropertyName - // ComputedPropertyName[?Yield] - const node = createNode(SyntaxKind.ComputedPropertyName); - parseExpected(SyntaxKind.OpenBracketToken); - - // We parse any expression (including a comma expression). But the grammar - // says that only an assignment expression is allowed, so the grammar checker - // will error if it sees a comma expression. - node.expression = allowInAnd(parseExpression); - - parseExpected(SyntaxKind.CloseBracketToken); - return finishNode(node); + else if (diagnosticMessage) { + parseErrorAtCurrentToken(diagnosticMessage, arg0); } - - function parseContextualModifier(t: SyntaxKind): boolean { - return token() === t && tryParse(nextTokenCanFollowModifier); + const result = createNode(kind); + if (kind === SyntaxKind.Identifier) { + (result as Identifier).escapedText = ("" as __String); } - - function nextTokenIsOnSameLineAndCanFollowModifier() { - nextToken(); - if (scanner.hasPrecedingLineBreak()) { - return false; - } - return canFollowModifier(); + else if (isLiteralKind(kind) || isTemplateLiteralKind(kind)) { + (result as LiteralLikeNode).text = ""; } - - 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); - } - return token() !== SyntaxKind.AsteriskToken && token() !== SyntaxKind.AsKeyword && token() !== SyntaxKind.OpenBraceToken && canFollowModifier(); - case SyntaxKind.DefaultKeyword: - return nextTokenCanFollowDefaultKeyword(); - case SyntaxKind.StaticKeyword: - case SyntaxKind.GetKeyword: - case SyntaxKind.SetKeyword: - nextToken(); - return canFollowModifier(); - default: - return nextTokenIsOnSameLineAndCanFollowModifier(); - } + return finishNode(result) 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): Identifier { + identifierCount++; + if (isIdentifier) { + const node = (createNode(SyntaxKind.Identifier)); + // Store original token kind if it is not just an Identifier so we can report appropriate error later in type checker + if (token() !== SyntaxKind.Identifier) { + node.originalKeywordKind = token(); + } + node.escapedText = escapeLeadingUnderscores(internIdentifier(scanner.getTokenValue())); + nextTokenWithoutCheck(); + return finishNode(node); } - - function parseAnyContextualModifier(): boolean { - return isModifierKind(token()) && tryParse(nextTokenCanFollowModifier); + // 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 parseIdentifier(diagnosticMessage?: DiagnosticMessage): Identifier { + return createIdentifier(isIdentifier(), diagnosticMessage); + } + function parseIdentifierName(diagnosticMessage?: DiagnosticMessage): Identifier { + return createIdentifier(tokenIsIdentifierOrKeyword(token()), diagnosticMessage); + } + function isLiteralPropertyName(): boolean { + return tokenIsIdentifierOrKeyword(token()) || + token() === SyntaxKind.StringLiteral || + token() === SyntaxKind.NumericLiteral; + } + function parsePropertyNameWorker(allowComputedPropertyNames: boolean): PropertyName { + if (token() === SyntaxKind.StringLiteral || token() === SyntaxKind.NumericLiteral) { + const node = (parseLiteralNode()); + node.text = internIdentifier(node.text); + return node; } - - function canFollowModifier(): boolean { - return token() === SyntaxKind.OpenBracketToken - || token() === SyntaxKind.OpenBraceToken - || token() === SyntaxKind.AsteriskToken - || token() === SyntaxKind.DotDotDotToken - || isLiteralPropertyName(); + if (allowComputedPropertyNames && token() === SyntaxKind.OpenBracketToken) { + return parseComputedPropertyName(); } - - function nextTokenCanFollowDefaultKeyword(): boolean { - nextToken(); - return token() === SyntaxKind.ClassKeyword || 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.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.HeritageClauseElement: - // If we see `{ ... }` then only consume it as an expression if it is followed by `,` or `{` - // That way we won't consume the body of a class in its heritage clause. - if (token() === SyntaxKind.OpenBraceToken) { - return lookAhead(isValidHeritageClauseObjectLiteral); - } - - if (!inErrorRecovery) { - return isStartOfLeftHandSideExpression() && !isHeritageClauseExtendsOrImplementsKeyword(); - } - else { - // If we're in error recovery we tighten up what we're willing to match. - // That way we don't treat something like "this" as a valid heritage clause - // element during recovery. - return isIdentifier() && !isHeritageClauseExtendsOrImplementsKeyword(); - } - case ParsingContext.VariableDeclarations: - return isIdentifierOrPattern(); - case ParsingContext.ArrayBindingElements: - return token() === SyntaxKind.CommaToken || token() === SyntaxKind.DotDotDotToken || isIdentifierOrPattern(); - case ParsingContext.TypeParameters: - return isIdentifier(); - case ParsingContext.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'."); + return parseIdentifierName(); + } + function parsePropertyName(): PropertyName { + return parsePropertyNameWorker(/*allowComputedPropertyNames*/ true); + } + function parseComputedPropertyName(): ComputedPropertyName { + // PropertyName [Yield]: + // LiteralPropertyName + // ComputedPropertyName[?Yield] + const node = (createNode(SyntaxKind.ComputedPropertyName)); + parseExpected(SyntaxKind.OpenBracketToken); + // We parse any expression (including a comma expression). But the grammar + // says that only an assignment expression is allowed, so the grammar checker + // will error if it sees a comma expression. + node.expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.CloseBracketToken); + return finishNode(node); + } + function parseContextualModifier(t: SyntaxKind): boolean { + return token() === t && tryParse(nextTokenCanFollowModifier); + } + function nextTokenIsOnSameLineAndCanFollowModifier() { + nextToken(); + if (scanner.hasPrecedingLineBreak()) { + return false; } - - 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 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); + } + return token() !== SyntaxKind.AsteriskToken && token() !== SyntaxKind.AsKeyword && token() !== SyntaxKind.OpenBraceToken && canFollowModifier(); + case SyntaxKind.DefaultKeyword: + return nextTokenCanFollowDefaultKeyword(); + case SyntaxKind.StaticKeyword: + case SyntaxKind.GetKeyword: + case SyntaxKind.SetKeyword: + nextToken(); + return canFollowModifier(); + default: + return nextTokenIsOnSameLineAndCanFollowModifier(); + } + } + function parseAnyContextualModifier(): boolean { + return isModifierKind(token()) && tryParse(nextTokenCanFollowModifier); + } + function canFollowModifier(): boolean { + return token() === SyntaxKind.OpenBracketToken + || token() === SyntaxKind.OpenBraceToken + || token() === SyntaxKind.AsteriskToken + || token() === SyntaxKind.DotDotDotToken + || isLiteralPropertyName(); + } + function nextTokenCanFollowDefaultKeyword(): boolean { + nextToken(); + return token() === SyntaxKind.ClassKeyword || 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; } - - function nextTokenIsIdentifier() { - nextToken(); - return isIdentifier(); + 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.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.HeritageClauseElement: + // If we see `{ ... }` then only consume it as an expression if it is followed by `,` or `{` + // That way we won't consume the body of a class in its heritage clause. + if (token() === SyntaxKind.OpenBraceToken) { + return lookAhead(isValidHeritageClauseObjectLiteral); + } + if (!inErrorRecovery) { + return isStartOfLeftHandSideExpression() && !isHeritageClauseExtendsOrImplementsKeyword(); + } + else { + // If we're in error recovery we tighten up what we're willing to match. + // That way we don't treat something like "this" as a valid heritage clause + // element during recovery. + return isIdentifier() && !isHeritageClauseExtendsOrImplementsKeyword(); + } + case ParsingContext.VariableDeclarations: + return isIdentifierOrPattern(); + case ParsingContext.ArrayBindingElements: + return token() === SyntaxKind.CommaToken || token() === SyntaxKind.DotDotDotToken || isIdentifierOrPattern(); + case ParsingContext.TypeParameters: + return isIdentifier(); + case ParsingContext.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; } - - function nextTokenIsIdentifierOrKeyword() { - nextToken(); - return tokenIsIdentifierOrKeyword(token()); + 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); } - - function nextTokenIsIdentifierOrKeywordOrGreaterThan() { - nextToken(); - return tokenIsIdentifierOrKeywordOrGreaterThan(token()); + 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; } - - function isHeritageClauseExtendsOrImplementsKeyword(): boolean { - if (token() === SyntaxKind.ImplementsKeyword || - token() === SyntaxKind.ExtendsKeyword) { - - return lookAhead(nextTokenIsStartOfExpression); - } - - return false; + switch (kind) { + case ParsingContext.BlockStatements: + case ParsingContext.SwitchClauses: + case ParsingContext.TypeMembers: + case ParsingContext.ClassMembers: + case ParsingContext.EnumMembers: + case ParsingContext.ObjectLiteralMembers: + case ParsingContext.ObjectBindingElements: + case ParsingContext.ImportOrExportSpecifiers: + return token() === SyntaxKind.CloseBraceToken; + case ParsingContext.SwitchClauseStatements: + return token() === SyntaxKind.CloseBraceToken || token() === SyntaxKind.CaseKeyword || token() === SyntaxKind.DefaultKeyword; + case ParsingContext.HeritageClauseElement: + return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.ExtendsKeyword || token() === SyntaxKind.ImplementsKeyword; + case ParsingContext.VariableDeclarations: + return isVariableDeclaratorListTerminator(); + case ParsingContext.TypeParameters: + // Tokens other than '>' are here for better error recovery + return token() === SyntaxKind.GreaterThanToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.ExtendsKeyword || token() === SyntaxKind.ImplementsKeyword; + case ParsingContext.ArgumentExpressions: + // Tokens other than ')' are here for better error recovery + return token() === SyntaxKind.CloseParenToken || token() === SyntaxKind.SemicolonToken; + case ParsingContext.ArrayLiteralMembers: + case ParsingContext.TupleElementTypes: + case ParsingContext.ArrayBindingElements: + return token() === SyntaxKind.CloseBracketToken; + case ParsingContext.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 nextTokenIsStartOfExpression() { - nextToken(); - return isStartOfExpression(); + } + 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; } - - function nextTokenIsStartOfType() { - nextToken(); - return isStartOfType(); + // 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; } - - // 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.EnumMembers: - case ParsingContext.ObjectLiteralMembers: - case ParsingContext.ObjectBindingElements: - case ParsingContext.ImportOrExportSpecifiers: - return token() === SyntaxKind.CloseBraceToken; - case ParsingContext.SwitchClauseStatements: - return token() === SyntaxKind.CloseBraceToken || token() === SyntaxKind.CaseKeyword || token() === SyntaxKind.DefaultKeyword; - case ParsingContext.HeritageClauseElement: - return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.ExtendsKeyword || token() === SyntaxKind.ImplementsKeyword; - case ParsingContext.VariableDeclarations: - return isVariableDeclaratorListTerminator(); - case ParsingContext.TypeParameters: - // Tokens other than '>' are here for better error recovery - return token() === SyntaxKind.GreaterThanToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.ExtendsKeyword || token() === SyntaxKind.ImplementsKeyword; - case ParsingContext.ArgumentExpressions: - // Tokens other than ')' are here for better error recovery - return token() === SyntaxKind.CloseParenToken || token() === SyntaxKind.SemicolonToken; - case ParsingContext.ArrayLiteralMembers: - case ParsingContext.TupleElementTypes: - case ParsingContext.ArrayBindingElements: - return token() === SyntaxKind.CloseBracketToken; - case ParsingContext.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; - } + // 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; } - - 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; + // 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; + } } - - // 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; + } + 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)) { + const element = parseListElement(kind, parseElement); + list.push(element); + continue; } - - // 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; + if (abortParsingListOrMoveToNextToken(kind)) { + break; } - - // 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; + parsingContext = saveParsingContext; + return createNodeArray(list, listPos); + } + function parseListElement(parsingContext: ParsingContext, parseElement: () => T): T { + const node = currentNode(parsingContext); + if (node) { + return consumeNode(node); } - - // 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)) { - const element = parseListElement(kind, parseElement); - list.push(element); - - continue; - } - - if (abortParsingListOrMoveToNextToken(kind)) { - break; - } - } - - parsingContext = saveParsingContext; - return createNodeArray(list, listPos); + return parseElement(); + } + function currentNode(parsingContext: ParsingContext): 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; } - - function parseListElement(parsingContext: ParsingContext, parseElement: () => T): T { - const node = currentNode(parsingContext); - if (node) { - return consumeNode(node); - } - - return parseElement(); + const node = syntaxCursor.currentNode(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; } - - function currentNode(parsingContext: ParsingContext): 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(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; + // 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; } - - function consumeNode(node: Node) { - // Move the scanner so it is after the node we just consumed. - scanner.setTextPos(node.end); - nextToken(); - return node; + // 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; } - - 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 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.PropertyDeclaration: + case SyntaxKind.SemicolonClassElement: return true; + 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); + const nameIsConstructor = methodDeclaration.name.kind === SyntaxKind.Identifier && + methodDeclaration.name.originalKeywordKind === SyntaxKind.ConstructorKeyword; + return !nameIsConstructor; } - 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.PropertyDeclaration: - case SyntaxKind.SemicolonClassElement: - return true; - 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; - 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 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.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + 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.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 isReusableEnumMember(node: Node) { - return node.kind === SyntaxKind.EnumMember; + return false; + } + function isReusableVariableDeclaration(node: Node) { + if (node.kind !== SyntaxKind.VariableDeclaration) { + return false; } - - 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; - } - } - + // 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 Listnode); + return variableDeclarator.initializer === undefined; + } + function isReusableParameter(node: Node) { + if (node.kind !== SyntaxKind.Parameter) { 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 Listnode; - return variableDeclarator.initializer === undefined; - } - - function isReusableParameter(node: Node) { - if (node.kind !== SyntaxKind.Parameter) { - return false; - } - - // See the comment in isReusableVariableDeclaration for why we do this. - const parameter = node; - return parameter.initializer === undefined; + // See the comment in isReusableVariableDeclaration for why we do this. + const parameter = (node); + return parameter.initializer === undefined; + } + // Returns true if we should abort parsing. + function abortParsingListOrMoveToNextToken(kind: ParsingContext) { + parseErrorAtCurrentToken(parsingContextErrors(kind)); + if (isInSomeParsingContext()) { + return true; } - - // Returns true if we should abort parsing. - function abortParsingListOrMoveToNextToken(kind: ParsingContext) { - parseErrorAtCurrentToken(parsingContextErrors(kind)); - if (isInSomeParsingContext()) { - return true; - } - - nextToken(); - return false; + nextToken(); + return false; + } + function parsingContextErrors(context: ParsingContext): DiagnosticMessage { + switch (context) { + case ParsingContext.SourceElements: return Diagnostics.Declaration_or_statement_expected; + case ParsingContext.BlockStatements: return Diagnostics.Declaration_or_statement_expected; + case ParsingContext.SwitchClauses: return Diagnostics.case_or_default_expected; + case ParsingContext.SwitchClauseStatements: return Diagnostics.Statement_expected; + case ParsingContext.RestProperties: // fallthrough + case ParsingContext.TypeMembers: return Diagnostics.Property_or_signature_expected; + case ParsingContext.ClassMembers: return Diagnostics.Unexpected_token_A_constructor_method_accessor_or_property_was_expected; + case ParsingContext.EnumMembers: return Diagnostics.Enum_member_expected; + case ParsingContext.HeritageClauseElement: return Diagnostics.Expression_expected; + case ParsingContext.VariableDeclarations: return Diagnostics.Variable_declaration_expected; + case ParsingContext.ObjectBindingElements: return Diagnostics.Property_destructuring_pattern_expected; + case ParsingContext.ArrayBindingElements: return Diagnostics.Array_element_destructuring_pattern_expected; + case ParsingContext.ArgumentExpressions: return Diagnostics.Argument_expression_expected; + case ParsingContext.ObjectLiteralMembers: return Diagnostics.Property_assignment_expected; + case ParsingContext.ArrayLiteralMembers: return Diagnostics.Expression_or_comma_expected; + case ParsingContext.JSDocParameters: return Diagnostics.Parameter_declaration_expected; + case ParsingContext.Parameters: return Diagnostics.Parameter_declaration_expected; + case ParsingContext.TypeParameters: return Diagnostics.Type_parameter_declaration_expected; + case ParsingContext.TypeArguments: return Diagnostics.Type_argument_expected; + case ParsingContext.TupleElementTypes: return Diagnostics.Type_expected; + case ParsingContext.HeritageClauses: return Diagnostics.Unexpected_token_expected; + case ParsingContext.ImportOrExportSpecifiers: return Diagnostics.Identifier_expected; + case ParsingContext.JsxAttributes: return Diagnostics.Identifier_expected; + case ParsingContext.JsxChildren: return Diagnostics.Identifier_expected; + default: return undefined!; // TODO: GH#18217 `default: Debug.assertNever(context);` } - - function parsingContextErrors(context: ParsingContext): DiagnosticMessage { - switch (context) { - case ParsingContext.SourceElements: return Diagnostics.Declaration_or_statement_expected; - case ParsingContext.BlockStatements: return Diagnostics.Declaration_or_statement_expected; - case ParsingContext.SwitchClauses: return Diagnostics.case_or_default_expected; - case ParsingContext.SwitchClauseStatements: return Diagnostics.Statement_expected; - case ParsingContext.RestProperties: // fallthrough - case ParsingContext.TypeMembers: return Diagnostics.Property_or_signature_expected; - case ParsingContext.ClassMembers: return Diagnostics.Unexpected_token_A_constructor_method_accessor_or_property_was_expected; - case ParsingContext.EnumMembers: return Diagnostics.Enum_member_expected; - case ParsingContext.HeritageClauseElement: return Diagnostics.Expression_expected; - case ParsingContext.VariableDeclarations: return Diagnostics.Variable_declaration_expected; - case ParsingContext.ObjectBindingElements: return Diagnostics.Property_destructuring_pattern_expected; - case ParsingContext.ArrayBindingElements: return Diagnostics.Array_element_destructuring_pattern_expected; - case ParsingContext.ArgumentExpressions: return Diagnostics.Argument_expression_expected; - case ParsingContext.ObjectLiteralMembers: return Diagnostics.Property_assignment_expected; - case ParsingContext.ArrayLiteralMembers: return Diagnostics.Expression_or_comma_expected; - case ParsingContext.JSDocParameters: return Diagnostics.Parameter_declaration_expected; - case ParsingContext.Parameters: return Diagnostics.Parameter_declaration_expected; - case ParsingContext.TypeParameters: return Diagnostics.Type_parameter_declaration_expected; - case ParsingContext.TypeArguments: return Diagnostics.Type_argument_expected; - case ParsingContext.TupleElementTypes: return Diagnostics.Type_expected; - case ParsingContext.HeritageClauses: return Diagnostics.Unexpected_token_expected; - case ParsingContext.ImportOrExportSpecifiers: return Diagnostics.Identifier_expected; - case ParsingContext.JsxAttributes: return Diagnostics.Identifier_expected; - case ParsingContext.JsxChildren: return Diagnostics.Identifier_expected; - default: return undefined!; // TODO: GH#18217 `default: Debug.assertNever(context);` - } - } - - // Parses a comma-delimited list of elements - function parseDelimitedList(kind: ParsingContext, parseElement: () => T, considerSemicolonAsDelimiter?: boolean): NodeArray { - const saveParsingContext = parsingContext; - parsingContext |= 1 << kind; - const list = []; - 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(); - list.push(parseListElement(kind, parseElement)); - 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(); - } + } + // Parses a comma-delimited list of elements + function parseDelimitedList(kind: ParsingContext, parseElement: () => T, considerSemicolonAsDelimiter?: boolean): NodeArray { + const saveParsingContext = parsingContext; + parsingContext |= 1 << kind; + const list = []; + 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(); + list.push(parseListElement(kind, parseElement)); + 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; } - - if (abortParsingListOrMoveToNextToken(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; } - - parsingContext = saveParsingContext; - const result = createNodeArray(list, listPos); - // Recording the trailing comma is deliberately done after the previous - // loop, and not just if we see a list terminator. This is because the list - // may have ended incorrectly, but it is still important to know if there - // was a trailing comma. - // Check if the last token was a comma. - if (commaStart >= 0) { - // Always preserve a trailing comma by marking it on the NodeArray - result.hasTrailingComma = true; - } - return result; - } - - 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; + if (isListTerminator(kind)) { + break; } - - return createMissingList(); - } - - function parseEntityName(allowReservedWords: boolean, diagnosticMessage?: DiagnosticMessage): EntityName { - let entity: EntityName = allowReservedWords ? parseIdentifierName(diagnosticMessage) : parseIdentifier(diagnosticMessage); - let dotPos = scanner.getStartPos(); - 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 = scanner.getStartPos(); - entity = createQualifiedName(entity, parseRightSideOfDot(allowReservedWords)); + if (abortParsingListOrMoveToNextToken(kind)) { + break; } - return entity; } - - function createQualifiedName(entity: EntityName, name: Identifier): QualifiedName { - const node = createNode(SyntaxKind.QualifiedName, entity.pos) as QualifiedName; - node.left = entity; - node.right = name; - return finishNode(node); + parsingContext = saveParsingContext; + const result = createNodeArray(list, listPos); + // Recording the trailing comma is deliberately done after the previous + // loop, and not just if we see a list terminator. This is because the list + // may have ended incorrectly, but it is still important to know if there + // was a trailing comma. + // Check if the last token was a comma. + if (commaStart >= 0) { + // Always preserve a trailing comma by marking it on the NodeArray + result.hasTrailingComma = true; } - - function parseRightSideOfDot(allowIdentifierNames: boolean): Identifier { - // 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); - } - } - - return allowIdentifierNames ? parseIdentifierName() : parseIdentifier(); - } - - function parseTemplateExpression(): TemplateExpression { - const template = createNode(SyntaxKind.TemplateExpression); - - template.head = parseTemplateHead(); - Debug.assert(template.head.kind === SyntaxKind.TemplateHead, "Template head has wrong token kind"); - - const list = []; - const listPos = getNodePos(); - - do { - list.push(parseTemplateSpan()); - } - while (last(list).literal.kind === SyntaxKind.TemplateMiddle); - - template.templateSpans = createNodeArray(list, listPos); - - return finishNode(template); - } - - function parseTemplateSpan(): TemplateSpan { - const span = createNode(SyntaxKind.TemplateSpan); - span.expression = allowInAnd(parseExpression); - - let literal: TemplateMiddle | TemplateTail; - if (token() === SyntaxKind.CloseBraceToken) { - reScanTemplateToken(); - literal = parseTemplateMiddleOrTemplateTail(); - } - else { - literal = parseExpectedToken(SyntaxKind.TemplateTail, Diagnostics._0_expected, tokenToString(SyntaxKind.CloseBraceToken)); - } - - span.literal = literal; - return finishNode(span); - } - - function parseLiteralNode(): LiteralExpression { - return parseLiteralLikeNode(token()); - } - - function parseTemplateHead(): TemplateHead { - const fragment = parseLiteralLikeNode(token()); - Debug.assert(fragment.kind === SyntaxKind.TemplateHead, "Template head has wrong token kind"); - return fragment; - } - - 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; - } - - function parseLiteralLikeNode(kind: SyntaxKind): LiteralLikeNode { - const node = createNode(kind); - node.text = scanner.getTokenValue(); - switch (kind) { - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.TemplateHead: - case SyntaxKind.TemplateMiddle: - case SyntaxKind.TemplateTail: - const isLast = kind === SyntaxKind.NoSubstitutionTemplateLiteral || kind === SyntaxKind.TemplateTail; - const tokenText = scanner.getTokenText(); - (node).rawText = tokenText.substring(1, tokenText.length - (scanner.isUnterminated() ? 0 : isLast ? 1 : 2)); - break; - } - - if (scanner.hasExtendedUnicodeEscape()) { - node.hasExtendedUnicodeEscape = true; - } - - if (scanner.isUnterminated()) { - node.isUnterminated = true; - } - - // Octal literals are not allowed in strict mode or ES5 - // Note that theoretically the following condition would hold true literals like 009, - // which is not octal.But because of how the scanner separates the tokens, we would - // never get a token like this. Instead, we would get 00 and 9 as two separate tokens. - // We also do not need to check for negatives because any prefix operator would be part of a - // parent unary expression. - if (node.kind === SyntaxKind.NumericLiteral) { - (node).numericLiteralFlags = scanner.getTokenFlags() & TokenFlags.NumericLiteralFlags; - } - - nextToken(); - finishNode(node); - - return node; + return result; + } + 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; } - - // TYPES - - function parseTypeReference(): TypeReferenceNode { - const node = createNode(SyntaxKind.TypeReference); - node.typeName = parseEntityName(/*allowReservedWords*/ true, Diagnostics.Type_expected); - if (!scanner.hasPrecedingLineBreak() && reScanLessThanToken() === SyntaxKind.LessThanToken) { - node.typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); + return createMissingList(); + } + function parseEntityName(allowReservedWords: boolean, diagnosticMessage?: DiagnosticMessage): EntityName { + let entity: EntityName = allowReservedWords ? parseIdentifierName(diagnosticMessage) : parseIdentifier(diagnosticMessage); + let dotPos = scanner.getStartPos(); + 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; } - return finishNode(node); + dotPos = scanner.getStartPos(); + entity = createQualifiedName(entity, parseRightSideOfDot(allowReservedWords)); } - - // 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; + return entity; + } + function createQualifiedName(entity: EntityName, name: Identifier): QualifiedName { + const node = (createNode(SyntaxKind.QualifiedName, entity.pos) as QualifiedName); + node.left = entity; + node.right = name; + return finishNode(node); + } + function parseRightSideOfDot(allowIdentifierNames: boolean): Identifier { + // 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); + } + } + return allowIdentifierNames ? parseIdentifierName() : parseIdentifier(); + } + function parseTemplateExpression(): TemplateExpression { + const template = (createNode(SyntaxKind.TemplateExpression)); + template.head = parseTemplateHead(); + Debug.assert(template.head.kind === SyntaxKind.TemplateHead, "Template head has wrong token kind"); + const list = []; + const listPos = getNodePos(); + do { + list.push(parseTemplateSpan()); + } while (last(list).literal.kind === SyntaxKind.TemplateMiddle); + template.templateSpans = createNodeArray(list, listPos); + return finishNode(template); + } + function parseTemplateSpan(): TemplateSpan { + const span = (createNode(SyntaxKind.TemplateSpan)); + span.expression = allowInAnd(parseExpression); + let literal: TemplateMiddle | TemplateTail; + if (token() === SyntaxKind.CloseBraceToken) { + reScanTemplateToken(); + literal = parseTemplateMiddleOrTemplateTail(); + } + else { + literal = (parseExpectedToken(SyntaxKind.TemplateTail, Diagnostics._0_expected, tokenToString(SyntaxKind.CloseBraceToken))); + } + span.literal = literal; + return finishNode(span); + } + function parseLiteralNode(): LiteralExpression { + return parseLiteralLikeNode(token()); + } + function parseTemplateHead(): TemplateHead { + const fragment = parseLiteralLikeNode(token()); + Debug.assert(fragment.kind === SyntaxKind.TemplateHead, "Template head has wrong token kind"); + return fragment; + } + 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; + } + function parseLiteralLikeNode(kind: SyntaxKind): LiteralLikeNode { + const node = (createNode(kind)); + node.text = scanner.getTokenValue(); + switch (kind) { + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TemplateHead: + case SyntaxKind.TemplateMiddle: + case SyntaxKind.TemplateTail: + const isLast = kind === SyntaxKind.NoSubstitutionTemplateLiteral || kind === SyntaxKind.TemplateTail; + const tokenText = scanner.getTokenText(); + (node).rawText = tokenText.substring(1, tokenText.length - (scanner.isUnterminated() ? 0 : isLast ? 1 : 2)); + break; + } + if (scanner.hasExtendedUnicodeEscape()) { + node.hasExtendedUnicodeEscape = true; + } + if (scanner.isUnterminated()) { + node.isUnterminated = true; + } + // Octal literals are not allowed in strict mode or ES5 + // Note that theoretically the following condition would hold true literals like 009, + // which is not octal.But because of how the scanner separates the tokens, we would + // never get a token like this. Instead, we would get 00 and 9 as two separate tokens. + // We also do not need to check for negatives because any prefix operator would be part of a + // parent unary expression. + if (node.kind === SyntaxKind.NumericLiteral) { + (node).numericLiteralFlags = scanner.getTokenFlags() & TokenFlags.NumericLiteralFlags; + } + nextToken(); + finishNode(node); + return node; + } + // TYPES + function parseTypeReference(): TypeReferenceNode { + const node = (createNode(SyntaxKind.TypeReference)); + node.typeName = parseEntityName(/*allowReservedWords*/ true, Diagnostics.Type_expected); + if (!scanner.hasPrecedingLineBreak() && reScanLessThanToken() === SyntaxKind.LessThanToken) { + node.typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); + } + return finishNode(node); + } + // 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(); - const node = createNode(SyntaxKind.TypePredicate, lhs.pos) as TypePredicateNode; - node.parameterName = lhs; - node.type = parseType(); - return finishNode(node); + } + function parseThisTypePredicate(lhs: ThisTypeNode): TypePredicateNode { + nextToken(); + const node = (createNode(SyntaxKind.TypePredicate, lhs.pos) as TypePredicateNode); + node.parameterName = lhs; + node.type = parseType(); + return finishNode(node); + } + function parseThisTypeNode(): ThisTypeNode { + const node = (createNode(SyntaxKind.ThisType) as ThisTypeNode); + nextToken(); + return finishNode(node); + } + function parseJSDocAllType(postFixEquals: boolean): JSDocAllType | JSDocOptionalType { + const result = (createNode(SyntaxKind.JSDocAllType) as JSDocAllType); + if (postFixEquals) { + return createPostfixType(SyntaxKind.JSDocOptionalType, result) as JSDocOptionalType; } - - function parseThisTypeNode(): ThisTypeNode { - const node = createNode(SyntaxKind.ThisType) as ThisTypeNode; + else { nextToken(); - return finishNode(node); } - - function parseJSDocAllType(postFixEquals: boolean): JSDocAllType | JSDocOptionalType { - const result = createNode(SyntaxKind.JSDocAllType) as JSDocAllType; - if (postFixEquals) { - return createPostfixType(SyntaxKind.JSDocOptionalType, result) as JSDocOptionalType; - } - else { - nextToken(); - } + return finishNode(result); + } + function parseJSDocNonNullableType(): TypeNode { + const result = (createNode(SyntaxKind.JSDocNonNullableType) as JSDocNonNullableType); + nextToken(); + result.type = parseNonArrayType(); + return finishNode(result); + } + function parseJSDocUnknownOrNullableType(): JSDocUnknownType | JSDocNullableType { + const pos = scanner.getStartPos(); + // skip the ? + nextToken(); + // Need to lookahead to decide if this is a nullable or unknown type. + // Here are cases where we'll pick the unknown type: + // + // Foo(?, + // { a: ? } + // Foo(?) + // Foo + // Foo(?= + // (?| + if (token() === SyntaxKind.CommaToken || + token() === SyntaxKind.CloseBraceToken || + token() === SyntaxKind.CloseParenToken || + token() === SyntaxKind.GreaterThanToken || + token() === SyntaxKind.EqualsToken || + token() === SyntaxKind.BarToken) { + const result = (createNode(SyntaxKind.JSDocUnknownType, pos)); return finishNode(result); } - - function parseJSDocNonNullableType(): TypeNode { - const result = createNode(SyntaxKind.JSDocNonNullableType) as JSDocNonNullableType; - nextToken(); - result.type = parseNonArrayType(); + else { + const result = (createNode(SyntaxKind.JSDocNullableType, pos)); + result.type = parseType(); return finishNode(result); } - - function parseJSDocUnknownOrNullableType(): JSDocUnknownType | JSDocNullableType { - const pos = scanner.getStartPos(); - // skip the ? + } + function parseJSDocFunctionType(): JSDocFunctionType | TypeReferenceNode { + if (lookAhead(nextTokenIsOpenParen)) { + const result = (createNodeWithJSDoc(SyntaxKind.JSDocFunctionType)); 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) { - - const result = createNode(SyntaxKind.JSDocUnknownType, pos); - return finishNode(result); - } - else { - const result = createNode(SyntaxKind.JSDocNullableType, pos); - result.type = parseType(); - return finishNode(result); - } + fillSignature(SyntaxKind.ColonToken, SignatureFlags.Type | SignatureFlags.JSDoc, result); + return finishNode(result); } - - function parseJSDocFunctionType(): JSDocFunctionType | TypeReferenceNode { - if (lookAhead(nextTokenIsOpenParen)) { - const result = createNodeWithJSDoc(SyntaxKind.JSDocFunctionType); - nextToken(); - fillSignature(SyntaxKind.ColonToken, SignatureFlags.Type | SignatureFlags.JSDoc, result); - return finishNode(result); - } - const node = createNode(SyntaxKind.TypeReference); - node.typeName = parseIdentifierName(); - return finishNode(node); + const node = (createNode(SyntaxKind.TypeReference)); + node.typeName = parseIdentifierName(); + return finishNode(node); + } + function parseJSDocParameter(): ParameterDeclaration { + const parameter = (createNode(SyntaxKind.Parameter) as ParameterDeclaration); + if (token() === SyntaxKind.ThisKeyword || token() === SyntaxKind.NewKeyword) { + parameter.name = parseIdentifierName(); + parseExpected(SyntaxKind.ColonToken); } - - function parseJSDocParameter(): ParameterDeclaration { - const parameter = createNode(SyntaxKind.Parameter) as ParameterDeclaration; - if (token() === SyntaxKind.ThisKeyword || token() === SyntaxKind.NewKeyword) { - parameter.name = parseIdentifierName(); - parseExpected(SyntaxKind.ColonToken); - } - parameter.type = parseJSDocType(); - return finishNode(parameter); - } - - function parseJSDocType(): TypeNode { - scanner.setInJSDocType(true); - const moduleSpecifier = parseOptionalToken(SyntaxKind.ModuleKeyword); - if (moduleSpecifier) { - const moduleTag = createNode(SyntaxKind.JSDocNamepathType, moduleSpecifier.pos) as JSDocNamepathType; - terminate: while (true) { - switch (token()) { - case SyntaxKind.CloseBraceToken: - case SyntaxKind.EndOfFileToken: - case SyntaxKind.CommaToken: - case SyntaxKind.WhitespaceTrivia: - break terminate; - default: - nextTokenJSDoc(); - } + parameter.type = parseJSDocType(); + return finishNode(parameter); + } + function parseJSDocType(): TypeNode { + scanner.setInJSDocType(true); + const moduleSpecifier = parseOptionalToken(SyntaxKind.ModuleKeyword); + if (moduleSpecifier) { + const moduleTag = (createNode(SyntaxKind.JSDocNamepathType, moduleSpecifier.pos) as JSDocNamepathType); + 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); } - - const dotdotdot = parseOptionalToken(SyntaxKind.DotDotDotToken); - let type = parseTypeOrTypePredicate(); scanner.setInJSDocType(false); - if (dotdotdot) { - const variadic = createNode(SyntaxKind.JSDocVariadicType, dotdotdot.pos) as JSDocVariadicType; - variadic.type = type; - type = finishNode(variadic); - } - if (token() === SyntaxKind.EqualsToken) { - return createPostfixType(SyntaxKind.JSDocOptionalType, type); - } - return type; + return finishNode(moduleTag); } - - function parseTypeQuery(): TypeQueryNode { - const node = createNode(SyntaxKind.TypeQuery); - parseExpected(SyntaxKind.TypeOfKeyword); - node.exprName = parseEntityName(/*allowReservedWords*/ true); - return finishNode(node); + const dotdotdot = parseOptionalToken(SyntaxKind.DotDotDotToken); + let type = parseTypeOrTypePredicate(); + scanner.setInJSDocType(false); + if (dotdotdot) { + const variadic = (createNode(SyntaxKind.JSDocVariadicType, dotdotdot.pos) as JSDocVariadicType); + variadic.type = type; + type = finishNode(variadic); } - - function parseTypeParameter(): TypeParameterDeclaration { - const node = createNode(SyntaxKind.TypeParameter); - node.name = parseIdentifier(); - if (parseOptional(SyntaxKind.ExtendsKeyword)) { - // It's not uncommon for people to write improper constraints to a generic. If the - // user writes a constraint that is an expression and not an actual type, then parse - // it out as an expression (so we can recover well), but report that a type is needed - // instead. - if (isStartOfType() || !isStartOfExpression()) { - node.constraint = parseType(); - } - else { - // It was not a type, and it looked like an expression. Parse out an expression - // here so we recover well. Note: it is important that we call parseUnaryExpression - // and not parseExpression here. If the user has: - // - // - // - // We do *not* want to consume the `>` as we're consuming the expression for "". - node.expression = parseUnaryExpressionOrHigher(); - } + if (token() === SyntaxKind.EqualsToken) { + return createPostfixType(SyntaxKind.JSDocOptionalType, type); + } + return type; + } + function parseTypeQuery(): TypeQueryNode { + const node = (createNode(SyntaxKind.TypeQuery)); + parseExpected(SyntaxKind.TypeOfKeyword); + node.exprName = parseEntityName(/*allowReservedWords*/ true); + return finishNode(node); + } + function parseTypeParameter(): TypeParameterDeclaration { + const node = (createNode(SyntaxKind.TypeParameter)); + node.name = parseIdentifier(); + if (parseOptional(SyntaxKind.ExtendsKeyword)) { + // It's not uncommon for people to write improper constraints to a generic. If the + // user writes a constraint that is an expression and not an actual type, then parse + // it out as an expression (so we can recover well), but report that a type is needed + // instead. + if (isStartOfType() || !isStartOfExpression()) { + node.constraint = parseType(); } - - if (parseOptional(SyntaxKind.EqualsToken)) { - node.default = 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 "". + node.expression = parseUnaryExpressionOrHigher(); } - - return finishNode(node); } - - function parseTypeParameters(): NodeArray | undefined { - if (token() === SyntaxKind.LessThanToken) { - return parseBracketedList(ParsingContext.TypeParameters, parseTypeParameter, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); - } + if (parseOptional(SyntaxKind.EqualsToken)) { + node.default = parseType(); } - - function parseParameterType(): TypeNode | undefined { - if (parseOptional(SyntaxKind.ColonToken)) { - return parseType(); - } - - return undefined; + return finishNode(node); + } + function parseTypeParameters(): NodeArray | undefined { + if (token() === SyntaxKind.LessThanToken) { + return parseBracketedList(ParsingContext.TypeParameters, parseTypeParameter, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); } - - function isStartOfParameter(isJSDocParameter: boolean): boolean { - return token() === SyntaxKind.DotDotDotToken || - isIdentifierOrPattern() || - isModifierKind(token()) || - token() === SyntaxKind.AtToken || - isStartOfType(/*inStartOfParameter*/ !isJSDocParameter); - } - - function parseParameter(): ParameterDeclaration { - const node = createNodeWithJSDoc(SyntaxKind.Parameter); - if (token() === SyntaxKind.ThisKeyword) { - node.name = createIdentifier(/*isIdentifier*/ true); - node.type = parseParameterType(); - return finishNode(node); - } - - node.decorators = parseDecorators(); - node.modifiers = parseModifiers(); - node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); - - // FormalParameter [Yield,Await]: - // BindingElement[?Yield,?Await] - node.name = parseIdentifierOrPattern(); - if (getFullWidth(node.name) === 0 && !hasModifiers(node) && 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(); - } - - node.questionToken = parseOptionalToken(SyntaxKind.QuestionToken); + } + function parseParameterType(): TypeNode | undefined { + if (parseOptional(SyntaxKind.ColonToken)) { + return parseType(); + } + return undefined; + } + function isStartOfParameter(isJSDocParameter: boolean): boolean { + return token() === SyntaxKind.DotDotDotToken || + isIdentifierOrPattern() || + isModifierKind(token()) || + token() === SyntaxKind.AtToken || + isStartOfType(/*inStartOfParameter*/ !isJSDocParameter); + } + function parseParameter(): ParameterDeclaration { + const node = (createNodeWithJSDoc(SyntaxKind.Parameter)); + if (token() === SyntaxKind.ThisKeyword) { + node.name = createIdentifier(/*isIdentifier*/ true); node.type = parseParameterType(); - node.initializer = parseInitializer(); - return finishNode(node); } - - /** - * Note: If returnToken is EqualsGreaterThanToken, `signature.type` will always be defined. - * @returns If return type parsing succeeds - */ - function fillSignature( - returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, - flags: SignatureFlags, - signature: SignatureDeclaration): boolean { - if (!(flags & SignatureFlags.JSDoc)) { - signature.typeParameters = parseTypeParameters(); - } - const parametersParsedSuccessfully = parseParameterList(signature, flags); - if (shouldParseReturnType(returnToken, !!(flags & SignatureFlags.Type))) { - signature.type = parseTypeOrTypePredicate(); - if (typeHasArrowFunctionBlockingParseError(signature.type)) return false; - } - return parametersParsedSuccessfully; - } - - 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; + node.decorators = parseDecorators(); + node.modifiers = parseModifiers(); + node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); + // FormalParameter [Yield,Await]: + // BindingElement[?Yield,?Await] + node.name = parseIdentifierOrPattern(); + if (getFullWidth(node.name) === 0 && !hasModifiers(node) && 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(); } - - // Returns true on success. - function parseParameterList(signature: SignatureDeclaration, flags: SignatureFlags): boolean { - // 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)) { - signature.parameters = createMissingList(); + node.questionToken = parseOptionalToken(SyntaxKind.QuestionToken); + node.type = parseParameterType(); + node.initializer = parseInitializer(); + return finishNode(node); + } + /** + * Note: If returnToken is EqualsGreaterThanToken, `signature.type` will always be defined. + * @returns If return type parsing succeeds + */ + function fillSignature(returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, flags: SignatureFlags, signature: SignatureDeclaration): boolean { + if (!(flags & SignatureFlags.JSDoc)) { + signature.typeParameters = parseTypeParameters(); + } + const parametersParsedSuccessfully = parseParameterList(signature, flags); + if (shouldParseReturnType(returnToken, !!(flags & SignatureFlags.Type))) { + signature.type = parseTypeOrTypePredicate(); + if (typeHasArrowFunctionBlockingParseError(signature.type)) return false; - } - - const savedYieldContext = inYieldContext(); - const savedAwaitContext = inAwaitContext(); - - setYieldContext(!!(flags & SignatureFlags.Yield)); - setAwaitContext(!!(flags & SignatureFlags.Await)); - - signature.parameters = flags & SignatureFlags.JSDoc ? - parseDelimitedList(ParsingContext.JSDocParameters, parseJSDocParameter) : - parseDelimitedList(ParsingContext.Parameters, parseParameter); - - setYieldContext(savedYieldContext); - setAwaitContext(savedAwaitContext); - - return parseExpected(SyntaxKind.CloseParenToken); - } - - 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 node = createNodeWithJSDoc(kind); - if (kind === SyntaxKind.ConstructSignature) { - parseExpected(SyntaxKind.NewKeyword); - } - fillSignature(SyntaxKind.ColonToken, SignatureFlags.Type, node); - parseTypeMemberSemicolon(); - return finishNode(node); + return parametersParsedSuccessfully; + } + function shouldParseReturnType(returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, isType: boolean): boolean { + if (returnToken === SyntaxKind.EqualsGreaterThanToken) { + parseExpected(returnToken); + return true; } - - function isIndexSignature(): boolean { - return token() === SyntaxKind.OpenBracketToken && lookAhead(isUnambiguouslyIndexSignature); + else if (parseOptional(SyntaxKind.ColonToken)) { + return true; } - - 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. + 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 token() === SyntaxKind.ColonToken || token() === SyntaxKind.CommaToken || token() === SyntaxKind.CloseBracketToken; + return true; } - - function parseIndexSignatureDeclaration(node: IndexSignatureDeclaration): IndexSignatureDeclaration { - node.kind = SyntaxKind.IndexSignature; - node.parameters = parseBracketedList(ParsingContext.Parameters, parseParameter, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken); - node.type = parseTypeAnnotation(); - parseTypeMemberSemicolon(); - return finishNode(node); + return false; + } + // Returns true on success. + function parseParameterList(signature: SignatureDeclaration, flags: SignatureFlags): boolean { + // 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)) { + signature.parameters = createMissingList(); + return false; } - - function parsePropertyOrMethodSignature(node: PropertySignature | MethodSignature): PropertySignature | MethodSignature { - node.name = parsePropertyName(); - node.questionToken = parseOptionalToken(SyntaxKind.QuestionToken); - if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { - node.kind = SyntaxKind.MethodSignature; - // Method signatures don't exist in expression contexts. So they have neither - // [Yield] nor [Await] - fillSignature(SyntaxKind.ColonToken, SignatureFlags.Type, node); - } - else { - node.kind = SyntaxKind.PropertySignature; - node.type = parseTypeAnnotation(); - if (token() === SyntaxKind.EqualsToken) { - // 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. - (node).initializer = parseInitializer(); - } - } - parseTypeMemberSemicolon(); - return finishNode(node); + const savedYieldContext = inYieldContext(); + const savedAwaitContext = inAwaitContext(); + setYieldContext(!!(flags & SignatureFlags.Yield)); + setAwaitContext(!!(flags & SignatureFlags.Await)); + signature.parameters = flags & SignatureFlags.JSDoc ? + parseDelimitedList(ParsingContext.JSDocParameters, parseJSDocParameter) : + parseDelimitedList(ParsingContext.Parameters, parseParameter); + setYieldContext(savedYieldContext); + setAwaitContext(savedAwaitContext); + return parseExpected(SyntaxKind.CloseParenToken); + } + 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; } - - function isTypeMemberStart(): boolean { - // Return true if we have the start of a signature member - if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { - 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) { + // Didn't have a comma. We must have a (possible ASI) semicolon. + parseSemicolon(); + } + function parseSignatureMember(kind: SyntaxKind.CallSignature | SyntaxKind.ConstructSignature): CallSignatureDeclaration | ConstructSignatureDeclaration { + const node = (createNodeWithJSDoc(kind)); + if (kind === SyntaxKind.ConstructSignature) { + parseExpected(SyntaxKind.NewKeyword); + } + fillSignature(SyntaxKind.ColonToken, SignatureFlags.Type, node); + parseTypeMemberSemicolon(); + return finishNode(node); + } + 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; } - // 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 node = createNodeWithJSDoc(SyntaxKind.Unknown); - node.modifiers = parseModifiers(); - if (isIndexSignature()) { - return parseIndexSignatureDeclaration(node); - } - return parsePropertyOrMethodSignature(node); + else if (!isIdentifier()) { + return false; } - - function nextTokenIsOpenParenOrLessThan() { + else { + // Skip the identifier nextToken(); - return token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken; } - - function nextTokenIsDot() { - return nextToken() === SyntaxKind.DotToken; + // 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; } - - function nextTokenIsOpenParenOrLessThanOrDot() { - switch (nextToken()) { - case SyntaxKind.OpenParenToken: - case SyntaxKind.LessThanToken: - case SyntaxKind.DotToken: - 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; } - - function parseTypeLiteral(): TypeLiteralNode { - const node = createNode(SyntaxKind.TypeLiteral); - node.members = parseObjectTypeMembers(); - return finishNode(node); + // 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(node: IndexSignatureDeclaration): IndexSignatureDeclaration { + node.kind = SyntaxKind.IndexSignature; + node.parameters = parseBracketedList(ParsingContext.Parameters, parseParameter, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken); + node.type = parseTypeAnnotation(); + parseTypeMemberSemicolon(); + return finishNode(node); + } + function parsePropertyOrMethodSignature(node: PropertySignature | MethodSignature): PropertySignature | MethodSignature { + node.name = parsePropertyName(); + node.questionToken = parseOptionalToken(SyntaxKind.QuestionToken); + if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { + node.kind = SyntaxKind.MethodSignature; + // Method signatures don't exist in expression contexts. So they have neither + // [Yield] nor [Await] + fillSignature(SyntaxKind.ColonToken, SignatureFlags.Type, (node)); } - - function parseObjectTypeMembers(): NodeArray { - let members: NodeArray; - if (parseExpected(SyntaxKind.OpenBraceToken)) { - members = parseList(ParsingContext.TypeMembers, parseTypeMember); - parseExpected(SyntaxKind.CloseBraceToken); - } - else { - members = createMissingList(); + else { + node.kind = SyntaxKind.PropertySignature; + node.type = parseTypeAnnotation(); + if (token() === SyntaxKind.EqualsToken) { + // 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. + (node).initializer = parseInitializer(); } - - return members; } - - function isStartOfMappedType() { + parseTypeMemberSemicolon(); + return finishNode(node); + } + function isTypeMemberStart(): boolean { + // Return true if we have the start of a signature member + if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { + 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(); - 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 node = createNode(SyntaxKind.TypeParameter); - node.name = parseIdentifier(); - parseExpected(SyntaxKind.InKeyword); - node.constraint = parseType(); - return finishNode(node); + // Index signatures and computed property names are type members + if (token() === SyntaxKind.OpenBracketToken) { + return true; } - - function parseMappedType() { - const node = createNode(SyntaxKind.MappedType); - parseExpected(SyntaxKind.OpenBraceToken); - if (token() === SyntaxKind.ReadonlyKeyword || token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) { - node.readonlyToken = parseTokenNode(); - if (node.readonlyToken.kind !== SyntaxKind.ReadonlyKeyword) { - parseExpectedToken(SyntaxKind.ReadonlyKeyword); - } - } - parseExpected(SyntaxKind.OpenBracketToken); - node.typeParameter = parseMappedTypeParameter(); - parseExpected(SyntaxKind.CloseBracketToken); - if (token() === SyntaxKind.QuestionToken || token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) { - node.questionToken = parseTokenNode(); - if (node.questionToken.kind !== SyntaxKind.QuestionToken) { - parseExpectedToken(SyntaxKind.QuestionToken); - } - } - node.type = parseTypeAnnotation(); - parseSemicolon(); - parseExpected(SyntaxKind.CloseBraceToken); - return finishNode(node); + // Try to get the first property-like token following all modifiers + if (isLiteralPropertyName()) { + idToken = true; + nextToken(); } - - function parseTupleElementType() { - const pos = getNodePos(); - if (parseOptional(SyntaxKind.DotDotDotToken)) { - const node = createNode(SyntaxKind.RestType, pos); - node.type = parseType(); - return finishNode(node); - } - const type = parseType(); - if (!(contextFlags & NodeFlags.JSDoc) && type.kind === SyntaxKind.JSDocNullableType && type.pos === (type).type.pos) { - type.kind = SyntaxKind.OptionalType; - } - return type; + // 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); } - - function parseTupleType(): TupleTypeNode { - const node = createNode(SyntaxKind.TupleType); - node.elementTypes = parseBracketedList(ParsingContext.TupleElementTypes, parseTupleElementType, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken); - return finishNode(node); + if (token() === SyntaxKind.NewKeyword && lookAhead(nextTokenIsOpenParenOrLessThan)) { + return parseSignatureMember(SyntaxKind.ConstructSignature); } - - function parseParenthesizedType(): TypeNode { - const node = createNode(SyntaxKind.ParenthesizedType); - parseExpected(SyntaxKind.OpenParenToken); - node.type = parseType(); - parseExpected(SyntaxKind.CloseParenToken); - return finishNode(node); + const node = (createNodeWithJSDoc(SyntaxKind.Unknown)); + node.modifiers = parseModifiers(); + if (isIndexSignature()) { + return parseIndexSignatureDeclaration((node)); } - - function parseFunctionOrConstructorType(): TypeNode { - const pos = getNodePos(); - const kind = parseOptional(SyntaxKind.NewKeyword) ? SyntaxKind.ConstructorType : SyntaxKind.FunctionType; - const node = createNodeWithJSDoc(kind, pos); - fillSignature(SyntaxKind.EqualsGreaterThanToken, SignatureFlags.Type, node); - return finishNode(node); + return parsePropertyOrMethodSignature((node)); + } + 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; } - - function parseKeywordAndNoDot(): TypeNode | undefined { - const node = parseTokenNode(); - return token() === SyntaxKind.DotToken ? undefined : node; - } - - function parseLiteralTypeNode(negative?: boolean): LiteralTypeNode { - const node = createNode(SyntaxKind.LiteralType) as LiteralTypeNode; - let unaryMinusExpression!: PrefixUnaryExpression; - if (negative) { - unaryMinusExpression = createNode(SyntaxKind.PrefixUnaryExpression) as PrefixUnaryExpression; - unaryMinusExpression.operator = SyntaxKind.MinusToken; - nextToken(); - } - let expression: BooleanLiteral | LiteralExpression | PrefixUnaryExpression = token() === SyntaxKind.TrueKeyword || token() === SyntaxKind.FalseKeyword - ? parseTokenNode() - : parseLiteralLikeNode(token()) as LiteralExpression; - if (negative) { - unaryMinusExpression.operand = expression; - finishNode(unaryMinusExpression); - expression = unaryMinusExpression; - } - node.literal = expression; - return finishNode(node); + return false; + } + function parseTypeLiteral(): TypeLiteralNode { + const node = (createNode(SyntaxKind.TypeLiteral)); + node.members = parseObjectTypeMembers(); + return finishNode(node); + } + 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; } - - function isStartOfTypeOfImportType() { + if (token() === SyntaxKind.ReadonlyKeyword) { nextToken(); - return token() === SyntaxKind.ImportKeyword; - } - - function parseImportType(): ImportTypeNode { - sourceFile.flags |= NodeFlags.PossiblyContainsDynamicImport; - const node = createNode(SyntaxKind.ImportType) as ImportTypeNode; - if (parseOptional(SyntaxKind.TypeOfKeyword)) { - node.isTypeOf = true; - } - parseExpected(SyntaxKind.ImportKeyword); - parseExpected(SyntaxKind.OpenParenToken); - node.argument = parseType(); - parseExpected(SyntaxKind.CloseParenToken); - if (parseOptional(SyntaxKind.DotToken)) { - node.qualifier = parseEntityName(/*allowReservedWords*/ true, Diagnostics.Type_expected); - } - if (!scanner.hasPrecedingLineBreak() && reScanLessThanToken() === SyntaxKind.LessThanToken) { - node.typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); - } + } + return token() === SyntaxKind.OpenBracketToken && nextTokenIsIdentifier() && nextToken() === SyntaxKind.InKeyword; + } + function parseMappedTypeParameter() { + const node = (createNode(SyntaxKind.TypeParameter)); + node.name = parseIdentifier(); + parseExpected(SyntaxKind.InKeyword); + node.constraint = parseType(); + return finishNode(node); + } + function parseMappedType() { + const node = (createNode(SyntaxKind.MappedType)); + parseExpected(SyntaxKind.OpenBraceToken); + if (token() === SyntaxKind.ReadonlyKeyword || token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) { + node.readonlyToken = parseTokenNode(); + if (node.readonlyToken.kind !== SyntaxKind.ReadonlyKeyword) { + parseExpectedToken(SyntaxKind.ReadonlyKeyword); + } + } + parseExpected(SyntaxKind.OpenBracketToken); + node.typeParameter = parseMappedTypeParameter(); + parseExpected(SyntaxKind.CloseBracketToken); + if (token() === SyntaxKind.QuestionToken || token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) { + node.questionToken = parseTokenNode(); + if (node.questionToken.kind !== SyntaxKind.QuestionToken) { + parseExpectedToken(SyntaxKind.QuestionToken); + } + } + node.type = parseTypeAnnotation(); + parseSemicolon(); + parseExpected(SyntaxKind.CloseBraceToken); + return finishNode(node); + } + function parseTupleElementType() { + const pos = getNodePos(); + if (parseOptional(SyntaxKind.DotDotDotToken)) { + const node = (createNode(SyntaxKind.RestType, pos)); + node.type = parseType(); return finishNode(node); } - - function nextTokenIsNumericOrBigIntLiteral() { + const type = parseType(); + if (!(contextFlags & NodeFlags.JSDoc) && type.kind === SyntaxKind.JSDocNullableType && type.pos === (type).type.pos) { + type.kind = SyntaxKind.OptionalType; + } + return type; + } + function parseTupleType(): TupleTypeNode { + const node = (createNode(SyntaxKind.TupleType)); + node.elementTypes = parseBracketedList(ParsingContext.TupleElementTypes, parseTupleElementType, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken); + return finishNode(node); + } + function parseParenthesizedType(): TypeNode { + const node = (createNode(SyntaxKind.ParenthesizedType)); + parseExpected(SyntaxKind.OpenParenToken); + node.type = parseType(); + parseExpected(SyntaxKind.CloseParenToken); + return finishNode(node); + } + function parseFunctionOrConstructorType(): TypeNode { + const pos = getNodePos(); + const kind = parseOptional(SyntaxKind.NewKeyword) ? SyntaxKind.ConstructorType : SyntaxKind.FunctionType; + const node = (createNodeWithJSDoc(kind, pos)); + fillSignature(SyntaxKind.EqualsGreaterThanToken, SignatureFlags.Type, node); + return finishNode(node); + } + function parseKeywordAndNoDot(): TypeNode | undefined { + const node = parseTokenNode(); + return token() === SyntaxKind.DotToken ? undefined : node; + } + function parseLiteralTypeNode(negative?: boolean): LiteralTypeNode { + const node = (createNode(SyntaxKind.LiteralType) as LiteralTypeNode); + let unaryMinusExpression!: PrefixUnaryExpression; + if (negative) { + unaryMinusExpression = (createNode(SyntaxKind.PrefixUnaryExpression) as PrefixUnaryExpression); + unaryMinusExpression.operator = SyntaxKind.MinusToken; nextToken(); - return token() === SyntaxKind.NumericLiteral || token() === SyntaxKind.BigIntLiteral; } - - function parseNonArrayType(): TypeNode { + let expression: BooleanLiteral | LiteralExpression | PrefixUnaryExpression = token() === SyntaxKind.TrueKeyword || token() === SyntaxKind.FalseKeyword + ? parseTokenNode() + : parseLiteralLikeNode(token()) as LiteralExpression; + if (negative) { + unaryMinusExpression.operand = expression; + finishNode(unaryMinusExpression); + expression = unaryMinusExpression; + } + node.literal = expression; + return finishNode(node); + } + function isStartOfTypeOfImportType() { + nextToken(); + return token() === SyntaxKind.ImportKeyword; + } + function parseImportType(): ImportTypeNode { + sourceFile.flags |= NodeFlags.PossiblyContainsDynamicImport; + const node = (createNode(SyntaxKind.ImportType) as ImportTypeNode); + if (parseOptional(SyntaxKind.TypeOfKeyword)) { + node.isTypeOf = true; + } + parseExpected(SyntaxKind.ImportKeyword); + parseExpected(SyntaxKind.OpenParenToken); + node.argument = parseType(); + parseExpected(SyntaxKind.CloseParenToken); + if (parseOptional(SyntaxKind.DotToken)) { + node.qualifier = parseEntityName(/*allowReservedWords*/ true, Diagnostics.Type_expected); + } + if (!scanner.hasPrecedingLineBreak() && reScanLessThanToken() === SyntaxKind.LessThanToken) { + node.typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); + } + return finishNode(node); + } + 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.AsteriskToken: + return parseJSDocAllType(/*postfixEquals*/ false); + case SyntaxKind.AsteriskEqualsToken: + return parseJSDocAllType(/*postfixEquals*/ true); + case SyntaxKind.QuestionQuestionToken: + // If there is '??', consider that is 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: + return parseLiteralTypeNode(); + case SyntaxKind.MinusToken: + return lookAhead(nextTokenIsNumericOrBigIntLiteral) ? parseLiteralTypeNode(/*negative*/ true) : parseTypeReference(); + case SyntaxKind.VoidKeyword: + case SyntaxKind.NullKeyword: + 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(); + 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: + 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 { + let type = parseNonArrayType(); + while (!scanner.hasPrecedingLineBreak()) { 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.AsteriskToken: - return parseJSDocAllType(/*postfixEquals*/ false); - case SyntaxKind.AsteriskEqualsToken: - return parseJSDocAllType(/*postfixEquals*/ true); - case SyntaxKind.QuestionQuestionToken: - // If there is '??', consider that is 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: - return parseLiteralTypeNode(); - case SyntaxKind.MinusToken: - return lookAhead(nextTokenIsNumericOrBigIntLiteral) ? parseLiteralTypeNode(/*negative*/ true) : parseTypeReference(); - case SyntaxKind.VoidKeyword: - case SyntaxKind.NullKeyword: - return parseTokenNode(); - case SyntaxKind.ThisKeyword: { - const thisKeyword = parseThisTypeNode(); - if (token() === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) { - return parseThisTypePredicate(thisKeyword); + type = createPostfixType(SyntaxKind.JSDocNonNullableType, type); + break; + case SyntaxKind.QuestionToken: + // If not in JSDoc and next token is start of a type we have a conditional type + if (!(contextFlags & NodeFlags.JSDoc) && lookAhead(nextTokenIsStartOfType)) { + return type; + } + type = createPostfixType(SyntaxKind.JSDocNullableType, type); + break; + case SyntaxKind.OpenBracketToken: + parseExpected(SyntaxKind.OpenBracketToken); + if (isStartOfType()) { + const node = (createNode(SyntaxKind.IndexedAccessType, type.pos) as IndexedAccessTypeNode); + node.objectType = type; + node.indexType = parseType(); + parseExpected(SyntaxKind.CloseBracketToken); + type = finishNode(node); } else { - return thisKeyword; + const node = (createNode(SyntaxKind.ArrayType, type.pos) as ArrayTypeNode); + node.elementType = type; + parseExpected(SyntaxKind.CloseBracketToken); + type = finishNode(node); } - } - 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(); + break; default: - return parseTypeReference(); + return type; } } - - 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: - 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(); - } + return type; + } + function createPostfixType(kind: SyntaxKind, type: TypeNode) { + nextToken(); + const postfix = (createNode(kind, type.pos) as OptionalTypeNode | JSDocOptionalType | JSDocNonNullableType | JSDocNullableType); + postfix.type = type; + return finishNode(postfix); + } + function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword) { + const node = (createNode(SyntaxKind.TypeOperator)); + parseExpected(operator); + node.operator = operator; + node.type = parseTypeOperatorOrHigher(); + return finishNode(node); + } + function parseInferType(): InferTypeNode { + const node = (createNode(SyntaxKind.InferType)); + parseExpected(SyntaxKind.InferKeyword); + const typeParameter = (createNode(SyntaxKind.TypeParameter)); + typeParameter.name = parseIdentifier(); + node.typeParameter = finishNode(typeParameter); + return finishNode(node); + } + 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 parsePostfixTypeOrHigher(); + } + function parseUnionOrIntersectionType(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, parseConstituentType: () => TypeNode, operator: SyntaxKind.BarToken | SyntaxKind.AmpersandToken): TypeNode { + const start = scanner.getStartPos(); + const hasLeadingOperator = parseOptional(operator); + let type = parseConstituentType(); + if (token() === operator || hasLeadingOperator) { + const types = [type]; + while (parseOptional(operator)) { + types.push(parseConstituentType()); + } + const node = (createNode(kind, start)); + node.types = createNodeArray(types, start); + type = finishNode(node); + } + return type; + } + function parseIntersectionTypeOrHigher(): TypeNode { + return parseUnionOrIntersectionType(SyntaxKind.IntersectionType, parseTypeOperatorOrHigher, SyntaxKind.AmpersandToken); + } + function parseUnionTypeOrHigher(): TypeNode { + return parseUnionOrIntersectionType(SyntaxKind.UnionType, parseIntersectionTypeOrHigher, SyntaxKind.BarToken); + } + function isStartOfFunctionType(): boolean { + if (token() === SyntaxKind.LessThanToken) { + return true; + } + return token() === SyntaxKind.OpenParenToken && lookAhead(isUnambiguouslyStartOfFunctionType); + } + function skipParameterStart(): boolean { + if (isModifierKind(token())) { + // Skip modifiers + parseModifiers(); } - - function isStartOfParenthesizedOrFunctionType() { + if (isIdentifier() || token() === SyntaxKind.ThisKeyword) { nextToken(); - return token() === SyntaxKind.CloseParenToken || isStartOfParameter(/*isJSDocParameter*/ false) || isStartOfType(); + return true; } - - function parsePostfixTypeOrHigher(): TypeNode { - let type = parseNonArrayType(); - while (!scanner.hasPrecedingLineBreak()) { - switch (token()) { - case SyntaxKind.ExclamationToken: - type = createPostfixType(SyntaxKind.JSDocNonNullableType, type); - break; - case SyntaxKind.QuestionToken: - // If not in JSDoc and next token is start of a type we have a conditional type - if (!(contextFlags & NodeFlags.JSDoc) && lookAhead(nextTokenIsStartOfType)) { - return type; - } - type = createPostfixType(SyntaxKind.JSDocNullableType, type); - break; - case SyntaxKind.OpenBracketToken: - parseExpected(SyntaxKind.OpenBracketToken); - if (isStartOfType()) { - const node = createNode(SyntaxKind.IndexedAccessType, type.pos) as IndexedAccessTypeNode; - node.objectType = type; - node.indexType = parseType(); - parseExpected(SyntaxKind.CloseBracketToken); - type = finishNode(node); - } - else { - const node = createNode(SyntaxKind.ArrayType, type.pos) as ArrayTypeNode; - node.elementType = type; - parseExpected(SyntaxKind.CloseBracketToken); - type = finishNode(node); - } - break; - default: - return type; + 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 type; } - - function createPostfixType(kind: SyntaxKind, type: TypeNode) { - nextToken(); - const postfix = createNode(kind, type.pos) as OptionalTypeNode | JSDocOptionalType | JSDocNonNullableType | JSDocNullableType; - postfix.type = type; - return finishNode(postfix); - } - - function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword) { - const node = createNode(SyntaxKind.TypeOperator); - parseExpected(operator); - node.operator = operator; - node.type = parseTypeOperatorOrHigher(); - return finishNode(node); - } - - function parseInferType(): InferTypeNode { - const node = createNode(SyntaxKind.InferType); - parseExpected(SyntaxKind.InferKeyword); - const typeParameter = createNode(SyntaxKind.TypeParameter); - typeParameter.name = parseIdentifier(); - node.typeParameter = finishNode(typeParameter); + return false; + } + function parseTypeOrTypePredicate(): TypeNode { + const typePredicateVariable = isIdentifier() && tryParse(parseTypePredicatePrefix); + const type = parseType(); + if (typePredicateVariable) { + const node = (createNode(SyntaxKind.TypePredicate, typePredicateVariable.pos)); + node.assertsModifier = undefined; + node.parameterName = typePredicateVariable; + node.type = type; return finishNode(node); } - - 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 parsePostfixTypeOrHigher(); - } - - function parseUnionOrIntersectionType(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, parseConstituentType: () => TypeNode, operator: SyntaxKind.BarToken | SyntaxKind.AmpersandToken): TypeNode { - const start = scanner.getStartPos(); - const hasLeadingOperator = parseOptional(operator); - let type = parseConstituentType(); - if (token() === operator || hasLeadingOperator) { - const types = [type]; - while (parseOptional(operator)) { - types.push(parseConstituentType()); - } - const node = createNode(kind, start); - node.types = createNodeArray(types, start); - type = finishNode(node); - } + else { return type; } - - function parseIntersectionTypeOrHigher(): TypeNode { - return parseUnionOrIntersectionType(SyntaxKind.IntersectionType, parseTypeOperatorOrHigher, SyntaxKind.AmpersandToken); + } + function parseTypePredicatePrefix() { + const id = parseIdentifier(); + if (token() === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) { + nextToken(); + return id; } - - function parseUnionTypeOrHigher(): TypeNode { - return parseUnionOrIntersectionType(SyntaxKind.UnionType, parseIntersectionTypeOrHigher, SyntaxKind.BarToken); + } + function parseAssertsTypePredicate(): TypeNode { + const node = (createNode(SyntaxKind.TypePredicate)); + node.assertsModifier = parseExpectedToken(SyntaxKind.AssertsKeyword); + node.parameterName = token() === SyntaxKind.ThisKeyword ? parseThisTypeNode() : parseIdentifier(); + node.type = parseOptional(SyntaxKind.IsKeyword) ? parseType() : undefined; + return finishNode(node); + } + function parseType(): TypeNode { + // The rules about 'yield' only apply to actual code/expression contexts. They don't + // apply to 'type' contexts. So we disable these parameters here before moving on. + return doOutsideOfContext(NodeFlags.TypeExcludesFlags, parseTypeWorker); + } + function parseTypeWorker(noConditionalTypes?: boolean): TypeNode { + if (isStartOfFunctionType() || token() === SyntaxKind.NewKeyword) { + return parseFunctionOrConstructorType(); + } + const type = parseUnionTypeOrHigher(); + if (!noConditionalTypes && !scanner.hasPrecedingLineBreak() && parseOptional(SyntaxKind.ExtendsKeyword)) { + const node = (createNode(SyntaxKind.ConditionalType, type.pos)); + node.checkType = type; + // The type following 'extends' is not permitted to be another conditional type + node.extendsType = parseTypeWorker(/*noConditionalTypes*/ true); + parseExpected(SyntaxKind.QuestionToken); + node.trueType = parseTypeWorker(); + parseExpected(SyntaxKind.ColonToken); + node.falseType = parseTypeWorker(); + return finishNode(node); } - - function isStartOfFunctionType(): boolean { - if (token() === SyntaxKind.LessThanToken) { + return type; + } + 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; - } - return token() === SyntaxKind.OpenParenToken && lookAhead(isUnambiguouslyStartOfFunctionType); + case SyntaxKind.ImportKeyword: + return lookAhead(nextTokenIsOpenParenOrLessThanOrDot); + default: + return isIdentifier(); } - - 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 isStartOfExpression(): boolean { + if (isStartOfLeftHandSideExpression()) { + return true; } - - function isUnambiguouslyStartOfFunctionType() { - nextToken(); - if (token() === SyntaxKind.CloseParenToken || token() === SyntaxKind.DotDotDotToken) { - // ( ) - // ( ... + 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: + // 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; - } - 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 = + 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; } - if (token() === SyntaxKind.CloseParenToken) { - nextToken(); - if (token() === SyntaxKind.EqualsGreaterThanToken) { - // ( xxx ) => - return true; - } - } - } - return false; + return isIdentifier(); } - - function parseTypeOrTypePredicate(): TypeNode { - const typePredicateVariable = isIdentifier() && tryParse(parseTypePredicatePrefix); - const type = parseType(); - if (typePredicateVariable) { - const node = createNode(SyntaxKind.TypePredicate, typePredicateVariable.pos); - node.assertsModifier = undefined; - node.parameterName = typePredicateVariable; - node.type = type; - return finishNode(node); - } - else { - return type; - } + } + function isStartOfExpressionStatement(): boolean { + // As per the grammar, none of '{' or 'function' or 'class' can start an expression statement. + return token() !== SyntaxKind.OpenBraceToken && + token() !== SyntaxKind.FunctionKeyword && + token() !== SyntaxKind.ClassKeyword && + token() !== SyntaxKind.AtToken && + isStartOfExpression(); + } + function parseExpression(): Expression { + // Expression[in]: + // AssignmentExpression[in] + // Expression[in] , AssignmentExpression[in] + // clear the decorator context when parsing Expression, as it should be unambiguous when parsing a decorator + const saveDecoratorContext = inDecoratorContext(); + if (saveDecoratorContext) { + setDecoratorContext(/*val*/ false); + } + let expr = parseAssignmentExpressionOrHigher(); + let operatorToken: BinaryOperatorToken; + while ((operatorToken = parseOptionalToken(SyntaxKind.CommaToken))) { + expr = makeBinaryExpression(expr, operatorToken, parseAssignmentExpressionOrHigher()); + } + if (saveDecoratorContext) { + setDecoratorContext(/*val*/ true); + } + return expr; + } + function parseInitializer(): Expression | undefined { + return parseOptional(SyntaxKind.EqualsToken) ? parseAssignmentExpressionOrHigher() : undefined; + } + function parseAssignmentExpressionOrHigher(): Expression { + // AssignmentExpression[in,yield]: + // 1) ConditionalExpression[?in,?yield] + // 2) LeftHandSideExpression = AssignmentExpression[?in,?yield] + // 3) LeftHandSideExpression AssignmentOperator AssignmentExpression[?in,?yield] + // 4) ArrowFunctionExpression[?in,?yield] + // 5) 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() || tryParseAsyncSimpleArrowFunctionExpression(); + 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 expr = parseBinaryExpressionOrHigher(/*precedence*/ 0); + // To avoid a look-ahead, we did not handle the case of an arrow function with a single un-parenthesized + // parameter ('x => ...') above. We handle it here by checking if the parsed expression was a single + // identifier and the current token is an arrow. + if (expr.kind === SyntaxKind.Identifier && token() === SyntaxKind.EqualsGreaterThanToken) { + return parseSimpleArrowFunctionExpression((expr)); + } + // Now see if we might be in cases '2' or '3'. + // If the expression was a LHS expression, and we have an assignment operator, then + // we're in '2' or '3'. Consume the assignment and return. + // + // Note: we call reScanGreaterToken so that we get an appropriately merged token + // for cases like `> > =` becoming `>>=` + if (isLeftHandSideExpression(expr) && isAssignmentOperator(reScanGreaterToken())) { + return makeBinaryExpression(expr, parseTokenNode(), parseAssignmentExpressionOrHigher()); } - - function parseTypePredicatePrefix() { - const id = parseIdentifier(); - if (token() === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) { - nextToken(); - return id; + // It wasn't an assignment or a lambda. This is a conditional expression: + return parseConditionalExpressionRest(expr); + } + function isYieldExpression(): boolean { + if (token() === SyntaxKind.YieldKeyword) { + // If we have a 'yield' keyword, and 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); } - - function parseAssertsTypePredicate(): TypeNode { - const node = createNode(SyntaxKind.TypePredicate); - node.assertsModifier = parseExpectedToken(SyntaxKind.AssertsKeyword); - node.parameterName = token() === SyntaxKind.ThisKeyword ? parseThisTypeNode() : parseIdentifier(); - node.type = parseOptional(SyntaxKind.IsKeyword) ? parseType() : undefined; + return false; + } + function nextTokenIsIdentifierOnSameLine() { + nextToken(); + return !scanner.hasPrecedingLineBreak() && isIdentifier(); + } + function parseYieldExpression(): YieldExpression { + const node = (createNode(SyntaxKind.YieldExpression)); + // YieldExpression[In] : + // yield + // yield [no LineTerminator here] [Lexical goal InputElementRegExp]AssignmentExpression[?In, Yield] + // yield [no LineTerminator here] * [Lexical goal InputElementRegExp]AssignmentExpression[?In, Yield] + nextToken(); + if (!scanner.hasPrecedingLineBreak() && + (token() === SyntaxKind.AsteriskToken || isStartOfExpression())) { + node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); + node.expression = parseAssignmentExpressionOrHigher(); return finishNode(node); } - - function parseType(): TypeNode { - // The rules about 'yield' only apply to actual code/expression contexts. They don't - // apply to 'type' contexts. So we disable these parameters here before moving on. - return doOutsideOfContext(NodeFlags.TypeExcludesFlags, parseTypeWorker); - } - - function parseTypeWorker(noConditionalTypes?: boolean): TypeNode { - if (isStartOfFunctionType() || token() === SyntaxKind.NewKeyword) { - return parseFunctionOrConstructorType(); - } - const type = parseUnionTypeOrHigher(); - if (!noConditionalTypes && !scanner.hasPrecedingLineBreak() && parseOptional(SyntaxKind.ExtendsKeyword)) { - const node = createNode(SyntaxKind.ConditionalType, type.pos); - node.checkType = type; - // The type following 'extends' is not permitted to be another conditional type - node.extendsType = parseTypeWorker(/*noConditionalTypes*/ true); - parseExpected(SyntaxKind.QuestionToken); - node.trueType = parseTypeWorker(); - parseExpected(SyntaxKind.ColonToken); - node.falseType = parseTypeWorker(); - return finishNode(node); - } - return type; + 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(node); } - - function parseTypeAnnotation(): TypeNode | undefined { - return parseOptional(SyntaxKind.ColonToken) ? parseType() : undefined; + } + function parseSimpleArrowFunctionExpression(identifier: Identifier, asyncModifier?: NodeArray | undefined): ArrowFunction { + Debug.assert(token() === SyntaxKind.EqualsGreaterThanToken, "parseSimpleArrowFunctionExpression should only have been called if we had a =>"); + let node: ArrowFunction; + if (asyncModifier) { + node = (createNode(SyntaxKind.ArrowFunction, asyncModifier.pos)); + node.modifiers = asyncModifier; } - - // 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.ImportKeyword: - return lookAhead(nextTokenIsOpenParenOrLessThanOrDot); - default: - return isIdentifier(); - } + else { + node = (createNode(SyntaxKind.ArrowFunction, identifier.pos)); + } + const parameter = (createNode(SyntaxKind.Parameter, identifier.pos)); + parameter.name = identifier; + finishNode(parameter); + node.parameters = createNodeArray([parameter], parameter.pos, parameter.end); + node.equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken); + node.body = parseArrowFunctionExpressionBody(/*isAsync*/ !!asyncModifier); + return addJSDocComment(finishNode(node)); + } + function tryParseParenthesizedArrowFunctionExpression(): Expression | undefined { + const triState = isParenthesizedArrowFunctionExpression(); + if (triState === Tristate.False) { + // It's definitely not a parenthesized arrow function expression. + return undefined; } - - 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: - // 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; - default: - // Error tolerance. If we see the start of some binary operator, we consider - // that the start of an expression. That way we'll parse out a missing identifier, - // give a good message about an identifier being missing, and then consume the - // rest of the binary expression. - if (isBinaryOperator()) { - return true; - } - - return isIdentifier(); - } - } - - function isStartOfExpressionStatement(): boolean { - // As per the grammar, none of '{' or 'function' or 'class' can start an expression statement. - return token() !== SyntaxKind.OpenBraceToken && - token() !== SyntaxKind.FunctionKeyword && - token() !== SyntaxKind.ClassKeyword && - token() !== SyntaxKind.AtToken && - isStartOfExpression(); - } - - function parseExpression(): Expression { - // Expression[in]: - // AssignmentExpression[in] - // Expression[in] , AssignmentExpression[in] - - // clear the decorator context when parsing Expression, as it should be unambiguous when parsing a decorator - const saveDecoratorContext = inDecoratorContext(); - if (saveDecoratorContext) { - setDecoratorContext(/*val*/ false); - } - - let expr = parseAssignmentExpressionOrHigher(); - let operatorToken: BinaryOperatorToken; - while ((operatorToken = parseOptionalToken(SyntaxKind.CommaToken))) { - expr = makeBinaryExpression(expr, operatorToken, parseAssignmentExpressionOrHigher()); - } - - if (saveDecoratorContext) { - setDecoratorContext(/*val*/ true); - } - return expr; - } - - function parseInitializer(): Expression | undefined { - return parseOptional(SyntaxKind.EqualsToken) ? parseAssignmentExpressionOrHigher() : undefined; - } - - function parseAssignmentExpressionOrHigher(): Expression { - // AssignmentExpression[in,yield]: - // 1) ConditionalExpression[?in,?yield] - // 2) LeftHandSideExpression = AssignmentExpression[?in,?yield] - // 3) LeftHandSideExpression AssignmentOperator AssignmentExpression[?in,?yield] - // 4) ArrowFunctionExpression[?in,?yield] - // 5) 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() || tryParseAsyncSimpleArrowFunctionExpression(); - 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 expr = parseBinaryExpressionOrHigher(/*precedence*/ 0); - - // To avoid a look-ahead, we did not handle the case of an arrow function with a single un-parenthesized - // parameter ('x => ...') above. We handle it here by checking if the parsed expression was a single - // identifier and the current token is an arrow. - if (expr.kind === SyntaxKind.Identifier && token() === SyntaxKind.EqualsGreaterThanToken) { - return parseSimpleArrowFunctionExpression(expr); - } - - // Now see if we might be in cases '2' or '3'. - // If the expression was a LHS expression, and we have an assignment operator, then - // we're in '2' or '3'. Consume the assignment and return. - // - // Note: we call reScanGreaterToken so that we get an appropriately merged token - // for cases like `> > =` becoming `>>=` - if (isLeftHandSideExpression(expr) && isAssignmentOperator(reScanGreaterToken())) { - return makeBinaryExpression(expr, parseTokenNode(), parseAssignmentExpressionOrHigher()); - } - - // It wasn't an assignment or a lambda. This is a conditional expression: - return parseConditionalExpressionRest(expr); - } - - function isYieldExpression(): boolean { - if (token() === SyntaxKind.YieldKeyword) { - // If we have a 'yield' keyword, and 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; + // 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. + const arrowFunction = triState === Tristate.True + ? parseParenthesizedArrowFunctionExpressionHead(/*allowAmbiguity*/ true) + : tryParse(parsePossibleParenthesizedArrowFunctionExpressionHead); + if (!arrowFunction) { + // Didn't appear to actually be a parenthesized arrow function. Just bail out. + return undefined; } - - function nextTokenIsIdentifierOnSameLine() { - nextToken(); - return !scanner.hasPrecedingLineBreak() && isIdentifier(); - } - - function parseYieldExpression(): YieldExpression { - const node = createNode(SyntaxKind.YieldExpression); - - // YieldExpression[In] : - // yield - // yield [no LineTerminator here] [Lexical goal InputElementRegExp]AssignmentExpression[?In, Yield] - // yield [no LineTerminator here] * [Lexical goal InputElementRegExp]AssignmentExpression[?In, Yield] + const isAsync = hasModifier(arrowFunction, ModifierFlags.Async); + // 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(); + arrowFunction.equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken); + arrowFunction.body = (lastToken === SyntaxKind.EqualsGreaterThanToken || lastToken === SyntaxKind.OpenBraceToken) + ? parseArrowFunctionExpressionBody(isAsync) + : parseIdentifier(); + return finishNode(arrowFunction); + } + // True -> We definitely expect a parenthesized arrow function here. + // False -> There *cannot* be a parenthesized arrow function here. + // Unknown -> There *might* be a parenthesized arrow function here. + // Speculatively look ahead to be sure, and rollback if not. + function isParenthesizedArrowFunctionExpression(): Tristate { + if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken || 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() && - (token() === SyntaxKind.AsteriskToken || isStartOfExpression())) { - node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); - node.expression = parseAssignmentExpressionOrHigher(); - return finishNode(node); + if (scanner.hasPrecedingLineBreak()) { + return Tristate.False; } - 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(node); + if (token() !== SyntaxKind.OpenParenToken && token() !== SyntaxKind.LessThanToken) { + return Tristate.False; } } - - function parseSimpleArrowFunctionExpression(identifier: Identifier, asyncModifier?: NodeArray | undefined): ArrowFunction { - Debug.assert(token() === SyntaxKind.EqualsGreaterThanToken, "parseSimpleArrowFunctionExpression should only have been called if we had a =>"); - - let node: ArrowFunction; - if (asyncModifier) { - node = createNode(SyntaxKind.ArrowFunction, asyncModifier.pos); - node.modifiers = asyncModifier; + 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; + } } - else { - node = createNode(SyntaxKind.ArrowFunction, identifier.pos); - } - - const parameter = createNode(SyntaxKind.Parameter, identifier.pos); - parameter.name = identifier; - finishNode(parameter); - - node.parameters = createNodeArray([parameter], parameter.pos, parameter.end); - - node.equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken); - node.body = parseArrowFunctionExpressionBody(/*isAsync*/ !!asyncModifier); - - return addJSDocComment(finishNode(node)); - } - - function tryParseParenthesizedArrowFunctionExpression(): Expression | undefined { - const triState = isParenthesizedArrowFunctionExpression(); - if (triState === Tristate.False) { - // It's definitely not a parenthesized arrow function expression. - return undefined; + // 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; } - - // 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. - const arrowFunction = triState === Tristate.True - ? parseParenthesizedArrowFunctionExpressionHead(/*allowAmbiguity*/ true) - : tryParse(parsePossibleParenthesizedArrowFunctionExpressionHead); - - if (!arrowFunction) { - // Didn't appear to actually be a parenthesized arrow function. Just bail out. - return undefined; + // Simple case: "(..." + // This is an arrow function with a rest parameter. + if (second === SyntaxKind.DotDotDotToken) { + return Tristate.True; } - - const isAsync = hasModifier(arrowFunction, ModifierFlags.Async); - - // 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(); - arrowFunction.equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken); - arrowFunction.body = (lastToken === SyntaxKind.EqualsGreaterThanToken || lastToken === SyntaxKind.OpenBraceToken) - ? parseArrowFunctionExpressionBody(isAsync) - : parseIdentifier(); - - return finishNode(arrowFunction); - } - - // True -> We definitely expect a parenthesized arrow function here. - // False -> There *cannot* be a parenthesized arrow function here. - // Unknown -> There *might* be a parenthesized arrow function here. - // Speculatively look ahead to be sure, and rollback if not. - function isParenthesizedArrowFunctionExpression(): Tristate { - if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken || 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. + // 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)) { return Tristate.True; } - // Definitely not a parenthesized arrow function. + // 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; } - - function isParenthesizedArrowFunctionExpressionWorker() { - if (token() === SyntaxKind.AsyncKeyword) { - nextToken(); - if (scanner.hasPrecedingLineBreak()) { - return Tristate.False; - } - if (token() !== SyntaxKind.OpenParenToken && token() !== SyntaxKind.LessThanToken) { - 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; } - - 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. + // JSX overrides + if (sourceFile.languageVariant === LanguageVariant.JSX) { + const isArrowFunctionInJsx = lookAhead(() => { const third = nextToken(); - switch (third) { - case SyntaxKind.EqualsGreaterThanToken: - case SyntaxKind.ColonToken: - case SyntaxKind.OpenBraceToken: - return Tristate.True; - default: - return Tristate.False; + if (third === SyntaxKind.ExtendsKeyword) { + const fourth = nextToken(); + switch (fourth) { + case SyntaxKind.EqualsToken: + case SyntaxKind.GreaterThanToken: + return false; + default: + return true; + } } - } - - // 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) { + else if (third === SyntaxKind.CommaToken) { + return true; + } + return false; + }); + if (isArrowFunctionInJsx) { 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)) { - 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 (sourceFile.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) { - return true; - } - return false; - }); - - if (isArrowFunctionInJsx) { - return Tristate.True; - } - - return Tristate.False; - } - - // This *could* be a parenthesized arrow function. - return Tristate.Unknown; - } + // This *could* be a parenthesized arrow function. + return Tristate.Unknown; } - - function parsePossibleParenthesizedArrowFunctionExpressionHead(): ArrowFunction | undefined { - const tokenPos = scanner.getTokenPos(); - if (notParenthesizedArrow && notParenthesizedArrow.has(tokenPos.toString())) { - return undefined; - } - - const result = parseParenthesizedArrowFunctionExpressionHead(/*allowAmbiguity*/ false); - if (!result) { - (notParenthesizedArrow || (notParenthesizedArrow = createMap())).set(tokenPos.toString(), true); - } - - return result; - } - - function tryParseAsyncSimpleArrowFunctionExpression(): 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 asyncModifier = parseModifiersForArrowFunction(); - const expr = parseBinaryExpressionOrHigher(/*precedence*/ 0); - return parseSimpleArrowFunctionExpression(expr, asyncModifier); - } - } + } + function parsePossibleParenthesizedArrowFunctionExpressionHead(): ArrowFunction | undefined { + const tokenPos = scanner.getTokenPos(); + if (notParenthesizedArrow && notParenthesizedArrow.has(tokenPos.toString())) { 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(/*precedence*/ 0); - if (!scanner.hasPrecedingLineBreak() && expr.kind === SyntaxKind.Identifier && token() === SyntaxKind.EqualsGreaterThanToken) { - return Tristate.True; - } - } - - return Tristate.False; - } - - function parseParenthesizedArrowFunctionExpressionHead(allowAmbiguity: boolean): ArrowFunction | undefined { - const node = createNodeWithJSDoc(SyntaxKind.ArrowFunction); - node.modifiers = parseModifiersForArrowFunction(); - const isAsync = hasModifier(node, ModifierFlags.Async) ? 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. - if (!fillSignature(SyntaxKind.ColonToken, isAsync, node) && !allowAmbiguity) { - 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. - // - // So we need just a bit of lookahead to ensure that it can only be a signature. - const hasJSDocFunctionType = node.type && isJSDocFunctionType(node.type); - 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; - } - - return node; - } - - function parseArrowFunctionExpressionBody(isAsync: boolean): Block | Expression { - if (token() === SyntaxKind.OpenBraceToken) { - return parseFunctionBlock(isAsync ? SignatureFlags.Await : SignatureFlags.None); - } - - if (token() !== SyntaxKind.SemicolonToken && - token() !== SyntaxKind.FunctionKeyword && - token() !== SyntaxKind.ClassKeyword && - isStartOfStatement() && - !isStartOfExpressionStatement()) { - // Check if we got a plain statement (i.e. no expression-statements, no function/class expressions/declarations) - // - // Here we try to recover from a potential error situation in the case where the - // user meant to supply a block. For example, if the user wrote: - // - // a => - // let v = 0; - // } - // - // they may be missing an open brace. Check to see if that's the case so we can - // try to recover better. If we don't do this, then the next close curly we see may end - // up preemptively closing the containing construct. - // - // Note: even when 'IgnoreMissingOpenBrace' is passed, parseBody will still error. - return parseFunctionBlock(SignatureFlags.IgnoreMissingOpenBrace | (isAsync ? SignatureFlags.Await : SignatureFlags.None)); - } - - return isAsync - ? doInAwaitContext(parseAssignmentExpressionOrHigher) - : doOutsideOfAwaitContext(parseAssignmentExpressionOrHigher); - } - - function parseConditionalExpressionRest(leftOperand: Expression): 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. - const node = createNode(SyntaxKind.ConditionalExpression, leftOperand.pos); - node.condition = leftOperand; - node.questionToken = questionToken; - node.whenTrue = doOutsideOfContext(disallowInAndDecoratorContext, parseAssignmentExpressionOrHigher); - node.colonToken = parseExpectedToken(SyntaxKind.ColonToken); - node.whenFalse = nodeIsPresent(node.colonToken) - ? parseAssignmentExpressionOrHigher() - : createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(SyntaxKind.ColonToken)); - return finishNode(node); - } - - function parseBinaryExpressionOrHigher(precedence: number): Expression { - const leftOperand = parseUnaryExpressionOrHigher(); - return parseBinaryExpressionRest(precedence, leftOperand); - } - - function isInOrOfKeyword(t: SyntaxKind) { - return t === SyntaxKind.InKeyword || t === SyntaxKind.OfKeyword; - } - - function parseBinaryExpressionRest(precedence: number, leftOperand: Expression): Expression { - while (true) { - // We either have a binary operator here, or we're finished. We call - // reScanGreaterToken so that we merge token sequences like > and = into >= - - reScanGreaterToken(); - 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) { - // Make sure we *do* perform ASI for constructs like this: - // var x = foo - // as (Bar) - // This should be parsed as an initialized variable, followed - // by a function call to 'as' with the argument 'Bar' - if (scanner.hasPrecedingLineBreak()) { - break; - } - else { - nextToken(); - leftOperand = makeAsExpression(leftOperand, parseType()); - } - } - else { - leftOperand = makeBinaryExpression(leftOperand, parseTokenNode(), parseBinaryExpressionOrHigher(newPrecedence)); - } - } - - return leftOperand; - } - - function isBinaryOperator() { - if (inDisallowInContext() && token() === SyntaxKind.InKeyword) { - return false; - } - - return getBinaryOperatorPrecedence(token()) > 0; - } - - function makeBinaryExpression(left: Expression, operatorToken: BinaryOperatorToken, right: Expression): BinaryExpression { - const node = createNode(SyntaxKind.BinaryExpression, left.pos); - node.left = left; - node.operatorToken = operatorToken; - node.right = right; - return finishNode(node); - } - - function makeAsExpression(left: Expression, right: TypeNode): AsExpression { - const node = createNode(SyntaxKind.AsExpression, left.pos); - node.expression = left; - node.type = right; - return finishNode(node); - } - - function parsePrefixUnaryExpression() { - const node = createNode(SyntaxKind.PrefixUnaryExpression); - node.operator = token(); - nextToken(); - node.operand = parseSimpleUnaryExpression(); - - return finishNode(node); - } - - function parseDeleteExpression() { - const node = createNode(SyntaxKind.DeleteExpression); - nextToken(); - node.expression = parseSimpleUnaryExpression(); - return finishNode(node); - } - - function parseTypeOfExpression() { - const node = createNode(SyntaxKind.TypeOfExpression); - nextToken(); - node.expression = parseSimpleUnaryExpression(); - return finishNode(node); - } - - function parseVoidExpression() { - const node = createNode(SyntaxKind.VoidExpression); - nextToken(); - node.expression = parseSimpleUnaryExpression(); - return finishNode(node); + const result = parseParenthesizedArrowFunctionExpressionHead(/*allowAmbiguity*/ false); + if (!result) { + (notParenthesizedArrow || (notParenthesizedArrow = createMap())).set(tokenPos.toString(), true); } - - function isAwaitExpression(): boolean { - if (token() === SyntaxKind.AwaitKeyword) { - if (inAwaitContext()) { - return true; - } - - // here we are using similar heuristics as 'isYieldExpression' - return lookAhead(nextTokenIsIdentifierOrKeywordOrLiteralOnSameLine); + return result; + } + function tryParseAsyncSimpleArrowFunctionExpression(): 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 asyncModifier = parseModifiersForArrowFunction(); + const expr = parseBinaryExpressionOrHigher(/*precedence*/ 0); + return parseSimpleArrowFunctionExpression((expr), asyncModifier); } - - return false; } - - function parseAwaitExpression() { - const node = createNode(SyntaxKind.AwaitExpression); + 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(); - node.expression = parseSimpleUnaryExpression(); - return finishNode(node); - } - - /** - * 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 updateExpression = parseUpdateExpression(); - return token() === SyntaxKind.AsteriskAsteriskToken ? - parseBinaryExpressionRest(getBinaryOperatorPrecedence(token()), updateExpression) : - 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)); - } + // 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; } - 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 for un-parenthesized AsyncArrowFunction + const expr = parseBinaryExpressionOrHigher(/*precedence*/ 0); + if (!scanner.hasPrecedingLineBreak() && expr.kind === SyntaxKind.Identifier && token() === SyntaxKind.EqualsGreaterThanToken) { + return Tristate.True; } } - - /** - * 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 (sourceFile.languageVariant !== LanguageVariant.JSX) { - return false; - } - // We are in JSX context and the token is part of JSXElement. - // falls through - default: - return true; - } + return Tristate.False; + } + function parseParenthesizedArrowFunctionExpressionHead(allowAmbiguity: boolean): ArrowFunction | undefined { + const node = (createNodeWithJSDoc(SyntaxKind.ArrowFunction)); + node.modifiers = parseModifiersForArrowFunction(); + const isAsync = hasModifier(node, ModifierFlags.Async) ? 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. + if (!fillSignature(SyntaxKind.ColonToken, isAsync, node) && !allowAmbiguity) { + return undefined; } - - /** - * 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 node = createNode(SyntaxKind.PrefixUnaryExpression); - node.operator = token(); - nextToken(); - node.operand = parseLeftHandSideExpressionOrHigher(); - return finishNode(node); - } - else if (sourceFile.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 node = createNode(SyntaxKind.PostfixUnaryExpression, expression.pos); - node.operand = expression; - node.operator = token(); - nextToken(); - return finishNode(node); - } - - return expression; + // 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. + // + // So we need just a bit of lookahead to ensure that it can only be a signature. + const hasJSDocFunctionType = node.type && isJSDocFunctionType(node.type); + 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; } - - 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. - 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 - sourceFile.flags |= NodeFlags.PossiblyContainsDynamicImport; - expression = parseTokenNode(); - } - else if (lookAhead(nextTokenIsDot)) { - // This is an 'import.*' metaproperty (i.e. 'import.meta') - const fullStart = scanner.getStartPos(); - nextToken(); // advance past the 'import' - nextToken(); // advance past the dot - const node = createNode(SyntaxKind.MetaProperty, fullStart) as MetaProperty; - node.keywordToken = SyntaxKind.ImportKeyword; - node.name = parseIdentifierName(); - expression = finishNode(node); - - sourceFile.flags |= 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(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 + return node; + } + function parseArrowFunctionExpressionBody(isAsync: boolean): Block | Expression { + if (token() === SyntaxKind.OpenBraceToken) { + return parseFunctionBlock(isAsync ? SignatureFlags.Await : SignatureFlags.None); + } + if (token() !== SyntaxKind.SemicolonToken && + token() !== SyntaxKind.FunctionKeyword && + token() !== SyntaxKind.ClassKeyword && + isStartOfStatement() && + !isStartOfExpressionStatement()) { + // Check if we got a plain statement (i.e. no expression-statements, no function/class expressions/declarations) // - // If you see: "new Foo()" + // 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: // - // 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. + // a => + // let v = 0; + // } // - // 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. + // 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. // - // 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 expression = parsePrimaryExpression(); - return parseMemberExpressionRest(expression, /*allowOptionalChain*/ true); - } - - function parseSuperExpression(): MemberExpression { - const 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 (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. - const node = createNode(SyntaxKind.PropertyAccessExpression, expression.pos); - node.expression = expression; - parseExpectedToken(SyntaxKind.DotToken, Diagnostics.super_must_be_followed_by_an_argument_list_or_member_access); - node.name = parseRightSideOfDot(/*allowIdentifierNames*/ true); - return finishNode(node); - } - - function parseJsxElementOrSelfClosingElementOrFragment(inExpressionContext: boolean): JsxElement | JsxSelfClosingElement | JsxFragment { - const opening = parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext); - let result: JsxElement | JsxSelfClosingElement | JsxFragment; - if (opening.kind === SyntaxKind.JsxOpeningElement) { - const node = createNode(SyntaxKind.JsxElement, opening.pos); - node.openingElement = opening; - - node.children = parseJsxChildren(node.openingElement); - node.closingElement = parseJsxClosingElement(inExpressionContext); - - if (!tagNamesAreEquivalent(node.openingElement.tagName, node.closingElement.tagName)) { - parseErrorAtRange(node.closingElement, Diagnostics.Expected_corresponding_JSX_closing_tag_for_0, getTextOfNodeFromSourceText(sourceText, node.openingElement.tagName)); - } - - result = finishNode(node); - } - else if (opening.kind === SyntaxKind.JsxOpeningFragment) { - const node = createNode(SyntaxKind.JsxFragment, opening.pos); - node.openingFragment = opening; - node.children = parseJsxChildren(node.openingFragment); - node.closingFragment = parseJsxClosingFragment(inExpressionContext); - - result = finishNode(node); - } - 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 invalidElement = tryParse(() => parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true)); - if (invalidElement) { - parseErrorAtCurrentToken(Diagnostics.JSX_expressions_must_have_one_parent_element); - const badNode = createNode(SyntaxKind.BinaryExpression, result.pos); - badNode.end = invalidElement.end; - badNode.left = result; - badNode.right = invalidElement; - badNode.operatorToken = createMissingNode(SyntaxKind.CommaToken, /*reportAtCurrentPosition*/ false); - badNode.operatorToken.pos = badNode.operatorToken.end = badNode.right.pos; - return badNode; - } - } - - return result; - } - - function parseJsxText(): JsxText { - const node = createNode(SyntaxKind.JsxText); - node.text = scanner.getTokenValue(); - node.containsOnlyTriviaWhiteSpaces = currentToken === SyntaxKind.JsxTextAllWhiteSpaces; - currentToken = scanner.scanJsxToken(); - return finishNode(node); + // Note: even when 'IgnoreMissingOpenBrace' is passed, parseBody will still error. + return parseFunctionBlock(SignatureFlags.IgnoreMissingOpenBrace | (isAsync ? SignatureFlags.Await : SignatureFlags.None)); } - - 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 { - parseErrorAtRange(openingTag.tagName, 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); - default: - return Debug.assertNever(token); - } + return isAsync + ? doInAwaitContext(parseAssignmentExpressionOrHigher) + : doOutsideOfAwaitContext(parseAssignmentExpressionOrHigher); + } + function parseConditionalExpressionRest(leftOperand: Expression): Expression { + // Note: we are passed in an expression which was produced from parseBinaryExpressionOrHigher. + const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); + if (!questionToken) { + return leftOperand; } - - 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); - } - - parsingContext = saveParsingContext; - return createNodeArray(list, listPos); - } - - function parseJsxAttributes(): JsxAttributes { - const jsxAttributes = createNode(SyntaxKind.JsxAttributes); - jsxAttributes.properties = parseList(ParsingContext.JsxAttributes, parseJsxAttribute); - return finishNode(jsxAttributes); - } - - function parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext: boolean): JsxOpeningElement | JsxSelfClosingElement | JsxOpeningFragment { - const fullStart = scanner.getStartPos(); - - parseExpected(SyntaxKind.LessThanToken); - - if (token() === SyntaxKind.GreaterThanToken) { - // See below for explanation of scanJsxText - const node: JsxOpeningFragment = createNode(SyntaxKind.JsxOpeningFragment, fullStart); - scanJsxText(); - return finishNode(node); + // Note: we explicitly 'allowIn' in the whenTrue part of the condition expression, and + // we do not that for the 'whenFalse' part. + const node = (createNode(SyntaxKind.ConditionalExpression, leftOperand.pos)); + node.condition = leftOperand; + node.questionToken = questionToken; + node.whenTrue = doOutsideOfContext(disallowInAndDecoratorContext, parseAssignmentExpressionOrHigher); + node.colonToken = parseExpectedToken(SyntaxKind.ColonToken); + node.whenFalse = nodeIsPresent(node.colonToken) + ? parseAssignmentExpressionOrHigher() + : createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(SyntaxKind.ColonToken)); + return finishNode(node); + } + function parseBinaryExpressionOrHigher(precedence: number): Expression { + const leftOperand = parseUnaryExpressionOrHigher(); + return parseBinaryExpressionRest(precedence, leftOperand); + } + function isInOrOfKeyword(t: SyntaxKind) { + return t === SyntaxKind.InKeyword || t === SyntaxKind.OfKeyword; + } + function parseBinaryExpressionRest(precedence: number, leftOperand: Expression): Expression { + while (true) { + // We either have a binary operator here, or we're finished. We call + // reScanGreaterToken so that we merge token sequences like > and = into >= + reScanGreaterToken(); + 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; } - - const tagName = parseJsxElementName(); - const typeArguments = tryParseTypeArguments(); - 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 - node = createNode(SyntaxKind.JsxOpeningElement, fullStart); - scanJsxText(); + if (token() === SyntaxKind.InKeyword && inDisallowInContext()) { + break; } - else { - parseExpected(SyntaxKind.SlashToken); - if (inExpressionContext) { - parseExpected(SyntaxKind.GreaterThanToken); + if (token() === SyntaxKind.AsKeyword) { + // Make sure we *do* perform ASI for constructs like this: + // var x = foo + // as (Bar) + // This should be parsed as an initialized variable, followed + // by a function call to 'as' with the argument 'Bar' + if (scanner.hasPrecedingLineBreak()) { + break; } else { - parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false); - scanJsxText(); + nextToken(); + leftOperand = makeAsExpression(leftOperand, parseType()); } - node = createNode(SyntaxKind.JsxSelfClosingElement, fullStart); - } - - node.tagName = tagName; - node.typeArguments = typeArguments; - node.attributes = attributes; - - return finishNode(node); - } - - function parseJsxElementName(): JsxTagNameExpression { - 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)) { - const propertyAccess: JsxTagNamePropertyAccess = createNode(SyntaxKind.PropertyAccessExpression, expression.pos); - propertyAccess.expression = expression; - propertyAccess.name = parseRightSideOfDot(/*allowIdentifierNames*/ true); - expression = finishNode(propertyAccess); - } - return expression; - } - - function parseJsxExpression(inExpressionContext: boolean): JsxExpression | undefined { - const node = createNode(SyntaxKind.JsxExpression); - - if (!parseExpected(SyntaxKind.OpenBraceToken)) { - return undefined; - } - - if (token() !== SyntaxKind.CloseBraceToken) { - node.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. - node.expression = parseExpression(); - } - if (inExpressionContext) { - parseExpected(SyntaxKind.CloseBraceToken); } else { - if (parseExpected(SyntaxKind.CloseBraceToken, /*message*/ undefined, /*shouldAdvance*/ false)) { - scanJsxText(); - } + leftOperand = makeBinaryExpression(leftOperand, parseTokenNode(), parseBinaryExpressionOrHigher(newPrecedence)); } - - return finishNode(node); } - - function parseJsxAttribute(): JsxAttribute | JsxSpreadAttribute { - if (token() === SyntaxKind.OpenBraceToken) { - return parseJsxSpreadAttribute(); - } - - scanJsxIdentifier(); - const node = createNode(SyntaxKind.JsxAttribute); - node.name = parseIdentifierName(); - if (token() === SyntaxKind.EqualsToken) { - switch (scanJsxAttributeValue()) { - case SyntaxKind.StringLiteral: - node.initializer = parseLiteralNode(); - break; - default: - node.initializer = parseJsxExpression(/*inExpressionContext*/ true); - break; - } - } - return finishNode(node); - } - - function parseJsxSpreadAttribute(): JsxSpreadAttribute { - const node = createNode(SyntaxKind.JsxSpreadAttribute); - parseExpected(SyntaxKind.OpenBraceToken); - parseExpected(SyntaxKind.DotDotDotToken); - node.expression = parseExpression(); - parseExpected(SyntaxKind.CloseBraceToken); - return finishNode(node); - } - - function parseJsxClosingElement(inExpressionContext: boolean): JsxClosingElement { - const node = createNode(SyntaxKind.JsxClosingElement); - parseExpected(SyntaxKind.LessThanSlashToken); - node.tagName = parseJsxElementName(); - if (inExpressionContext) { - parseExpected(SyntaxKind.GreaterThanToken); - } - else { - parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false); - scanJsxText(); - } - return finishNode(node); + return leftOperand; + } + function isBinaryOperator() { + if (inDisallowInContext() && token() === SyntaxKind.InKeyword) { + return false; } - - function parseJsxClosingFragment(inExpressionContext: boolean): JsxClosingFragment { - const node = createNode(SyntaxKind.JsxClosingFragment); - parseExpected(SyntaxKind.LessThanSlashToken); - if (tokenIsIdentifierOrKeyword(token())) { - parseErrorAtRange(parseJsxElementName(), Diagnostics.Expected_corresponding_closing_tag_for_JSX_fragment); - } - if (inExpressionContext) { - parseExpected(SyntaxKind.GreaterThanToken); - } - else { - parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false); - scanJsxText(); + return getBinaryOperatorPrecedence(token()) > 0; + } + function makeBinaryExpression(left: Expression, operatorToken: BinaryOperatorToken, right: Expression): BinaryExpression { + const node = (createNode(SyntaxKind.BinaryExpression, left.pos)); + node.left = left; + node.operatorToken = operatorToken; + node.right = right; + return finishNode(node); + } + function makeAsExpression(left: Expression, right: TypeNode): AsExpression { + const node = (createNode(SyntaxKind.AsExpression, left.pos)); + node.expression = left; + node.type = right; + return finishNode(node); + } + function parsePrefixUnaryExpression() { + const node = (createNode(SyntaxKind.PrefixUnaryExpression)); + node.operator = (token()); + nextToken(); + node.operand = parseSimpleUnaryExpression(); + return finishNode(node); + } + function parseDeleteExpression() { + const node = (createNode(SyntaxKind.DeleteExpression)); + nextToken(); + node.expression = parseSimpleUnaryExpression(); + return finishNode(node); + } + function parseTypeOfExpression() { + const node = (createNode(SyntaxKind.TypeOfExpression)); + nextToken(); + node.expression = parseSimpleUnaryExpression(); + return finishNode(node); + } + function parseVoidExpression() { + const node = (createNode(SyntaxKind.VoidExpression)); + nextToken(); + node.expression = parseSimpleUnaryExpression(); + return finishNode(node); + } + function isAwaitExpression(): boolean { + if (token() === SyntaxKind.AwaitKeyword) { + if (inAwaitContext()) { + return true; } - return finishNode(node); + // here we are using similar heuristics as 'isYieldExpression' + return lookAhead(nextTokenIsIdentifierOrKeywordOrLiteralOnSameLine); } - - function parseTypeAssertion(): TypeAssertion { - const node = createNode(SyntaxKind.TypeAssertionExpression); - parseExpected(SyntaxKind.LessThanToken); - node.type = parseType(); - parseExpected(SyntaxKind.GreaterThanToken); - node.expression = parseSimpleUnaryExpression(); - return finishNode(node); - } - - function nextTokenIsIdentifierOrKeywordOrOpenBracketOrTemplate() { - nextToken(); - return tokenIsIdentifierOrKeyword(token()) - || token() === SyntaxKind.OpenBracketToken - || isTemplateStartOfTaggedTemplate(); - } - - function isStartOfOptionalPropertyOrElementAccessChain() { - return token() === SyntaxKind.QuestionDotToken - && lookAhead(nextTokenIsIdentifierOrKeywordOrOpenBracketOrTemplate); - } - - function parsePropertyAccessExpressionRest(expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) { - const propertyAccess = createNode(SyntaxKind.PropertyAccessExpression, expression.pos); - propertyAccess.expression = expression; - propertyAccess.questionDotToken = questionDotToken; - propertyAccess.name = parseRightSideOfDot(/*allowIdentifierNames*/ true); - if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { - propertyAccess.flags |= NodeFlags.OptionalChain; - } - return finishNode(propertyAccess); + return false; + } + function parseAwaitExpression() { + const node = (createNode(SyntaxKind.AwaitExpression)); + nextToken(); + node.expression = parseSimpleUnaryExpression(); + return finishNode(node); + } + /** + * 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 updateExpression = parseUpdateExpression(); + return token() === SyntaxKind.AsteriskAsteriskToken ? + parseBinaryExpressionRest(getBinaryOperatorPrecedence(token()), updateExpression) : + updateExpression; } - - function parseElementAccessExpressionRest(expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) { - const indexedAccess = createNode(SyntaxKind.ElementAccessExpression, expression.pos); - indexedAccess.expression = expression; - indexedAccess.questionDotToken = questionDotToken; - - if (token() === SyntaxKind.CloseBracketToken) { - indexedAccess.argumentExpression = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.An_element_access_expression_should_take_an_argument); + /** + * 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 { - const argument = allowInAnd(parseExpression); - if (isStringOrNumericLiteralLike(argument)) { - argument.text = internIdentifier(argument.text); - } - indexedAccess.argumentExpression = argument; + 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)); } - - parseExpected(SyntaxKind.CloseBracketToken); - if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { - indexedAccess.flags |= NodeFlags.OptionalChain; - } - return finishNode(indexedAccess); } - - function parseMemberExpressionRest(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(expression, questionDotToken); - continue; - } - - if (!questionDotToken && token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { - nextToken(); - const nonNullExpression = createNode(SyntaxKind.NonNullExpression, expression.pos); - nonNullExpression.expression = expression; - expression = finishNode(nonNullExpression); - 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(expression, questionDotToken); - continue; - } - - if (isTemplateStartOfTaggedTemplate()) { - expression = parseTaggedTemplateRest(expression, questionDotToken, /*typeArguments*/ undefined); - continue; - } - - return expression; - } - } - - function isTemplateStartOfTaggedTemplate() { - return token() === SyntaxKind.NoSubstitutionTemplateLiteral || token() === SyntaxKind.TemplateHead; - } - - function parseTaggedTemplateRest(tag: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined, typeArguments: NodeArray | undefined) { - const tagExpression = createNode(SyntaxKind.TaggedTemplateExpression, tag.pos); - tagExpression.tag = tag; - tagExpression.questionDotToken = questionDotToken; - tagExpression.typeArguments = typeArguments; - tagExpression.template = token() === SyntaxKind.NoSubstitutionTemplateLiteral - ? parseLiteralNode() - : parseTemplateExpression(); - if (questionDotToken || tag.flags & NodeFlags.OptionalChain) { - tagExpression.flags |= NodeFlags.OptionalChain; - } - return finishNode(tagExpression); - } - - function parseCallExpressionRest(expression: LeftHandSideExpression): LeftHandSideExpression { - while (true) { - expression = parseMemberExpressionRest(expression, /*allowOptionalChain*/ true); - const questionDotToken = parseOptionalToken(SyntaxKind.QuestionDotToken); - - // handle 'foo<()' - if (token() === SyntaxKind.LessThanToken || token() === SyntaxKind.LessThanLessThanToken) { - // See if this is the start of a generic invocation. If so, consume it and - // keep checking for postfix expressions. Otherwise, it's just a '<' that's - // part of an arithmetic expression. Break out so we consume it higher in the - // stack. - const typeArguments = tryParse(parseTypeArgumentsInExpression); - if (typeArguments) { - if (isTemplateStartOfTaggedTemplate()) { - expression = parseTaggedTemplateRest(expression, questionDotToken, typeArguments); - continue; - } - - const callExpr = createNode(SyntaxKind.CallExpression, expression.pos); - callExpr.expression = expression; - callExpr.questionDotToken = questionDotToken; - callExpr.typeArguments = typeArguments; - callExpr.arguments = parseArgumentList(); - if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { - callExpr.flags |= NodeFlags.OptionalChain; - } - expression = finishNode(callExpr); - continue; - } - } - else if (token() === SyntaxKind.OpenParenToken) { - const callExpr = createNode(SyntaxKind.CallExpression, expression.pos); - callExpr.expression = expression; - callExpr.questionDotToken = questionDotToken; - callExpr.arguments = parseArgumentList(); - if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { - callExpr.flags |= NodeFlags.OptionalChain; - } - expression = finishNode(callExpr); - continue; - } - if (questionDotToken) { - // We failed to parse anything, so report a missing identifier here. - const propertyAccess = createNode(SyntaxKind.PropertyAccessExpression, expression.pos) as PropertyAccessExpression; - propertyAccess.expression = expression; - propertyAccess.questionDotToken = questionDotToken; - propertyAccess.name = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, Diagnostics.Identifier_expected); - propertyAccess.flags |= NodeFlags.OptionalChain; - expression = finishNode(propertyAccess); - } - break; - } - return expression; - } - - function parseArgumentList() { - parseExpected(SyntaxKind.OpenParenToken); - const result = parseDelimitedList(ParsingContext.ArgumentExpressions, parseArgumentExpression); - parseExpected(SyntaxKind.CloseParenToken); - return result; - } - - function parseTypeArgumentsInExpression() { - if (reScanLessThanToken() !== SyntaxKind.LessThanToken) { - return undefined; - } - nextToken(); - - const typeArguments = parseDelimitedList(ParsingContext.TypeArguments, parseType); - if (!parseExpected(SyntaxKind.GreaterThanToken)) { - // If it doesn't have the closing `>` then it's definitely not an type argument list. - return undefined; - } - - // If we have a '<', then only parse this as a argument list if the type arguments - // are complete and we have an open paren. if we don't, rewind and return nothing. - return typeArguments && canFollowTypeArgumentsInExpression() - ? typeArguments - : undefined; - } - - function canFollowTypeArgumentsInExpression(): boolean { - switch (token()) { - case SyntaxKind.OpenParenToken: // foo( - case SyntaxKind.NoSubstitutionTemplateLiteral: // foo `...` - case SyntaxKind.TemplateHead: // foo `...${100}...` - // these are the only tokens can legally follow a type argument - // list. So we definitely want to treat them as type arg lists. - // falls through - case SyntaxKind.DotToken: // foo. - case SyntaxKind.CloseParenToken: // foo) - case SyntaxKind.CloseBracketToken: // foo] - case SyntaxKind.ColonToken: // foo: - case SyntaxKind.SemicolonToken: // foo; - case SyntaxKind.QuestionToken: // foo? - case SyntaxKind.EqualsEqualsToken: // foo == - case SyntaxKind.EqualsEqualsEqualsToken: // foo === - case SyntaxKind.ExclamationEqualsToken: // foo != - case SyntaxKind.ExclamationEqualsEqualsToken: // foo !== - case SyntaxKind.AmpersandAmpersandToken: // foo && - case SyntaxKind.BarBarToken: // foo || - case SyntaxKind.QuestionQuestionToken: // foo ?? - case SyntaxKind.CaretToken: // foo ^ - case SyntaxKind.AmpersandToken: // foo & - case SyntaxKind.BarToken: // foo | - case SyntaxKind.CloseBraceToken: // foo } - case SyntaxKind.EndOfFileToken: // foo - // these cases can't legally follow a type arg list. However, they're not legal - // expressions either. The user is probably in the middle of a generic type. So - // treat it as such. - return true; - - case SyntaxKind.CommaToken: // foo, - case SyntaxKind.OpenBraceToken: // foo { - // We don't want to treat these as type arguments. Otherwise we'll parse this - // as an invocation expression. Instead, we want to parse out the expression - // in isolation from the type arguments. - // falls through - default: - // Anything else treat as an expression. - return false; - } - } - - 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(); - } - - return parseIdentifier(Diagnostics.Expression_expected); + 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(); } - - function parseParenthesizedExpression(): ParenthesizedExpression { - const node = createNodeWithJSDoc(SyntaxKind.ParenthesizedExpression); - parseExpected(SyntaxKind.OpenParenToken); - node.expression = allowInAnd(parseExpression); - parseExpected(SyntaxKind.CloseParenToken); - return finishNode(node); + } + /** + * 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 (sourceFile.languageVariant !== LanguageVariant.JSX) { + return false; + } + // We are in JSX context and the token is part of JSXElement. + // falls through + default: + return true; } - - function parseSpreadElement(): Expression { - const node = createNode(SyntaxKind.SpreadElement); - parseExpected(SyntaxKind.DotDotDotToken); - node.expression = parseAssignmentExpressionOrHigher(); + } + /** + * 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 node = (createNode(SyntaxKind.PrefixUnaryExpression)); + node.operator = (token()); + nextToken(); + node.operand = parseLeftHandSideExpressionOrHigher(); return finishNode(node); } - - function parseArgumentOrArrayLiteralElement(): Expression { - return token() === SyntaxKind.DotDotDotToken ? parseSpreadElement() : - token() === SyntaxKind.CommaToken ? createNode(SyntaxKind.OmittedExpression) : - parseAssignmentExpressionOrHigher(); - } - - function parseArgumentExpression(): Expression { - return doOutsideOfContext(disallowInAndDecoratorContext, parseArgumentOrArrayLiteralElement); + else if (sourceFile.languageVariant === LanguageVariant.JSX && token() === SyntaxKind.LessThanToken && lookAhead(nextTokenIsIdentifierOrKeywordOrGreaterThan)) { + // JSXElement is part of primaryExpression + return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true); } - - function parseArrayLiteralExpression(): ArrayLiteralExpression { - const node = createNode(SyntaxKind.ArrayLiteralExpression); - parseExpected(SyntaxKind.OpenBracketToken); - if (scanner.hasPrecedingLineBreak()) { - node.multiLine = true; - } - node.elements = parseDelimitedList(ParsingContext.ArrayLiteralMembers, parseArgumentOrArrayLiteralElement); - parseExpected(SyntaxKind.CloseBracketToken); + const expression = parseLeftHandSideExpressionOrHigher(); + Debug.assert(isLeftHandSideExpression(expression)); + if ((token() === SyntaxKind.PlusPlusToken || token() === SyntaxKind.MinusMinusToken) && !scanner.hasPrecedingLineBreak()) { + const node = (createNode(SyntaxKind.PostfixUnaryExpression, expression.pos)); + node.operand = expression; + node.operator = (token()); + nextToken(); return finishNode(node); } - - function parseObjectLiteralElement(): ObjectLiteralElementLike { - const node = createNodeWithJSDoc(SyntaxKind.Unknown); - - if (parseOptionalToken(SyntaxKind.DotDotDotToken)) { - node.kind = SyntaxKind.SpreadAssignment; - (node).expression = parseAssignmentExpressionOrHigher(); - return finishNode(node); - } - - node.decorators = parseDecorators(); - node.modifiers = parseModifiers(); - - if (parseContextualModifier(SyntaxKind.GetKeyword)) { - return parseAccessorDeclaration(node, SyntaxKind.GetAccessor); - } - if (parseContextualModifier(SyntaxKind.SetKeyword)) { - return parseAccessorDeclaration(node, SyntaxKind.SetAccessor); - } - - const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); - const tokenIsIdentifier = isIdentifier(); - node.name = parsePropertyName(); - // Disallowing of optional property assignments and definite assignment assertion happens in the grammar checker. - (node).questionToken = parseOptionalToken(SyntaxKind.QuestionToken); - (node).exclamationToken = parseOptionalToken(SyntaxKind.ExclamationToken); - - if (asteriskToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { - return parseMethodDeclaration(node, asteriskToken); - } - - // 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 - const isShorthandPropertyAssignment = tokenIsIdentifier && (token() !== SyntaxKind.ColonToken); - if (isShorthandPropertyAssignment) { - node.kind = SyntaxKind.ShorthandPropertyAssignment; - const equalsToken = parseOptionalToken(SyntaxKind.EqualsToken); - if (equalsToken) { - (node).equalsToken = equalsToken; - (node).objectAssignmentInitializer = allowInAnd(parseAssignmentExpressionOrHigher); - } + 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. + 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 + sourceFile.flags |= NodeFlags.PossiblyContainsDynamicImport; + expression = parseTokenNode(); + } + else if (lookAhead(nextTokenIsDot)) { + // This is an 'import.*' metaproperty (i.e. 'import.meta') + const fullStart = scanner.getStartPos(); + nextToken(); // advance past the 'import' + nextToken(); // advance past the dot + const node = (createNode(SyntaxKind.MetaProperty, fullStart) as MetaProperty); + node.keywordToken = SyntaxKind.ImportKeyword; + node.name = parseIdentifierName(); + expression = finishNode(node); + sourceFile.flags |= NodeFlags.PossiblyContainsImportMeta; } else { - node.kind = SyntaxKind.PropertyAssignment; - parseExpected(SyntaxKind.ColonToken); - (node).initializer = allowInAnd(parseAssignmentExpressionOrHigher); + expression = parseMemberExpressionOrHigher(); } - return finishNode(node); } - - function parseObjectLiteralExpression(): ObjectLiteralExpression { - const node = createNode(SyntaxKind.ObjectLiteralExpression); - parseExpected(SyntaxKind.OpenBraceToken); - if (scanner.hasPrecedingLineBreak()) { - node.multiLine = true; + 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(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 expression = parsePrimaryExpression(); + return parseMemberExpressionRest(expression, /*allowOptionalChain*/ true); + } + function parseSuperExpression(): MemberExpression { + const 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); } - - node.properties = parseDelimitedList(ParsingContext.ObjectLiteralMembers, parseObjectLiteralElement, /*considerSemicolonAsDelimiter*/ true); - parseExpected(SyntaxKind.CloseBraceToken); - return finishNode(node); } - - function parseFunctionExpression(): FunctionExpression { - // GeneratorExpression: - // function* BindingIdentifier [Yield][opt](FormalParameters[Yield]){ GeneratorBody } - // - // FunctionExpression: - // function BindingIdentifier[opt](FormalParameters){ FunctionBody } - const saveDecoratorContext = inDecoratorContext(); - if (saveDecoratorContext) { - setDecoratorContext(/*val*/ false); - } - - const node = createNodeWithJSDoc(SyntaxKind.FunctionExpression); - node.modifiers = parseModifiers(); - parseExpected(SyntaxKind.FunctionKeyword); - node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); - - const isGenerator = node.asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; - const isAsync = hasModifier(node, ModifierFlags.Async) ? SignatureFlags.Await : SignatureFlags.None; - node.name = - isGenerator && isAsync ? doInYieldAndAwaitContext(parseOptionalIdentifier) : - isGenerator ? doInYieldContext(parseOptionalIdentifier) : - isAsync ? doInAwaitContext(parseOptionalIdentifier) : - parseOptionalIdentifier(); - - fillSignature(SyntaxKind.ColonToken, isGenerator | isAsync, node); - node.body = parseFunctionBlock(isGenerator | isAsync); - - if (saveDecoratorContext) { - setDecoratorContext(/*val*/ true); - } - - return finishNode(node); + if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.DotToken || token() === SyntaxKind.OpenBracketToken) { + return expression; } - - function parseOptionalIdentifier(): Identifier | undefined { - return isIdentifier() ? parseIdentifier() : undefined; + // 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. + const node = (createNode(SyntaxKind.PropertyAccessExpression, expression.pos)); + node.expression = expression; + parseExpectedToken(SyntaxKind.DotToken, Diagnostics.super_must_be_followed_by_an_argument_list_or_member_access); + node.name = parseRightSideOfDot(/*allowIdentifierNames*/ true); + return finishNode(node); + } + function parseJsxElementOrSelfClosingElementOrFragment(inExpressionContext: boolean): JsxElement | JsxSelfClosingElement | JsxFragment { + const opening = parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext); + let result: JsxElement | JsxSelfClosingElement | JsxFragment; + if (opening.kind === SyntaxKind.JsxOpeningElement) { + const node = (createNode(SyntaxKind.JsxElement, opening.pos)); + node.openingElement = opening; + node.children = parseJsxChildren(node.openingElement); + node.closingElement = parseJsxClosingElement(inExpressionContext); + if (!tagNamesAreEquivalent(node.openingElement.tagName, node.closingElement.tagName)) { + parseErrorAtRange(node.closingElement, Diagnostics.Expected_corresponding_JSX_closing_tag_for_0, getTextOfNodeFromSourceText(sourceText, node.openingElement.tagName)); + } + result = finishNode(node); + } + else if (opening.kind === SyntaxKind.JsxOpeningFragment) { + const node = (createNode(SyntaxKind.JsxFragment, opening.pos)); + node.openingFragment = opening; + node.children = parseJsxChildren(node.openingFragment); + node.closingFragment = parseJsxClosingFragment(inExpressionContext); + result = finishNode(node); } - - function parseNewExpressionOrNewDotTarget(): NewExpression | MetaProperty { - const fullStart = scanner.getStartPos(); - parseExpected(SyntaxKind.NewKeyword); - if (parseOptional(SyntaxKind.DotToken)) { - const node = createNode(SyntaxKind.MetaProperty, fullStart); - node.keywordToken = SyntaxKind.NewKeyword; - node.name = parseIdentifierName(); - return finishNode(node); + 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 invalidElement = tryParse(() => parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true)); + if (invalidElement) { + parseErrorAtCurrentToken(Diagnostics.JSX_expressions_must_have_one_parent_element); + const badNode = (createNode(SyntaxKind.BinaryExpression, result.pos)); + badNode.end = invalidElement.end; + badNode.left = result; + badNode.right = invalidElement; + badNode.operatorToken = createMissingNode(SyntaxKind.CommaToken, /*reportAtCurrentPosition*/ false); + badNode.operatorToken.pos = badNode.operatorToken.end = badNode.right.pos; + return badNode; } - - let expression: MemberExpression = parsePrimaryExpression(); - let typeArguments; - while (true) { - expression = parseMemberExpressionRest(expression, /*allowOptionalChain*/ false); - typeArguments = tryParse(parseTypeArgumentsInExpression); - if (isTemplateStartOfTaggedTemplate()) { - Debug.assert(!!typeArguments, - "Expected a type argument list; all plain tagged template starts should be consumed in 'parseMemberExpressionRest'"); - expression = parseTaggedTemplateRest(expression, /*optionalChain*/ undefined, typeArguments); - typeArguments = undefined; + } + return result; + } + function parseJsxText(): JsxText { + const node = (createNode(SyntaxKind.JsxText)); + node.text = scanner.getTokenValue(); + node.containsOnlyTriviaWhiteSpaces = currentToken === SyntaxKind.JsxTextAllWhiteSpaces; + currentToken = scanner.scanJsxToken(); + return finishNode(node); + } + 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 { + parseErrorAtRange(openingTag.tagName, 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); + 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; - } - - const node = createNode(SyntaxKind.NewExpression, fullStart); - node.expression = expression; - node.typeArguments = typeArguments; - if (node.typeArguments || token() === SyntaxKind.OpenParenToken) { - node.arguments = parseArgumentList(); - } + list.push(child); + } + parsingContext = saveParsingContext; + return createNodeArray(list, listPos); + } + function parseJsxAttributes(): JsxAttributes { + const jsxAttributes = (createNode(SyntaxKind.JsxAttributes)); + jsxAttributes.properties = parseList(ParsingContext.JsxAttributes, parseJsxAttribute); + return finishNode(jsxAttributes); + } + function parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext: boolean): JsxOpeningElement | JsxSelfClosingElement | JsxOpeningFragment { + const fullStart = scanner.getStartPos(); + parseExpected(SyntaxKind.LessThanToken); + if (token() === SyntaxKind.GreaterThanToken) { + // See below for explanation of scanJsxText + const node: JsxOpeningFragment = (createNode(SyntaxKind.JsxOpeningFragment, fullStart)); + scanJsxText(); return finishNode(node); } - - // STATEMENTS - function parseBlock(ignoreMissingOpenBrace: boolean, diagnosticMessage?: DiagnosticMessage): Block { - const node = createNode(SyntaxKind.Block); - const openBracePosition = scanner.getTokenPos(); - if (parseExpected(SyntaxKind.OpenBraceToken, diagnosticMessage) || ignoreMissingOpenBrace) { - if (scanner.hasPrecedingLineBreak()) { - node.multiLine = true; - } - - node.statements = parseList(ParsingContext.BlockStatements, parseStatement); - if (!parseExpected(SyntaxKind.CloseBraceToken)) { - const lastError = lastOrUndefined(parseDiagnostics); - if (lastError && lastError.code === Diagnostics._0_expected.code) { - addRelatedInfo( - lastError, - createFileDiagnostic(sourceFile, openBracePosition, 1, Diagnostics.The_parser_expected_to_find_a_to_match_the_token_here) - ); - } - } + const tagName = parseJsxElementName(); + const typeArguments = tryParseTypeArguments(); + 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 + node = (createNode(SyntaxKind.JsxOpeningElement, fullStart)); + scanJsxText(); + } + else { + parseExpected(SyntaxKind.SlashToken); + if (inExpressionContext) { + parseExpected(SyntaxKind.GreaterThanToken); } else { - node.statements = createMissingList(); + parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false); + scanJsxText(); } - return finishNode(node); + node = (createNode(SyntaxKind.JsxSelfClosingElement, fullStart)); } - - function parseFunctionBlock(flags: SignatureFlags, diagnosticMessage?: DiagnosticMessage): Block { - const savedYieldContext = inYieldContext(); - setYieldContext(!!(flags & SignatureFlags.Yield)); - - const savedAwaitContext = inAwaitContext(); - setAwaitContext(!!(flags & SignatureFlags.Await)); - - // 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); - } - - setYieldContext(savedYieldContext); - setAwaitContext(savedAwaitContext); - - return block; - } - - function parseEmptyStatement(): Statement { - const node = createNode(SyntaxKind.EmptyStatement); - parseExpected(SyntaxKind.SemicolonToken); - return finishNode(node); + node.tagName = tagName; + node.typeArguments = typeArguments; + node.attributes = attributes; + return finishNode(node); + } + function parseJsxElementName(): JsxTagNameExpression { + 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)) { + const propertyAccess: JsxTagNamePropertyAccess = (createNode(SyntaxKind.PropertyAccessExpression, expression.pos)); + propertyAccess.expression = expression; + propertyAccess.name = parseRightSideOfDot(/*allowIdentifierNames*/ true); + expression = finishNode(propertyAccess); } - - function parseIfStatement(): IfStatement { - const node = createNode(SyntaxKind.IfStatement); - parseExpected(SyntaxKind.IfKeyword); - parseExpected(SyntaxKind.OpenParenToken); - node.expression = allowInAnd(parseExpression); - parseExpected(SyntaxKind.CloseParenToken); - node.thenStatement = parseStatement(); - node.elseStatement = parseOptional(SyntaxKind.ElseKeyword) ? parseStatement() : undefined; - return finishNode(node); + return expression; + } + function parseJsxExpression(inExpressionContext: boolean): JsxExpression | undefined { + const node = (createNode(SyntaxKind.JsxExpression)); + if (!parseExpected(SyntaxKind.OpenBraceToken)) { + return undefined; } - - function parseDoStatement(): DoStatement { - const node = createNode(SyntaxKind.DoStatement); - parseExpected(SyntaxKind.DoKeyword); - node.statement = parseStatement(); - parseExpected(SyntaxKind.WhileKeyword); - parseExpected(SyntaxKind.OpenParenToken); - node.expression = allowInAnd(parseExpression); - parseExpected(SyntaxKind.CloseParenToken); - - // From: https://mail.mozilla.org/pipermail/es-discuss/2011-August/016188.html - // 157 min --- All allen at wirfs-brock.com CONF --- "do{;}while(false)false" prohibited in - // spec but allowed in consensus reality. Approved -- this is the de-facto standard whereby - // do;while(0)x will have a semicolon inserted before x. - parseOptional(SyntaxKind.SemicolonToken); - return finishNode(node); + if (token() !== SyntaxKind.CloseBraceToken) { + node.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. + node.expression = parseExpression(); } - - function parseWhileStatement(): WhileStatement { - const node = createNode(SyntaxKind.WhileStatement); - parseExpected(SyntaxKind.WhileKeyword); - parseExpected(SyntaxKind.OpenParenToken); - node.expression = allowInAnd(parseExpression); - parseExpected(SyntaxKind.CloseParenToken); - node.statement = parseStatement(); - return finishNode(node); + if (inExpressionContext) { + parseExpected(SyntaxKind.CloseBraceToken); } - - function parseForOrForInOrForOfStatement(): Statement { - const pos = getNodePos(); - 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 forOrForInOrForOfStatement: IterationStatement; - if (awaitToken ? parseExpected(SyntaxKind.OfKeyword) : parseOptional(SyntaxKind.OfKeyword)) { - const forOfStatement = createNode(SyntaxKind.ForOfStatement, pos); - forOfStatement.awaitModifier = awaitToken; - forOfStatement.initializer = initializer; - forOfStatement.expression = allowInAnd(parseAssignmentExpressionOrHigher); - parseExpected(SyntaxKind.CloseParenToken); - forOrForInOrForOfStatement = forOfStatement; - } - else if (parseOptional(SyntaxKind.InKeyword)) { - const forInStatement = createNode(SyntaxKind.ForInStatement, pos); - forInStatement.initializer = initializer; - forInStatement.expression = allowInAnd(parseExpression); - parseExpected(SyntaxKind.CloseParenToken); - forOrForInOrForOfStatement = forInStatement; + else { + if (parseExpected(SyntaxKind.CloseBraceToken, /*message*/ undefined, /*shouldAdvance*/ false)) { + scanJsxText(); } - else { - const forStatement = createNode(SyntaxKind.ForStatement, pos); - forStatement.initializer = initializer; - parseExpected(SyntaxKind.SemicolonToken); - if (token() !== SyntaxKind.SemicolonToken && token() !== SyntaxKind.CloseParenToken) { - forStatement.condition = allowInAnd(parseExpression); - } - parseExpected(SyntaxKind.SemicolonToken); - if (token() !== SyntaxKind.CloseParenToken) { - forStatement.incrementor = allowInAnd(parseExpression); - } - parseExpected(SyntaxKind.CloseParenToken); - forOrForInOrForOfStatement = forStatement; - } - - forOrForInOrForOfStatement.statement = parseStatement(); - - return finishNode(forOrForInOrForOfStatement); - } - - function parseBreakOrContinueStatement(kind: SyntaxKind): BreakOrContinueStatement { - const node = createNode(kind); - - parseExpected(kind === SyntaxKind.BreakStatement ? SyntaxKind.BreakKeyword : SyntaxKind.ContinueKeyword); - if (!canParseSemicolon()) { - node.label = parseIdentifier(); - } - - parseSemicolon(); - return finishNode(node); } - - function parseReturnStatement(): ReturnStatement { - const node = createNode(SyntaxKind.ReturnStatement); - - parseExpected(SyntaxKind.ReturnKeyword); - if (!canParseSemicolon()) { - node.expression = allowInAnd(parseExpression); + return finishNode(node); + } + function parseJsxAttribute(): JsxAttribute | JsxSpreadAttribute { + if (token() === SyntaxKind.OpenBraceToken) { + return parseJsxSpreadAttribute(); + } + scanJsxIdentifier(); + const node = (createNode(SyntaxKind.JsxAttribute)); + node.name = parseIdentifierName(); + if (token() === SyntaxKind.EqualsToken) { + switch (scanJsxAttributeValue()) { + case SyntaxKind.StringLiteral: + node.initializer = (parseLiteralNode()); + break; + default: + node.initializer = parseJsxExpression(/*inExpressionContext*/ true); + break; } - - parseSemicolon(); - return finishNode(node); } - - function parseWithStatement(): WithStatement { - const node = createNode(SyntaxKind.WithStatement); - parseExpected(SyntaxKind.WithKeyword); - parseExpected(SyntaxKind.OpenParenToken); - node.expression = allowInAnd(parseExpression); - parseExpected(SyntaxKind.CloseParenToken); - node.statement = doInsideOfContext(NodeFlags.InWithStatement, parseStatement); - return finishNode(node); + return finishNode(node); + } + function parseJsxSpreadAttribute(): JsxSpreadAttribute { + const node = (createNode(SyntaxKind.JsxSpreadAttribute)); + parseExpected(SyntaxKind.OpenBraceToken); + parseExpected(SyntaxKind.DotDotDotToken); + node.expression = parseExpression(); + parseExpected(SyntaxKind.CloseBraceToken); + return finishNode(node); + } + function parseJsxClosingElement(inExpressionContext: boolean): JsxClosingElement { + const node = (createNode(SyntaxKind.JsxClosingElement)); + parseExpected(SyntaxKind.LessThanSlashToken); + node.tagName = parseJsxElementName(); + if (inExpressionContext) { + parseExpected(SyntaxKind.GreaterThanToken); } - - function parseCaseClause(): CaseClause { - const node = createNode(SyntaxKind.CaseClause); - parseExpected(SyntaxKind.CaseKeyword); - node.expression = allowInAnd(parseExpression); - parseExpected(SyntaxKind.ColonToken); - node.statements = parseList(ParsingContext.SwitchClauseStatements, parseStatement); - return finishNode(node); + else { + parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false); + scanJsxText(); } - - function parseDefaultClause(): DefaultClause { - const node = createNode(SyntaxKind.DefaultClause); - parseExpected(SyntaxKind.DefaultKeyword); - parseExpected(SyntaxKind.ColonToken); - node.statements = parseList(ParsingContext.SwitchClauseStatements, parseStatement); - return finishNode(node); + return finishNode(node); + } + function parseJsxClosingFragment(inExpressionContext: boolean): JsxClosingFragment { + const node = (createNode(SyntaxKind.JsxClosingFragment)); + parseExpected(SyntaxKind.LessThanSlashToken); + if (tokenIsIdentifierOrKeyword(token())) { + parseErrorAtRange(parseJsxElementName(), Diagnostics.Expected_corresponding_closing_tag_for_JSX_fragment); } - - function parseCaseOrDefaultClause(): CaseOrDefaultClause { - return token() === SyntaxKind.CaseKeyword ? parseCaseClause() : parseDefaultClause(); + if (inExpressionContext) { + parseExpected(SyntaxKind.GreaterThanToken); } - - function parseSwitchStatement(): SwitchStatement { - const node = createNode(SyntaxKind.SwitchStatement); - parseExpected(SyntaxKind.SwitchKeyword); - parseExpected(SyntaxKind.OpenParenToken); - node.expression = allowInAnd(parseExpression); - parseExpected(SyntaxKind.CloseParenToken); - const caseBlock = createNode(SyntaxKind.CaseBlock); - parseExpected(SyntaxKind.OpenBraceToken); - caseBlock.clauses = parseList(ParsingContext.SwitchClauses, parseCaseOrDefaultClause); - parseExpected(SyntaxKind.CloseBraceToken); - node.caseBlock = finishNode(caseBlock); - return finishNode(node); + else { + parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false); + scanJsxText(); } - - function parseThrowStatement(): ThrowStatement { - // ThrowStatement[Yield] : - // throw [no LineTerminator here]Expression[In, ?Yield]; - - // Because of automatic semicolon insertion, we need to report error if this - // throw could be terminated with a semicolon. Note: we can't call 'parseExpression' - // directly as that might consume an expression on the following line. - // We just return 'undefined' in that case. The actual error will be reported in the - // grammar walker. - const node = createNode(SyntaxKind.ThrowStatement); - parseExpected(SyntaxKind.ThrowKeyword); - node.expression = scanner.hasPrecedingLineBreak() ? undefined : allowInAnd(parseExpression); - parseSemicolon(); - return finishNode(node); + return finishNode(node); + } + function parseTypeAssertion(): TypeAssertion { + const node = (createNode(SyntaxKind.TypeAssertionExpression)); + parseExpected(SyntaxKind.LessThanToken); + node.type = parseType(); + parseExpected(SyntaxKind.GreaterThanToken); + node.expression = parseSimpleUnaryExpression(); + return finishNode(node); + } + function nextTokenIsIdentifierOrKeywordOrOpenBracketOrTemplate() { + nextToken(); + return tokenIsIdentifierOrKeyword(token()) + || token() === SyntaxKind.OpenBracketToken + || isTemplateStartOfTaggedTemplate(); + } + function isStartOfOptionalPropertyOrElementAccessChain() { + return token() === SyntaxKind.QuestionDotToken + && lookAhead(nextTokenIsIdentifierOrKeywordOrOpenBracketOrTemplate); + } + function parsePropertyAccessExpressionRest(expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) { + const propertyAccess = (createNode(SyntaxKind.PropertyAccessExpression, expression.pos)); + propertyAccess.expression = expression; + propertyAccess.questionDotToken = questionDotToken; + propertyAccess.name = parseRightSideOfDot(/*allowIdentifierNames*/ true); + if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { + propertyAccess.flags |= NodeFlags.OptionalChain; + } + return finishNode(propertyAccess); + } + function parseElementAccessExpressionRest(expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) { + const indexedAccess = (createNode(SyntaxKind.ElementAccessExpression, expression.pos)); + indexedAccess.expression = expression; + indexedAccess.questionDotToken = questionDotToken; + if (token() === SyntaxKind.CloseBracketToken) { + indexedAccess.argumentExpression = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.An_element_access_expression_should_take_an_argument); } - - // TODO: Review for error recovery - function parseTryStatement(): TryStatement { - const node = createNode(SyntaxKind.TryStatement); - - parseExpected(SyntaxKind.TryKeyword); - node.tryBlock = parseBlock(/*ignoreMissingOpenBrace*/ false); - node.catchClause = token() === SyntaxKind.CatchKeyword ? parseCatchClause() : undefined; - - // If we don't have a catch clause, then we must have a finally clause. Try to parse - // one out no matter what. - if (!node.catchClause || token() === SyntaxKind.FinallyKeyword) { - parseExpected(SyntaxKind.FinallyKeyword); - node.finallyBlock = parseBlock(/*ignoreMissingOpenBrace*/ false); - } - - return finishNode(node); + else { + const argument = allowInAnd(parseExpression); + if (isStringOrNumericLiteralLike(argument)) { + argument.text = internIdentifier(argument.text); + } + indexedAccess.argumentExpression = argument; } - - function parseCatchClause(): CatchClause { - const result = createNode(SyntaxKind.CatchClause); - parseExpected(SyntaxKind.CatchKeyword); - - if (parseOptional(SyntaxKind.OpenParenToken)) { - result.variableDeclaration = parseVariableDeclaration(); - parseExpected(SyntaxKind.CloseParenToken); + parseExpected(SyntaxKind.CloseBracketToken); + if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { + indexedAccess.flags |= NodeFlags.OptionalChain; + } + return finishNode(indexedAccess); + } + function parseMemberExpressionRest(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 { - // Keep shape of node to avoid degrading performance. - result.variableDeclaration = undefined; + isPropertyAccess = parseOptional(SyntaxKind.DotToken); } - - result.block = parseBlock(/*ignoreMissingOpenBrace*/ false); - return finishNode(result); - } - - function parseDebuggerStatement(): Statement { - const node = createNode(SyntaxKind.DebuggerStatement); - parseExpected(SyntaxKind.DebuggerKeyword); - parseSemicolon(); - return finishNode(node); + if (isPropertyAccess) { + expression = parsePropertyAccessExpressionRest(expression, questionDotToken); + continue; + } + if (!questionDotToken && token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { + nextToken(); + const nonNullExpression = (createNode(SyntaxKind.NonNullExpression, expression.pos)); + nonNullExpression.expression = expression; + expression = finishNode(nonNullExpression); + 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(expression, questionDotToken); + continue; + } + if (isTemplateStartOfTaggedTemplate()) { + expression = parseTaggedTemplateRest(expression, questionDotToken, /*typeArguments*/ undefined); + continue; + } + return expression; } - - 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 node = createNodeWithJSDoc(SyntaxKind.Unknown); - const expression = allowInAnd(parseExpression); - if (expression.kind === SyntaxKind.Identifier && parseOptional(SyntaxKind.ColonToken)) { - node.kind = SyntaxKind.LabeledStatement; - (node).label = expression; - (node).statement = parseStatement(); + } + function isTemplateStartOfTaggedTemplate() { + return token() === SyntaxKind.NoSubstitutionTemplateLiteral || token() === SyntaxKind.TemplateHead; + } + function parseTaggedTemplateRest(tag: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined, typeArguments: NodeArray | undefined) { + const tagExpression = (createNode(SyntaxKind.TaggedTemplateExpression, tag.pos)); + tagExpression.tag = tag; + tagExpression.questionDotToken = questionDotToken; + tagExpression.typeArguments = typeArguments; + tagExpression.template = token() === SyntaxKind.NoSubstitutionTemplateLiteral + ? parseLiteralNode() + : parseTemplateExpression(); + if (questionDotToken || tag.flags & NodeFlags.OptionalChain) { + tagExpression.flags |= NodeFlags.OptionalChain; + } + return finishNode(tagExpression); + } + function parseCallExpressionRest(expression: LeftHandSideExpression): LeftHandSideExpression { + while (true) { + expression = parseMemberExpressionRest(expression, /*allowOptionalChain*/ true); + const questionDotToken = parseOptionalToken(SyntaxKind.QuestionDotToken); + // handle 'foo<()' + if (token() === SyntaxKind.LessThanToken || token() === SyntaxKind.LessThanLessThanToken) { + // See if this is the start of a generic invocation. If so, consume it and + // keep checking for postfix expressions. Otherwise, it's just a '<' that's + // part of an arithmetic expression. Break out so we consume it higher in the + // stack. + const typeArguments = tryParse(parseTypeArgumentsInExpression); + if (typeArguments) { + if (isTemplateStartOfTaggedTemplate()) { + expression = parseTaggedTemplateRest(expression, questionDotToken, typeArguments); + continue; + } + const callExpr = (createNode(SyntaxKind.CallExpression, expression.pos)); + callExpr.expression = expression; + callExpr.questionDotToken = questionDotToken; + callExpr.typeArguments = typeArguments; + callExpr.arguments = parseArgumentList(); + if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { + callExpr.flags |= NodeFlags.OptionalChain; + } + expression = finishNode(callExpr); + continue; + } } - else { - node.kind = SyntaxKind.ExpressionStatement; - (node).expression = expression; - parseSemicolon(); + else if (token() === SyntaxKind.OpenParenToken) { + const callExpr = (createNode(SyntaxKind.CallExpression, expression.pos)); + callExpr.expression = expression; + callExpr.questionDotToken = questionDotToken; + callExpr.arguments = parseArgumentList(); + if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { + callExpr.flags |= NodeFlags.OptionalChain; + } + expression = finishNode(callExpr); + continue; } - return finishNode(node); + if (questionDotToken) { + // We failed to parse anything, so report a missing identifier here. + const propertyAccess = (createNode(SyntaxKind.PropertyAccessExpression, expression.pos) as PropertyAccessExpression); + propertyAccess.expression = expression; + propertyAccess.questionDotToken = questionDotToken; + propertyAccess.name = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, Diagnostics.Identifier_expected); + propertyAccess.flags |= NodeFlags.OptionalChain; + expression = finishNode(propertyAccess); + } + break; } - - function nextTokenIsIdentifierOrKeywordOnSameLine() { - nextToken(); - return tokenIsIdentifierOrKeyword(token()) && !scanner.hasPrecedingLineBreak(); + return expression; + } + function parseArgumentList() { + parseExpected(SyntaxKind.OpenParenToken); + const result = parseDelimitedList(ParsingContext.ArgumentExpressions, parseArgumentExpression); + parseExpected(SyntaxKind.CloseParenToken); + return result; + } + function parseTypeArgumentsInExpression() { + if (reScanLessThanToken() !== SyntaxKind.LessThanToken) { + return undefined; } - - function nextTokenIsClassKeywordOnSameLine() { - nextToken(); - return token() === SyntaxKind.ClassKeyword && !scanner.hasPrecedingLineBreak(); + nextToken(); + const typeArguments = parseDelimitedList(ParsingContext.TypeArguments, parseType); + if (!parseExpected(SyntaxKind.GreaterThanToken)) { + // If it doesn't have the closing `>` then it's definitely not an type argument list. + return undefined; } - - function nextTokenIsFunctionKeywordOnSameLine() { - nextToken(); - return token() === SyntaxKind.FunctionKeyword && !scanner.hasPrecedingLineBreak(); + // If we have a '<', then only parse this as a argument list if the type arguments + // are complete and we have an open paren. if we don't, rewind and return nothing. + return typeArguments && canFollowTypeArgumentsInExpression() + ? typeArguments + : undefined; + } + function canFollowTypeArgumentsInExpression(): boolean { + switch (token()) { + case SyntaxKind.OpenParenToken: // foo( + case SyntaxKind.NoSubstitutionTemplateLiteral: // foo `...` + case SyntaxKind.TemplateHead: // foo `...${100}...` + // these are the only tokens can legally follow a type argument + // list. So we definitely want to treat them as type arg lists. + // falls through + case SyntaxKind.DotToken: // foo. + case SyntaxKind.CloseParenToken: // foo) + case SyntaxKind.CloseBracketToken: // foo] + case SyntaxKind.ColonToken: // foo: + case SyntaxKind.SemicolonToken: // foo; + case SyntaxKind.QuestionToken: // foo? + case SyntaxKind.EqualsEqualsToken: // foo == + case SyntaxKind.EqualsEqualsEqualsToken: // foo === + case SyntaxKind.ExclamationEqualsToken: // foo != + case SyntaxKind.ExclamationEqualsEqualsToken: // foo !== + case SyntaxKind.AmpersandAmpersandToken: // foo && + case SyntaxKind.BarBarToken: // foo || + case SyntaxKind.QuestionQuestionToken: // foo ?? + case SyntaxKind.CaretToken: // foo ^ + case SyntaxKind.AmpersandToken: // foo & + case SyntaxKind.BarToken: // foo | + case SyntaxKind.CloseBraceToken: // foo } + case SyntaxKind.EndOfFileToken: // foo + // these cases can't legally follow a type arg list. However, they're not legal + // expressions either. The user is probably in the middle of a generic type. So + // treat it as such. + return true; + case SyntaxKind.CommaToken: // foo, + case SyntaxKind.OpenBraceToken: // foo { + // We don't want to treat these as type arguments. Otherwise we'll parse this + // as an invocation expression. Instead, we want to parse out the expression + // in isolation from the type arguments. + // falls through + default: + // Anything else treat as an expression. + return false; } - - function nextTokenIsIdentifierOrKeywordOrLiteralOnSameLine() { - nextToken(); - return (tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.NumericLiteral || token() === SyntaxKind.BigIntLiteral || token() === SyntaxKind.StringLiteral) && !scanner.hasPrecedingLineBreak(); + } + 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(); } - - 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; - - // '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.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: - nextToken(); - if (token() === SyntaxKind.EqualsToken || token() === SyntaxKind.AsteriskToken || - token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.DefaultKeyword || - token() === SyntaxKind.AsKeyword) { - return true; - } - continue; - - case SyntaxKind.StaticKeyword: - nextToken(); - continue; - default: - return false; + return parseIdentifier(Diagnostics.Expression_expected); + } + function parseParenthesizedExpression(): ParenthesizedExpression { + const node = (createNodeWithJSDoc(SyntaxKind.ParenthesizedExpression)); + parseExpected(SyntaxKind.OpenParenToken); + node.expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.CloseParenToken); + return finishNode(node); + } + function parseSpreadElement(): Expression { + const node = (createNode(SyntaxKind.SpreadElement)); + parseExpected(SyntaxKind.DotDotDotToken); + node.expression = parseAssignmentExpressionOrHigher(); + return finishNode(node); + } + function parseArgumentOrArrayLiteralElement(): Expression { + return token() === SyntaxKind.DotDotDotToken ? parseSpreadElement() : + token() === SyntaxKind.CommaToken ? createNode(SyntaxKind.OmittedExpression) : + parseAssignmentExpressionOrHigher(); + } + function parseArgumentExpression(): Expression { + return doOutsideOfContext(disallowInAndDecoratorContext, parseArgumentOrArrayLiteralElement); + } + function parseArrayLiteralExpression(): ArrayLiteralExpression { + const node = (createNode(SyntaxKind.ArrayLiteralExpression)); + parseExpected(SyntaxKind.OpenBracketToken); + if (scanner.hasPrecedingLineBreak()) { + node.multiLine = true; + } + node.elements = parseDelimitedList(ParsingContext.ArrayLiteralMembers, parseArgumentOrArrayLiteralElement); + parseExpected(SyntaxKind.CloseBracketToken); + return finishNode(node); + } + function parseObjectLiteralElement(): ObjectLiteralElementLike { + const node = (createNodeWithJSDoc(SyntaxKind.Unknown)); + if (parseOptionalToken(SyntaxKind.DotDotDotToken)) { + node.kind = SyntaxKind.SpreadAssignment; + (node).expression = parseAssignmentExpressionOrHigher(); + return finishNode(node); + } + node.decorators = parseDecorators(); + node.modifiers = parseModifiers(); + if (parseContextualModifier(SyntaxKind.GetKeyword)) { + return parseAccessorDeclaration((node), SyntaxKind.GetAccessor); + } + if (parseContextualModifier(SyntaxKind.SetKeyword)) { + return parseAccessorDeclaration((node), SyntaxKind.SetAccessor); + } + const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); + const tokenIsIdentifier = isIdentifier(); + node.name = parsePropertyName(); + // Disallowing of optional property assignments and definite assignment assertion happens in the grammar checker. + (node).questionToken = parseOptionalToken(SyntaxKind.QuestionToken); + (node).exclamationToken = parseOptionalToken(SyntaxKind.ExclamationToken); + if (asteriskToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { + return parseMethodDeclaration((node), asteriskToken); + } + // 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 + const isShorthandPropertyAssignment = tokenIsIdentifier && (token() !== SyntaxKind.ColonToken); + if (isShorthandPropertyAssignment) { + node.kind = SyntaxKind.ShorthandPropertyAssignment; + const equalsToken = parseOptionalToken(SyntaxKind.EqualsToken); + if (equalsToken) { + (node).equalsToken = equalsToken; + (node).objectAssignmentInitializer = allowInAnd(parseAssignmentExpressionOrHigher); + } + } + else { + node.kind = SyntaxKind.PropertyAssignment; + parseExpected(SyntaxKind.ColonToken); + (node).initializer = allowInAnd(parseAssignmentExpressionOrHigher); + } + return finishNode(node); + } + function parseObjectLiteralExpression(): ObjectLiteralExpression { + const node = (createNode(SyntaxKind.ObjectLiteralExpression)); + parseExpected(SyntaxKind.OpenBraceToken); + if (scanner.hasPrecedingLineBreak()) { + node.multiLine = true; + } + node.properties = parseDelimitedList(ParsingContext.ObjectLiteralMembers, parseObjectLiteralElement, /*considerSemicolonAsDelimiter*/ true); + parseExpected(SyntaxKind.CloseBraceToken); + return finishNode(node); + } + function parseFunctionExpression(): FunctionExpression { + // GeneratorExpression: + // function* BindingIdentifier [Yield][opt](FormalParameters[Yield]){ GeneratorBody } + // + // FunctionExpression: + // function BindingIdentifier[opt](FormalParameters){ FunctionBody } + const saveDecoratorContext = inDecoratorContext(); + if (saveDecoratorContext) { + setDecoratorContext(/*val*/ false); + } + const node = (createNodeWithJSDoc(SyntaxKind.FunctionExpression)); + node.modifiers = parseModifiers(); + parseExpected(SyntaxKind.FunctionKeyword); + node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); + const isGenerator = node.asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; + const isAsync = hasModifier(node, ModifierFlags.Async) ? SignatureFlags.Await : SignatureFlags.None; + node.name = + isGenerator && isAsync ? doInYieldAndAwaitContext(parseOptionalIdentifier) : + isGenerator ? doInYieldContext(parseOptionalIdentifier) : + isAsync ? doInAwaitContext(parseOptionalIdentifier) : + parseOptionalIdentifier(); + fillSignature(SyntaxKind.ColonToken, isGenerator | isAsync, node); + node.body = parseFunctionBlock(isGenerator | isAsync); + if (saveDecoratorContext) { + setDecoratorContext(/*val*/ true); + } + return finishNode(node); + } + function parseOptionalIdentifier(): Identifier | undefined { + return isIdentifier() ? parseIdentifier() : undefined; + } + function parseNewExpressionOrNewDotTarget(): NewExpression | MetaProperty { + const fullStart = scanner.getStartPos(); + parseExpected(SyntaxKind.NewKeyword); + if (parseOptional(SyntaxKind.DotToken)) { + const node = (createNode(SyntaxKind.MetaProperty, fullStart)); + node.keywordToken = SyntaxKind.NewKeyword; + node.name = parseIdentifierName(); + return finishNode(node); + } + let expression: MemberExpression = parsePrimaryExpression(); + let typeArguments; + while (true) { + expression = parseMemberExpressionRest(expression, /*allowOptionalChain*/ false); + typeArguments = tryParse(parseTypeArgumentsInExpression); + if (isTemplateStartOfTaggedTemplate()) { + Debug.assert(!!typeArguments, "Expected a type argument list; all plain tagged template starts should be consumed in 'parseMemberExpressionRest'"); + expression = parseTaggedTemplateRest(expression, /*optionalChain*/ undefined, typeArguments); + typeArguments = undefined; + } + break; + } + const node = (createNode(SyntaxKind.NewExpression, fullStart)); + node.expression = expression; + node.typeArguments = typeArguments; + if (node.typeArguments || token() === SyntaxKind.OpenParenToken) { + node.arguments = parseArgumentList(); + } + return finishNode(node); + } + // STATEMENTS + function parseBlock(ignoreMissingOpenBrace: boolean, diagnosticMessage?: DiagnosticMessage): Block { + const node = (createNode(SyntaxKind.Block)); + const openBracePosition = scanner.getTokenPos(); + if (parseExpected(SyntaxKind.OpenBraceToken, diagnosticMessage) || ignoreMissingOpenBrace) { + if (scanner.hasPrecedingLineBreak()) { + node.multiLine = true; + } + node.statements = parseList(ParsingContext.BlockStatements, parseStatement); + if (!parseExpected(SyntaxKind.CloseBraceToken)) { + const lastError = lastOrUndefined(parseDiagnostics); + if (lastError && lastError.code === Diagnostics._0_expected.code) { + addRelatedInfo(lastError, createFileDiagnostic(sourceFile, openBracePosition, 1, Diagnostics.The_parser_expected_to_find_a_to_match_the_token_here)); } } } - - function isStartOfDeclaration(): boolean { - return lookAhead(isDeclaration); + else { + node.statements = createMissingList(); + } + return finishNode(node); + } + function parseFunctionBlock(flags: SignatureFlags, diagnosticMessage?: DiagnosticMessage): Block { + const savedYieldContext = inYieldContext(); + setYieldContext(!!(flags & SignatureFlags.Yield)); + const savedAwaitContext = inAwaitContext(); + setAwaitContext(!!(flags & SignatureFlags.Await)); + // 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); + } + setYieldContext(savedYieldContext); + setAwaitContext(savedAwaitContext); + return block; + } + function parseEmptyStatement(): Statement { + const node = (createNode(SyntaxKind.EmptyStatement)); + parseExpected(SyntaxKind.SemicolonToken); + return finishNode(node); + } + function parseIfStatement(): IfStatement { + const node = (createNode(SyntaxKind.IfStatement)); + parseExpected(SyntaxKind.IfKeyword); + parseExpected(SyntaxKind.OpenParenToken); + node.expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.CloseParenToken); + node.thenStatement = parseStatement(); + node.elseStatement = parseOptional(SyntaxKind.ElseKeyword) ? parseStatement() : undefined; + return finishNode(node); + } + function parseDoStatement(): DoStatement { + const node = (createNode(SyntaxKind.DoStatement)); + parseExpected(SyntaxKind.DoKeyword); + node.statement = parseStatement(); + parseExpected(SyntaxKind.WhileKeyword); + parseExpected(SyntaxKind.OpenParenToken); + node.expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.CloseParenToken); + // From: https://mail.mozilla.org/pipermail/es-discuss/2011-August/016188.html + // 157 min --- All allen at wirfs-brock.com CONF --- "do{;}while(false)false" prohibited in + // spec but allowed in consensus reality. Approved -- this is the de-facto standard whereby + // do;while(0)x will have a semicolon inserted before x. + parseOptional(SyntaxKind.SemicolonToken); + return finishNode(node); + } + function parseWhileStatement(): WhileStatement { + const node = (createNode(SyntaxKind.WhileStatement)); + parseExpected(SyntaxKind.WhileKeyword); + parseExpected(SyntaxKind.OpenParenToken); + node.expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.CloseParenToken); + node.statement = parseStatement(); + return finishNode(node); + } + function parseForOrForInOrForOfStatement(): Statement { + const pos = getNodePos(); + 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 forOrForInOrForOfStatement: IterationStatement; + if (awaitToken ? parseExpected(SyntaxKind.OfKeyword) : parseOptional(SyntaxKind.OfKeyword)) { + const forOfStatement = (createNode(SyntaxKind.ForOfStatement, pos)); + forOfStatement.awaitModifier = awaitToken; + forOfStatement.initializer = initializer; + forOfStatement.expression = allowInAnd(parseAssignmentExpressionOrHigher); + parseExpected(SyntaxKind.CloseParenToken); + forOrForInOrForOfStatement = forOfStatement; } - - 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.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.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(); + else if (parseOptional(SyntaxKind.InKeyword)) { + const forInStatement = (createNode(SyntaxKind.ForInStatement, pos)); + forInStatement.initializer = initializer; + forInStatement.expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.CloseParenToken); + forOrForInOrForOfStatement = forInStatement; + } + else { + const forStatement = (createNode(SyntaxKind.ForStatement, pos)); + forStatement.initializer = initializer; + parseExpected(SyntaxKind.SemicolonToken); + if (token() !== SyntaxKind.SemicolonToken && token() !== SyntaxKind.CloseParenToken) { + forStatement.condition = allowInAnd(parseExpression); + } + parseExpected(SyntaxKind.SemicolonToken); + if (token() !== SyntaxKind.CloseParenToken) { + forStatement.incrementor = allowInAnd(parseExpression); } + parseExpected(SyntaxKind.CloseParenToken); + forOrForInOrForOfStatement = forStatement; } - - function nextTokenIsIdentifierOrStartOfDestructuring() { - nextToken(); - return isIdentifier() || token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.OpenBracketToken; + forOrForInOrForOfStatement.statement = parseStatement(); + return finishNode(forOrForInOrForOfStatement); + } + function parseBreakOrContinueStatement(kind: SyntaxKind): BreakOrContinueStatement { + const node = (createNode(kind)); + parseExpected(kind === SyntaxKind.BreakStatement ? SyntaxKind.BreakKeyword : SyntaxKind.ContinueKeyword); + if (!canParseSemicolon()) { + node.label = parseIdentifier(); + } + parseSemicolon(); + return finishNode(node); + } + function parseReturnStatement(): ReturnStatement { + const node = (createNode(SyntaxKind.ReturnStatement)); + parseExpected(SyntaxKind.ReturnKeyword); + if (!canParseSemicolon()) { + node.expression = allowInAnd(parseExpression); + } + parseSemicolon(); + return finishNode(node); + } + function parseWithStatement(): WithStatement { + const node = (createNode(SyntaxKind.WithStatement)); + parseExpected(SyntaxKind.WithKeyword); + parseExpected(SyntaxKind.OpenParenToken); + node.expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.CloseParenToken); + node.statement = doInsideOfContext(NodeFlags.InWithStatement, parseStatement); + return finishNode(node); + } + function parseCaseClause(): CaseClause { + const node = (createNode(SyntaxKind.CaseClause)); + parseExpected(SyntaxKind.CaseKeyword); + node.expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.ColonToken); + node.statements = parseList(ParsingContext.SwitchClauseStatements, parseStatement); + return finishNode(node); + } + function parseDefaultClause(): DefaultClause { + const node = (createNode(SyntaxKind.DefaultClause)); + parseExpected(SyntaxKind.DefaultKeyword); + parseExpected(SyntaxKind.ColonToken); + node.statements = parseList(ParsingContext.SwitchClauseStatements, parseStatement); + return finishNode(node); + } + function parseCaseOrDefaultClause(): CaseOrDefaultClause { + return token() === SyntaxKind.CaseKeyword ? parseCaseClause() : parseDefaultClause(); + } + function parseSwitchStatement(): SwitchStatement { + const node = (createNode(SyntaxKind.SwitchStatement)); + parseExpected(SyntaxKind.SwitchKeyword); + parseExpected(SyntaxKind.OpenParenToken); + node.expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.CloseParenToken); + const caseBlock = (createNode(SyntaxKind.CaseBlock)); + parseExpected(SyntaxKind.OpenBraceToken); + caseBlock.clauses = parseList(ParsingContext.SwitchClauses, parseCaseOrDefaultClause); + parseExpected(SyntaxKind.CloseBraceToken); + node.caseBlock = finishNode(caseBlock); + return finishNode(node); + } + function parseThrowStatement(): ThrowStatement { + // ThrowStatement[Yield] : + // throw [no LineTerminator here]Expression[In, ?Yield]; + // Because of automatic semicolon insertion, we need to report error if this + // throw could be terminated with a semicolon. Note: we can't call 'parseExpression' + // directly as that might consume an expression on the following line. + // We just return 'undefined' in that case. The actual error will be reported in the + // grammar walker. + const node = (createNode(SyntaxKind.ThrowStatement)); + parseExpected(SyntaxKind.ThrowKeyword); + node.expression = scanner.hasPrecedingLineBreak() ? undefined : allowInAnd(parseExpression); + parseSemicolon(); + return finishNode(node); + } + // TODO: Review for error recovery + function parseTryStatement(): TryStatement { + const node = (createNode(SyntaxKind.TryStatement)); + parseExpected(SyntaxKind.TryKeyword); + node.tryBlock = parseBlock(/*ignoreMissingOpenBrace*/ false); + node.catchClause = token() === SyntaxKind.CatchKeyword ? parseCatchClause() : undefined; + // If we don't have a catch clause, then we must have a finally clause. Try to parse + // one out no matter what. + if (!node.catchClause || token() === SyntaxKind.FinallyKeyword) { + parseExpected(SyntaxKind.FinallyKeyword); + node.finallyBlock = parseBlock(/*ignoreMissingOpenBrace*/ false); + } + return finishNode(node); + } + function parseCatchClause(): CatchClause { + const result = (createNode(SyntaxKind.CatchClause)); + parseExpected(SyntaxKind.CatchKeyword); + if (parseOptional(SyntaxKind.OpenParenToken)) { + result.variableDeclaration = parseVariableDeclaration(); + parseExpected(SyntaxKind.CloseParenToken); + } + else { + // Keep shape of node to avoid degrading performance. + result.variableDeclaration = undefined; } - - function isLetDeclaration() { - // In ES6 'let' always starts a lexical declaration if followed by an identifier or { - // or [. - return lookAhead(nextTokenIsIdentifierOrStartOfDestructuring); + result.block = parseBlock(/*ignoreMissingOpenBrace*/ false); + return finishNode(result); + } + function parseDebuggerStatement(): Statement { + const node = (createNode(SyntaxKind.DebuggerStatement)); + parseExpected(SyntaxKind.DebuggerKeyword); + parseSemicolon(); + return finishNode(node); + } + function parseExpressionOrLabeledStatement(): ExpressionStatement | LabeledStatement { + // Avoiding having to do the lookahead for a labeled statement by just trying to parse + // out an expression, seeing if it is identifier and then seeing if it is followed by + // a colon. + const node = (createNodeWithJSDoc(SyntaxKind.Unknown)); + const expression = allowInAnd(parseExpression); + if (expression.kind === SyntaxKind.Identifier && parseOptional(SyntaxKind.ColonToken)) { + node.kind = SyntaxKind.LabeledStatement; + (node).label = (expression); + (node).statement = parseStatement(); + } + else { + node.kind = SyntaxKind.ExpressionStatement; + (node).expression = expression; + parseSemicolon(); } - - function parseStatement(): Statement { + return finishNode(node); + } + 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.SemicolonToken: - return parseEmptyStatement(); - case SyntaxKind.OpenBraceToken: - return parseBlock(/*ignoreMissingOpenBrace*/ false); case SyntaxKind.VarKeyword: - return parseVariableStatement(createNodeWithJSDoc(SyntaxKind.VariableDeclaration)); case SyntaxKind.LetKeyword: - if (isLetDeclaration()) { - return parseVariableStatement(createNodeWithJSDoc(SyntaxKind.VariableDeclaration)); - } - break; + case SyntaxKind.ConstKeyword: case SyntaxKind.FunctionKeyword: - return parseFunctionDeclaration(createNodeWithJSDoc(SyntaxKind.FunctionDeclaration)); case SyntaxKind.ClassKeyword: - return parseClassDeclaration(createNodeWithJSDoc(SyntaxKind.ClassDeclaration)); - 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.EnumKeyword: + return true; + // '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.AsyncKeyword: 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.StaticKeyword: case SyntaxKind.ReadonlyKeyword: - case SyntaxKind.GlobalKeyword: - if (isStartOfDeclaration()) { - return parseDeclaration(); + nextToken(); + // ASI takes effect for this modifier. + if (scanner.hasPrecedingLineBreak()) { + return false; } - break; - } - return parseExpressionOrLabeledStatement(); - } - - function isDeclareModifier(modifier: Modifier) { - return modifier.kind === SyntaxKind.DeclareKeyword; - } - - function parseDeclaration(): Statement { - const modifiers = lookAhead(() => (parseDecorators(), parseModifiers())); - // `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 isAmbient = some(modifiers, isDeclareModifier); - if (isAmbient) { - const node = tryReuseAmbientDeclaration(); - if (node) { - return node; - } - } - - const node = createNodeWithJSDoc(SyntaxKind.Unknown); - node.decorators = parseDecorators(); - node.modifiers = parseModifiers(); - if (isAmbient) { - for (const m of node.modifiers!) { - m.flags |= NodeFlags.Ambient; - } - return doInsideOfContext(NodeFlags.Ambient, () => parseDeclarationWorker(node)); - } - else { - return parseDeclarationWorker(node); - } - } - - function tryReuseAmbientDeclaration(): Statement | undefined { - return doInsideOfContext(NodeFlags.Ambient, () => { - const node = currentNode(parsingContext); - if (node) { - return consumeNode(node) as Statement; - } - }); - } - - function parseDeclarationWorker(node: Statement): Statement { - switch (token()) { - case SyntaxKind.VarKeyword: - case SyntaxKind.LetKeyword: - case SyntaxKind.ConstKeyword: - return parseVariableStatement(node); - case SyntaxKind.FunctionKeyword: - return parseFunctionDeclaration(node); - case SyntaxKind.ClassKeyword: - return parseClassDeclaration(node); - case SyntaxKind.InterfaceKeyword: - return parseInterfaceDeclaration(node); - case SyntaxKind.TypeKeyword: - return parseTypeAliasDeclaration(node); - case SyntaxKind.EnumKeyword: - return parseEnumDeclaration(node); + continue; case SyntaxKind.GlobalKeyword: - case SyntaxKind.ModuleKeyword: - case SyntaxKind.NamespaceKeyword: - return parseModuleDeclaration(node); + nextToken(); + return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.Identifier || token() === SyntaxKind.ExportKeyword; case SyntaxKind.ImportKeyword: - return parseImportDeclarationOrImportEqualsDeclaration(node); + nextToken(); + return token() === SyntaxKind.StringLiteral || token() === SyntaxKind.AsteriskToken || + token() === SyntaxKind.OpenBraceToken || tokenIsIdentifierOrKeyword(token()); case SyntaxKind.ExportKeyword: nextToken(); - switch (token()) { - case SyntaxKind.DefaultKeyword: - case SyntaxKind.EqualsToken: - return parseExportAssignment(node); - case SyntaxKind.AsKeyword: - return parseNamespaceExportDeclaration(node); - default: - return parseExportDeclaration(node); + if (token() === SyntaxKind.EqualsToken || token() === SyntaxKind.AsteriskToken || + token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.DefaultKeyword || + token() === SyntaxKind.AsKeyword) { + return true; } + continue; + case SyntaxKind.StaticKeyword: + nextToken(); + continue; default: - if (node.decorators || node.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); - missing.pos = node.pos; - missing.decorators = node.decorators; - missing.modifiers = node.modifiers; - return finishNode(missing); - } - return undefined!; // TODO: GH#18217 + return false; } } - - function nextTokenIsIdentifierOrStringLiteralOnSameLine() { - nextToken(); - return !scanner.hasPrecedingLineBreak() && (isIdentifier() || token() === SyntaxKind.StringLiteral); + } + 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.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.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 parseFunctionBlockOrSemicolon(flags: SignatureFlags, diagnosticMessage?: DiagnosticMessage): Block | undefined { - if (token() !== SyntaxKind.OpenBraceToken && canParseSemicolon()) { - parseSemicolon(); - return; - } - - return parseFunctionBlock(flags, diagnosticMessage); + } + function nextTokenIsIdentifierOrStartOfDestructuring() { + nextToken(); + return isIdentifier() || 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(nextTokenIsIdentifierOrStartOfDestructuring); + } + function parseStatement(): Statement { + switch (token()) { + case SyntaxKind.SemicolonToken: + return parseEmptyStatement(); + case SyntaxKind.OpenBraceToken: + return parseBlock(/*ignoreMissingOpenBrace*/ false); + case SyntaxKind.VarKeyword: + return parseVariableStatement((createNodeWithJSDoc(SyntaxKind.VariableDeclaration))); + case SyntaxKind.LetKeyword: + if (isLetDeclaration()) { + return parseVariableStatement((createNodeWithJSDoc(SyntaxKind.VariableDeclaration))); + } + break; + case SyntaxKind.FunctionKeyword: + return parseFunctionDeclaration((createNodeWithJSDoc(SyntaxKind.FunctionDeclaration))); + case SyntaxKind.ClassKeyword: + return parseClassDeclaration((createNodeWithJSDoc(SyntaxKind.ClassDeclaration))); + 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.StaticKeyword: + case SyntaxKind.ReadonlyKeyword: + case SyntaxKind.GlobalKeyword: + if (isStartOfDeclaration()) { + return parseDeclaration(); + } + break; } - - // DECLARATIONS - - function parseArrayBindingElement(): ArrayBindingElement { - if (token() === SyntaxKind.CommaToken) { - return createNode(SyntaxKind.OmittedExpression); + return parseExpressionOrLabeledStatement(); + } + function isDeclareModifier(modifier: Modifier) { + return modifier.kind === SyntaxKind.DeclareKeyword; + } + function parseDeclaration(): Statement { + const modifiers = lookAhead(() => (parseDecorators(), parseModifiers())); + // `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 isAmbient = some(modifiers, isDeclareModifier); + if (isAmbient) { + const node = tryReuseAmbientDeclaration(); + if (node) { + return node; } - const node = createNode(SyntaxKind.BindingElement); - node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); - node.name = parseIdentifierOrPattern(); - node.initializer = parseInitializer(); - return finishNode(node); } - - function parseObjectBindingElement(): BindingElement { - const node = createNode(SyntaxKind.BindingElement); - node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); - const tokenIsIdentifier = isIdentifier(); - const propertyName = parsePropertyName(); - if (tokenIsIdentifier && token() !== SyntaxKind.ColonToken) { - node.name = propertyName; - } - else { - parseExpected(SyntaxKind.ColonToken); - node.propertyName = propertyName; - node.name = parseIdentifierOrPattern(); + const node = (createNodeWithJSDoc(SyntaxKind.Unknown)); + node.decorators = parseDecorators(); + node.modifiers = parseModifiers(); + if (isAmbient) { + for (const m of node.modifiers!) { + m.flags |= NodeFlags.Ambient; } - node.initializer = parseInitializer(); - return finishNode(node); - } - - function parseObjectBindingPattern(): ObjectBindingPattern { - const node = createNode(SyntaxKind.ObjectBindingPattern); - parseExpected(SyntaxKind.OpenBraceToken); - node.elements = parseDelimitedList(ParsingContext.ObjectBindingElements, parseObjectBindingElement); - parseExpected(SyntaxKind.CloseBraceToken); - return finishNode(node); - } - - function parseArrayBindingPattern(): ArrayBindingPattern { - const node = createNode(SyntaxKind.ArrayBindingPattern); - parseExpected(SyntaxKind.OpenBracketToken); - node.elements = parseDelimitedList(ParsingContext.ArrayBindingElements, parseArrayBindingElement); - parseExpected(SyntaxKind.CloseBracketToken); - return finishNode(node); + return doInsideOfContext(NodeFlags.Ambient, () => parseDeclarationWorker(node)); } - - function isIdentifierOrPattern() { - return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.OpenBracketToken || isIdentifier(); + else { + return parseDeclarationWorker(node); } - - function parseIdentifierOrPattern(): Identifier | BindingPattern { - if (token() === SyntaxKind.OpenBracketToken) { - return parseArrayBindingPattern(); - } - if (token() === SyntaxKind.OpenBraceToken) { - return parseObjectBindingPattern(); + } + function tryReuseAmbientDeclaration(): Statement | undefined { + return doInsideOfContext(NodeFlags.Ambient, () => { + const node = currentNode(parsingContext); + if (node) { + return consumeNode(node) as Statement; } - return parseIdentifier(); + }); + } + function parseDeclarationWorker(node: Statement): Statement { + switch (token()) { + case SyntaxKind.VarKeyword: + case SyntaxKind.LetKeyword: + case SyntaxKind.ConstKeyword: + return parseVariableStatement((node)); + case SyntaxKind.FunctionKeyword: + return parseFunctionDeclaration((node)); + case SyntaxKind.ClassKeyword: + return parseClassDeclaration((node)); + case SyntaxKind.InterfaceKeyword: + return parseInterfaceDeclaration((node)); + case SyntaxKind.TypeKeyword: + return parseTypeAliasDeclaration((node)); + case SyntaxKind.EnumKeyword: + return parseEnumDeclaration((node)); + case SyntaxKind.GlobalKeyword: + case SyntaxKind.ModuleKeyword: + case SyntaxKind.NamespaceKeyword: + return parseModuleDeclaration((node)); + case SyntaxKind.ImportKeyword: + return parseImportDeclarationOrImportEqualsDeclaration((node)); + case SyntaxKind.ExportKeyword: + nextToken(); + switch (token()) { + case SyntaxKind.DefaultKeyword: + case SyntaxKind.EqualsToken: + return parseExportAssignment((node)); + case SyntaxKind.AsKeyword: + return parseNamespaceExportDeclaration((node)); + default: + return parseExportDeclaration((node)); + } + default: + if (node.decorators || node.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); + missing.pos = node.pos; + missing.decorators = node.decorators; + missing.modifiers = node.modifiers; + return finishNode(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 && canParseSemicolon()) { + parseSemicolon(); + return; } - - function parseVariableDeclarationAllowExclamation() { - return parseVariableDeclaration(/*allowExclamation*/ true); + return parseFunctionBlock(flags, diagnosticMessage); + } + // DECLARATIONS + function parseArrayBindingElement(): ArrayBindingElement { + if (token() === SyntaxKind.CommaToken) { + return createNode(SyntaxKind.OmittedExpression); + } + const node = (createNode(SyntaxKind.BindingElement)); + node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); + node.name = parseIdentifierOrPattern(); + node.initializer = parseInitializer(); + return finishNode(node); + } + function parseObjectBindingElement(): BindingElement { + const node = (createNode(SyntaxKind.BindingElement)); + node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); + const tokenIsIdentifier = isIdentifier(); + const propertyName = parsePropertyName(); + if (tokenIsIdentifier && token() !== SyntaxKind.ColonToken) { + node.name = (propertyName); } - - function parseVariableDeclaration(allowExclamation?: boolean): VariableDeclaration { - const node = createNode(SyntaxKind.VariableDeclaration); + else { + parseExpected(SyntaxKind.ColonToken); + node.propertyName = propertyName; node.name = parseIdentifierOrPattern(); - if (allowExclamation && node.name.kind === SyntaxKind.Identifier && - token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { - node.exclamationToken = parseTokenNode>(); - } - node.type = parseTypeAnnotation(); - if (!isInOrOfKeyword(token())) { - node.initializer = parseInitializer(); - } - return finishNode(node); } - - function parseVariableDeclarationList(inForStatementInitializer: boolean): VariableDeclarationList { - const node = createNode(SyntaxKind.VariableDeclarationList); - - switch (token()) { - case SyntaxKind.VarKeyword: - break; - case SyntaxKind.LetKeyword: - node.flags |= NodeFlags.Let; - break; - case SyntaxKind.ConstKeyword: - node.flags |= NodeFlags.Const; - break; - default: - Debug.fail(); - } - - nextToken(); - - // The user may have written the following: - // - // for (let of X) { } - // - // In this case, we want to parse an empty declaration list, and then parse 'of' - // as a keyword. The reason this is not automatic is that 'of' is a valid identifier. - // So we need to look ahead to determine if 'of' should be treated as a keyword in - // this context. - // The checker will then give an error that there is an empty declaration list. - if (token() === SyntaxKind.OfKeyword && lookAhead(canFollowContextualOfKeyword)) { - node.declarations = createMissingList(); - } - else { - const savedDisallowIn = inDisallowInContext(); - setDisallowInContext(inForStatementInitializer); - - node.declarations = parseDelimitedList(ParsingContext.VariableDeclarations, - inForStatementInitializer ? parseVariableDeclaration : parseVariableDeclarationAllowExclamation); - - setDisallowInContext(savedDisallowIn); - } - - return finishNode(node); + node.initializer = parseInitializer(); + return finishNode(node); + } + function parseObjectBindingPattern(): ObjectBindingPattern { + const node = (createNode(SyntaxKind.ObjectBindingPattern)); + parseExpected(SyntaxKind.OpenBraceToken); + node.elements = parseDelimitedList(ParsingContext.ObjectBindingElements, parseObjectBindingElement); + parseExpected(SyntaxKind.CloseBraceToken); + return finishNode(node); + } + function parseArrayBindingPattern(): ArrayBindingPattern { + const node = (createNode(SyntaxKind.ArrayBindingPattern)); + parseExpected(SyntaxKind.OpenBracketToken); + node.elements = parseDelimitedList(ParsingContext.ArrayBindingElements, parseArrayBindingElement); + parseExpected(SyntaxKind.CloseBracketToken); + return finishNode(node); + } + function isIdentifierOrPattern() { + return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.OpenBracketToken || isIdentifier(); + } + function parseIdentifierOrPattern(): Identifier | BindingPattern { + if (token() === SyntaxKind.OpenBracketToken) { + return parseArrayBindingPattern(); } - - function canFollowContextualOfKeyword(): boolean { - return nextTokenIsIdentifier() && nextToken() === SyntaxKind.CloseParenToken; + if (token() === SyntaxKind.OpenBraceToken) { + return parseObjectBindingPattern(); } - - function parseVariableStatement(node: VariableStatement): VariableStatement { - node.kind = SyntaxKind.VariableStatement; - node.declarationList = parseVariableDeclarationList(/*inForStatementInitializer*/ false); - parseSemicolon(); - return finishNode(node); + return parseIdentifier(); + } + function parseVariableDeclarationAllowExclamation() { + return parseVariableDeclaration(/*allowExclamation*/ true); + } + function parseVariableDeclaration(allowExclamation?: boolean): VariableDeclaration { + const node = (createNode(SyntaxKind.VariableDeclaration)); + node.name = parseIdentifierOrPattern(); + if (allowExclamation && node.name.kind === SyntaxKind.Identifier && + token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { + node.exclamationToken = parseTokenNode>(); + } + node.type = parseTypeAnnotation(); + if (!isInOrOfKeyword(token())) { + node.initializer = parseInitializer(); } - - function parseFunctionDeclaration(node: FunctionDeclaration): FunctionDeclaration { - node.kind = SyntaxKind.FunctionDeclaration; - parseExpected(SyntaxKind.FunctionKeyword); - node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); - node.name = hasModifier(node, ModifierFlags.Default) ? parseOptionalIdentifier() : parseIdentifier(); - const isGenerator = node.asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; - const isAsync = hasModifier(node, ModifierFlags.Async) ? SignatureFlags.Await : SignatureFlags.None; - fillSignature(SyntaxKind.ColonToken, isGenerator | isAsync, node); - node.body = parseFunctionBlockOrSemicolon(isGenerator | isAsync, Diagnostics.or_expected); - return finishNode(node); + return finishNode(node); + } + function parseVariableDeclarationList(inForStatementInitializer: boolean): VariableDeclarationList { + const node = (createNode(SyntaxKind.VariableDeclarationList)); + switch (token()) { + case SyntaxKind.VarKeyword: + break; + case SyntaxKind.LetKeyword: + node.flags |= NodeFlags.Let; + break; + case SyntaxKind.ConstKeyword: + node.flags |= NodeFlags.Const; + break; + default: + Debug.fail(); } - - 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; - }); - } + nextToken(); + // The user may have written the following: + // + // for (let of X) { } + // + // In this case, we want to parse an empty declaration list, and then parse 'of' + // as a keyword. The reason this is not automatic is that 'of' is a valid identifier. + // So we need to look ahead to determine if 'of' should be treated as a keyword in + // this context. + // The checker will then give an error that there is an empty declaration list. + if (token() === SyntaxKind.OfKeyword && lookAhead(canFollowContextualOfKeyword)) { + node.declarations = createMissingList(); + } + else { + const savedDisallowIn = inDisallowInContext(); + setDisallowInContext(inForStatementInitializer); + node.declarations = parseDelimitedList(ParsingContext.VariableDeclarations, inForStatementInitializer ? parseVariableDeclaration : parseVariableDeclarationAllowExclamation); + setDisallowInContext(savedDisallowIn); + } + return finishNode(node); + } + function canFollowContextualOfKeyword(): boolean { + return nextTokenIsIdentifier() && nextToken() === SyntaxKind.CloseParenToken; + } + function parseVariableStatement(node: VariableStatement): VariableStatement { + node.kind = SyntaxKind.VariableStatement; + node.declarationList = parseVariableDeclarationList(/*inForStatementInitializer*/ false); + parseSemicolon(); + return finishNode(node); + } + function parseFunctionDeclaration(node: FunctionDeclaration): FunctionDeclaration { + node.kind = SyntaxKind.FunctionDeclaration; + parseExpected(SyntaxKind.FunctionKeyword); + node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); + node.name = hasModifier(node, ModifierFlags.Default) ? parseOptionalIdentifier() : parseIdentifier(); + const isGenerator = node.asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; + const isAsync = hasModifier(node, ModifierFlags.Async) ? SignatureFlags.Await : SignatureFlags.None; + fillSignature(SyntaxKind.ColonToken, isGenerator | isAsync, node); + node.body = parseFunctionBlockOrSemicolon(isGenerator | isAsync, Diagnostics.or_expected); + return finishNode(node); + } + function parseConstructorName() { + if (token() === SyntaxKind.ConstructorKeyword) { + return parseExpected(SyntaxKind.ConstructorKeyword); } - - function tryParseConstructorDeclaration(node: ConstructorDeclaration): ConstructorDeclaration | undefined { + if (token() === SyntaxKind.StringLiteral && lookAhead(nextToken) === SyntaxKind.OpenParenToken) { return tryParse(() => { - if (parseConstructorName()) { - node.kind = SyntaxKind.Constructor; - fillSignature(SyntaxKind.ColonToken, SignatureFlags.None, node); - node.body = parseFunctionBlockOrSemicolon(SignatureFlags.None, Diagnostics.or_expected); - return finishNode(node); - } + const literalNode = parseLiteralNode(); + return literalNode.text === "constructor" ? literalNode : undefined; }); } - - function parseMethodDeclaration(node: MethodDeclaration, asteriskToken: AsteriskToken, diagnosticMessage?: DiagnosticMessage): MethodDeclaration { - node.kind = SyntaxKind.MethodDeclaration; - node.asteriskToken = asteriskToken; - const isGenerator = asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; - const isAsync = hasModifier(node, ModifierFlags.Async) ? SignatureFlags.Await : SignatureFlags.None; - fillSignature(SyntaxKind.ColonToken, isGenerator | isAsync, node); - node.body = parseFunctionBlockOrSemicolon(isGenerator | isAsync, diagnosticMessage); - return finishNode(node); + } + function tryParseConstructorDeclaration(node: ConstructorDeclaration): ConstructorDeclaration | undefined { + return tryParse(() => { + if (parseConstructorName()) { + node.kind = SyntaxKind.Constructor; + fillSignature(SyntaxKind.ColonToken, SignatureFlags.None, node); + node.body = parseFunctionBlockOrSemicolon(SignatureFlags.None, Diagnostics.or_expected); + return finishNode(node); + } + }); + } + function parseMethodDeclaration(node: MethodDeclaration, asteriskToken: AsteriskToken, diagnosticMessage?: DiagnosticMessage): MethodDeclaration { + node.kind = SyntaxKind.MethodDeclaration; + node.asteriskToken = asteriskToken; + const isGenerator = asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; + const isAsync = hasModifier(node, ModifierFlags.Async) ? SignatureFlags.Await : SignatureFlags.None; + fillSignature(SyntaxKind.ColonToken, isGenerator | isAsync, node); + node.body = parseFunctionBlockOrSemicolon(isGenerator | isAsync, diagnosticMessage); + return finishNode(node); + } + function parsePropertyDeclaration(node: PropertyDeclaration): PropertyDeclaration { + node.kind = SyntaxKind.PropertyDeclaration; + if (!node.questionToken && token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { + node.exclamationToken = parseTokenNode>(); + } + node.type = parseTypeAnnotation(); + node.initializer = doOutsideOfContext(NodeFlags.YieldContext | NodeFlags.AwaitContext | NodeFlags.DisallowInContext, parseInitializer); + parseSemicolon(); + return finishNode(node); + } + function parsePropertyOrMethodDeclaration(node: PropertyDeclaration | MethodDeclaration): PropertyDeclaration | MethodDeclaration { + const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); + node.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. + node.questionToken = parseOptionalToken(SyntaxKind.QuestionToken); + if (asteriskToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { + return parseMethodDeclaration((node), asteriskToken, Diagnostics.or_expected); + } + return parsePropertyDeclaration((node)); + } + function parseAccessorDeclaration(node: AccessorDeclaration, kind: AccessorDeclaration["kind"]): AccessorDeclaration { + node.kind = kind; + node.name = parsePropertyName(); + fillSignature(SyntaxKind.ColonToken, SignatureFlags.None, node); + node.body = parseFunctionBlockOrSemicolon(SignatureFlags.None); + return finishNode(node); + } + function isClassMemberStart(): boolean { + let idToken: SyntaxKind | undefined; + if (token() === SyntaxKind.AtToken) { + return true; } - - function parsePropertyDeclaration(node: PropertyDeclaration): PropertyDeclaration { - node.kind = SyntaxKind.PropertyDeclaration; - if (!node.questionToken && token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { - node.exclamationToken = parseTokenNode>(); + // 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; } - node.type = parseTypeAnnotation(); - node.initializer = doOutsideOfContext(NodeFlags.YieldContext | NodeFlags.AwaitContext | NodeFlags.DisallowInContext, parseInitializer); - - parseSemicolon(); - return finishNode(node); + 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(); } - - function parsePropertyOrMethodDeclaration(node: PropertyDeclaration | MethodDeclaration): PropertyDeclaration | MethodDeclaration { - const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); - node.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. - node.questionToken = parseOptionalToken(SyntaxKind.QuestionToken); - if (asteriskToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { - return parseMethodDeclaration(node, asteriskToken, Diagnostics.or_expected); - } - return parsePropertyDeclaration(node); - } - - function parseAccessorDeclaration(node: AccessorDeclaration, kind: AccessorDeclaration["kind"]): AccessorDeclaration { - node.kind = kind; - node.name = parsePropertyName(); - fillSignature(SyntaxKind.ColonToken, SignatureFlags.None, node); - node.body = parseFunctionBlockOrSemicolon(SignatureFlags.None); - return finishNode(node); + // Index signatures and computed properties are class members; we can parse. + if (token() === SyntaxKind.OpenBracketToken) { + return true; } - - function isClassMemberStart(): boolean { - let idToken: SyntaxKind | undefined; - - if (token() === SyntaxKind.AtToken) { + // 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; } - - // 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)) { + // 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; - } - - 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; + 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(); } - - // 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 parseDecorators(): NodeArray | undefined { + let list: Decorator[] | undefined; + const listPos = getNodePos(); + while (true) { + const decoratorStart = getNodePos(); + if (!parseOptional(SyntaxKind.AtToken)) { + break; } - - return false; + const decorator = (createNode(SyntaxKind.Decorator, decoratorStart)); + decorator.expression = doInDecoratorContext(parseLeftHandSideExpressionOrHigher); + finishNode(decorator); + (list || (list = [])).push(decorator); } - - function parseDecorators(): NodeArray | undefined { - let list: Decorator[] | undefined; - const listPos = getNodePos(); - while (true) { - const decoratorStart = getNodePos(); - if (!parseOptional(SyntaxKind.AtToken)) { + return list && createNodeArray(list, listPos); + } + /* + * 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): NodeArray | undefined { + let list: Modifier[] | undefined; + const listPos = getNodePos(); + while (true) { + const modifierStart = scanner.getStartPos(); + const modifierKind = 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)) { break; } - const decorator = createNode(SyntaxKind.Decorator, decoratorStart); - decorator.expression = doInDecoratorContext(parseLeftHandSideExpressionOrHigher); - finishNode(decorator); - (list || (list = [])).push(decorator); - } - return list && createNodeArray(list, listPos); - } - - /* - * 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): NodeArray | undefined { - let list: Modifier[] | undefined; - const listPos = getNodePos(); - while (true) { - const modifierStart = scanner.getStartPos(); - const modifierKind = 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)) { - break; - } - } - else { - if (!parseAnyContextualModifier()) { - break; - } + } + else { + if (!parseAnyContextualModifier()) { + break; } - - const modifier = finishNode(createNode(modifierKind, modifierStart)); - (list || (list = [])).push(modifier); - } - return list && createNodeArray(list, listPos); - } - - function parseModifiersForArrowFunction(): NodeArray | undefined { - let modifiers: NodeArray | undefined; - if (token() === SyntaxKind.AsyncKeyword) { - const modifierStart = scanner.getStartPos(); - const modifierKind = token(); - nextToken(); - const modifier = finishNode(createNode(modifierKind, modifierStart)); - modifiers = createNodeArray([modifier], modifierStart); } - return modifiers; + const modifier = finishNode((createNode(modifierKind, modifierStart))); + (list || (list = [])).push(modifier); } - - function parseClassElement(): ClassElement { - if (token() === SyntaxKind.SemicolonToken) { - const result = createNode(SyntaxKind.SemicolonClassElement); - nextToken(); - return finishNode(result); - } - - const node = createNodeWithJSDoc(SyntaxKind.Unknown); - node.decorators = parseDecorators(); - node.modifiers = parseModifiers(/*permitInvalidConstAsModifier*/ true); - - if (parseContextualModifier(SyntaxKind.GetKeyword)) { - return parseAccessorDeclaration(node, SyntaxKind.GetAccessor); - } - - if (parseContextualModifier(SyntaxKind.SetKeyword)) { - return parseAccessorDeclaration(node, SyntaxKind.SetAccessor); - } - - if (token() === SyntaxKind.ConstructorKeyword || token() === SyntaxKind.StringLiteral) { - const constructorDeclaration = tryParseConstructorDeclaration(node); - if (constructorDeclaration) { - return constructorDeclaration; - } + return list && createNodeArray(list, listPos); + } + function parseModifiersForArrowFunction(): NodeArray | undefined { + let modifiers: NodeArray | undefined; + if (token() === SyntaxKind.AsyncKeyword) { + const modifierStart = scanner.getStartPos(); + const modifierKind = token(); + nextToken(); + const modifier = finishNode((createNode(modifierKind, modifierStart))); + modifiers = createNodeArray([modifier], modifierStart); + } + return modifiers; + } + function parseClassElement(): ClassElement { + if (token() === SyntaxKind.SemicolonToken) { + const result = (createNode(SyntaxKind.SemicolonClassElement)); + nextToken(); + return finishNode(result); + } + const node = (createNodeWithJSDoc(SyntaxKind.Unknown)); + node.decorators = parseDecorators(); + node.modifiers = parseModifiers(/*permitInvalidConstAsModifier*/ true); + if (parseContextualModifier(SyntaxKind.GetKeyword)) { + return parseAccessorDeclaration((node), SyntaxKind.GetAccessor); + } + if (parseContextualModifier(SyntaxKind.SetKeyword)) { + return parseAccessorDeclaration((node), SyntaxKind.SetAccessor); + } + if (token() === SyntaxKind.ConstructorKeyword || token() === SyntaxKind.StringLiteral) { + const constructorDeclaration = tryParseConstructorDeclaration((node)); + if (constructorDeclaration) { + return constructorDeclaration; } - - if (isIndexSignature()) { - return parseIndexSignatureDeclaration(node); - } - - // 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 = node.modifiers && some(node.modifiers, isDeclareModifier); - if (isAmbient) { - for (const m of node.modifiers!) { - m.flags |= NodeFlags.Ambient; - } - return doInsideOfContext(NodeFlags.Ambient, () => parsePropertyOrMethodDeclaration(node as PropertyDeclaration | MethodDeclaration)); - } - else { - return parsePropertyOrMethodDeclaration(node as PropertyDeclaration | MethodDeclaration); + } + if (isIndexSignature()) { + return parseIndexSignatureDeclaration((node)); + } + // 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 = node.modifiers && some(node.modifiers, isDeclareModifier); + if (isAmbient) { + for (const m of node.modifiers!) { + m.flags |= NodeFlags.Ambient; } - } - - if (node.decorators || node.modifiers) { - // treat this as a property declaration with a missing name. - node.name = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Declaration_expected); - return parsePropertyDeclaration(node); - } - - // 'isClassMemberStart' should have hinted not to attempt parsing. - return Debug.fail("Should not have attempted to parse class member declaration."); - } - - function parseClassExpression(): ClassExpression { - return parseClassDeclarationOrExpression(createNodeWithJSDoc(SyntaxKind.Unknown), SyntaxKind.ClassExpression); - } - - function parseClassDeclaration(node: ClassLikeDeclaration): ClassDeclaration { - return parseClassDeclarationOrExpression(node, SyntaxKind.ClassDeclaration); - } - - function parseClassDeclarationOrExpression(node: ClassLikeDeclaration, kind: ClassLikeDeclaration["kind"]): ClassLikeDeclaration { - node.kind = kind; - parseExpected(SyntaxKind.ClassKeyword); - node.name = parseNameOfClassDeclarationOrExpression(); - node.typeParameters = parseTypeParameters(); - node.heritageClauses = parseHeritageClauses(); - - if (parseExpected(SyntaxKind.OpenBraceToken)) { - // ClassTail[Yield,Await] : (Modified) See 14.5 - // ClassHeritage[?Yield,?Await]opt { ClassBody[?Yield,?Await]opt } - node.members = parseClassMembers(); - parseExpected(SyntaxKind.CloseBraceToken); + return doInsideOfContext(NodeFlags.Ambient, () => parsePropertyOrMethodDeclaration((node as PropertyDeclaration | MethodDeclaration))); } else { - node.members = createMissingList(); + return parsePropertyOrMethodDeclaration((node as PropertyDeclaration | MethodDeclaration)); } - - return finishNode(node); } - - 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 isIdentifier() && !isImplementsClause() - ? parseIdentifier() - : undefined; - } - - function isImplementsClause() { - return token() === SyntaxKind.ImplementsKeyword && lookAhead(nextTokenIsIdentifierOrKeyword); - } - - function parseHeritageClauses(): NodeArray | undefined { + if (node.decorators || node.modifiers) { + // treat this as a property declaration with a missing name. + node.name = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Declaration_expected); + return parsePropertyDeclaration((node)); + } + // 'isClassMemberStart' should have hinted not to attempt parsing. + return Debug.fail("Should not have attempted to parse class member declaration."); + } + function parseClassExpression(): ClassExpression { + return parseClassDeclarationOrExpression((createNodeWithJSDoc(SyntaxKind.Unknown)), SyntaxKind.ClassExpression); + } + function parseClassDeclaration(node: ClassLikeDeclaration): ClassDeclaration { + return parseClassDeclarationOrExpression(node, SyntaxKind.ClassDeclaration); + } + function parseClassDeclarationOrExpression(node: ClassLikeDeclaration, kind: ClassLikeDeclaration["kind"]): ClassLikeDeclaration { + node.kind = kind; + parseExpected(SyntaxKind.ClassKeyword); + node.name = parseNameOfClassDeclarationOrExpression(); + node.typeParameters = parseTypeParameters(); + node.heritageClauses = parseHeritageClauses(); + if (parseExpected(SyntaxKind.OpenBraceToken)) { // ClassTail[Yield,Await] : (Modified) See 14.5 // ClassHeritage[?Yield,?Await]opt { ClassBody[?Yield,?Await]opt } - - if (isHeritageClause()) { - return parseList(ParsingContext.HeritageClauses, parseHeritageClause); - } - - return undefined; + node.members = parseClassMembers(); + parseExpected(SyntaxKind.CloseBraceToken); } - - function parseHeritageClause(): HeritageClause { - const tok = token(); - Debug.assert(tok === SyntaxKind.ExtendsKeyword || tok === SyntaxKind.ImplementsKeyword); // isListElement() should ensure this. - const node = createNode(SyntaxKind.HeritageClause); - node.token = tok as SyntaxKind.ExtendsKeyword | SyntaxKind.ImplementsKeyword; - nextToken(); - node.types = parseDelimitedList(ParsingContext.HeritageClauseElement, parseExpressionWithTypeArguments); - return finishNode(node); + else { + node.members = createMissingList(); } - - function parseExpressionWithTypeArguments(): ExpressionWithTypeArguments { - const node = createNode(SyntaxKind.ExpressionWithTypeArguments); - node.expression = parseLeftHandSideExpressionOrHigher(); - node.typeArguments = tryParseTypeArguments(); - return finishNode(node); + return finishNode(node); + } + 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 isIdentifier() && !isImplementsClause() + ? parseIdentifier() + : 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); } - - function tryParseTypeArguments(): NodeArray | undefined { - return token() === SyntaxKind.LessThanToken ? - parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken) : undefined; + return undefined; + } + function parseHeritageClause(): HeritageClause { + const tok = token(); + Debug.assert(tok === SyntaxKind.ExtendsKeyword || tok === SyntaxKind.ImplementsKeyword); // isListElement() should ensure this. + const node = (createNode(SyntaxKind.HeritageClause)); + node.token = (tok as SyntaxKind.ExtendsKeyword | SyntaxKind.ImplementsKeyword); + nextToken(); + node.types = parseDelimitedList(ParsingContext.HeritageClauseElement, parseExpressionWithTypeArguments); + return finishNode(node); + } + function parseExpressionWithTypeArguments(): ExpressionWithTypeArguments { + const node = (createNode(SyntaxKind.ExpressionWithTypeArguments)); + node.expression = parseLeftHandSideExpressionOrHigher(); + node.typeArguments = tryParseTypeArguments(); + return finishNode(node); + } + 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 parseInterfaceDeclaration(node: InterfaceDeclaration): InterfaceDeclaration { + node.kind = SyntaxKind.InterfaceDeclaration; + parseExpected(SyntaxKind.InterfaceKeyword); + node.name = parseIdentifier(); + node.typeParameters = parseTypeParameters(); + node.heritageClauses = parseHeritageClauses(); + node.members = parseObjectTypeMembers(); + return finishNode(node); + } + function parseTypeAliasDeclaration(node: TypeAliasDeclaration): TypeAliasDeclaration { + node.kind = SyntaxKind.TypeAliasDeclaration; + parseExpected(SyntaxKind.TypeKeyword); + node.name = parseIdentifier(); + node.typeParameters = parseTypeParameters(); + parseExpected(SyntaxKind.EqualsToken); + node.type = parseType(); + parseSemicolon(); + return finishNode(node); + } + // In an ambient declaration, the grammar only allows integer literals as initializers. + // In a non-ambient declaration, the grammar allows uninitialized members only in a + // ConstantEnumMemberSection, which starts at the beginning of an enum declaration + // or any time an integer literal initializer is encountered. + function parseEnumMember(): EnumMember { + const node = (createNodeWithJSDoc(SyntaxKind.EnumMember)); + node.name = parsePropertyName(); + node.initializer = allowInAnd(parseInitializer); + return finishNode(node); + } + function parseEnumDeclaration(node: EnumDeclaration): EnumDeclaration { + node.kind = SyntaxKind.EnumDeclaration; + parseExpected(SyntaxKind.EnumKeyword); + node.name = parseIdentifier(); + if (parseExpected(SyntaxKind.OpenBraceToken)) { + node.members = doOutsideOfYieldAndAwaitContext(() => parseDelimitedList(ParsingContext.EnumMembers, parseEnumMember)); + parseExpected(SyntaxKind.CloseBraceToken); } - - function isHeritageClause(): boolean { - return token() === SyntaxKind.ExtendsKeyword || token() === SyntaxKind.ImplementsKeyword; + else { + node.members = createMissingList(); } - - function parseClassMembers(): NodeArray { - return parseList(ParsingContext.ClassMembers, parseClassElement); + return finishNode(node); + } + function parseModuleBlock(): ModuleBlock { + const node = (createNode(SyntaxKind.ModuleBlock)); + if (parseExpected(SyntaxKind.OpenBraceToken)) { + node.statements = parseList(ParsingContext.BlockStatements, parseStatement); + parseExpected(SyntaxKind.CloseBraceToken); } - - function parseInterfaceDeclaration(node: InterfaceDeclaration): InterfaceDeclaration { - node.kind = SyntaxKind.InterfaceDeclaration; - parseExpected(SyntaxKind.InterfaceKeyword); - node.name = parseIdentifier(); - node.typeParameters = parseTypeParameters(); - node.heritageClauses = parseHeritageClauses(); - node.members = parseObjectTypeMembers(); - return finishNode(node); + else { + node.statements = createMissingList(); } - - function parseTypeAliasDeclaration(node: TypeAliasDeclaration): TypeAliasDeclaration { - node.kind = SyntaxKind.TypeAliasDeclaration; - parseExpected(SyntaxKind.TypeKeyword); + return finishNode(node); + } + function parseModuleOrNamespaceDeclaration(node: ModuleDeclaration, flags: NodeFlags): ModuleDeclaration { + node.kind = SyntaxKind.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; + node.flags |= flags; + node.name = parseIdentifier(); + node.body = parseOptional(SyntaxKind.DotToken) + ? parseModuleOrNamespaceDeclaration((createNode(SyntaxKind.Unknown)), NodeFlags.NestedNamespace | namespaceFlag) + : parseModuleBlock(); + return finishNode(node); + } + function parseAmbientExternalModuleDeclaration(node: ModuleDeclaration): ModuleDeclaration { + node.kind = SyntaxKind.ModuleDeclaration; + if (token() === SyntaxKind.GlobalKeyword) { + // parse 'global' as name of global scope augmentation node.name = parseIdentifier(); - node.typeParameters = parseTypeParameters(); - parseExpected(SyntaxKind.EqualsToken); - node.type = parseType(); - parseSemicolon(); - return finishNode(node); + node.flags |= NodeFlags.GlobalAugmentation; } - - // 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 node = createNodeWithJSDoc(SyntaxKind.EnumMember); - node.name = parsePropertyName(); - node.initializer = allowInAnd(parseInitializer); - return finishNode(node); + else { + node.name = (parseLiteralNode()); + node.name.text = internIdentifier(node.name.text); } - - function parseEnumDeclaration(node: EnumDeclaration): EnumDeclaration { - node.kind = SyntaxKind.EnumDeclaration; - parseExpected(SyntaxKind.EnumKeyword); - node.name = parseIdentifier(); - if (parseExpected(SyntaxKind.OpenBraceToken)) { - node.members = doOutsideOfYieldAndAwaitContext(() => parseDelimitedList(ParsingContext.EnumMembers, parseEnumMember)); - parseExpected(SyntaxKind.CloseBraceToken); - } - else { - node.members = createMissingList(); - } - return finishNode(node); + if (token() === SyntaxKind.OpenBraceToken) { + node.body = parseModuleBlock(); } - - function parseModuleBlock(): ModuleBlock { - const node = createNode(SyntaxKind.ModuleBlock); - if (parseExpected(SyntaxKind.OpenBraceToken)) { - node.statements = parseList(ParsingContext.BlockStatements, parseStatement); - parseExpected(SyntaxKind.CloseBraceToken); - } - else { - node.statements = createMissingList(); - } - return finishNode(node); + else { + parseSemicolon(); } - - function parseModuleOrNamespaceDeclaration(node: ModuleDeclaration, flags: NodeFlags): ModuleDeclaration { - node.kind = SyntaxKind.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; - node.flags |= flags; - node.name = parseIdentifier(); - node.body = parseOptional(SyntaxKind.DotToken) - ? parseModuleOrNamespaceDeclaration(createNode(SyntaxKind.Unknown), NodeFlags.NestedNamespace | namespaceFlag) - : parseModuleBlock(); - return finishNode(node); + return finishNode(node); + } + function parseModuleDeclaration(node: ModuleDeclaration): ModuleDeclaration { + let flags: NodeFlags = 0; + if (token() === SyntaxKind.GlobalKeyword) { + // global augmentation + return parseAmbientExternalModuleDeclaration(node); } - - function parseAmbientExternalModuleDeclaration(node: ModuleDeclaration): ModuleDeclaration { - node.kind = SyntaxKind.ModuleDeclaration; - if (token() === SyntaxKind.GlobalKeyword) { - // parse 'global' as name of global scope augmentation - node.name = parseIdentifier(); - node.flags |= NodeFlags.GlobalAugmentation; - } - else { - node.name = parseLiteralNode(); - node.name.text = internIdentifier(node.name.text); - } - if (token() === SyntaxKind.OpenBraceToken) { - node.body = parseModuleBlock(); - } - else { - parseSemicolon(); - } - return finishNode(node); + else if (parseOptional(SyntaxKind.NamespaceKeyword)) { + flags |= NodeFlags.Namespace; } - - function parseModuleDeclaration(node: ModuleDeclaration): ModuleDeclaration { - let flags: NodeFlags = 0; - if (token() === SyntaxKind.GlobalKeyword) { - // global augmentation + else { + parseExpected(SyntaxKind.ModuleKeyword); + if (token() === SyntaxKind.StringLiteral) { return parseAmbientExternalModuleDeclaration(node); } - else if (parseOptional(SyntaxKind.NamespaceKeyword)) { - flags |= NodeFlags.Namespace; - } - else { - parseExpected(SyntaxKind.ModuleKeyword); - if (token() === SyntaxKind.StringLiteral) { - return parseAmbientExternalModuleDeclaration(node); - } - } - return parseModuleOrNamespaceDeclaration(node, flags); - } - - function isExternalModuleReference() { - return token() === SyntaxKind.RequireKeyword && - lookAhead(nextTokenIsOpenParen); } - - function nextTokenIsOpenParen() { - return nextToken() === SyntaxKind.OpenParenToken; + return parseModuleOrNamespaceDeclaration(node, flags); + } + function isExternalModuleReference() { + return token() === SyntaxKind.RequireKeyword && + lookAhead(nextTokenIsOpenParen); + } + function nextTokenIsOpenParen() { + return nextToken() === SyntaxKind.OpenParenToken; + } + function nextTokenIsSlash() { + return nextToken() === SyntaxKind.SlashToken; + } + function parseNamespaceExportDeclaration(node: NamespaceExportDeclaration): NamespaceExportDeclaration { + node.kind = SyntaxKind.NamespaceExportDeclaration; + parseExpected(SyntaxKind.AsKeyword); + parseExpected(SyntaxKind.NamespaceKeyword); + node.name = parseIdentifier(); + parseSemicolon(); + return finishNode(node); + } + function parseImportDeclarationOrImportEqualsDeclaration(node: ImportEqualsDeclaration | ImportDeclaration): ImportEqualsDeclaration | ImportDeclaration { + parseExpected(SyntaxKind.ImportKeyword); + const afterImportPos = scanner.getStartPos(); + let identifier: Identifier | undefined; + if (isIdentifier()) { + identifier = parseIdentifier(); + if (token() !== SyntaxKind.CommaToken && token() !== SyntaxKind.FromKeyword) { + return parseImportEqualsDeclaration((node), identifier); + } + } + // Import statement + node.kind = SyntaxKind.ImportDeclaration; + // ImportDeclaration: + // import ImportClause from ModuleSpecifier ; + // import ModuleSpecifier; + if (identifier || // import id + token() === SyntaxKind.AsteriskToken || // import * + token() === SyntaxKind.OpenBraceToken) { // import { + (node).importClause = parseImportClause(identifier, afterImportPos); + parseExpected(SyntaxKind.FromKeyword); + } + (node).moduleSpecifier = parseModuleSpecifier(); + parseSemicolon(); + return finishNode(node); + } + function parseImportEqualsDeclaration(node: ImportEqualsDeclaration, identifier: Identifier): ImportEqualsDeclaration { + node.kind = SyntaxKind.ImportEqualsDeclaration; + node.name = identifier; + parseExpected(SyntaxKind.EqualsToken); + node.moduleReference = parseModuleReference(); + parseSemicolon(); + return finishNode(node); + } + function parseImportClause(identifier: Identifier | undefined, fullStart: number) { + // ImportClause: + // ImportedDefaultBinding + // NameSpaceImport + // NamedImports + // ImportedDefaultBinding, NameSpaceImport + // ImportedDefaultBinding, NamedImports + const importClause = (createNode(SyntaxKind.ImportClause, fullStart)); + if (identifier) { + // ImportedDefaultBinding: + // ImportedBinding + importClause.name = identifier; + } + // If there was no default import or if there is comma token after default import + // parse namespace or named imports + if (!importClause.name || + parseOptional(SyntaxKind.CommaToken)) { + importClause.namedBindings = token() === SyntaxKind.AsteriskToken ? parseNamespaceImport() : parseNamedImportsOrExports(SyntaxKind.NamedImports); + } + return finishNode(importClause); + } + function parseModuleReference() { + return isExternalModuleReference() + ? parseExternalModuleReference() + : parseEntityName(/*allowReservedWords*/ false); + } + function parseExternalModuleReference() { + const node = (createNode(SyntaxKind.ExternalModuleReference)); + parseExpected(SyntaxKind.RequireKeyword); + parseExpected(SyntaxKind.OpenParenToken); + node.expression = parseModuleSpecifier(); + parseExpected(SyntaxKind.CloseParenToken); + return finishNode(node); + } + function parseModuleSpecifier(): Expression { + if (token() === SyntaxKind.StringLiteral) { + const result = parseLiteralNode(); + result.text = internIdentifier(result.text); + return result; } - - function nextTokenIsSlash() { - return nextToken() === SyntaxKind.SlashToken; + 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 parseNamespaceExportDeclaration(node: NamespaceExportDeclaration): NamespaceExportDeclaration { - node.kind = SyntaxKind.NamespaceExportDeclaration; + } + function parseNamespaceImport(): NamespaceImport { + // NameSpaceImport: + // * as ImportedBinding + const namespaceImport = (createNode(SyntaxKind.NamespaceImport)); + parseExpected(SyntaxKind.AsteriskToken); + parseExpected(SyntaxKind.AsKeyword); + namespaceImport.name = parseIdentifier(); + return finishNode(namespaceImport); + } + function parseNamedImportsOrExports(kind: SyntaxKind.NamedImports): NamedImports; + function parseNamedImportsOrExports(kind: SyntaxKind.NamedExports): NamedExports; + function parseNamedImportsOrExports(kind: SyntaxKind): NamedImportsOrExports { + const node = (createNode(kind)); + // NamedImports: + // { } + // { ImportsList } + // { ImportsList, } + // ImportsList: + // ImportSpecifier + // ImportsList, ImportSpecifier + node.elements = ( | NodeArray>parseBracketedList(ParsingContext.ImportOrExportSpecifiers, kind === SyntaxKind.NamedImports ? parseImportSpecifier : parseExportSpecifier, SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken)); + return finishNode(node); + } + function parseExportSpecifier() { + return parseImportOrExportSpecifier(SyntaxKind.ExportSpecifier); + } + function parseImportSpecifier() { + return parseImportOrExportSpecifier(SyntaxKind.ImportSpecifier); + } + function parseImportOrExportSpecifier(kind: SyntaxKind): ImportOrExportSpecifier { + const node = (createNode(kind)); + // ImportSpecifier: + // BindingIdentifier + // IdentifierName as BindingIdentifier + // ExportSpecifier: + // IdentifierName + // IdentifierName as IdentifierName + let checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier(); + let checkIdentifierStart = scanner.getTokenPos(); + let checkIdentifierEnd = scanner.getTextPos(); + const identifierName = parseIdentifierName(); + if (token() === SyntaxKind.AsKeyword) { + node.propertyName = identifierName; parseExpected(SyntaxKind.AsKeyword); - parseExpected(SyntaxKind.NamespaceKeyword); - node.name = parseIdentifier(); - parseSemicolon(); - return finishNode(node); - } - - function parseImportDeclarationOrImportEqualsDeclaration(node: ImportEqualsDeclaration | ImportDeclaration): ImportEqualsDeclaration | ImportDeclaration { - parseExpected(SyntaxKind.ImportKeyword); - const afterImportPos = scanner.getStartPos(); - - let identifier: Identifier | undefined; - if (isIdentifier()) { - identifier = parseIdentifier(); - if (token() !== SyntaxKind.CommaToken && token() !== SyntaxKind.FromKeyword) { - return parseImportEqualsDeclaration(node, identifier); - } - } - - // Import statement - node.kind = SyntaxKind.ImportDeclaration; - // ImportDeclaration: - // import ImportClause from ModuleSpecifier ; - // import ModuleSpecifier; - if (identifier || // import id - token() === SyntaxKind.AsteriskToken || // import * - token() === SyntaxKind.OpenBraceToken) { // import { - (node).importClause = parseImportClause(identifier, afterImportPos); - parseExpected(SyntaxKind.FromKeyword); - } - - (node).moduleSpecifier = parseModuleSpecifier(); - parseSemicolon(); - return finishNode(node); - } - - function parseImportEqualsDeclaration(node: ImportEqualsDeclaration, identifier: Identifier): ImportEqualsDeclaration { - node.kind = SyntaxKind.ImportEqualsDeclaration; - node.name = identifier; - parseExpected(SyntaxKind.EqualsToken); - node.moduleReference = parseModuleReference(); - parseSemicolon(); - return finishNode(node); + checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier(); + checkIdentifierStart = scanner.getTokenPos(); + checkIdentifierEnd = scanner.getTextPos(); + node.name = parseIdentifierName(); } - - function parseImportClause(identifier: Identifier | undefined, fullStart: number) { - // ImportClause: - // ImportedDefaultBinding - // NameSpaceImport - // NamedImports - // ImportedDefaultBinding, NameSpaceImport - // ImportedDefaultBinding, NamedImports - - const importClause = createNode(SyntaxKind.ImportClause, fullStart); - if (identifier) { - // ImportedDefaultBinding: - // ImportedBinding - importClause.name = identifier; - } - - // If there was no default import or if there is comma token after default import - // parse namespace or named imports - if (!importClause.name || - parseOptional(SyntaxKind.CommaToken)) { - importClause.namedBindings = token() === SyntaxKind.AsteriskToken ? parseNamespaceImport() : parseNamedImportsOrExports(SyntaxKind.NamedImports); - } - - return finishNode(importClause); - } - - function parseModuleReference() { - return isExternalModuleReference() - ? parseExternalModuleReference() - : parseEntityName(/*allowReservedWords*/ false); - } - - function parseExternalModuleReference() { - const node = createNode(SyntaxKind.ExternalModuleReference); - parseExpected(SyntaxKind.RequireKeyword); - parseExpected(SyntaxKind.OpenParenToken); - node.expression = parseModuleSpecifier(); - parseExpected(SyntaxKind.CloseParenToken); - return finishNode(node); + else { + node.name = identifierName; } - - 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 namespaceImport = createNode(SyntaxKind.NamespaceImport); - parseExpected(SyntaxKind.AsteriskToken); - parseExpected(SyntaxKind.AsKeyword); - namespaceImport.name = parseIdentifier(); - return finishNode(namespaceImport); - } - - function parseNamedImportsOrExports(kind: SyntaxKind.NamedImports): NamedImports; - function parseNamedImportsOrExports(kind: SyntaxKind.NamedExports): NamedExports; - function parseNamedImportsOrExports(kind: SyntaxKind): NamedImportsOrExports { - const node = createNode(kind); - - // NamedImports: - // { } - // { ImportsList } - // { ImportsList, } - - // ImportsList: - // ImportSpecifier - // ImportsList, ImportSpecifier - node.elements = | NodeArray>parseBracketedList(ParsingContext.ImportOrExportSpecifiers, - kind === SyntaxKind.NamedImports ? parseImportSpecifier : parseExportSpecifier, - SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken); - return finishNode(node); + if (kind === SyntaxKind.ImportSpecifier && checkIdentifierIsKeyword) { + parseErrorAt(checkIdentifierStart, checkIdentifierEnd, Diagnostics.Identifier_expected); } - - function parseExportSpecifier() { - return parseImportOrExportSpecifier(SyntaxKind.ExportSpecifier); - } - - function parseImportSpecifier() { - return parseImportOrExportSpecifier(SyntaxKind.ImportSpecifier); - } - - function parseImportOrExportSpecifier(kind: SyntaxKind): ImportOrExportSpecifier { - const node = createNode(kind); - // ImportSpecifier: - // BindingIdentifier - // IdentifierName as BindingIdentifier - // ExportSpecifier: - // IdentifierName - // IdentifierName as IdentifierName - let checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier(); - let checkIdentifierStart = scanner.getTokenPos(); - let checkIdentifierEnd = scanner.getTextPos(); - const identifierName = parseIdentifierName(); - if (token() === SyntaxKind.AsKeyword) { - node.propertyName = identifierName; - parseExpected(SyntaxKind.AsKeyword); - checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier(); - checkIdentifierStart = scanner.getTokenPos(); - checkIdentifierEnd = scanner.getTextPos(); - node.name = parseIdentifierName(); - } - else { - node.name = identifierName; - } - if (kind === SyntaxKind.ImportSpecifier && checkIdentifierIsKeyword) { - parseErrorAt(checkIdentifierStart, checkIdentifierEnd, Diagnostics.Identifier_expected); - } - return finishNode(node); + return finishNode(node); + } + function parseExportDeclaration(node: ExportDeclaration): ExportDeclaration { + node.kind = SyntaxKind.ExportDeclaration; + if (parseOptional(SyntaxKind.AsteriskToken)) { + parseExpected(SyntaxKind.FromKeyword); + node.moduleSpecifier = parseModuleSpecifier(); } - - function parseExportDeclaration(node: ExportDeclaration): ExportDeclaration { - node.kind = SyntaxKind.ExportDeclaration; - if (parseOptional(SyntaxKind.AsteriskToken)) { + else { + node.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); node.moduleSpecifier = parseModuleSpecifier(); } - else { - node.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); - node.moduleSpecifier = parseModuleSpecifier(); - } - } - parseSemicolon(); - return finishNode(node); } - - function parseExportAssignment(node: ExportAssignment): ExportAssignment { - node.kind = SyntaxKind.ExportAssignment; - if (parseOptional(SyntaxKind.EqualsToken)) { - node.isExportEquals = true; - } - else { - parseExpected(SyntaxKind.DefaultKeyword); + parseSemicolon(); + return finishNode(node); + } + function parseExportAssignment(node: ExportAssignment): ExportAssignment { + node.kind = SyntaxKind.ExportAssignment; + if (parseOptional(SyntaxKind.EqualsToken)) { + node.isExportEquals = true; + } + else { + parseExpected(SyntaxKind.DefaultKeyword); + } + node.expression = parseAssignmentExpressionOrHigher(); + parseSemicolon(); + return finishNode(node); + } + function setExternalModuleIndicator(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. + sourceFile.externalModuleIndicator = + forEach(sourceFile.statements, isAnExternalModuleIndicatorNode) || + getImportMetaIfNecessary(sourceFile); + } + function isAnExternalModuleIndicatorNode(node: Node) { + return hasModifier(node, ModifierFlags.Export) + || node.kind === SyntaxKind.ImportEqualsDeclaration && (node).moduleReference.kind === SyntaxKind.ExternalModuleReference + || node.kind === SyntaxKind.ImportDeclaration + || node.kind === SyntaxKind.ExportAssignment + || node.kind === SyntaxKind.ExportDeclaration ? node : undefined; + } + function getImportMetaIfNecessary(sourceFile: SourceFile) { + return sourceFile.flags & NodeFlags.PossiblyContainsImportMeta ? + walkTreeForExternalModuleIndicators(sourceFile) : + undefined; + } + function walkTreeForExternalModuleIndicators(node: Node): Node | undefined { + return isImportMeta(node) ? node : forEachChild(node, walkTreeForExternalModuleIndicators); + } + function isImportMeta(node: Node): boolean { + return isMetaProperty(node) && node.keywordToken === SyntaxKind.ImportKeyword && node.name.escapedText === "meta"; + } + const enum ParsingContext { + SourceElements, + BlockStatements, + SwitchClauses, + SwitchClauseStatements, + TypeMembers, + ClassMembers, + EnumMembers, + HeritageClauseElement, + VariableDeclarations, + ObjectBindingElements, + ArrayBindingElements, + ArgumentExpressions, + ObjectLiteralMembers, + JsxAttributes, + JsxChildren, + ArrayLiteralMembers, + Parameters, + JSDocParameters, + RestProperties, + TypeParameters, + TypeArguments, + TupleElementTypes, + HeritageClauses, + ImportOrExportSpecifiers, + 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(content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS); + sourceFile = createSourceFile("file.js", ScriptTarget.Latest, ScriptKind.JS, /*isDeclarationFile*/ false); + scanner.setText(content, start, length); + currentToken = scanner.scan(); + const jsDocTypeExpression = parseJSDocTypeExpression(); + const diagnostics = parseDiagnostics; + clearState(); + return jsDocTypeExpression ? { jsDocTypeExpression, diagnostics } : undefined; + } + // Parses out a JSDoc type expression. + export function parseJSDocTypeExpression(mayOmitBraces?: boolean): JSDocTypeExpression { + const result = (createNode(SyntaxKind.JSDocTypeExpression)); + const hasBrace = (mayOmitBraces ? parseOptional : parseExpected)(SyntaxKind.OpenBraceToken); + result.type = doInsideOfContext(NodeFlags.JSDoc, parseJSDocType); + if (!mayOmitBraces || hasBrace) { + parseExpectedJSDoc(SyntaxKind.CloseBraceToken); } - node.expression = parseAssignmentExpressionOrHigher(); - parseSemicolon(); - return finishNode(node); + fixupParentReferences(result); + return finishNode(result); } - - function setExternalModuleIndicator(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. - sourceFile.externalModuleIndicator = - forEach(sourceFile.statements, isAnExternalModuleIndicatorNode) || - getImportMetaIfNecessary(sourceFile); - } - - function isAnExternalModuleIndicatorNode(node: Node) { - return hasModifier(node, ModifierFlags.Export) - || node.kind === SyntaxKind.ImportEqualsDeclaration && (node).moduleReference.kind === SyntaxKind.ExternalModuleReference - || node.kind === SyntaxKind.ImportDeclaration - || node.kind === SyntaxKind.ExportAssignment - || node.kind === SyntaxKind.ExportDeclaration ? node : undefined; - } - - function getImportMetaIfNecessary(sourceFile: SourceFile) { - return sourceFile.flags & NodeFlags.PossiblyContainsImportMeta ? - walkTreeForExternalModuleIndicators(sourceFile) : - undefined; - } - - function walkTreeForExternalModuleIndicators(node: Node): Node | undefined { - return isImportMeta(node) ? node : forEachChild(node, walkTreeForExternalModuleIndicators); - } - - function isImportMeta(node: Node): boolean { - return isMetaProperty(node) && node.keywordToken === SyntaxKind.ImportKeyword && node.name.escapedText === "meta"; - } - - 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 - 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 - 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(content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS); - sourceFile = createSourceFile("file.js", ScriptTarget.Latest, ScriptKind.JS, /*isDeclarationFile*/ false); - scanner.setText(content, start, length); - currentToken = scanner.scan(); - const jsDocTypeExpression = parseJSDocTypeExpression(); - const diagnostics = parseDiagnostics; - clearState(); - - return jsDocTypeExpression ? { jsDocTypeExpression, diagnostics } : undefined; - } - - // Parses out a JSDoc type expression. - export function parseJSDocTypeExpression(mayOmitBraces?: boolean): JSDocTypeExpression { - const result = createNode(SyntaxKind.JSDocTypeExpression); - - const hasBrace = (mayOmitBraces ? parseOptional : parseExpected)(SyntaxKind.OpenBraceToken); - result.type = doInsideOfContext(NodeFlags.JSDoc, parseJSDocType); - if (!mayOmitBraces || hasBrace) { - parseExpectedJSDoc(SyntaxKind.CloseBraceToken); - } - - fixupParentReferences(result); - return finishNode(result); + export function parseIsolatedJSDocComment(content: string, start: number | undefined, length: number | undefined): { + jsDoc: JSDoc; + diagnostics: Diagnostic[]; + } | undefined { + initializeState(content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS); + sourceFile = ({ languageVariant: LanguageVariant.Standard, text: content }); + const jsDoc = doInsideOfContext(NodeFlags.JSDoc, () => parseJSDocCommentWorker(start, length)); + const diagnostics = parseDiagnostics; + 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)); + if (comment) { + comment.parent = parent; + } + if (contextFlags & NodeFlags.JavaScriptFile) { + if (!sourceFile.jsDocDiagnostics) { + sourceFile.jsDocDiagnostics = []; + } + sourceFile.jsDocDiagnostics.push(...parseDiagnostics); + } + currentToken = saveToken; + parseDiagnostics.length = saveParseDiagnosticsLength; + parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode; + return comment; + } + const enum JSDocState { + BeginningOfLine, + SawAsterisk, + SavingComments, + SavingBackticks + } + 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; } - - export function parseIsolatedJSDocComment(content: string, start: number | undefined, length: number | undefined): { jsDoc: JSDoc, diagnostics: Diagnostic[] } | undefined { - initializeState(content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS); - sourceFile = { languageVariant: LanguageVariant.Standard, text: content }; - const jsDoc = doInsideOfContext(NodeFlags.JSDoc, () => parseJSDocCommentWorker(start, length)); - const diagnostics = parseDiagnostics; - 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)); - if (comment) { - comment.parent = parent; - } - - if (contextFlags & NodeFlags.JavaScriptFile) { - if (!sourceFile.jsDocDiagnostics) { - sourceFile.jsDocDiagnostics = []; + let tags: JSDocTag[]; + let tagsPos: number; + let tagsEnd: number; + const comments: string[] = []; + // + 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 '/** ' + let indent = start - Math.max(content.lastIndexOf("\n", start), 0) + 4; + function pushComment(text: string) { + if (!margin) { + margin = indent; } - sourceFile.jsDocDiagnostics.push(...parseDiagnostics); + comments.push(text); + indent += text.length; } - 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; + nextTokenJSDoc(); + while (parseOptionalJsdoc(SyntaxKind.WhitespaceTrivia)) + ; + if (parseOptionalJsdoc(SyntaxKind.NewLineTrivia)) { + state = JSDocState.BeginningOfLine; + indent = 0; } - - let tags: JSDocTag[]; - let tagsPos: number; - let tagsEnd: number; - const comments: string[] = []; - - // + 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 '/** ' - let indent = start - Math.max(content.lastIndexOf("\n", start), 0) + 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); - 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()); + loop: while (true) { + switch (token()) { + case SyntaxKind.AtToken: + if (state === JSDocState.BeginningOfLine || state === JSDocState.SawAsterisk) { + removeTrailingWhitespace(comments); + 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; - 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 - 1)); - } - indent += whitespace.length; - break; - case SyntaxKind.EndOfFileToken: - break loop; - 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; + margin = undefined; + } + else { pushComment(scanner.getTokenText()); - break; - } - nextTokenJSDoc(); - } - removeLeadingNewlines(comments); - removeTrailingWhitespace(comments); - return createJSDocComment(); - }); - - 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(); + } + 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 - 1)); + } + indent += whitespace.length; + break; + case SyntaxKind.EndOfFileToken: + break loop; + 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(); } - - function createJSDocComment(): JSDoc { - const result = createNode(SyntaxKind.JSDocComment, start); - result.tags = tags && createNodeArray(tags, tagsPos, tagsEnd); - result.comment = comments.length ? comments.join("") : undefined; - return finishNode(result, end); + removeLeadingNewlines(comments); + removeTrailingWhitespace(comments); + return createJSDocComment(); + }); + function removeLeadingNewlines(comments: string[]) { + while (comments.length && (comments[0] === "\n" || comments[0] === "\r")) { + comments.shift(); } - - 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 removeTrailingWhitespace(comments: string[]) { + while (comments.length && comments[comments.length - 1].trim() === "") { + comments.pop(); } - - 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 - } + } + function createJSDocComment(): JSDoc { + const result = (createNode(SyntaxKind.JSDocComment, start)); + result.tags = tags && createNodeArray(tags, tagsPos, tagsEnd); + result.comment = comments.length ? comments.join("") : undefined; + return finishNode(result, end); + } + 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; } - while (token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { - nextTokenJSDoc(); + if (!(token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia)) { + return false; } } - - 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(); + } + 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 } - return seenLineBreak ? indentText : ""; } - - function parseTag(margin: number) { - Debug.assert(token() === SyntaxKind.AtToken); - const start = scanner.getTokenPos(); + while (token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { nextTokenJSDoc(); - - const tagName = parseJSDocIdentifierName(/*message*/ undefined); - const indentText = skipWhitespaceOrAsterisk(); - - let tag: JSDocTag | undefined; - switch (tagName.escapedText) { - case "author": - tag = parseAuthorTag(start, tagName, margin); - break; - case "augments": - case "extends": - tag = parseAugmentsTag(start, tagName); - break; - case "class": - case "constructor": - tag = parseClassTag(start, tagName); - break; - case "this": - tag = parseThisTag(start, tagName); - break; - case "enum": - tag = parseEnumTag(start, tagName); - break; - case "arg": - case "argument": - case "param": - return parseParameterOrPropertyTag(start, tagName, PropertyLikeParse.Parameter, margin); - case "return": - case "returns": - tag = parseReturnTag(start, tagName); - break; - case "template": - tag = parseTemplateTag(start, tagName); - break; - case "type": - tag = parseTypeTag(start, tagName); - break; - case "typedef": - tag = parseTypedefTag(start, tagName, margin); - break; - case "callback": - tag = parseCallbackTag(start, tagName, margin); - break; - default: - tag = parseUnknownTag(start, tagName); - break; + } + } + 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 } - - if (!tag.comment) { - // some tags, like typedef and callback, have already parsed their comments earlier - if (!indentText) { - margin += tag.end - tag.pos; - } - tag.comment = parseTagComments(margin, indentText.slice(margin)); + } + 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 = ""; } - return tag; + else if (token() === SyntaxKind.AsteriskToken) { + precedingLineBreak = false; + } + nextTokenJSDoc(); } - - function parseTagComments(indent: number, initialMargin?: string): string | undefined { - const comments: string[] = []; - let state = JSDocState.BeginningOfLine; - let margin: number | undefined; - function pushComment(text: string) { - if (!margin) { - margin = indent; - } - comments.push(text); - indent += text.length; + 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); + break; + case "augments": + case "extends": + tag = parseAugmentsTag(start, tagName); + break; + case "class": + case "constructor": + tag = parseClassTag(start, tagName); + break; + case "this": + tag = parseThisTag(start, tagName); + break; + case "enum": + tag = parseEnumTag(start, tagName); + break; + case "arg": + case "argument": + case "param": + return parseParameterOrPropertyTag(start, tagName, PropertyLikeParse.Parameter, margin); + case "return": + case "returns": + tag = parseReturnTag(start, tagName); + break; + case "template": + tag = parseTemplateTag(start, tagName); + break; + case "type": + tag = parseTypeTag(start, tagName); + break; + case "typedef": + tag = parseTypedefTag(start, tagName, margin); + break; + case "callback": + tag = parseCallbackTag(start, tagName, margin); + break; + default: + tag = parseUnknownTag(start, tagName); + break; + } + if (!tag.comment) { + // some tags, like typedef and callback, have already parsed their comments earlier + if (!indentText) { + margin += tag.end - tag.pos; } - if (initialMargin) { - // jump straight to saving comments if there is some initial indentation - pushComment(initialMargin); - state = JSDocState.SavingComments; + tag.comment = parseTagComments(margin, indentText.slice(margin)); + } + return tag; + } + function parseTagComments(indent: number, initialMargin?: string): string | undefined { + const comments: string[] = []; + let state = JSDocState.BeginningOfLine; + let margin: number | undefined; + function pushComment(text: string) { + if (!margin) { + margin = indent; } - let tok = token() as JSDocSyntaxKind; - loop: while (true) { - switch (tok) { - case SyntaxKind.NewLineTrivia: - if (state >= JSDocState.SawAsterisk) { - 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) { - 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; - } + comments.push(text); + indent += text.length; + } + if (initialMargin) { + // jump straight to saving comments if there is some initial indentation + pushComment(initialMargin); + state = JSDocState.SavingComments; + } + let tok = (token() as JSDocSyntaxKind); + loop: while (true) { + switch (tok) { + case SyntaxKind.NewLineTrivia: + if (state >= JSDocState.SawAsterisk) { + 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) { + comments.push(scanner.getTokenText()); break; - case SyntaxKind.OpenBraceToken: - state = JSDocState.SavingComments; - if (lookAhead(() => nextTokenJSDoc() === SyntaxKind.AtToken && tokenIsIdentifierOrKeyword(nextTokenJSDoc()) && scanner.getTokenText() === "link")) { - pushComment(scanner.getTokenText()); - nextTokenJSDoc(); - pushComment(scanner.getTokenText()); - nextTokenJSDoc(); - } + } + 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()); - break; - case SyntaxKind.BacktickToken: - if (state === JSDocState.SavingBackticks) { - state = JSDocState.SavingComments; - } - else { - state = JSDocState.SavingBackticks; + } + 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; + if (lookAhead(() => nextTokenJSDoc() === SyntaxKind.AtToken && tokenIsIdentifierOrKeyword(nextTokenJSDoc()) && scanner.getTokenText() === "link")) { 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 - } + nextTokenJSDoc(); pushComment(scanner.getTokenText()); + nextTokenJSDoc(); + } + 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; - } - tok = nextTokenJSDoc(); + } + // 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; } - - removeLeadingNewlines(comments); - removeTrailingWhitespace(comments); - return comments.length === 0 ? undefined : comments.join(""); + tok = nextTokenJSDoc(); } - - function parseUnknownTag(start: number, tagName: Identifier) { - const result = createNode(SyntaxKind.JSDocTag, start); - result.tagName = tagName; - return finishNode(result); + removeLeadingNewlines(comments); + removeTrailingWhitespace(comments); + return comments.length === 0 ? undefined : comments.join(""); + } + function parseUnknownTag(start: number, tagName: Identifier) { + const result = (createNode(SyntaxKind.JSDocTag, start)); + result.tagName = tagName; + return finishNode(result); + } + function addTag(tag: JSDocTag | undefined): void { + if (!tag) { + return; } - - function addTag(tag: JSDocTag | undefined): void { - if (!tag) { - return; - } - if (!tags) { - tags = [tag]; - tagsPos = tag.pos; - } - else { - tags.push(tag); - } - tagsEnd = tag.end; + if (!tags) { + tags = [tag]; + tagsPos = tag.pos; } - - function tryParseTypeExpression(): JSDocTypeExpression | undefined { - skipWhitespaceOrAsterisk(); - return token() === SyntaxKind.OpenBraceToken ? parseJSDocTypeExpression() : undefined; + else { + tags.push(tag); } - - 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 }; + 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(); } - - 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"; - } + // 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); } - - function parseParameterOrPropertyTag(start: number, tagName: Identifier, target: PropertyLikeParse, indent: number): JSDocParameterTag | JSDocPropertyTag { - let typeExpression = tryParseTypeExpression(); - let isNameFirst = !typeExpression; - skipWhitespaceOrAsterisk(); - - const { name, isBracketed } = parseBracketNameInPropertyAndParamTag(); + if (isBracketed) { skipWhitespace(); - - if (isNameFirst) { - typeExpression = tryParseTypeExpression(); - } - - const result = target === PropertyLikeParse.Property ? - createNode(SyntaxKind.JSDocPropertyTag, start) : - createNode(SyntaxKind.JSDocParameterTag, start); - const comment = parseTagComments(indent + scanner.getStartPos() - start); - const nestedTypeLiteral = target !== PropertyLikeParse.CallbackParameter && parseNestedTypeLiteral(typeExpression, name, target, indent); - if (nestedTypeLiteral) { - typeExpression = nestedTypeLiteral; - isNameFirst = true; + // May have an optional default, e.g. '[foo = 42]' + if (parseOptionalToken(SyntaxKind.EqualsToken)) { + parseExpression(); } - result.tagName = tagName; - result.typeExpression = typeExpression; - result.name = name; - result.isNameFirst = isNameFirst; - result.isBracketed = isBracketed; - result.comment = comment; - return finishNode(result); + parseExpected(SyntaxKind.CloseBracketToken); } - - function parseNestedTypeLiteral(typeExpression: JSDocTypeExpression | undefined, name: EntityName, target: PropertyLikeParse, indent: number) { - if (typeExpression && isObjectOrObjectArrayTypeReference(typeExpression.type)) { - const typeLiteralExpression = createNode(SyntaxKind.JSDocTypeExpression, scanner.getTokenPos()); - let child: JSDocPropertyLikeTag | JSDocTypeTag | false; - let jsdocTypeLiteral: JSDocTypeLiteral; - const start = scanner.getStartPos(); - 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) { - jsdocTypeLiteral = createNode(SyntaxKind.JSDocTypeLiteral, start); - jsdocTypeLiteral.jsDocPropertyTags = children; - if (typeExpression.type.kind === SyntaxKind.ArrayType) { - jsdocTypeLiteral.isArrayType = true; - } - typeLiteralExpression.type = finishNode(jsdocTypeLiteral); - return finishNode(typeLiteralExpression); + 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"; + } + } + function parseParameterOrPropertyTag(start: number, tagName: Identifier, target: PropertyLikeParse, indent: number): JSDocParameterTag | JSDocPropertyTag { + let typeExpression = tryParseTypeExpression(); + let isNameFirst = !typeExpression; + skipWhitespaceOrAsterisk(); + const { name, isBracketed } = parseBracketNameInPropertyAndParamTag(); + skipWhitespace(); + if (isNameFirst) { + typeExpression = tryParseTypeExpression(); + } + const result = target === PropertyLikeParse.Property ? + createNode(SyntaxKind.JSDocPropertyTag, start) : + createNode(SyntaxKind.JSDocParameterTag, start); + const comment = parseTagComments(indent + scanner.getStartPos() - start); + const nestedTypeLiteral = target !== PropertyLikeParse.CallbackParameter && parseNestedTypeLiteral(typeExpression, name, target, indent); + if (nestedTypeLiteral) { + typeExpression = nestedTypeLiteral; + isNameFirst = true; + } + result.tagName = tagName; + result.typeExpression = typeExpression; + result.name = name; + result.isNameFirst = isNameFirst; + result.isBracketed = isBracketed; + result.comment = comment; + return finishNode(result); + } + function parseNestedTypeLiteral(typeExpression: JSDocTypeExpression | undefined, name: EntityName, target: PropertyLikeParse, indent: number) { + if (typeExpression && isObjectOrObjectArrayTypeReference(typeExpression.type)) { + const typeLiteralExpression = (createNode(SyntaxKind.JSDocTypeExpression, scanner.getTokenPos())); + let child: JSDocPropertyLikeTag | JSDocTypeTag | false; + let jsdocTypeLiteral: JSDocTypeLiteral; + const start = scanner.getStartPos(); + let children: JSDocPropertyLikeTag[] | undefined; + while (child = tryParse(() => parseChildParameterOrPropertyTag(target, indent, name))) { + if (child.kind === SyntaxKind.JSDocParameterTag || child.kind === SyntaxKind.JSDocPropertyTag) { + children = append(children, child); } } - } - - function parseReturnTag(start: number, tagName: Identifier): JSDocReturnTag { - if (some(tags, isJSDocReturnTag)) { - parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText); - } - - const result = createNode(SyntaxKind.JSDocReturnTag, start); - result.tagName = tagName; - result.typeExpression = tryParseTypeExpression(); - return finishNode(result); - } - - function parseTypeTag(start: number, tagName: Identifier): JSDocTypeTag { - if (some(tags, isJSDocTypeTag)) { - parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText); - } - - const result = createNode(SyntaxKind.JSDocTypeTag, start); - result.tagName = tagName; - result.typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); - return finishNode(result); - } - - function parseAuthorTag(start: number, tagName: Identifier, indent: number): JSDocAuthorTag { - const result = createNode(SyntaxKind.JSDocAuthorTag, start); - result.tagName = tagName; - - const authorInfoWithEmail = tryParse(() => tryParseAuthorNameAndEmail()); - if (!authorInfoWithEmail) { - return finishNode(result); - } - - result.comment = authorInfoWithEmail; - - if (lookAhead(() => nextToken() !== SyntaxKind.NewLineTrivia)) { - const comment = parseTagComments(indent); - if (comment) { - result.comment += comment; + if (children) { + jsdocTypeLiteral = (createNode(SyntaxKind.JSDocTypeLiteral, start)); + jsdocTypeLiteral.jsDocPropertyTags = children; + if (typeExpression.type.kind === SyntaxKind.ArrayType) { + jsdocTypeLiteral.isArrayType = true; } + typeLiteralExpression.type = finishNode(jsdocTypeLiteral); + return finishNode(typeLiteralExpression); } - - return finishNode(result); } - - function tryParseAuthorNameAndEmail(): string | undefined { - const comments: string[] = []; - let seenLessThan = false; - let seenGreaterThan = false; - let token = scanner.getToken(); - - loop: while (true) { - switch (token) { - case SyntaxKind.Identifier: - case SyntaxKind.WhitespaceTrivia: - case SyntaxKind.DotToken: - case SyntaxKind.AtToken: - comments.push(scanner.getTokenText()); - break; - case SyntaxKind.LessThanToken: - if (seenLessThan || seenGreaterThan) { - return; - } - seenLessThan = true; - comments.push(scanner.getTokenText()); - break; - case SyntaxKind.GreaterThanToken: - if (!seenLessThan || seenGreaterThan) { - return; - } - seenGreaterThan = true; - comments.push(scanner.getTokenText()); - scanner.setTextPos(scanner.getTokenPos() + 1); - break loop; - case SyntaxKind.NewLineTrivia: - case SyntaxKind.EndOfFileToken: - break loop; - } - - token = nextTokenJSDoc(); - } - - if (seenLessThan && seenGreaterThan) { - return comments.length === 0 ? undefined : comments.join(""); - } + } + function parseReturnTag(start: number, tagName: Identifier): JSDocReturnTag { + if (some(tags, isJSDocReturnTag)) { + parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText); + } + const result = (createNode(SyntaxKind.JSDocReturnTag, start)); + result.tagName = tagName; + result.typeExpression = tryParseTypeExpression(); + return finishNode(result); + } + function parseTypeTag(start: number, tagName: Identifier): JSDocTypeTag { + if (some(tags, isJSDocTypeTag)) { + parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText); } - - function parseAugmentsTag(start: number, tagName: Identifier): JSDocAugmentsTag { - const result = createNode(SyntaxKind.JSDocAugmentsTag, start); - result.tagName = tagName; - result.class = parseExpressionWithTypeArgumentsForAugments(); + const result = (createNode(SyntaxKind.JSDocTypeTag, start)); + result.tagName = tagName; + result.typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); + return finishNode(result); + } + function parseAuthorTag(start: number, tagName: Identifier, indent: number): JSDocAuthorTag { + const result = (createNode(SyntaxKind.JSDocAuthorTag, start)); + result.tagName = tagName; + const authorInfoWithEmail = tryParse(() => tryParseAuthorNameAndEmail()); + if (!authorInfoWithEmail) { return finishNode(result); } - - function parseExpressionWithTypeArgumentsForAugments(): ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression } { - const usedBrace = parseOptional(SyntaxKind.OpenBraceToken); - const node = createNode(SyntaxKind.ExpressionWithTypeArguments) as ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression }; - node.expression = parsePropertyAccessEntityNameExpression(); - node.typeArguments = tryParseTypeArguments(); - const res = finishNode(node); - if (usedBrace) { - parseExpected(SyntaxKind.CloseBraceToken); + result.comment = authorInfoWithEmail; + if (lookAhead(() => nextToken() !== SyntaxKind.NewLineTrivia)) { + const comment = parseTagComments(indent); + if (comment) { + result.comment += comment; } - return res; } - - function parsePropertyAccessEntityNameExpression() { - let node: Identifier | PropertyAccessEntityNameExpression = parseJSDocIdentifierName(); - while (parseOptional(SyntaxKind.DotToken)) { - const prop: PropertyAccessEntityNameExpression = createNode(SyntaxKind.PropertyAccessExpression, node.pos) as PropertyAccessEntityNameExpression; - prop.expression = node; - prop.name = parseJSDocIdentifierName(); - node = finishNode(prop); + return finishNode(result); + } + function tryParseAuthorNameAndEmail(): string | undefined { + const comments: string[] = []; + let seenLessThan = false; + let seenGreaterThan = false; + let token = scanner.getToken(); + loop: while (true) { + switch (token) { + case SyntaxKind.Identifier: + case SyntaxKind.WhitespaceTrivia: + case SyntaxKind.DotToken: + case SyntaxKind.AtToken: + comments.push(scanner.getTokenText()); + break; + case SyntaxKind.LessThanToken: + if (seenLessThan || seenGreaterThan) { + return; + } + seenLessThan = true; + comments.push(scanner.getTokenText()); + break; + case SyntaxKind.GreaterThanToken: + if (!seenLessThan || seenGreaterThan) { + return; + } + seenGreaterThan = true; + comments.push(scanner.getTokenText()); + scanner.setTextPos(scanner.getTokenPos() + 1); + break loop; + case SyntaxKind.NewLineTrivia: + case SyntaxKind.EndOfFileToken: + break loop; } - return node; - } - - function parseClassTag(start: number, tagName: Identifier): JSDocClassTag { - const tag = createNode(SyntaxKind.JSDocClassTag, start); - tag.tagName = tagName; - return finishNode(tag); + token = nextTokenJSDoc(); } - - function parseThisTag(start: number, tagName: Identifier): JSDocThisTag { - const tag = createNode(SyntaxKind.JSDocThisTag, start); - tag.tagName = tagName; - tag.typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); - skipWhitespace(); - return finishNode(tag); + if (seenLessThan && seenGreaterThan) { + return comments.length === 0 ? undefined : comments.join(""); } - - function parseEnumTag(start: number, tagName: Identifier): JSDocEnumTag { - const tag = createNode(SyntaxKind.JSDocEnumTag, start); - tag.tagName = tagName; - tag.typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); - skipWhitespace(); - return finishNode(tag); + } + function parseAugmentsTag(start: number, tagName: Identifier): JSDocAugmentsTag { + const result = (createNode(SyntaxKind.JSDocAugmentsTag, start)); + result.tagName = tagName; + result.class = parseExpressionWithTypeArgumentsForAugments(); + return finishNode(result); + } + function parseExpressionWithTypeArgumentsForAugments(): ExpressionWithTypeArguments & { + expression: Identifier | PropertyAccessEntityNameExpression; + } { + const usedBrace = parseOptional(SyntaxKind.OpenBraceToken); + const node = (createNode(SyntaxKind.ExpressionWithTypeArguments) as ExpressionWithTypeArguments & { + expression: Identifier | PropertyAccessEntityNameExpression; + }); + node.expression = parsePropertyAccessEntityNameExpression(); + node.typeArguments = tryParseTypeArguments(); + const res = finishNode(node); + if (usedBrace) { + parseExpected(SyntaxKind.CloseBraceToken); + } + return res; + } + function parsePropertyAccessEntityNameExpression() { + let node: Identifier | PropertyAccessEntityNameExpression = parseJSDocIdentifierName(); + while (parseOptional(SyntaxKind.DotToken)) { + const prop: PropertyAccessEntityNameExpression = (createNode(SyntaxKind.PropertyAccessExpression, node.pos) as PropertyAccessEntityNameExpression); + prop.expression = node; + prop.name = parseJSDocIdentifierName(); + node = finishNode(prop); } - - function parseTypedefTag(start: number, tagName: Identifier, indent: number): JSDocTypedefTag { - const typeExpression = tryParseTypeExpression(); - skipWhitespaceOrAsterisk(); - - const typedefTag = createNode(SyntaxKind.JSDocTypedefTag, start); - typedefTag.tagName = tagName; - typedefTag.fullName = parseJSDocTypeNameWithNamespace(); - typedefTag.name = getJSDocTypeAliasName(typedefTag.fullName); - skipWhitespace(); - typedefTag.comment = parseTagComments(indent); - - typedefTag.typeExpression = typeExpression; - let end: number | undefined; - if (!typeExpression || isObjectOrObjectArrayTypeReference(typeExpression.type)) { - let child: JSDocTypeTag | JSDocPropertyTag | false; - let jsdocTypeLiteral: JSDocTypeLiteral | undefined; - let childTypeTag: JSDocTypeTag | undefined; - while (child = tryParse(() => parseChildPropertyTag(indent))) { - if (!jsdocTypeLiteral) { - jsdocTypeLiteral = createNode(SyntaxKind.JSDocTypeLiteral, start); - } - if (child.kind === SyntaxKind.JSDocTypeTag) { - if (childTypeTag) { - break; - } - else { - childTypeTag = child; - } + return node; + } + function parseClassTag(start: number, tagName: Identifier): JSDocClassTag { + const tag = (createNode(SyntaxKind.JSDocClassTag, start)); + tag.tagName = tagName; + return finishNode(tag); + } + function parseThisTag(start: number, tagName: Identifier): JSDocThisTag { + const tag = (createNode(SyntaxKind.JSDocThisTag, start)); + tag.tagName = tagName; + tag.typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); + skipWhitespace(); + return finishNode(tag); + } + function parseEnumTag(start: number, tagName: Identifier): JSDocEnumTag { + const tag = (createNode(SyntaxKind.JSDocEnumTag, start)); + tag.tagName = tagName; + tag.typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); + skipWhitespace(); + return finishNode(tag); + } + function parseTypedefTag(start: number, tagName: Identifier, indent: number): JSDocTypedefTag { + const typeExpression = tryParseTypeExpression(); + skipWhitespaceOrAsterisk(); + const typedefTag = (createNode(SyntaxKind.JSDocTypedefTag, start)); + typedefTag.tagName = tagName; + typedefTag.fullName = parseJSDocTypeNameWithNamespace(); + typedefTag.name = getJSDocTypeAliasName(typedefTag.fullName); + skipWhitespace(); + typedefTag.comment = parseTagComments(indent); + typedefTag.typeExpression = typeExpression; + let end: number | undefined; + if (!typeExpression || isObjectOrObjectArrayTypeReference(typeExpression.type)) { + let child: JSDocTypeTag | JSDocPropertyTag | false; + let jsdocTypeLiteral: JSDocTypeLiteral | undefined; + let childTypeTag: JSDocTypeTag | undefined; + while (child = tryParse(() => parseChildPropertyTag(indent))) { + if (!jsdocTypeLiteral) { + jsdocTypeLiteral = (createNode(SyntaxKind.JSDocTypeLiteral, start)); + } + if (child.kind === SyntaxKind.JSDocTypeTag) { + if (childTypeTag) { + break; } else { - jsdocTypeLiteral.jsDocPropertyTags = append(jsdocTypeLiteral.jsDocPropertyTags as MutableNodeArray, child); + childTypeTag = child; } } - if (jsdocTypeLiteral) { - if (typeExpression && typeExpression.type.kind === SyntaxKind.ArrayType) { - jsdocTypeLiteral.isArrayType = true; - } - typedefTag.typeExpression = childTypeTag && childTypeTag.typeExpression && !isObjectOrObjectArrayTypeReference(childTypeTag.typeExpression.type) ? - childTypeTag.typeExpression : - finishNode(jsdocTypeLiteral); - end = typedefTag.typeExpression.end; + else { + jsdocTypeLiteral.jsDocPropertyTags = append((jsdocTypeLiteral.jsDocPropertyTags as MutableNodeArray), child); } } - - // Only include the characters between the name end and the next token if a comment was actually parsed out - otherwise it's just whitespace - return finishNode(typedefTag, end || typedefTag.comment !== undefined ? scanner.getStartPos() : (typedefTag.fullName || typedefTag.typeExpression || typedefTag.tagName).end); - } - - function parseJSDocTypeNameWithNamespace(nested?: boolean) { - const pos = scanner.getTokenPos(); - if (!tokenIsIdentifierOrKeyword(token())) { - return undefined; - } - const typeNameOrNamespaceName = parseJSDocIdentifierName(); - if (parseOptional(SyntaxKind.DotToken)) { - const jsDocNamespaceNode = createNode(SyntaxKind.ModuleDeclaration, pos); - if (nested) { - jsDocNamespaceNode.flags |= NodeFlags.NestedNamespace; + if (jsdocTypeLiteral) { + if (typeExpression && typeExpression.type.kind === SyntaxKind.ArrayType) { + jsdocTypeLiteral.isArrayType = true; } - jsDocNamespaceNode.name = typeNameOrNamespaceName; - jsDocNamespaceNode.body = parseJSDocTypeNameWithNamespace(/*nested*/ true); - return finishNode(jsDocNamespaceNode); - } - - if (nested) { - typeNameOrNamespaceName.isInJSDocNamespace = true; + typedefTag.typeExpression = childTypeTag && childTypeTag.typeExpression && !isObjectOrObjectArrayTypeReference(childTypeTag.typeExpression.type) ? + childTypeTag.typeExpression : + finishNode(jsdocTypeLiteral); + end = typedefTag.typeExpression.end; } - return typeNameOrNamespaceName; } - - function parseCallbackTag(start: number, tagName: Identifier, indent: number): JSDocCallbackTag { - const callbackTag = createNode(SyntaxKind.JSDocCallbackTag, start) as JSDocCallbackTag; - callbackTag.tagName = tagName; - callbackTag.fullName = parseJSDocTypeNameWithNamespace(); - callbackTag.name = getJSDocTypeAliasName(callbackTag.fullName); - skipWhitespace(); - callbackTag.comment = parseTagComments(indent); - - let child: JSDocParameterTag | false; - const jsdocSignature = createNode(SyntaxKind.JSDocSignature, start) as JSDocSignature; - jsdocSignature.parameters = []; - while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.CallbackParameter, indent) as JSDocParameterTag)) { - jsdocSignature.parameters = append(jsdocSignature.parameters as MutableNodeArray, child); + // Only include the characters between the name end and the next token if a comment was actually parsed out - otherwise it's just whitespace + return finishNode(typedefTag, end || typedefTag.comment !== undefined ? scanner.getStartPos() : (typedefTag.fullName || typedefTag.typeExpression || typedefTag.tagName).end); + } + function parseJSDocTypeNameWithNamespace(nested?: boolean) { + const pos = scanner.getTokenPos(); + if (!tokenIsIdentifierOrKeyword(token())) { + return undefined; + } + const typeNameOrNamespaceName = parseJSDocIdentifierName(); + if (parseOptional(SyntaxKind.DotToken)) { + const jsDocNamespaceNode = (createNode(SyntaxKind.ModuleDeclaration, pos)); + if (nested) { + jsDocNamespaceNode.flags |= NodeFlags.NestedNamespace; } - const returnTag = tryParse(() => { - if (parseOptionalJsdoc(SyntaxKind.AtToken)) { - const tag = parseTag(indent); - if (tag && tag.kind === SyntaxKind.JSDocReturnTag) { - return tag as JSDocReturnTag; - } + jsDocNamespaceNode.name = typeNameOrNamespaceName; + jsDocNamespaceNode.body = parseJSDocTypeNameWithNamespace(/*nested*/ true); + return finishNode(jsDocNamespaceNode); + } + if (nested) { + typeNameOrNamespaceName.isInJSDocNamespace = true; + } + return typeNameOrNamespaceName; + } + function parseCallbackTag(start: number, tagName: Identifier, indent: number): JSDocCallbackTag { + const callbackTag = (createNode(SyntaxKind.JSDocCallbackTag, start) as JSDocCallbackTag); + callbackTag.tagName = tagName; + callbackTag.fullName = parseJSDocTypeNameWithNamespace(); + callbackTag.name = getJSDocTypeAliasName(callbackTag.fullName); + skipWhitespace(); + callbackTag.comment = parseTagComments(indent); + let child: JSDocParameterTag | false; + const jsdocSignature = (createNode(SyntaxKind.JSDocSignature, start) as JSDocSignature); + jsdocSignature.parameters = []; + while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.CallbackParameter, indent) as JSDocParameterTag)) { + jsdocSignature.parameters = append((jsdocSignature.parameters as MutableNodeArray), child); + } + const returnTag = tryParse(() => { + if (parseOptionalJsdoc(SyntaxKind.AtToken)) { + const tag = parseTag(indent); + if (tag && tag.kind === SyntaxKind.JSDocReturnTag) { + return tag as JSDocReturnTag; } - }); - if (returnTag) { - jsdocSignature.type = returnTag; } - callbackTag.typeExpression = finishNode(jsdocSignature); - return finishNode(callbackTag); + }); + if (returnTag) { + jsdocSignature.type = returnTag; } - - function getJSDocTypeAliasName(fullName: JSDocNamespaceBody | undefined) { - if (fullName) { - let rightNode = fullName; - while (true) { - if (ts.isIdentifier(rightNode) || !rightNode.body) { - return ts.isIdentifier(rightNode) ? rightNode : rightNode.name; - } - rightNode = rightNode.body; + callbackTag.typeExpression = finishNode(jsdocSignature); + return finishNode(callbackTag); + } + function getJSDocTypeAliasName(fullName: JSDocNamespaceBody | undefined) { + if (fullName) { + let rightNode = fullName; + while (true) { + if (ts.isIdentifier(rightNode) || !rightNode.body) { + return ts.isIdentifier(rightNode) ? rightNode : rightNode.name; } + rightNode = rightNode.body; } } - - 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; - } + } + 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; + 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; } - seenAsterisk = true; - break; - case SyntaxKind.Identifier: + return child; + } + seenAsterisk = false; + break; + case SyntaxKind.NewLineTrivia: + canParseTag = true; + seenAsterisk = false; + break; + case SyntaxKind.AsteriskToken: + if (seenAsterisk) { 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; + } + seenAsterisk = true; break; - case "arg": - case "argument": - case "param": - t = PropertyLikeParse.Parameter | PropertyLikeParse.CallbackParameter; + case SyntaxKind.Identifier: + canParseTag = false; break; - default: + case SyntaxKind.EndOfFileToken: return false; } - if (!(target & t)) { + } + } + 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; - } - return parseParameterOrPropertyTag(start, tagName, target, indent); } - - function parseTemplateTag(start: number, tagName: Identifier): JSDocTemplateTag { - // the template tag looks like '@template {Constraint} T,U,V' - let constraint: JSDocTypeExpression | undefined; - if (token() === SyntaxKind.OpenBraceToken) { - constraint = parseJSDocTypeExpression(); - } - - const typeParameters = []; - const typeParametersPos = getNodePos(); - do { - skipWhitespace(); - const typeParameter = createNode(SyntaxKind.TypeParameter); - typeParameter.name = parseJSDocIdentifierName(Diagnostics.Unexpected_token_A_type_parameter_name_was_expected_without_curly_braces); - finishNode(typeParameter); - skipWhitespace(); - typeParameters.push(typeParameter); - } while (parseOptionalJsdoc(SyntaxKind.CommaToken)); - - const result = createNode(SyntaxKind.JSDocTemplateTag, start); - result.tagName = tagName; - result.constraint = constraint; - result.typeParameters = createNodeArray(typeParameters, typeParametersPos); - finishNode(result); - return result; + if (!(target & t)) { + return false; + } + return parseParameterOrPropertyTag(start, tagName, target, indent); + } + function parseTemplateTag(start: number, tagName: Identifier): JSDocTemplateTag { + // the template tag looks like '@template {Constraint} T,U,V' + let constraint: JSDocTypeExpression | undefined; + if (token() === SyntaxKind.OpenBraceToken) { + constraint = parseJSDocTypeExpression(); + } + const typeParameters = []; + const typeParametersPos = getNodePos(); + do { + skipWhitespace(); + const typeParameter = (createNode(SyntaxKind.TypeParameter)); + typeParameter.name = parseJSDocIdentifierName(Diagnostics.Unexpected_token_A_type_parameter_name_was_expected_without_curly_braces); + finishNode(typeParameter); + skipWhitespace(); + typeParameters.push(typeParameter); + } while (parseOptionalJsdoc(SyntaxKind.CommaToken)); + const result = (createNode(SyntaxKind.JSDocTemplateTag, start)); + result.tagName = tagName; + result.constraint = constraint; + result.typeParameters = createNodeArray(typeParameters, typeParametersPos); + finishNode(result); + return result; + } + function parseOptionalJsdoc(t: JSDocSyntaxKind): boolean { + if (token() === t) { + nextTokenJSDoc(); + return true; } - - function parseOptionalJsdoc(t: JSDocSyntaxKind): boolean { - if (token() === t) { - nextTokenJSDoc(); - return true; - } - return false; + 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. } - - function parseJSDocEntityName(): EntityName { - let entity: EntityName = parseJSDocIdentifierName(); + while (parseOptional(SyntaxKind.DotToken)) { + const name = 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; + entity = createQualifiedName(entity, name); } - - 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 result = createNode(SyntaxKind.Identifier, pos); - if (token() !== SyntaxKind.Identifier) { - result.originalKeywordKind = token(); - } - result.escapedText = escapeLeadingUnderscores(internIdentifier(scanner.getTokenValue())); - finishNode(result, end); - - nextTokenJSDoc(); - return result; + 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 result = (createNode(SyntaxKind.Identifier, pos)); + if (token() !== SyntaxKind.Identifier) { + result.originalKeywordKind = token(); + } + result.escapedText = escapeLeadingUnderscores(internIdentifier(scanner.getTokenValue())); + finishNode(result, 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); - } - - // 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; - Debug.assert(!incrementalSourceFile.hasBeenIncrementallyParsed); - incrementalSourceFile.hasBeenIncrementallyParsed = true; - - 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); - - 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); + } + // 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); + Debug.assert(!incrementalSourceFile.hasBeenIncrementallyParsed); + incrementalSourceFile.hasBeenIncrementallyParsed = true; + 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); + return result; + } + function moveElementEntirelyPastChangeRange(element: IncrementalElement, isArray: boolean, delta: number, oldText: string, newText: string, aggressiveChecks: boolean) { + if (isArray) { + visitArray(element); + } + else { + visitNode(element); } - - function moveElementEntirelyPastChangeRange(element: IncrementalElement, isArray: boolean, delta: number, oldText: string, newText: string, aggressiveChecks: boolean) { - if (isArray) { - visitArray(element); + return; + function visitNode(node: IncrementalNode) { + let text = ""; + if (aggressiveChecks && shouldCheckNode(node)) { + text = oldText.substring(node.pos, node.end); } - else { - visitNode(element); + // Ditch any existing LS children we may have created. This way we can avoid + // moving them forward. + if (node._children) { + node._children = undefined; } - 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; + 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)); } - - node.pos += delta; - node.end += delta; - - if (aggressiveChecks && shouldCheckNode(node)) { - Debug.assert(text === newText.substring(node.pos, node.end)); + } + checkNodePositions(node, aggressiveChecks); + } + function visitArray(array: IncrementalNodeArray) { + array._children = undefined; + 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. + element.pos = Math.min(element.pos, changeRangeNewEnd); + // If the 'end' is after the change range, then we always adjust it by the delta + // amount. However, if the end is in the change range, then how we adjust it + // will depend on if delta is positive or negative. If delta is positive then we + // have something like: + // + // -------------------AAA----------------- + // -------------------BBBCCCCCCC----------------- + // + // In this case, we consider any node that ended inside the change range to keep its + // end position. + // + // however, if the delta is negative, then we instead have something like this: + // + // -------------------XXXYYYYYYY----------------- + // -------------------ZZZ----------------- + // + // In this case, any element that ended in the 'X' range will keep its position. + // However any element 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. + if (element.end >= changeRangeOldEnd) { + // Element ends after the change range. Always adjust the end pos. + element.end += delta; + } + else { + // Element ends in the change range. The element will keep its position if + // possible. Or Move backward to the new-end if it's in the 'Y' range. + element.end = Math.min(element.end, changeRangeNewEnd); + } + Debug.assert(element.pos <= element.end); + if (element.parent) { + Debug.assert(element.pos >= element.parent.pos); + Debug.assert(element.end <= element.parent.end); + } + } + function checkNodePositions(node: Node, aggressiveChecks: boolean) { + if (aggressiveChecks) { + let pos = node.pos; + 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, visitArray); - 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)); } } - checkNodePositions(node, aggressiveChecks); + 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; } - - function visitArray(array: IncrementalNodeArray) { + // 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; - array.pos += delta; - array.end += delta; - + // Adjust the pos or end (or both) of the intersecting array accordingly. + adjustIntersectingElement(array, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); for (const node of array) { visitNode(node); } + return; } + // Otherwise, the array is entirely before the change range. No need to do anything with it. + Debug.assert(fullEnd < changeStart); } - - function 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. - element.pos = Math.min(element.pos, changeRangeNewEnd); - - // If the 'end' is after the change range, then we always adjust it by the delta - // amount. However, if the end is in the change range, then how we adjust it - // will depend on if delta is positive or negative. If delta is positive then we - // have something like: - // - // -------------------AAA----------------- - // -------------------BBBCCCCCCC----------------- - // - // In this case, we consider any node that ended inside the change range to keep its - // end position. - // - // however, if the delta is negative, then we instead have something like this: - // - // -------------------XXXYYYYYYY----------------- - // -------------------ZZZ----------------- - // - // In this case, any element that ended in the 'X' range will keep its position. - // However any element 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. - if (element.end >= changeRangeOldEnd) { - // Element ends after the change range. Always adjust the end pos. - element.end += delta; - } - else { - // Element ends in the change range. The element will keep its position if - // possible. Or Move backward to the new-end if it's in the 'Y' range. - element.end = Math.min(element.end, changeRangeNewEnd); - } - - Debug.assert(element.pos <= element.end); - if (element.parent) { - Debug.assert(element.pos >= element.parent.pos); - Debug.assert(element.end <= element.parent.end); - } - } - - function checkNodePositions(node: Node, aggressiveChecks: boolean) { - if (aggressiveChecks) { - let pos = node.pos; - 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); - } - } - 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; + } + 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; } - - // 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) { - visitNode(node); - } - return; + else { + return node; } - - // 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; } - - 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; - } + // 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.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; + 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); - } + } + 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 TextRange { - 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; - } - - 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); - } + } + interface IncrementalElement extends TextRange { + 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; + } + 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]; } - - // 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; - } - }; - - // 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; + // 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); } - - // 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; + // 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; + } + }; + // 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; } - 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; } + // 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 fileExtensionIs(fileName, Extension.Dts); - } - - /*@internal*/ - export interface PragmaContext { - languageVersion: ScriptTarget; - pragmas?: PragmaMap; - checkJsDirective?: CheckJsDirective; - referencedFiles: FileReference[]; - typeReferenceDirectives: FileReference[]; - libReferenceDirectives: FileReference[]; - amdDependencies: AmdDependency[]; - hasNoDefaultLib?: boolean; - moduleName?: string; - } - - /*@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 = createMap() 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; + } + const enum InvalidPosition { + Value = -1 + } +} +/** @internal */ +export function isDeclarationFileName(fileName: string): boolean { + return fileExtensionIs(fileName, Extension.Dts); +} +/*@internal*/ +export interface PragmaContext { + languageVersion: ScriptTarget; + pragmas?: PragmaMap; + checkJsDirective?: CheckJsDirective; + referencedFiles: FileReference[]; + typeReferenceDirectives: FileReference[]; + libReferenceDirectives: FileReference[]; + amdDependencies: AmdDependency[]; + hasNoDefaultLib?: boolean; + moduleName?: string; +} +/*@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 = (createMap() 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); } - context.pragmas.set(pragma.name, pragma.args); - } - } - - /*@internal*/ - 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 } = arg.arguments; - if (arg.arguments["no-default-lib"]) { - context.hasNoDefaultLib = true; - } - else if (types) { - typeReferenceDirectives.push({ pos: types.pos, end: types.end, fileName: types.value }); - } - 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.pragmas.set(pragma.name, [currentValue, pragma.args]); + } + continue; + } + context.pragmas.set(pragma.name, pragma.args); + } +} +/*@internal*/ +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: 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 } = arg.arguments; + if (arg.arguments["no-default-lib"]) { + context.hasNoDefaultLib = true; + } + else if (types) { + typeReferenceDirectives.push({ pos: types.pos, end: types.end, fileName: types.value }); + } + 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 { - context.moduleName = (entryOrList as PragmaPseudoMap["amd-module"]).arguments.name; + reportDiagnostic(arg.range.pos, arg.range.end - arg.range.pos, Diagnostics.Invalid_reference_directive_syntax); } - 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": return; // Accessed directly - default: Debug.fail("Unhandled pragma kind"); // Can this be made into an assertNever in the future? + }); + break; } - }); - } - - const namedArgRegExCache = createMap(); - function getNamedArgRegEx(name: string): RegExp { - if (namedArgRegExCache.has(name)) { - return namedArgRegExCache.get(name)!; - } - const result = new RegExp(`(\\s${name}\\s*=\\s*)('|")(.+?)\\2`, "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; + case "amd-dependency": { + context.amdDependencies = map((toArray(entryOrList) as PragmaPseudoMap["amd-dependency"][]), x => ({ name: x.arguments.name, path: x.arguments.path })); + break; } - 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) { - if (arg.captureSpan) { - const startPos = range.pos + matchResult.index + matchResult[1].length + matchResult[2].length; - argument[arg.name] = { - value: matchResult[3], - pos: startPos, - end: startPos + matchResult[3].length - }; - } - else { - argument[arg.name] = matchResult[3]; + 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; } } - pragmas.push({ name, args: { arguments: argument, range } } as PragmaPseudoMapEntry); + else { + context.moduleName = (entryOrList as PragmaPseudoMap["amd-module"]).arguments.name; + } + break; } - else { - pragmas.push({ name, args: { arguments: {}, range } } as PragmaPseudoMapEntry); + 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; } - return; + case "jsx": return; // Accessed directly + default: Debug.fail("Unhandled pragma kind"); // Can this be made into an assertNever in the future? } - - const singleLine = range.kind === SyntaxKind.SingleLineCommentTrivia && singleLinePragmaRegEx.exec(text); - if (singleLine) { - return addPragmaForMatch(pragmas, range, PragmaKindFlags.SingleLine, singleLine); + }); +} +const namedArgRegExCache = createMap(); +function getNamedArgRegEx(name: string): RegExp { + if (namedArgRegExCache.has(name)) { + return namedArgRegExCache.get(name)!; + } + const result = new RegExp(`(\\s${name}\\s*=\\s*)('|")(.+?)\\2`, "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 (range.kind === SyntaxKind.MultiLineCommentTrivia) { - const multiLinePragmaRegEx = /\s*@(\S+)\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); + 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) { + if (arg.captureSpan) { + const startPos = range.pos + matchResult.index + matchResult[1].length + matchResult[2].length; + argument[arg.name] = { + value: matchResult[3], + pos: startPos, + end: startPos + matchResult[3].length + }; + } + else { + argument[arg.name] = matchResult[3]; + } + } } + pragmas.push(({ name, args: { arguments: argument, range } } as PragmaPseudoMapEntry)); } - } - - 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; + else { + pragmas.push(({ name, args: { arguments: {}, range } } as PragmaPseudoMapEntry)); } - 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 = 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; + const singleLine = range.kind === SyntaxKind.SingleLineCommentTrivia && singleLinePragmaRegEx.exec(text); + if (singleLine) { + return addPragmaForMatch(pragmas, range, PragmaKindFlags.SingleLine, singleLine); } - - /** @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).escapedText; + if (range.kind === SyntaxKind.MultiLineCommentTrivia) { + const multiLinePragmaRegEx = /\s*@(\S+)\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); } - - 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).name.escapedText === (rhs).name.escapedText && - tagNamesAreEquivalent((lhs).expression as JsxTagNameExpression, (rhs).expression as JsxTagNameExpression); } } +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 = 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).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).name.escapedText === (rhs).name.escapedText && + tagNamesAreEquivalent(((lhs).expression as JsxTagNameExpression), ((rhs).expression as JsxTagNameExpression)); +} diff --git a/src/compiler/path.ts b/src/compiler/path.ts index d7d8d964d542b..f4f0da2d94602 100644 --- a/src/compiler/path.ts +++ b/src/compiler/path.ts @@ -1,855 +1,867 @@ +import { CharacterCodes, stringContains, endsWith, Path, startsWith, equateStringsCaseInsensitive, equateStringsCaseSensitive, lastOrUndefined, some, Comparison, compareStringsCaseInsensitive, compareValues, compareStringsCaseSensitive, getStringComparer, GetCanonicalFileName, Debug, identity } from "./ts"; /* @internal */ -namespace ts { - /** - * Internally, we represent paths as strings with '/' as the directory separator. - * When we make system calls (eg: LanguageServiceHost.getDirectory()), - * we expect the host to correctly handle paths in our specified format. - */ - export const directorySeparator = "/"; - const altDirectorySeparator = "\\"; - const urlSchemeSeparator = "://"; - const backslashRegExp = /\\/g; - - //// Path Tests - - /** - * Determines whether a charCode corresponds to `/` or `\`. - */ - export function isAnyDirectorySeparator(charCode: number): boolean { - return charCode === CharacterCodes.slash || charCode === CharacterCodes.backslash; - } - - /** - * Determines whether a path starts with a URL scheme (e.g. starts with `http://`, `ftp://`, `file://`, etc.). - */ - export function isUrl(path: string) { - return getEncodedRootLength(path) < 0; - } - - /** - * Determines whether a path is an absolute disk path (e.g. starts with `/`, or a dos path - * like `c:`, `c:\` or `c:/`). - */ - export function isRootedDiskPath(path: string) { - return getEncodedRootLength(path) > 0; - } - - /** - * Determines whether a path consists only of a path root. - */ - export function isDiskPathRoot(path: string) { - const rootLength = getEncodedRootLength(path); - return rootLength > 0 && rootLength === path.length; - } - - /** - * Determines whether a path starts with an absolute path component (i.e. `/`, `c:/`, `file://`, etc.). - * - * ```ts - * // POSIX - * pathIsAbsolute("/path/to/file.ext") === true - * // DOS - * pathIsAbsolute("c:/path/to/file.ext") === true - * // URL - * pathIsAbsolute("file:///path/to/file.ext") === true - * // Non-absolute - * pathIsAbsolute("path/to/file.ext") === false - * pathIsAbsolute("./path/to/file.ext") === false - * ``` - */ - export function pathIsAbsolute(path: string): boolean { - return getEncodedRootLength(path) !== 0; - } - - /** - * Determines whether a path starts with a relative path component (i.e. `.` or `..`). - */ - export function pathIsRelative(path: string): boolean { - return /^\.\.?($|[\\/])/.test(path); - } - - export function hasExtension(fileName: string): boolean { - return stringContains(getBaseFileName(fileName), "."); - } - - export function fileExtensionIs(path: string, extension: string): boolean { - return path.length > extension.length && endsWith(path, extension); - } - - export function fileExtensionIsOneOf(path: string, extensions: readonly string[]): boolean { - for (const extension of extensions) { - if (fileExtensionIs(path, extension)) { - return true; - } - } - - return false; - } - - /** - * Determines whether a path has a trailing separator (`/` or `\\`). - */ - export function hasTrailingDirectorySeparator(path: string) { - return path.length > 0 && isAnyDirectorySeparator(path.charCodeAt(path.length - 1)); - } - - //// Path Parsing - - function isVolumeCharacter(charCode: number) { - return (charCode >= CharacterCodes.a && charCode <= CharacterCodes.z) || - (charCode >= CharacterCodes.A && charCode <= CharacterCodes.Z); - } - - function getFileUrlVolumeSeparatorEnd(url: string, start: number) { - const ch0 = url.charCodeAt(start); - if (ch0 === CharacterCodes.colon) return start + 1; - if (ch0 === CharacterCodes.percent && url.charCodeAt(start + 1) === CharacterCodes._3) { - const ch2 = url.charCodeAt(start + 2); - if (ch2 === CharacterCodes.a || ch2 === CharacterCodes.A) return start + 3; +/** + * Internally, we represent paths as strings with '/' as the directory separator. + * When we make system calls (eg: LanguageServiceHost.getDirectory()), + * we expect the host to correctly handle paths in our specified format. + */ +export const directorySeparator = "/"; +/* @internal */ +const altDirectorySeparator = "\\"; +/* @internal */ +const urlSchemeSeparator = "://"; +/* @internal */ +const backslashRegExp = /\\/g; +//// Path Tests +/** + * Determines whether a charCode corresponds to `/` or `\`. + */ +/* @internal */ +export function isAnyDirectorySeparator(charCode: number): boolean { + return charCode === CharacterCodes.slash || charCode === CharacterCodes.backslash; +} +/** + * Determines whether a path starts with a URL scheme (e.g. starts with `http://`, `ftp://`, `file://`, etc.). + */ +/* @internal */ +export function isUrl(path: string) { + return getEncodedRootLength(path) < 0; +} +/** + * Determines whether a path is an absolute disk path (e.g. starts with `/`, or a dos path + * like `c:`, `c:\` or `c:/`). + */ +/* @internal */ +export function isRootedDiskPath(path: string) { + return getEncodedRootLength(path) > 0; +} +/** + * Determines whether a path consists only of a path root. + */ +/* @internal */ +export function isDiskPathRoot(path: string) { + const rootLength = getEncodedRootLength(path); + return rootLength > 0 && rootLength === path.length; +} +/** + * Determines whether a path starts with an absolute path component (i.e. `/`, `c:/`, `file://`, etc.). + * + * ```ts + * // POSIX + * pathIsAbsolute("/path/to/file.ext") === true + * // DOS + * pathIsAbsolute("c:/path/to/file.ext") === true + * // URL + * pathIsAbsolute("file:///path/to/file.ext") === true + * // Non-absolute + * pathIsAbsolute("path/to/file.ext") === false + * pathIsAbsolute("./path/to/file.ext") === false + * ``` + */ +/* @internal */ +export function pathIsAbsolute(path: string): boolean { + return getEncodedRootLength(path) !== 0; +} +/** + * Determines whether a path starts with a relative path component (i.e. `.` or `..`). + */ +/* @internal */ +export function pathIsRelative(path: string): boolean { + return /^\.\.?($|[\\/])/.test(path); +} +/* @internal */ +export function hasExtension(fileName: string): boolean { + return stringContains(getBaseFileName(fileName), "."); +} +/* @internal */ +export function fileExtensionIs(path: string, extension: string): boolean { + return path.length > extension.length && endsWith(path, extension); +} +/* @internal */ +export function fileExtensionIsOneOf(path: string, extensions: readonly string[]): boolean { + for (const extension of extensions) { + if (fileExtensionIs(path, extension)) { + return true; } - return -1; } - - /** - * Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files"). - * If the root is part of a URL, the twos-complement of the root length is returned. - */ - function getEncodedRootLength(path: string): number { - if (!path) return 0; - const ch0 = path.charCodeAt(0); - - // POSIX or UNC - if (ch0 === CharacterCodes.slash || ch0 === CharacterCodes.backslash) { - if (path.charCodeAt(1) !== ch0) return 1; // POSIX: "/" (or non-normalized "\") - - const p1 = path.indexOf(ch0 === CharacterCodes.slash ? directorySeparator : altDirectorySeparator, 2); - if (p1 < 0) return path.length; // UNC: "//server" or "\\server" - - return p1 + 1; // UNC: "//server/" or "\\server\" - } - - // DOS - if (isVolumeCharacter(ch0) && path.charCodeAt(1) === CharacterCodes.colon) { - const ch2 = path.charCodeAt(2); - if (ch2 === CharacterCodes.slash || ch2 === CharacterCodes.backslash) return 3; // DOS: "c:/" or "c:\" - if (path.length === 2) return 2; // DOS: "c:" (but not "c:d") - } - - // URL - const schemeEnd = path.indexOf(urlSchemeSeparator); - if (schemeEnd !== -1) { - const authorityStart = schemeEnd + urlSchemeSeparator.length; - const authorityEnd = path.indexOf(directorySeparator, authorityStart); - if (authorityEnd !== -1) { // URL: "file:///", "file://server/", "file://server/path" - // For local "file" URLs, include the leading DOS volume (if present). - // Per https://www.ietf.org/rfc/rfc1738.txt, a host of "" or "localhost" is a - // special case interpreted as "the machine from which the URL is being interpreted". - const scheme = path.slice(0, schemeEnd); - const authority = path.slice(authorityStart, authorityEnd); - if (scheme === "file" && (authority === "" || authority === "localhost") && - isVolumeCharacter(path.charCodeAt(authorityEnd + 1))) { - const volumeSeparatorEnd = getFileUrlVolumeSeparatorEnd(path, authorityEnd + 2); - if (volumeSeparatorEnd !== -1) { - if (path.charCodeAt(volumeSeparatorEnd) === CharacterCodes.slash) { - // URL: "file:///c:/", "file://localhost/c:/", "file:///c%3a/", "file://localhost/c%3a/" - return ~(volumeSeparatorEnd + 1); - } - if (volumeSeparatorEnd === path.length) { - // URL: "file:///c:", "file://localhost/c:", "file:///c$3a", "file://localhost/c%3a" - // but not "file:///c:d" or "file:///c%3ad" - return ~volumeSeparatorEnd; - } + return false; +} +/** + * Determines whether a path has a trailing separator (`/` or `\\`). + */ +/* @internal */ +export function hasTrailingDirectorySeparator(path: string) { + return path.length > 0 && isAnyDirectorySeparator(path.charCodeAt(path.length - 1)); +} +//// Path Parsing +/* @internal */ +function isVolumeCharacter(charCode: number) { + return (charCode >= CharacterCodes.a && charCode <= CharacterCodes.z) || + (charCode >= CharacterCodes.A && charCode <= CharacterCodes.Z); +} +/* @internal */ +function getFileUrlVolumeSeparatorEnd(url: string, start: number) { + const ch0 = url.charCodeAt(start); + if (ch0 === CharacterCodes.colon) + return start + 1; + if (ch0 === CharacterCodes.percent && url.charCodeAt(start + 1) === CharacterCodes._3) { + const ch2 = url.charCodeAt(start + 2); + if (ch2 === CharacterCodes.a || ch2 === CharacterCodes.A) + return start + 3; + } + return -1; +} +/** + * Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files"). + * If the root is part of a URL, the twos-complement of the root length is returned. + */ +/* @internal */ +function getEncodedRootLength(path: string): number { + if (!path) + return 0; + const ch0 = path.charCodeAt(0); + // POSIX or UNC + if (ch0 === CharacterCodes.slash || ch0 === CharacterCodes.backslash) { + if (path.charCodeAt(1) !== ch0) + return 1; // POSIX: "/" (or non-normalized "\") + const p1 = path.indexOf(ch0 === CharacterCodes.slash ? directorySeparator : altDirectorySeparator, 2); + if (p1 < 0) + return path.length; // UNC: "//server" or "\\server" + return p1 + 1; // UNC: "//server/" or "\\server\" + } + // DOS + if (isVolumeCharacter(ch0) && path.charCodeAt(1) === CharacterCodes.colon) { + const ch2 = path.charCodeAt(2); + if (ch2 === CharacterCodes.slash || ch2 === CharacterCodes.backslash) + return 3; // DOS: "c:/" or "c:\" + if (path.length === 2) + return 2; // DOS: "c:" (but not "c:d") + } + // URL + const schemeEnd = path.indexOf(urlSchemeSeparator); + if (schemeEnd !== -1) { + const authorityStart = schemeEnd + urlSchemeSeparator.length; + const authorityEnd = path.indexOf(directorySeparator, authorityStart); + if (authorityEnd !== -1) { // URL: "file:///", "file://server/", "file://server/path" + // For local "file" URLs, include the leading DOS volume (if present). + // Per https://www.ietf.org/rfc/rfc1738.txt, a host of "" or "localhost" is a + // special case interpreted as "the machine from which the URL is being interpreted". + const scheme = path.slice(0, schemeEnd); + const authority = path.slice(authorityStart, authorityEnd); + if (scheme === "file" && (authority === "" || authority === "localhost") && + isVolumeCharacter(path.charCodeAt(authorityEnd + 1))) { + const volumeSeparatorEnd = getFileUrlVolumeSeparatorEnd(path, authorityEnd + 2); + if (volumeSeparatorEnd !== -1) { + if (path.charCodeAt(volumeSeparatorEnd) === CharacterCodes.slash) { + // URL: "file:///c:/", "file://localhost/c:/", "file:///c%3a/", "file://localhost/c%3a/" + return ~(volumeSeparatorEnd + 1); + } + if (volumeSeparatorEnd === path.length) { + // URL: "file:///c:", "file://localhost/c:", "file:///c$3a", "file://localhost/c%3a" + // but not "file:///c:d" or "file:///c%3ad" + return ~volumeSeparatorEnd; } } - return ~(authorityEnd + 1); // URL: "file://server/", "http://server/" - } - return ~path.length; // URL: "file://server", "http://server" - } - - // relative - return 0; - } - - /** - * Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files"). - * - * For example: - * ```ts - * getRootLength("a") === 0 // "" - * getRootLength("/") === 1 // "/" - * getRootLength("c:") === 2 // "c:" - * getRootLength("c:d") === 0 // "" - * getRootLength("c:/") === 3 // "c:/" - * getRootLength("c:\\") === 3 // "c:\\" - * getRootLength("//server") === 7 // "//server" - * getRootLength("//server/share") === 8 // "//server/" - * getRootLength("\\\\server") === 7 // "\\\\server" - * getRootLength("\\\\server\\share") === 8 // "\\\\server\\" - * getRootLength("file:///path") === 8 // "file:///" - * getRootLength("file:///c:") === 10 // "file:///c:" - * getRootLength("file:///c:d") === 8 // "file:///" - * getRootLength("file:///c:/path") === 11 // "file:///c:/" - * getRootLength("file://server") === 13 // "file://server" - * getRootLength("file://server/path") === 14 // "file://server/" - * getRootLength("http://server") === 13 // "http://server" - * getRootLength("http://server/path") === 14 // "http://server/" - * ``` - */ - export function getRootLength(path: string) { - const rootLength = getEncodedRootLength(path); - return rootLength < 0 ? ~rootLength : rootLength; - } - - /** - * Returns the path except for its basename. Semantics align with NodeJS's `path.dirname` - * except that we support URLs as well. - * - * ```ts - * // POSIX - * getDirectoryPath("/path/to/file.ext") === "/path/to" - * getDirectoryPath("/path/to/") === "/path" - * getDirectoryPath("/") === "/" - * // DOS - * getDirectoryPath("c:/path/to/file.ext") === "c:/path/to" - * getDirectoryPath("c:/path/to/") === "c:/path" - * getDirectoryPath("c:/") === "c:/" - * getDirectoryPath("c:") === "c:" - * // URL - * getDirectoryPath("http://typescriptlang.org/path/to/file.ext") === "http://typescriptlang.org/path/to" - * getDirectoryPath("http://typescriptlang.org/path/to") === "http://typescriptlang.org/path" - * getDirectoryPath("http://typescriptlang.org/") === "http://typescriptlang.org/" - * getDirectoryPath("http://typescriptlang.org") === "http://typescriptlang.org" - * ``` - */ - export function getDirectoryPath(path: Path): Path; - /** - * Returns the path except for its basename. Semantics align with NodeJS's `path.dirname` - * except that we support URLs as well. - * - * ```ts - * // POSIX - * getDirectoryPath("/path/to/file.ext") === "/path/to" - * getDirectoryPath("/path/to/") === "/path" - * getDirectoryPath("/") === "/" - * // DOS - * getDirectoryPath("c:/path/to/file.ext") === "c:/path/to" - * getDirectoryPath("c:/path/to/") === "c:/path" - * getDirectoryPath("c:/") === "c:/" - * getDirectoryPath("c:") === "c:" - * // URL - * getDirectoryPath("http://typescriptlang.org/path/to/file.ext") === "http://typescriptlang.org/path/to" - * getDirectoryPath("http://typescriptlang.org/path/to") === "http://typescriptlang.org/path" - * getDirectoryPath("http://typescriptlang.org/") === "http://typescriptlang.org/" - * getDirectoryPath("http://typescriptlang.org") === "http://typescriptlang.org" - * getDirectoryPath("file://server/path/to/file.ext") === "file://server/path/to" - * getDirectoryPath("file://server/path/to") === "file://server/path" - * getDirectoryPath("file://server/") === "file://server/" - * getDirectoryPath("file://server") === "file://server" - * getDirectoryPath("file:///path/to/file.ext") === "file:///path/to" - * getDirectoryPath("file:///path/to") === "file:///path" - * getDirectoryPath("file:///") === "file:///" - * getDirectoryPath("file://") === "file://" - * ``` - */ - export function getDirectoryPath(path: string): string; - export function getDirectoryPath(path: string): string { - path = normalizeSlashes(path); - - // If the path provided is itself the root, then return it. - const rootLength = getRootLength(path); - if (rootLength === path.length) return path; - - // return the leading portion of the path up to the last (non-terminal) directory separator - // but not including any trailing directory separator. - path = removeTrailingDirectorySeparator(path); - return path.slice(0, Math.max(rootLength, path.lastIndexOf(directorySeparator))); - } - - /** - * Returns the path except for its containing directory name. - * Semantics align with NodeJS's `path.basename` except that we support URL's as well. - * - * ```ts - * // POSIX - * getBaseFileName("/path/to/file.ext") === "file.ext" - * getBaseFileName("/path/to/") === "to" - * getBaseFileName("/") === "" - * // DOS - * getBaseFileName("c:/path/to/file.ext") === "file.ext" - * getBaseFileName("c:/path/to/") === "to" - * getBaseFileName("c:/") === "" - * getBaseFileName("c:") === "" - * // URL - * getBaseFileName("http://typescriptlang.org/path/to/file.ext") === "file.ext" - * getBaseFileName("http://typescriptlang.org/path/to/") === "to" - * getBaseFileName("http://typescriptlang.org/") === "" - * getBaseFileName("http://typescriptlang.org") === "" - * getBaseFileName("file://server/path/to/file.ext") === "file.ext" - * getBaseFileName("file://server/path/to/") === "to" - * getBaseFileName("file://server/") === "" - * getBaseFileName("file://server") === "" - * getBaseFileName("file:///path/to/file.ext") === "file.ext" - * getBaseFileName("file:///path/to/") === "to" - * getBaseFileName("file:///") === "" - * getBaseFileName("file://") === "" - * ``` - */ - export function getBaseFileName(path: string): string; - /** - * Gets the portion of a path following the last (non-terminal) separator (`/`). - * Semantics align with NodeJS's `path.basename` except that we support URL's as well. - * If the base name has any one of the provided extensions, it is removed. - * - * ```ts - * getBaseFileName("/path/to/file.ext", ".ext", true) === "file" - * getBaseFileName("/path/to/file.js", ".ext", true) === "file.js" - * getBaseFileName("/path/to/file.js", [".ext", ".js"], true) === "file" - * getBaseFileName("/path/to/file.ext", ".EXT", false) === "file.ext" - * ``` - */ - export function getBaseFileName(path: string, extensions: string | readonly string[], ignoreCase: boolean): string; - export function getBaseFileName(path: string, extensions?: string | readonly string[], ignoreCase?: boolean) { - path = normalizeSlashes(path); - - // if the path provided is itself the root, then it has not file name. - const rootLength = getRootLength(path); - if (rootLength === path.length) return ""; - - // return the trailing portion of the path starting after the last (non-terminal) directory - // separator but not including any trailing directory separator. - path = removeTrailingDirectorySeparator(path); - const name = path.slice(Math.max(getRootLength(path), path.lastIndexOf(directorySeparator) + 1)); - const extension = extensions !== undefined && ignoreCase !== undefined ? getAnyExtensionFromPath(name, extensions, ignoreCase) : undefined; - return extension ? name.slice(0, name.length - extension.length) : name; - } - - function tryGetExtensionFromPath(path: string, extension: string, stringEqualityComparer: (a: string, b: string) => boolean) { - if (!startsWith(extension, ".")) extension = "." + extension; - if (path.length >= extension.length && path.charCodeAt(path.length - extension.length) === CharacterCodes.dot) { - const pathExtension = path.slice(path.length - extension.length); - if (stringEqualityComparer(pathExtension, extension)) { - return pathExtension; } + return ~(authorityEnd + 1); // URL: "file://server/", "http://server/" } - } - - function getAnyExtensionFromPathWorker(path: string, extensions: string | readonly string[], stringEqualityComparer: (a: string, b: string) => boolean) { - if (typeof extensions === "string") { - return tryGetExtensionFromPath(path, extensions, stringEqualityComparer) || ""; - } - for (const extension of extensions) { - const result = tryGetExtensionFromPath(path, extension, stringEqualityComparer); - if (result) return result; - } + return ~path.length; // URL: "file://server", "http://server" + } + // relative + return 0; +} +/** + * Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files"). + * + * For example: + * ```ts + * getRootLength("a") === 0 // "" + * getRootLength("/") === 1 // "/" + * getRootLength("c:") === 2 // "c:" + * getRootLength("c:d") === 0 // "" + * getRootLength("c:/") === 3 // "c:/" + * getRootLength("c:\\") === 3 // "c:\\" + * getRootLength("//server") === 7 // "//server" + * getRootLength("//server/share") === 8 // "//server/" + * getRootLength("\\\\server") === 7 // "\\\\server" + * getRootLength("\\\\server\\share") === 8 // "\\\\server\\" + * getRootLength("file:///path") === 8 // "file:///" + * getRootLength("file:///c:") === 10 // "file:///c:" + * getRootLength("file:///c:d") === 8 // "file:///" + * getRootLength("file:///c:/path") === 11 // "file:///c:/" + * getRootLength("file://server") === 13 // "file://server" + * getRootLength("file://server/path") === 14 // "file://server/" + * getRootLength("http://server") === 13 // "http://server" + * getRootLength("http://server/path") === 14 // "http://server/" + * ``` + */ +/* @internal */ +export function getRootLength(path: string) { + const rootLength = getEncodedRootLength(path); + return rootLength < 0 ? ~rootLength : rootLength; +} +/** + * Returns the path except for its basename. Semantics align with NodeJS's `path.dirname` + * except that we support URLs as well. + * + * ```ts + * // POSIX + * getDirectoryPath("/path/to/file.ext") === "/path/to" + * getDirectoryPath("/path/to/") === "/path" + * getDirectoryPath("/") === "/" + * // DOS + * getDirectoryPath("c:/path/to/file.ext") === "c:/path/to" + * getDirectoryPath("c:/path/to/") === "c:/path" + * getDirectoryPath("c:/") === "c:/" + * getDirectoryPath("c:") === "c:" + * // URL + * getDirectoryPath("http://typescriptlang.org/path/to/file.ext") === "http://typescriptlang.org/path/to" + * getDirectoryPath("http://typescriptlang.org/path/to") === "http://typescriptlang.org/path" + * getDirectoryPath("http://typescriptlang.org/") === "http://typescriptlang.org/" + * getDirectoryPath("http://typescriptlang.org") === "http://typescriptlang.org" + * ``` + */ +/* @internal */ +export function getDirectoryPath(path: Path): Path; +/** + * Returns the path except for its basename. Semantics align with NodeJS's `path.dirname` + * except that we support URLs as well. + * + * ```ts + * // POSIX + * getDirectoryPath("/path/to/file.ext") === "/path/to" + * getDirectoryPath("/path/to/") === "/path" + * getDirectoryPath("/") === "/" + * // DOS + * getDirectoryPath("c:/path/to/file.ext") === "c:/path/to" + * getDirectoryPath("c:/path/to/") === "c:/path" + * getDirectoryPath("c:/") === "c:/" + * getDirectoryPath("c:") === "c:" + * // URL + * getDirectoryPath("http://typescriptlang.org/path/to/file.ext") === "http://typescriptlang.org/path/to" + * getDirectoryPath("http://typescriptlang.org/path/to") === "http://typescriptlang.org/path" + * getDirectoryPath("http://typescriptlang.org/") === "http://typescriptlang.org/" + * getDirectoryPath("http://typescriptlang.org") === "http://typescriptlang.org" + * getDirectoryPath("file://server/path/to/file.ext") === "file://server/path/to" + * getDirectoryPath("file://server/path/to") === "file://server/path" + * getDirectoryPath("file://server/") === "file://server/" + * getDirectoryPath("file://server") === "file://server" + * getDirectoryPath("file:///path/to/file.ext") === "file:///path/to" + * getDirectoryPath("file:///path/to") === "file:///path" + * getDirectoryPath("file:///") === "file:///" + * getDirectoryPath("file://") === "file://" + * ``` + */ +/* @internal */ +export function getDirectoryPath(path: string): string; +/* @internal */ +export function getDirectoryPath(path: string): string { + path = normalizeSlashes(path); + // If the path provided is itself the root, then return it. + const rootLength = getRootLength(path); + if (rootLength === path.length) + return path; + // return the leading portion of the path up to the last (non-terminal) directory separator + // but not including any trailing directory separator. + path = removeTrailingDirectorySeparator(path); + return path.slice(0, Math.max(rootLength, path.lastIndexOf(directorySeparator))); +} +/** + * Returns the path except for its containing directory name. + * Semantics align with NodeJS's `path.basename` except that we support URL's as well. + * + * ```ts + * // POSIX + * getBaseFileName("/path/to/file.ext") === "file.ext" + * getBaseFileName("/path/to/") === "to" + * getBaseFileName("/") === "" + * // DOS + * getBaseFileName("c:/path/to/file.ext") === "file.ext" + * getBaseFileName("c:/path/to/") === "to" + * getBaseFileName("c:/") === "" + * getBaseFileName("c:") === "" + * // URL + * getBaseFileName("http://typescriptlang.org/path/to/file.ext") === "file.ext" + * getBaseFileName("http://typescriptlang.org/path/to/") === "to" + * getBaseFileName("http://typescriptlang.org/") === "" + * getBaseFileName("http://typescriptlang.org") === "" + * getBaseFileName("file://server/path/to/file.ext") === "file.ext" + * getBaseFileName("file://server/path/to/") === "to" + * getBaseFileName("file://server/") === "" + * getBaseFileName("file://server") === "" + * getBaseFileName("file:///path/to/file.ext") === "file.ext" + * getBaseFileName("file:///path/to/") === "to" + * getBaseFileName("file:///") === "" + * getBaseFileName("file://") === "" + * ``` + */ +/* @internal */ +export function getBaseFileName(path: string): string; +/** + * Gets the portion of a path following the last (non-terminal) separator (`/`). + * Semantics align with NodeJS's `path.basename` except that we support URL's as well. + * If the base name has any one of the provided extensions, it is removed. + * + * ```ts + * getBaseFileName("/path/to/file.ext", ".ext", true) === "file" + * getBaseFileName("/path/to/file.js", ".ext", true) === "file.js" + * getBaseFileName("/path/to/file.js", [".ext", ".js"], true) === "file" + * getBaseFileName("/path/to/file.ext", ".EXT", false) === "file.ext" + * ``` + */ +/* @internal */ +export function getBaseFileName(path: string, extensions: string | readonly string[], ignoreCase: boolean): string; +/* @internal */ +export function getBaseFileName(path: string, extensions?: string | readonly string[], ignoreCase?: boolean) { + path = normalizeSlashes(path); + // if the path provided is itself the root, then it has not file name. + const rootLength = getRootLength(path); + if (rootLength === path.length) return ""; - } - - /** - * Gets the file extension for a path. - * - * ```ts - * getAnyExtensionFromPath("/path/to/file.ext") === ".ext" - * getAnyExtensionFromPath("/path/to/file.ext/") === ".ext" - * getAnyExtensionFromPath("/path/to/file") === "" - * getAnyExtensionFromPath("/path/to.ext/file") === "" - * ``` - */ - export function getAnyExtensionFromPath(path: string): string; - /** - * Gets the file extension for a path, provided it is one of the provided extensions. - * - * ```ts - * getAnyExtensionFromPath("/path/to/file.ext", ".ext", true) === ".ext" - * getAnyExtensionFromPath("/path/to/file.js", ".ext", true) === "" - * getAnyExtensionFromPath("/path/to/file.js", [".ext", ".js"], true) === ".js" - * getAnyExtensionFromPath("/path/to/file.ext", ".EXT", false) === "" - */ - export function getAnyExtensionFromPath(path: string, extensions: string | readonly string[], ignoreCase: boolean): string; - export function getAnyExtensionFromPath(path: string, extensions?: string | readonly string[], ignoreCase?: boolean): string { - // Retrieves any string from the final "." onwards from a base file name. - // Unlike extensionFromPath, which throws an exception on unrecognized extensions. - if (extensions) { - return getAnyExtensionFromPathWorker(removeTrailingDirectorySeparator(path), extensions, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive); - } - const baseFileName = getBaseFileName(path); - const extensionIndex = baseFileName.lastIndexOf("."); - if (extensionIndex >= 0) { - return baseFileName.substring(extensionIndex); + // return the trailing portion of the path starting after the last (non-terminal) directory + // separator but not including any trailing directory separator. + path = removeTrailingDirectorySeparator(path); + const name = path.slice(Math.max(getRootLength(path), path.lastIndexOf(directorySeparator) + 1)); + const extension = extensions !== undefined && ignoreCase !== undefined ? getAnyExtensionFromPath(name, extensions, ignoreCase) : undefined; + return extension ? name.slice(0, name.length - extension.length) : name; +} +/* @internal */ +function tryGetExtensionFromPath(path: string, extension: string, stringEqualityComparer: (a: string, b: string) => boolean) { + if (!startsWith(extension, ".")) + extension = "." + extension; + if (path.length >= extension.length && path.charCodeAt(path.length - extension.length) === CharacterCodes.dot) { + const pathExtension = path.slice(path.length - extension.length); + if (stringEqualityComparer(pathExtension, extension)) { + return pathExtension; } - return ""; - } - - function pathComponents(path: string, rootLength: number) { - const root = path.substring(0, rootLength); - const rest = path.substring(rootLength).split(directorySeparator); - if (rest.length && !lastOrUndefined(rest)) rest.pop(); - return [root, ...rest]; - } - - /** - * Parse a path into an array containing a root component (at index 0) and zero or more path - * components (at indices > 0). The result is not normalized. - * If the path is relative, the root component is `""`. - * If the path is absolute, the root component includes the first path separator (`/`). - * - * ```ts - * // POSIX - * getPathComponents("/path/to/file.ext") === ["/", "path", "to", "file.ext"] - * getPathComponents("/path/to/") === ["/", "path", "to"] - * getPathComponents("/") === ["/"] - * // DOS - * getPathComponents("c:/path/to/file.ext") === ["c:/", "path", "to", "file.ext"] - * getPathComponents("c:/path/to/") === ["c:/", "path", "to"] - * getPathComponents("c:/") === ["c:/"] - * getPathComponents("c:") === ["c:"] - * // URL - * getPathComponents("http://typescriptlang.org/path/to/file.ext") === ["http://typescriptlang.org/", "path", "to", "file.ext"] - * getPathComponents("http://typescriptlang.org/path/to/") === ["http://typescriptlang.org/", "path", "to"] - * getPathComponents("http://typescriptlang.org/") === ["http://typescriptlang.org/"] - * getPathComponents("http://typescriptlang.org") === ["http://typescriptlang.org"] - * getPathComponents("file://server/path/to/file.ext") === ["file://server/", "path", "to", "file.ext"] - * getPathComponents("file://server/path/to/") === ["file://server/", "path", "to"] - * getPathComponents("file://server/") === ["file://server/"] - * getPathComponents("file://server") === ["file://server"] - * getPathComponents("file:///path/to/file.ext") === ["file:///", "path", "to", "file.ext"] - * getPathComponents("file:///path/to/") === ["file:///", "path", "to"] - * getPathComponents("file:///") === ["file:///"] - * getPathComponents("file://") === ["file://"] - */ - export function getPathComponents(path: string, currentDirectory = "") { - path = combinePaths(currentDirectory, path); - return pathComponents(path, getRootLength(path)); } - - //// Path Formatting - - /** - * Formats a parsed path consisting of a root component (at index 0) and zero or more path - * segments (at indices > 0). - * - * ```ts - * getPathFromPathComponents(["/", "path", "to", "file.ext"]) === "/path/to/file.ext" - * ``` - */ - export function getPathFromPathComponents(pathComponents: readonly string[]) { - if (pathComponents.length === 0) return ""; - - const root = pathComponents[0] && ensureTrailingDirectorySeparator(pathComponents[0]); - return root + pathComponents.slice(1).join(directorySeparator); +} +/* @internal */ +function getAnyExtensionFromPathWorker(path: string, extensions: string | readonly string[], stringEqualityComparer: (a: string, b: string) => boolean) { + if (typeof extensions === "string") { + return tryGetExtensionFromPath(path, extensions, stringEqualityComparer) || ""; } - - //// Path Normalization - - /** - * Normalize path separators, converting `\` into `/`. - */ - export function normalizeSlashes(path: string): string { - return path.replace(backslashRegExp, directorySeparator); + for (const extension of extensions) { + const result = tryGetExtensionFromPath(path, extension, stringEqualityComparer); + if (result) + return result; } - - /** - * Reduce an array of path components to a more simplified path by navigating any - * `"."` or `".."` entries in the path. - */ - export function reducePathComponents(components: readonly string[]) { - if (!some(components)) return []; - const reduced = [components[0]]; - for (let i = 1; i < components.length; i++) { - const component = components[i]; - if (!component) continue; - if (component === ".") continue; - if (component === "..") { - if (reduced.length > 1) { - if (reduced[reduced.length - 1] !== "..") { - reduced.pop(); - continue; - } + return ""; +} +/** + * Gets the file extension for a path. + * + * ```ts + * getAnyExtensionFromPath("/path/to/file.ext") === ".ext" + * getAnyExtensionFromPath("/path/to/file.ext/") === ".ext" + * getAnyExtensionFromPath("/path/to/file") === "" + * getAnyExtensionFromPath("/path/to.ext/file") === "" + * ``` + */ +/* @internal */ +export function getAnyExtensionFromPath(path: string): string; +/** + * Gets the file extension for a path, provided it is one of the provided extensions. + * + * ```ts + * getAnyExtensionFromPath("/path/to/file.ext", ".ext", true) === ".ext" + * getAnyExtensionFromPath("/path/to/file.js", ".ext", true) === "" + * getAnyExtensionFromPath("/path/to/file.js", [".ext", ".js"], true) === ".js" + * getAnyExtensionFromPath("/path/to/file.ext", ".EXT", false) === "" + */ +/* @internal */ +export function getAnyExtensionFromPath(path: string, extensions: string | readonly string[], ignoreCase: boolean): string; +/* @internal */ +export function getAnyExtensionFromPath(path: string, extensions?: string | readonly string[], ignoreCase?: boolean): string { + // Retrieves any string from the final "." onwards from a base file name. + // Unlike extensionFromPath, which throws an exception on unrecognized extensions. + if (extensions) { + return getAnyExtensionFromPathWorker(removeTrailingDirectorySeparator(path), extensions, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive); + } + const baseFileName = getBaseFileName(path); + const extensionIndex = baseFileName.lastIndexOf("."); + if (extensionIndex >= 0) { + return baseFileName.substring(extensionIndex); + } + return ""; +} +/* @internal */ +function pathComponents(path: string, rootLength: number) { + const root = path.substring(0, rootLength); + const rest = path.substring(rootLength).split(directorySeparator); + if (rest.length && !lastOrUndefined(rest)) + rest.pop(); + return [root, ...rest]; +} +/** + * Parse a path into an array containing a root component (at index 0) and zero or more path + * components (at indices > 0). The result is not normalized. + * If the path is relative, the root component is `""`. + * If the path is absolute, the root component includes the first path separator (`/`). + * + * ```ts + * // POSIX + * getPathComponents("/path/to/file.ext") === ["/", "path", "to", "file.ext"] + * getPathComponents("/path/to/") === ["/", "path", "to"] + * getPathComponents("/") === ["/"] + * // DOS + * getPathComponents("c:/path/to/file.ext") === ["c:/", "path", "to", "file.ext"] + * getPathComponents("c:/path/to/") === ["c:/", "path", "to"] + * getPathComponents("c:/") === ["c:/"] + * getPathComponents("c:") === ["c:"] + * // URL + * getPathComponents("http://typescriptlang.org/path/to/file.ext") === ["http://typescriptlang.org/", "path", "to", "file.ext"] + * getPathComponents("http://typescriptlang.org/path/to/") === ["http://typescriptlang.org/", "path", "to"] + * getPathComponents("http://typescriptlang.org/") === ["http://typescriptlang.org/"] + * getPathComponents("http://typescriptlang.org") === ["http://typescriptlang.org"] + * getPathComponents("file://server/path/to/file.ext") === ["file://server/", "path", "to", "file.ext"] + * getPathComponents("file://server/path/to/") === ["file://server/", "path", "to"] + * getPathComponents("file://server/") === ["file://server/"] + * getPathComponents("file://server") === ["file://server"] + * getPathComponents("file:///path/to/file.ext") === ["file:///", "path", "to", "file.ext"] + * getPathComponents("file:///path/to/") === ["file:///", "path", "to"] + * getPathComponents("file:///") === ["file:///"] + * getPathComponents("file://") === ["file://"] + */ +/* @internal */ +export function getPathComponents(path: string, currentDirectory = "") { + path = combinePaths(currentDirectory, path); + return pathComponents(path, getRootLength(path)); +} +//// Path Formatting +/** + * Formats a parsed path consisting of a root component (at index 0) and zero or more path + * segments (at indices > 0). + * + * ```ts + * getPathFromPathComponents(["/", "path", "to", "file.ext"]) === "/path/to/file.ext" + * ``` + */ +/* @internal */ +export function getPathFromPathComponents(pathComponents: readonly string[]) { + if (pathComponents.length === 0) + return ""; + const root = pathComponents[0] && ensureTrailingDirectorySeparator(pathComponents[0]); + return root + pathComponents.slice(1).join(directorySeparator); +} +//// Path Normalization +/** + * Normalize path separators, converting `\` into `/`. + */ +/* @internal */ +export function normalizeSlashes(path: string): string { + return path.replace(backslashRegExp, directorySeparator); +} +/** + * Reduce an array of path components to a more simplified path by navigating any + * `"."` or `".."` entries in the path. + */ +/* @internal */ +export function reducePathComponents(components: readonly string[]) { + if (!some(components)) + return []; + const reduced = [components[0]]; + for (let i = 1; i < components.length; i++) { + const component = components[i]; + if (!component) + continue; + if (component === ".") + continue; + if (component === "..") { + if (reduced.length > 1) { + if (reduced[reduced.length - 1] !== "..") { + reduced.pop(); + continue; } - else if (reduced[0]) continue; - } - reduced.push(component); - } - return reduced; - } - - /** - * Combines paths. If a path is absolute, it replaces any previous path. Relative paths are not simplified. - * - * ```ts - * // Non-rooted - * combinePaths("path", "to", "file.ext") === "path/to/file.ext" - * combinePaths("path", "dir", "..", "to", "file.ext") === "path/dir/../to/file.ext" - * // POSIX - * combinePaths("/path", "to", "file.ext") === "/path/to/file.ext" - * combinePaths("/path", "/to", "file.ext") === "/to/file.ext" - * // DOS - * combinePaths("c:/path", "to", "file.ext") === "c:/path/to/file.ext" - * combinePaths("c:/path", "c:/to", "file.ext") === "c:/to/file.ext" - * // URL - * combinePaths("file:///path", "to", "file.ext") === "file:///path/to/file.ext" - * combinePaths("file:///path", "file:///to", "file.ext") === "file:///to/file.ext" - * ``` - */ - export function combinePaths(path: string, ...paths: (string | undefined)[]): string { - if (path) path = normalizeSlashes(path); - for (let relativePath of paths) { - if (!relativePath) continue; - relativePath = normalizeSlashes(relativePath); - if (!path || getRootLength(relativePath) !== 0) { - path = relativePath; - } - else { - path = ensureTrailingDirectorySeparator(path) + relativePath; } + else if (reduced[0]) + continue; } - return path; - } - - /** - * Combines and resolves paths. If a path is absolute, it replaces any previous path. Any - * `.` and `..` path components are resolved. Trailing directory separators are preserved. - * - * ```ts - * resolvePath("/path", "to", "file.ext") === "path/to/file.ext" - * resolvePath("/path", "to", "file.ext/") === "path/to/file.ext/" - * resolvePath("/path", "dir", "..", "to", "file.ext") === "path/to/file.ext" - * ``` - */ - export function resolvePath(path: string, ...paths: (string | undefined)[]): string { - return normalizePath(some(paths) ? combinePaths(path, ...paths) : normalizeSlashes(path)); - } - - /** - * Parse a path into an array containing a root component (at index 0) and zero or more path - * components (at indices > 0). The result is normalized. - * If the path is relative, the root component is `""`. - * If the path is absolute, the root component includes the first path separator (`/`). - * - * ```ts - * getNormalizedPathComponents("to/dir/../file.ext", "/path/") === ["/", "path", "to", "file.ext"] - */ - export function getNormalizedPathComponents(path: string, currentDirectory: string | undefined) { - return reducePathComponents(getPathComponents(path, currentDirectory)); - } - - export function getNormalizedAbsolutePath(fileName: string, currentDirectory: string | undefined) { - return getPathFromPathComponents(getNormalizedPathComponents(fileName, currentDirectory)); - } - - export function normalizePath(path: string): string { - path = normalizeSlashes(path); - const normalized = getPathFromPathComponents(reducePathComponents(getPathComponents(path))); - return normalized && hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(normalized) : normalized; - } - - function getPathWithoutRoot(pathComponents: readonly string[]) { - if (pathComponents.length === 0) return ""; - return pathComponents.slice(1).join(directorySeparator); - } - - export function getNormalizedAbsolutePathWithoutRoot(fileName: string, currentDirectory: string | undefined) { - return getPathWithoutRoot(getNormalizedPathComponents(fileName, currentDirectory)); - } - - export function toPath(fileName: string, basePath: string | undefined, getCanonicalFileName: (path: string) => string): Path { - const nonCanonicalizedPath = isRootedDiskPath(fileName) - ? normalizePath(fileName) - : getNormalizedAbsolutePath(fileName, basePath); - return getCanonicalFileName(nonCanonicalizedPath); - } - - export function normalizePathAndParts(path: string): { path: string, parts: string[] } { + reduced.push(component); + } + return reduced; +} +/** + * Combines paths. If a path is absolute, it replaces any previous path. Relative paths are not simplified. + * + * ```ts + * // Non-rooted + * combinePaths("path", "to", "file.ext") === "path/to/file.ext" + * combinePaths("path", "dir", "..", "to", "file.ext") === "path/dir/../to/file.ext" + * // POSIX + * combinePaths("/path", "to", "file.ext") === "/path/to/file.ext" + * combinePaths("/path", "/to", "file.ext") === "/to/file.ext" + * // DOS + * combinePaths("c:/path", "to", "file.ext") === "c:/path/to/file.ext" + * combinePaths("c:/path", "c:/to", "file.ext") === "c:/to/file.ext" + * // URL + * combinePaths("file:///path", "to", "file.ext") === "file:///path/to/file.ext" + * combinePaths("file:///path", "file:///to", "file.ext") === "file:///to/file.ext" + * ``` + */ +/* @internal */ +export function combinePaths(path: string, ...paths: (string | undefined)[]): string { + if (path) path = normalizeSlashes(path); - const [root, ...parts] = reducePathComponents(getPathComponents(path)); - if (parts.length) { - const joinedParts = root + parts.join(directorySeparator); - return { path: hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(joinedParts) : joinedParts, parts }; + for (let relativePath of paths) { + if (!relativePath) + continue; + relativePath = normalizeSlashes(relativePath); + if (!path || getRootLength(relativePath) !== 0) { + path = relativePath; } else { - return { path: root, parts }; - } - } - - //// Path Mutation - - /** - * Removes a trailing directory separator from a path, if it does not already have one. - * - * ```ts - * removeTrailingDirectorySeparator("/path/to/file.ext") === "/path/to/file.ext" - * removeTrailingDirectorySeparator("/path/to/file.ext/") === "/path/to/file.ext" - * ``` - */ - export function removeTrailingDirectorySeparator(path: Path): Path; - export function removeTrailingDirectorySeparator(path: string): string; - export function removeTrailingDirectorySeparator(path: string) { - if (hasTrailingDirectorySeparator(path)) { - return path.substr(0, path.length - 1); - } - - return path; - } - - /** - * Adds a trailing directory separator to a path, if it does not already have one. - * - * ```ts - * ensureTrailingDirectorySeparator("/path/to/file.ext") === "/path/to/file.ext/" - * ensureTrailingDirectorySeparator("/path/to/file.ext/") === "/path/to/file.ext/" - * ``` - */ - export function ensureTrailingDirectorySeparator(path: Path): Path; - export function ensureTrailingDirectorySeparator(path: string): string; - export function ensureTrailingDirectorySeparator(path: string) { - if (!hasTrailingDirectorySeparator(path)) { - return path + directorySeparator; + path = ensureTrailingDirectorySeparator(path) + relativePath; } - - return path; - } - - /** - * Ensures a path is either absolute (prefixed with `/` or `c:`) or dot-relative (prefixed - * with `./` or `../`) so as not to be confused with an unprefixed module name. - * - * ```ts - * ensurePathIsNonModuleName("/path/to/file.ext") === "/path/to/file.ext" - * ensurePathIsNonModuleName("./path/to/file.ext") === "./path/to/file.ext" - * ensurePathIsNonModuleName("../path/to/file.ext") === "../path/to/file.ext" - * ensurePathIsNonModuleName("path/to/file.ext") === "./path/to/file.ext" - * ``` - */ - export function ensurePathIsNonModuleName(path: string): string { - return !pathIsAbsolute(path) && !pathIsRelative(path) ? "./" + path : path; - } - - /** - * Changes the extension of a path to the provided extension. - * - * ```ts - * changeAnyExtension("/path/to/file.ext", ".js") === "/path/to/file.js" - * ``` - */ - export function changeAnyExtension(path: string, ext: string): string; - /** - * Changes the extension of a path to the provided extension if it has one of the provided extensions. - * - * ```ts - * changeAnyExtension("/path/to/file.ext", ".js", ".ext") === "/path/to/file.js" - * changeAnyExtension("/path/to/file.ext", ".js", ".ts") === "/path/to/file.ext" - * changeAnyExtension("/path/to/file.ext", ".js", [".ext", ".ts"]) === "/path/to/file.js" - * ``` - */ - export function changeAnyExtension(path: string, ext: string, extensions: string | readonly string[], ignoreCase: boolean): string; - export function changeAnyExtension(path: string, ext: string, extensions?: string | readonly string[], ignoreCase?: boolean) { - const pathext = extensions !== undefined && ignoreCase !== undefined ? getAnyExtensionFromPath(path, extensions, ignoreCase) : getAnyExtensionFromPath(path); - return pathext ? path.slice(0, path.length - pathext.length) + (startsWith(ext, ".") ? ext : "." + ext) : path; } - - //// Path Comparisons - - // check path for these segments: '', '.'. '..' - const relativePathSegmentRegExp = /(^|\/)\.{0,2}($|\/)/; - - function comparePathsWorker(a: string, b: string, componentComparer: (a: string, b: string) => Comparison) { - if (a === b) return Comparison.EqualTo; - if (a === undefined) return Comparison.LessThan; - if (b === undefined) return Comparison.GreaterThan; - - // NOTE: Performance optimization - shortcut if the root segments differ as there would be no - // need to perform path reduction. - const aRoot = a.substring(0, getRootLength(a)); - const bRoot = b.substring(0, getRootLength(b)); - const result = compareStringsCaseInsensitive(aRoot, bRoot); + return path; +} +/** + * Combines and resolves paths. If a path is absolute, it replaces any previous path. Any + * `.` and `..` path components are resolved. Trailing directory separators are preserved. + * + * ```ts + * resolvePath("/path", "to", "file.ext") === "path/to/file.ext" + * resolvePath("/path", "to", "file.ext/") === "path/to/file.ext/" + * resolvePath("/path", "dir", "..", "to", "file.ext") === "path/to/file.ext" + * ``` + */ +/* @internal */ +export function resolvePath(path: string, ...paths: (string | undefined)[]): string { + return normalizePath(some(paths) ? combinePaths(path, ...paths) : normalizeSlashes(path)); +} +/** + * Parse a path into an array containing a root component (at index 0) and zero or more path + * components (at indices > 0). The result is normalized. + * If the path is relative, the root component is `""`. + * If the path is absolute, the root component includes the first path separator (`/`). + * + * ```ts + * getNormalizedPathComponents("to/dir/../file.ext", "/path/") === ["/", "path", "to", "file.ext"] + */ +/* @internal */ +export function getNormalizedPathComponents(path: string, currentDirectory: string | undefined) { + return reducePathComponents(getPathComponents(path, currentDirectory)); +} +/* @internal */ +export function getNormalizedAbsolutePath(fileName: string, currentDirectory: string | undefined) { + return getPathFromPathComponents(getNormalizedPathComponents(fileName, currentDirectory)); +} +/* @internal */ +export function normalizePath(path: string): string { + path = normalizeSlashes(path); + const normalized = getPathFromPathComponents(reducePathComponents(getPathComponents(path))); + return normalized && hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(normalized) : normalized; +} +/* @internal */ +function getPathWithoutRoot(pathComponents: readonly string[]) { + if (pathComponents.length === 0) + return ""; + return pathComponents.slice(1).join(directorySeparator); +} +/* @internal */ +export function getNormalizedAbsolutePathWithoutRoot(fileName: string, currentDirectory: string | undefined) { + return getPathWithoutRoot(getNormalizedPathComponents(fileName, currentDirectory)); +} +/* @internal */ +export function toPath(fileName: string, basePath: string | undefined, getCanonicalFileName: (path: string) => string): Path { + const nonCanonicalizedPath = isRootedDiskPath(fileName) + ? normalizePath(fileName) + : getNormalizedAbsolutePath(fileName, basePath); + return getCanonicalFileName(nonCanonicalizedPath); +} +/* @internal */ +export function normalizePathAndParts(path: string): { + path: string; + parts: string[]; +} { + path = normalizeSlashes(path); + const [root, ...parts] = reducePathComponents(getPathComponents(path)); + if (parts.length) { + const joinedParts = root + parts.join(directorySeparator); + return { path: hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(joinedParts) : joinedParts, parts }; + } + else { + return { path: root, parts }; + } +} +//// Path Mutation +/** + * Removes a trailing directory separator from a path, if it does not already have one. + * + * ```ts + * removeTrailingDirectorySeparator("/path/to/file.ext") === "/path/to/file.ext" + * removeTrailingDirectorySeparator("/path/to/file.ext/") === "/path/to/file.ext" + * ``` + */ +/* @internal */ +export function removeTrailingDirectorySeparator(path: Path): Path; +/* @internal */ +export function removeTrailingDirectorySeparator(path: string): string; +/* @internal */ +export function removeTrailingDirectorySeparator(path: string) { + if (hasTrailingDirectorySeparator(path)) { + return path.substr(0, path.length - 1); + } + return path; +} +/** + * Adds a trailing directory separator to a path, if it does not already have one. + * + * ```ts + * ensureTrailingDirectorySeparator("/path/to/file.ext") === "/path/to/file.ext/" + * ensureTrailingDirectorySeparator("/path/to/file.ext/") === "/path/to/file.ext/" + * ``` + */ +/* @internal */ +export function ensureTrailingDirectorySeparator(path: Path): Path; +/* @internal */ +export function ensureTrailingDirectorySeparator(path: string): string; +/* @internal */ +export function ensureTrailingDirectorySeparator(path: string) { + if (!hasTrailingDirectorySeparator(path)) { + return path + directorySeparator; + } + return path; +} +/** + * Ensures a path is either absolute (prefixed with `/` or `c:`) or dot-relative (prefixed + * with `./` or `../`) so as not to be confused with an unprefixed module name. + * + * ```ts + * ensurePathIsNonModuleName("/path/to/file.ext") === "/path/to/file.ext" + * ensurePathIsNonModuleName("./path/to/file.ext") === "./path/to/file.ext" + * ensurePathIsNonModuleName("../path/to/file.ext") === "../path/to/file.ext" + * ensurePathIsNonModuleName("path/to/file.ext") === "./path/to/file.ext" + * ``` + */ +/* @internal */ +export function ensurePathIsNonModuleName(path: string): string { + return !pathIsAbsolute(path) && !pathIsRelative(path) ? "./" + path : path; +} +/** + * Changes the extension of a path to the provided extension. + * + * ```ts + * changeAnyExtension("/path/to/file.ext", ".js") === "/path/to/file.js" + * ``` + */ +/* @internal */ +export function changeAnyExtension(path: string, ext: string): string; +/** + * Changes the extension of a path to the provided extension if it has one of the provided extensions. + * + * ```ts + * changeAnyExtension("/path/to/file.ext", ".js", ".ext") === "/path/to/file.js" + * changeAnyExtension("/path/to/file.ext", ".js", ".ts") === "/path/to/file.ext" + * changeAnyExtension("/path/to/file.ext", ".js", [".ext", ".ts"]) === "/path/to/file.js" + * ``` + */ +/* @internal */ +export function changeAnyExtension(path: string, ext: string, extensions: string | readonly string[], ignoreCase: boolean): string; +/* @internal */ +export function changeAnyExtension(path: string, ext: string, extensions?: string | readonly string[], ignoreCase?: boolean) { + const pathext = extensions !== undefined && ignoreCase !== undefined ? getAnyExtensionFromPath(path, extensions, ignoreCase) : getAnyExtensionFromPath(path); + return pathext ? path.slice(0, path.length - pathext.length) + (startsWith(ext, ".") ? ext : "." + ext) : path; +} +//// Path Comparisons +// check path for these segments: '', '.'. '..' +/* @internal */ +const relativePathSegmentRegExp = /(^|\/)\.{0,2}($|\/)/; +/* @internal */ +function comparePathsWorker(a: string, b: string, componentComparer: (a: string, b: string) => Comparison) { + if (a === b) + return Comparison.EqualTo; + if (a === undefined) + return Comparison.LessThan; + if (b === undefined) + return Comparison.GreaterThan; + // NOTE: Performance optimization - shortcut if the root segments differ as there would be no + // need to perform path reduction. + const aRoot = a.substring(0, getRootLength(a)); + const bRoot = b.substring(0, getRootLength(b)); + const result = compareStringsCaseInsensitive(aRoot, bRoot); + if (result !== Comparison.EqualTo) { + return result; + } + // NOTE: Performance optimization - shortcut if there are no relative path segments in + // the non-root portion of the path + const aRest = a.substring(aRoot.length); + const bRest = b.substring(bRoot.length); + if (!relativePathSegmentRegExp.test(aRest) && !relativePathSegmentRegExp.test(bRest)) { + return componentComparer(aRest, bRest); + } + // The path contains a relative path segment. Normalize the paths and perform a slower component + // by component comparison. + const aComponents = reducePathComponents(getPathComponents(a)); + const bComponents = reducePathComponents(getPathComponents(b)); + const sharedLength = Math.min(aComponents.length, bComponents.length); + for (let i = 1; i < sharedLength; i++) { + const result = componentComparer(aComponents[i], bComponents[i]); if (result !== Comparison.EqualTo) { return result; } - - // NOTE: Performance optimization - shortcut if there are no relative path segments in - // the non-root portion of the path - const aRest = a.substring(aRoot.length); - const bRest = b.substring(bRoot.length); - if (!relativePathSegmentRegExp.test(aRest) && !relativePathSegmentRegExp.test(bRest)) { - return componentComparer(aRest, bRest); - } - - // The path contains a relative path segment. Normalize the paths and perform a slower component - // by component comparison. - const aComponents = reducePathComponents(getPathComponents(a)); - const bComponents = reducePathComponents(getPathComponents(b)); - const sharedLength = Math.min(aComponents.length, bComponents.length); - for (let i = 1; i < sharedLength; i++) { - const result = componentComparer(aComponents[i], bComponents[i]); - if (result !== Comparison.EqualTo) { - return result; - } - } - return compareValues(aComponents.length, bComponents.length); - } - - /** - * Performs a case-sensitive comparison of two paths. Path roots are always compared case-insensitively. - */ - export function comparePathsCaseSensitive(a: string, b: string) { - return comparePathsWorker(a, b, compareStringsCaseSensitive); } - - /** - * Performs a case-insensitive comparison of two paths. - */ - export function comparePathsCaseInsensitive(a: string, b: string) { - return comparePathsWorker(a, b, compareStringsCaseInsensitive); + return compareValues(aComponents.length, bComponents.length); +} +/** + * Performs a case-sensitive comparison of two paths. Path roots are always compared case-insensitively. + */ +/* @internal */ +export function comparePathsCaseSensitive(a: string, b: string) { + return comparePathsWorker(a, b, compareStringsCaseSensitive); +} +/** + * Performs a case-insensitive comparison of two paths. + */ +/* @internal */ +export function comparePathsCaseInsensitive(a: string, b: string) { + return comparePathsWorker(a, b, compareStringsCaseInsensitive); +} +/** + * Compare two paths using the provided case sensitivity. + */ +/* @internal */ +export function comparePaths(a: string, b: string, ignoreCase?: boolean): Comparison; +/* @internal */ +export function comparePaths(a: string, b: string, currentDirectory: string, ignoreCase?: boolean): Comparison; +/* @internal */ +export function comparePaths(a: string, b: string, currentDirectory?: string | boolean, ignoreCase?: boolean) { + if (typeof currentDirectory === "string") { + a = combinePaths(currentDirectory, a); + b = combinePaths(currentDirectory, b); + } + else if (typeof currentDirectory === "boolean") { + ignoreCase = currentDirectory; + } + return comparePathsWorker(a, b, getStringComparer(ignoreCase)); +} +/** + * Determines whether a `parent` path contains a `child` path using the provide case sensitivity. + */ +/* @internal */ +export function containsPath(parent: string, child: string, ignoreCase?: boolean): boolean; +/* @internal */ +export function containsPath(parent: string, child: string, currentDirectory: string, ignoreCase?: boolean): boolean; +/* @internal */ +export function containsPath(parent: string, child: string, currentDirectory?: string | boolean, ignoreCase?: boolean) { + if (typeof currentDirectory === "string") { + parent = combinePaths(currentDirectory, parent); + child = combinePaths(currentDirectory, child); } - - /** - * Compare two paths using the provided case sensitivity. - */ - export function comparePaths(a: string, b: string, ignoreCase?: boolean): Comparison; - export function comparePaths(a: string, b: string, currentDirectory: string, ignoreCase?: boolean): Comparison; - export function comparePaths(a: string, b: string, currentDirectory?: string | boolean, ignoreCase?: boolean) { - if (typeof currentDirectory === "string") { - a = combinePaths(currentDirectory, a); - b = combinePaths(currentDirectory, b); - } - else if (typeof currentDirectory === "boolean") { - ignoreCase = currentDirectory; - } - return comparePathsWorker(a, b, getStringComparer(ignoreCase)); + else if (typeof currentDirectory === "boolean") { + ignoreCase = currentDirectory; } - - /** - * Determines whether a `parent` path contains a `child` path using the provide case sensitivity. - */ - export function containsPath(parent: string, child: string, ignoreCase?: boolean): boolean; - export function containsPath(parent: string, child: string, currentDirectory: string, ignoreCase?: boolean): boolean; - export function containsPath(parent: string, child: string, currentDirectory?: string | boolean, ignoreCase?: boolean) { - if (typeof currentDirectory === "string") { - parent = combinePaths(currentDirectory, parent); - child = combinePaths(currentDirectory, child); - } - else if (typeof currentDirectory === "boolean") { - ignoreCase = currentDirectory; - } - if (parent === undefined || child === undefined) return false; - if (parent === child) return true; - const parentComponents = reducePathComponents(getPathComponents(parent)); - const childComponents = reducePathComponents(getPathComponents(child)); - if (childComponents.length < parentComponents.length) { - return false; - } - - const componentEqualityComparer = ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive; - for (let i = 0; i < parentComponents.length; i++) { - const equalityComparer = i === 0 ? equateStringsCaseInsensitive : componentEqualityComparer; - if (!equalityComparer(parentComponents[i], childComponents[i])) { - return false; - } - } - + if (parent === undefined || child === undefined) + return false; + if (parent === child) return true; + const parentComponents = reducePathComponents(getPathComponents(parent)); + const childComponents = reducePathComponents(getPathComponents(child)); + if (childComponents.length < parentComponents.length) { + return false; } - - /** - * Determines whether `fileName` starts with the specified `directoryName` using the provided path canonicalization callback. - * Comparison is case-sensitive between the canonical paths. - * - * @deprecated Use `containsPath` if possible. - */ - export function startsWithDirectory(fileName: string, directoryName: string, getCanonicalFileName: GetCanonicalFileName): boolean { - const canonicalFileName = getCanonicalFileName(fileName); - const canonicalDirectoryName = getCanonicalFileName(directoryName); - return startsWith(canonicalFileName, canonicalDirectoryName + "/") || startsWith(canonicalFileName, canonicalDirectoryName + "\\"); - } - - //// Relative Paths - - export function getPathComponentsRelativeTo(from: string, to: string, stringEqualityComparer: (a: string, b: string) => boolean, getCanonicalFileName: GetCanonicalFileName) { - const fromComponents = reducePathComponents(getPathComponents(from)); - const toComponents = reducePathComponents(getPathComponents(to)); - - let start: number; - for (start = 0; start < fromComponents.length && start < toComponents.length; start++) { - const fromComponent = getCanonicalFileName(fromComponents[start]); - const toComponent = getCanonicalFileName(toComponents[start]); - const comparer = start === 0 ? equateStringsCaseInsensitive : stringEqualityComparer; - if (!comparer(fromComponent, toComponent)) break; - } - - if (start === 0) { - return toComponents; - } - - const components = toComponents.slice(start); - const relative: string[] = []; - for (; start < fromComponents.length; start++) { - relative.push(".."); + const componentEqualityComparer = ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive; + for (let i = 0; i < parentComponents.length; i++) { + const equalityComparer = i === 0 ? equateStringsCaseInsensitive : componentEqualityComparer; + if (!equalityComparer(parentComponents[i], childComponents[i])) { + return false; } - return ["", ...relative, ...components]; - } - - /** - * Gets a relative path that can be used to traverse between `from` and `to`. - */ - export function getRelativePathFromDirectory(from: string, to: string, ignoreCase: boolean): string; - /** - * Gets a relative path that can be used to traverse between `from` and `to`. - */ - export function getRelativePathFromDirectory(fromDirectory: string, to: string, getCanonicalFileName: GetCanonicalFileName): string; // eslint-disable-line @typescript-eslint/unified-signatures - export function getRelativePathFromDirectory(fromDirectory: string, to: string, getCanonicalFileNameOrIgnoreCase: GetCanonicalFileName | boolean) { - Debug.assert((getRootLength(fromDirectory) > 0) === (getRootLength(to) > 0), "Paths must either both be absolute or both be relative"); - const getCanonicalFileName = typeof getCanonicalFileNameOrIgnoreCase === "function" ? getCanonicalFileNameOrIgnoreCase : identity; - const ignoreCase = typeof getCanonicalFileNameOrIgnoreCase === "boolean" ? getCanonicalFileNameOrIgnoreCase : false; - const pathComponents = getPathComponentsRelativeTo(fromDirectory, to, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive, getCanonicalFileName); - return getPathFromPathComponents(pathComponents); - } - - export function convertToRelativePath(absoluteOrRelativePath: string, basePath: string, getCanonicalFileName: (path: string) => string): string { - return !isRootedDiskPath(absoluteOrRelativePath) - ? absoluteOrRelativePath - : getRelativePathToDirectoryOrUrl(basePath, absoluteOrRelativePath, basePath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); - } - - export function getRelativePathFromFile(from: string, to: string, getCanonicalFileName: GetCanonicalFileName) { - return ensurePathIsNonModuleName(getRelativePathFromDirectory(getDirectoryPath(from), to, getCanonicalFileName)); } - - export function getRelativePathToDirectoryOrUrl(directoryPathOrUrl: string, relativeOrAbsolutePath: string, currentDirectory: string, getCanonicalFileName: GetCanonicalFileName, isAbsolutePathAnUrl: boolean) { - const pathComponents = getPathComponentsRelativeTo( - resolvePath(currentDirectory, directoryPathOrUrl), - resolvePath(currentDirectory, relativeOrAbsolutePath), - equateStringsCaseSensitive, - getCanonicalFileName - ); - - const firstComponent = pathComponents[0]; - if (isAbsolutePathAnUrl && isRootedDiskPath(firstComponent)) { - const prefix = firstComponent.charAt(0) === directorySeparator ? "file://" : "file:///"; - pathComponents[0] = prefix + firstComponent; + return true; +} +/** + * Determines whether `fileName` starts with the specified `directoryName` using the provided path canonicalization callback. + * Comparison is case-sensitive between the canonical paths. + * + * @deprecated Use `containsPath` if possible. + */ +/* @internal */ +export function startsWithDirectory(fileName: string, directoryName: string, getCanonicalFileName: GetCanonicalFileName): boolean { + const canonicalFileName = getCanonicalFileName(fileName); + const canonicalDirectoryName = getCanonicalFileName(directoryName); + return startsWith(canonicalFileName, canonicalDirectoryName + "/") || startsWith(canonicalFileName, canonicalDirectoryName + "\\"); +} +//// Relative Paths +/* @internal */ +export function getPathComponentsRelativeTo(from: string, to: string, stringEqualityComparer: (a: string, b: string) => boolean, getCanonicalFileName: GetCanonicalFileName) { + const fromComponents = reducePathComponents(getPathComponents(from)); + const toComponents = reducePathComponents(getPathComponents(to)); + let start: number; + for (start = 0; start < fromComponents.length && start < toComponents.length; start++) { + const fromComponent = getCanonicalFileName(fromComponents[start]); + const toComponent = getCanonicalFileName(toComponents[start]); + const comparer = start === 0 ? equateStringsCaseInsensitive : stringEqualityComparer; + if (!comparer(fromComponent, toComponent)) + break; + } + if (start === 0) { + return toComponents; + } + const components = toComponents.slice(start); + const relative: string[] = []; + for (; start < fromComponents.length; start++) { + relative.push(".."); + } + return ["", ...relative, ...components]; +} +/** + * Gets a relative path that can be used to traverse between `from` and `to`. + */ +/* @internal */ +export function getRelativePathFromDirectory(from: string, to: string, ignoreCase: boolean): string; +/** + * Gets a relative path that can be used to traverse between `from` and `to`. + */ +/* @internal */ +export function getRelativePathFromDirectory(fromDirectory: string, to: string, getCanonicalFileName: GetCanonicalFileName): string; // eslint-disable-line @typescript-eslint/unified-signatures +/* @internal */ +export function getRelativePathFromDirectory(fromDirectory: string, to: string, getCanonicalFileNameOrIgnoreCase: GetCanonicalFileName | boolean) { + Debug.assert((getRootLength(fromDirectory) > 0) === (getRootLength(to) > 0), "Paths must either both be absolute or both be relative"); + const getCanonicalFileName = typeof getCanonicalFileNameOrIgnoreCase === "function" ? getCanonicalFileNameOrIgnoreCase : identity; + const ignoreCase = typeof getCanonicalFileNameOrIgnoreCase === "boolean" ? getCanonicalFileNameOrIgnoreCase : false; + const pathComponents = getPathComponentsRelativeTo(fromDirectory, to, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive, getCanonicalFileName); + return getPathFromPathComponents(pathComponents); +} +/* @internal */ +export function convertToRelativePath(absoluteOrRelativePath: string, basePath: string, getCanonicalFileName: (path: string) => string): string { + return !isRootedDiskPath(absoluteOrRelativePath) + ? absoluteOrRelativePath + : getRelativePathToDirectoryOrUrl(basePath, absoluteOrRelativePath, basePath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); +} +/* @internal */ +export function getRelativePathFromFile(from: string, to: string, getCanonicalFileName: GetCanonicalFileName) { + return ensurePathIsNonModuleName(getRelativePathFromDirectory(getDirectoryPath(from), to, getCanonicalFileName)); +} +/* @internal */ +export function getRelativePathToDirectoryOrUrl(directoryPathOrUrl: string, relativeOrAbsolutePath: string, currentDirectory: string, getCanonicalFileName: GetCanonicalFileName, isAbsolutePathAnUrl: boolean) { + const pathComponents = getPathComponentsRelativeTo(resolvePath(currentDirectory, directoryPathOrUrl), resolvePath(currentDirectory, relativeOrAbsolutePath), equateStringsCaseSensitive, getCanonicalFileName); + const firstComponent = pathComponents[0]; + if (isAbsolutePathAnUrl && isRootedDiskPath(firstComponent)) { + const prefix = firstComponent.charAt(0) === directorySeparator ? "file://" : "file:///"; + pathComponents[0] = prefix + firstComponent; + } + return getPathFromPathComponents(pathComponents); +} +//// Path Traversal +/** + * Calls `callback` on `directory` and every ancestor directory it has, returning the first defined result. + */ +/* @internal */ +export function forEachAncestorDirectory(directory: Path, callback: (directory: Path) => T | undefined): T | undefined; +/* @internal */ +export function forEachAncestorDirectory(directory: string, callback: (directory: string) => T | undefined): T | undefined; +/* @internal */ +export function forEachAncestorDirectory(directory: Path, callback: (directory: Path) => T | undefined): T | undefined { + while (true) { + const result = callback(directory); + if (result !== undefined) { + return result; } - - return getPathFromPathComponents(pathComponents); - } - - //// Path Traversal - - /** - * Calls `callback` on `directory` and every ancestor directory it has, returning the first defined result. - */ - export function forEachAncestorDirectory(directory: Path, callback: (directory: Path) => T | undefined): T | undefined; - export function forEachAncestorDirectory(directory: string, callback: (directory: string) => T | undefined): T | undefined; - export function forEachAncestorDirectory(directory: Path, callback: (directory: Path) => T | undefined): T | undefined { - while (true) { - const result = callback(directory); - if (result !== undefined) { - return result; - } - - const parentPath = getDirectoryPath(directory); - if (parentPath === directory) { - return undefined; - } - - directory = parentPath; + const parentPath = getDirectoryPath(directory); + if (parentPath === directory) { + return undefined; } + directory = parentPath; } -} \ No newline at end of file +} diff --git a/src/compiler/perfLogger.ts b/src/compiler/perfLogger.ts index 8ed8feac90c4c..1315c4cdce5fc 100644 --- a/src/compiler/perfLogger.ts +++ b/src/compiler/perfLogger.ts @@ -1,41 +1,42 @@ +import { noop } from "./ts"; /* @internal */ -namespace ts { - type PerfLogger = typeof import("@microsoft/typescript-etw"); - const nullLogger: PerfLogger = { - logEvent: noop, - logErrEvent: noop, - logPerfEvent: noop, - logInfoEvent: noop, - logStartCommand: noop, - logStopCommand: noop, - logStartUpdateProgram: noop, - logStopUpdateProgram: noop, - logStartUpdateGraph: noop, - logStopUpdateGraph: noop, - logStartResolveModule: noop, - logStopResolveModule: noop, - logStartParseSourceFile: noop, - logStopParseSourceFile: noop, - logStartReadFile: noop, - logStopReadFile: noop, - logStartBindFile: noop, - logStopBindFile: noop, - logStartScheduledOperation: noop, - logStopScheduledOperation: noop, - }; - - // Load optional module to enable Event Tracing for Windows - // See https://github.com/microsoft/typescript-etw for more information - let etwModule; - try { - // require() will throw an exception if the module is not installed - // It may also return undefined if not installed properly - etwModule = require("@microsoft/typescript-etw"); - } - catch (e) { - etwModule = undefined; - } - - /** Performance logger that will generate ETW events if possible - check for `logEvent` member, as `etwModule` will be `{}` when browserified */ - export const perfLogger: PerfLogger = etwModule && etwModule.logEvent ? etwModule : nullLogger; +type PerfLogger = typeof import("@microsoft/typescript-etw"); +/* @internal */ +const nullLogger: PerfLogger = { + logEvent: noop, + logErrEvent: noop, + logPerfEvent: noop, + logInfoEvent: noop, + logStartCommand: noop, + logStopCommand: noop, + logStartUpdateProgram: noop, + logStopUpdateProgram: noop, + logStartUpdateGraph: noop, + logStopUpdateGraph: noop, + logStartResolveModule: noop, + logStopResolveModule: noop, + logStartParseSourceFile: noop, + logStopParseSourceFile: noop, + logStartReadFile: noop, + logStopReadFile: noop, + logStartBindFile: noop, + logStopBindFile: noop, + logStartScheduledOperation: noop, + logStopScheduledOperation: noop, +}; +// Load optional module to enable Event Tracing for Windows +// See https://github.com/microsoft/typescript-etw for more information +/* @internal */ +let etwModule; +/* @internal */ +try { + // require() will throw an exception if the module is not installed + // It may also return undefined if not installed properly + etwModule = require("@microsoft/typescript-etw"); } +catch (e) { + etwModule = undefined; +} +/** Performance logger that will generate ETW events if possible - check for `logEvent` member, as `etwModule` will be `{}` when browserified */ +/* @internal */ +export const perfLogger: PerfLogger = etwModule && etwModule.logEvent ? etwModule : nullLogger; diff --git a/src/compiler/performance.ts b/src/compiler/performance.ts index 4a7f5c99278b2..daf52919d4be5 100644 --- a/src/compiler/performance.ts +++ b/src/compiler/performance.ts @@ -1,122 +1,127 @@ +import { Debug, noop, timestamp, createMap } from "./ts"; +import * as ts from "./ts"; /*@internal*/ /** Performance measurements for the compiler. */ -namespace ts.performance { - declare const onProfilerEvent: { (markName: string): void; profiler: boolean; }; - - // NOTE: cannot use ts.noop as core.ts loads after this - const profilerEvent: (markName: string) => void = typeof onProfilerEvent === "function" && onProfilerEvent.profiler === true ? onProfilerEvent : () => { /*empty*/ }; - - let enabled = false; - let profilerStart = 0; - let counts: Map; - let marks: Map; - let measures: Map; - - export interface Timer { - enter(): void; - exit(): void; - } - - export function createTimerIf(condition: boolean, measureName: string, startMarkName: string, endMarkName: string) { - return condition ? createTimer(measureName, startMarkName, endMarkName) : nullTimer; - } - - export function createTimer(measureName: string, startMarkName: string, endMarkName: string): Timer { - let enterCount = 0; - return { - enter, - exit - }; - - function enter() { - if (++enterCount === 1) { - mark(startMarkName); - } - } - - function exit() { - if (--enterCount === 0) { - mark(endMarkName); - measure(measureName, startMarkName, endMarkName); - } - else if (enterCount < 0) { - Debug.fail("enter/exit count does not match."); - } +declare const onProfilerEvent: { + (markName: string): void; + profiler: boolean; +}; +// NOTE: cannot use ts.noop as core.ts loads after this +/* @internal */ +const profilerEvent: (markName: string) => void = typeof onProfilerEvent === "function" && onProfilerEvent.profiler === true ? onProfilerEvent : () => { }; +/* @internal */ +let enabled = false; +/* @internal */ +let profilerStart = 0; +/* @internal */ +let counts: ts.Map; +/* @internal */ +let marks: ts.Map; +/* @internal */ +let measures: ts.Map; +/* @internal */ +export interface Timer { + enter(): void; + exit(): void; +} +/* @internal */ +export function createTimerIf(condition: boolean, measureName: string, startMarkName: string, endMarkName: string) { + return condition ? createTimer(measureName, startMarkName, endMarkName) : nullTimer; +} +/* @internal */ +export function createTimer(measureName: string, startMarkName: string, endMarkName: string): Timer { + let enterCount = 0; + return { + enter, + exit + }; + function enter() { + if (++enterCount === 1) { + mark(startMarkName); } } - - export const nullTimer: Timer = { enter: noop, exit: noop }; - - /** - * Marks a performance event. - * - * @param markName The name of the mark. - */ - export function mark(markName: string) { - if (enabled) { - marks.set(markName, timestamp()); - counts.set(markName, (counts.get(markName) || 0) + 1); - profilerEvent(markName); + function exit() { + if (--enterCount === 0) { + mark(endMarkName); + measure(measureName, startMarkName, endMarkName); } - } - - /** - * Adds a performance measurement with the specified name. - * - * @param measureName The name of the performance measurement. - * @param startMarkName The name of the starting mark. If not supplied, the point at which the - * profiler was enabled is used. - * @param endMarkName The name of the ending mark. If not supplied, the current timestamp is - * used. - */ - export function measure(measureName: string, startMarkName?: string, endMarkName?: string) { - if (enabled) { - const end = endMarkName && marks.get(endMarkName) || timestamp(); - const start = startMarkName && marks.get(startMarkName) || profilerStart; - measures.set(measureName, (measures.get(measureName) || 0) + (end - start)); + else if (enterCount < 0) { + Debug.fail("enter/exit count does not match."); } } - - /** - * Gets the number of times a marker was encountered. - * - * @param markName The name of the mark. - */ - export function getCount(markName: string) { - return counts && counts.get(markName) || 0; - } - - /** - * Gets the total duration of all measurements with the supplied name. - * - * @param measureName The name of the measure whose durations should be accumulated. - */ - export function getDuration(measureName: string) { - return measures && measures.get(measureName) || 0; - } - - /** - * Iterate over each measure, performing some action - * - * @param cb The action to perform for each measure - */ - export function forEachMeasure(cb: (measureName: string, duration: number) => void) { - measures.forEach((measure, key) => { - cb(key, measure); - }); - } - - /** Enables (and resets) performance measurements for the compiler. */ - export function enable() { - counts = createMap(); - marks = createMap(); - measures = createMap(); - enabled = true; - profilerStart = timestamp(); +} +/* @internal */ +export const nullTimer: Timer = { enter: noop, exit: noop }; +/** + * Marks a performance event. + * + * @param markName The name of the mark. + */ +/* @internal */ +export function mark(markName: string) { + if (enabled) { + marks.set(markName, timestamp()); + counts.set(markName, (counts.get(markName) || 0) + 1); + profilerEvent(markName); } - - /** Disables performance measurements for the compiler. */ - export function disable() { - enabled = false; +} +/** + * Adds a performance measurement with the specified name. + * + * @param measureName The name of the performance measurement. + * @param startMarkName The name of the starting mark. If not supplied, the point at which the + * profiler was enabled is used. + * @param endMarkName The name of the ending mark. If not supplied, the current timestamp is + * used. + */ +/* @internal */ +export function measure(measureName: string, startMarkName?: string, endMarkName?: string) { + if (enabled) { + const end = endMarkName && marks.get(endMarkName) || timestamp(); + const start = startMarkName && marks.get(startMarkName) || profilerStart; + measures.set(measureName, (measures.get(measureName) || 0) + (end - start)); } } +/** + * Gets the number of times a marker was encountered. + * + * @param markName The name of the mark. + */ +/* @internal */ +export function getCount(markName: string) { + return counts && counts.get(markName) || 0; +} +/** + * Gets the total duration of all measurements with the supplied name. + * + * @param measureName The name of the measure whose durations should be accumulated. + */ +/* @internal */ +export function getDuration(measureName: string) { + return measures && measures.get(measureName) || 0; +} +/** + * Iterate over each measure, performing some action + * + * @param cb The action to perform for each measure + */ +/* @internal */ +export function forEachMeasure(cb: (measureName: string, duration: number) => void) { + measures.forEach((measure, key) => { + cb(key, measure); + }); +} +/** Enables (and resets) performance measurements for the compiler. */ +/* @internal */ +export function enable() { + counts = createMap(); + marks = createMap(); + measures = createMap(); + enabled = true; + profilerStart = timestamp(); +} +/** Disables performance measurements for the compiler. */ +/* @internal */ +export function disable() { + enabled = false; +} diff --git a/src/compiler/performanceTimestamp.ts b/src/compiler/performanceTimestamp.ts index 044e7a65c0992..912c2cc4d900d 100644 --- a/src/compiler/performanceTimestamp.ts +++ b/src/compiler/performanceTimestamp.ts @@ -1,6 +1,7 @@ /*@internal*/ -namespace ts { - declare const performance: { now?(): number } | undefined; - /** Gets a timestamp with (at least) ms resolution */ - export const timestamp = typeof performance !== "undefined" && performance.now ? () => performance.now!() : Date.now ? Date.now : () => +(new Date()); -} \ No newline at end of file +declare const performance: { + now?(): number; +} | undefined; +/** Gets a timestamp with (at least) ms resolution */ +/* @internal */ +export const timestamp = typeof performance !== "undefined" && performance.now ? () => performance.now!() : Date.now ? Date.now : () => +(new Date()); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 847a85ec9f396..962a77eb4c6d0 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1,1652 +1,1499 @@ -namespace ts { - const ignoreDiagnosticCommentRegEx = /(^\s*$)|(^\s*\/\/\/?\s*(@ts-ignore)?)/; - - export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName = "tsconfig.json"): string | undefined { - return forEachAncestorDirectory(searchPath, ancestor => { - const fileName = combinePaths(ancestor, configName); - return fileExists(fileName) ? fileName : undefined; - }); - } - - export function resolveTripleslashReference(moduleName: string, containingFile: string): string { - const basePath = getDirectoryPath(containingFile); - const referencedFileName = isRootedDiskPath(moduleName) ? moduleName : combinePaths(basePath, moduleName); - return normalizePath(referencedFileName); - } - - /* @internal */ - export function computeCommonSourceDirectoryOfFilenames(fileNames: string[], currentDirectory: string, getCanonicalFileName: GetCanonicalFileName): string { - let commonPathComponents: string[] | undefined; - const failed = forEach(fileNames, sourceFile => { - // Each file contributes into common source file path - const sourcePathComponents = getNormalizedPathComponents(sourceFile, currentDirectory); - sourcePathComponents.pop(); // The base file name is not part of the common directory path - - if (!commonPathComponents) { - // first file - commonPathComponents = sourcePathComponents; - return; - } - - const n = Math.min(commonPathComponents.length, sourcePathComponents.length); - for (let i = 0; i < n; i++) { - if (getCanonicalFileName(commonPathComponents[i]) !== getCanonicalFileName(sourcePathComponents[i])) { - if (i === 0) { - // Failed to find any common path component - return true; - } - - // New common path found that is 0 -> i-1 - commonPathComponents.length = i; - break; +import { forEachAncestorDirectory, combinePaths, getDirectoryPath, isRootedDiskPath, normalizePath, GetCanonicalFileName, forEach, getNormalizedPathComponents, getPathFromPathComponents, CompilerOptions, CompilerHost, sys, createMap, createGetCanonicalFileName, ScriptTarget, SourceFile, createSourceFile, writeFileEnsuringDirectories, isWatchSet, missingFileModifiedTime, getNewLineCharacter, getDefaultLibFileName, memoize, maybeBind, WriteFileCallback, Path, fileExtensionIs, Extension, isBuildInfoFile, isDeclarationFileName, Program, CancellationToken, Diagnostic, BuilderProgram, addRange, getEmitDeclarations, sortAndDeduplicateDiagnostics, emptyArray, diagnosticCategoryName, getLineAndCharacterOfPosition, convertToRelativePath, DiagnosticCategory, Debug, getPositionOfLineAndCharacter, DiagnosticMessageChain, isString, ResolvedProjectReference, TextRange, RefFileKind, HasInvalidatedResolution, ProjectReference, arrayIsEqualTo, compareDataObjects, projectReferenceIsEqualTo, contains, ParsedCommandLine, sourceFileAffectingCompilerOptions, isJsonEqual, getCompilerOptionValue, CreateProgramOptions, isArray, TypeChecker, UnderscoreEscapedMap, MultiMap, DiagnosticWithLocation, ResolvedTypeReferenceDirective, createDiagnosticCollection, getSupportedExtensions, getSuppoertedExtensionsWithJsonIfResolveJsonModule, ObjectLiteralExpression, ModuleResolutionCache, ResolvedModuleFull, returnFalse, clone, extensionFromPath, createModuleResolutionCache, resolveModuleName, resolveTypeReferenceDirective, createMultiMap, SourceOfProjectReferenceRedirect, StructureIsReused, getEmitModuleKind, ModuleKind, changeExtension, getOutputDeclarationFileName, getAutomaticTypeDirectiveNames, arrayFrom, mapDefinedIterator, stableSort, compareValues, containsPath, getBaseFileName, removeSuffix, removePrefix, libs, ResolvedModuleWithFailedLookupLocations, resolveModuleNameFromCache, filter, sourceFileMayBeEmitted, getNormalizedAbsolutePath, normalizeSlashes, directorySeparator, createUnderscoreEscapedMap, copyEntries, isTraceEnabled, trace, Diagnostics, getResolvedModule, changesAffectModuleResolution, NodeFlags, hasChangesInResolutions, moduleResolutionIsEqualTo, zipToMap, map, typeDirectiveIsEqualTo, EmitHost, EmitResult, emitFiles, notImplementedResolver, noTransformers, equateStringsCaseSensitive, equateStringsCaseInsensitive, some, createTypeChecker, CustomTransformers, getTransformers, flatMap, skipTypeChecking, append, isSourceFileJS, concatenate, OperationCanceledException, isCheckJsEnabledForFile, ScriptKind, getLineStarts, computeLineAndCharacterOfPosition, Node, SyntaxKind, ParameterDeclaration, PropertyDeclaration, MethodDeclaration, FunctionLikeDeclaration, VariableDeclaration, ExportAssignment, HeritageClause, tokenToString, AsExpression, forEachChild, NodeArray, DeclarationWithTypeParameterChildren, Modifier, NodeWithTypeArguments, DiagnosticMessage, createFileDiagnostic, createDiagnosticForNodeInSourceFile, noop, SortedReadonlyArray, FileReference, StringLiteralLike, Identifier, isExternalModule, StringLiteral, createLiteral, externalHelpersModuleNameText, createImportDeclaration, addEmitFlags, EmitFlags, Statement, isAnyImportOrReExport, getExternalModuleName, isStringLiteral, isExternalModuleNameRelative, isModuleDeclaration, isAmbientModule, hasModifier, ModifierFlags, getTextOfIdentifierOrLiteral, ModuleBlock, ModuleDeclaration, isRequireCall, isImportCall, isStringLiteralLike, isLiteralImportTypeNode, hasJSDocNodes, libMap, UnparsedSource, hasExtension, hasJSFileExtension, PackageId, find, stringContains, nodeModulesPathPart, getNormalizedAbsolutePathWithoutRoot, packageIdToString, setResolvedTypeReferenceDirective, getSpellingSuggestion, identity, createCompilerDiagnostic, setResolvedModule, resolutionExtensionIsTSOrJson, isInJSFile, skipTrivia, mapDefined, arrayToSet, JsonSourceFile, parseJsonSourceFileConfigFileContent, getStrictOptionValue, isIncrementalCompilation, hasProperty, hasZeroOrOneAsteriskCharacter, getErrorSpanForNode, getEmitModuleResolutionKind, ModuleResolutionKind, hasJsonModuleEmitEnabled, getRootLength, parseIsolatedEntityName, isIdentifierText, forEachEmittedFile, chainDiagnosticMessages, createCompilerDiagnosticFromMessageChain, elementAt, getTsBuildInfoEmitOutputFilePath, isObjectLiteralExpression, getPropertyAssignment, isArrayLiteralExpression, PropertyAssignment, firstDefined, getTsConfigPropArray, getTsConfigObjectLiteralExpression, removeFileExtension, fileExtensionIsOneOf, supportedJSExtensions, comparePaths, Comparison, discoverProbableSymlinks, ProgramToEmitFilesAndReportErrors, DiagnosticReporter, DirectoryStructureHost, ParseConfigFileHost, returnUndefined, InputFiles, getOutputPathsForBundle, createInputFiles, ResolvedConfigFileName, resolveConfigFileProjectName } from "./ts"; +import { mark, measure } from "./ts.performance"; +import * as ts from "./ts"; +const ignoreDiagnosticCommentRegEx = /(^\s*$)|(^\s*\/\/\/?\s*(@ts-ignore)?)/; +export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName = "tsconfig.json"): string | undefined { + return forEachAncestorDirectory(searchPath, ancestor => { + const fileName = combinePaths(ancestor, configName); + return fileExists(fileName) ? fileName : undefined; + }); +} +export function resolveTripleslashReference(moduleName: string, containingFile: string): string { + const basePath = getDirectoryPath(containingFile); + const referencedFileName = isRootedDiskPath(moduleName) ? moduleName : combinePaths(basePath, moduleName); + return normalizePath(referencedFileName); +} +/* @internal */ +export function computeCommonSourceDirectoryOfFilenames(fileNames: string[], currentDirectory: string, getCanonicalFileName: GetCanonicalFileName): string { + let commonPathComponents: string[] | undefined; + const failed = forEach(fileNames, sourceFile => { + // Each file contributes into common source file path + const sourcePathComponents = getNormalizedPathComponents(sourceFile, currentDirectory); + sourcePathComponents.pop(); // The base file name is not part of the common directory path + if (!commonPathComponents) { + // first file + commonPathComponents = sourcePathComponents; + return; + } + const n = Math.min(commonPathComponents.length, sourcePathComponents.length); + for (let i = 0; i < n; i++) { + if (getCanonicalFileName(commonPathComponents[i]) !== getCanonicalFileName(sourcePathComponents[i])) { + if (i === 0) { + // Failed to find any common path component + return true; } + // New common path found that is 0 -> i-1 + commonPathComponents.length = i; + break; } - - // If the sourcePathComponents was shorter than the commonPathComponents, truncate to the sourcePathComponents - if (sourcePathComponents.length < commonPathComponents.length) { - commonPathComponents.length = sourcePathComponents.length; - } - }); - - // A common path can not be found when paths span multiple drives on windows, for example - if (failed) { - return ""; - } - - if (!commonPathComponents) { // Can happen when all input files are .d.ts files - return currentDirectory; - } - - return getPathFromPathComponents(commonPathComponents); - } - - interface OutputFingerprint { - hash: string; - byteOrderMark: boolean; - mtime: Date; - } - - export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost { - return createCompilerHostWorker(options, setParentNodes); - } - - /*@internal*/ - // TODO(shkamat): update this after reworking ts build API - export function createCompilerHostWorker(options: CompilerOptions, setParentNodes?: boolean, system = sys): CompilerHost { - const existingDirectories = createMap(); - const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); - function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile | undefined { - let text: string | undefined; - try { - performance.mark("beforeIORead"); - text = compilerHost.readFile(fileName); - performance.mark("afterIORead"); - performance.measure("I/O Read", "beforeIORead", "afterIORead"); - } - catch (e) { - if (onError) { - onError(e.message); - } - text = ""; - } - return text !== undefined ? createSourceFile(fileName, text, languageVersion, setParentNodes) : undefined; - } - - function directoryExists(directoryPath: string): boolean { - if (existingDirectories.has(directoryPath)) { - return true; - } - if ((compilerHost.directoryExists || system.directoryExists)(directoryPath)) { - existingDirectories.set(directoryPath, true); - return true; + } + // If the sourcePathComponents was shorter than the commonPathComponents, truncate to the sourcePathComponents + if (sourcePathComponents.length < commonPathComponents.length) { + commonPathComponents.length = sourcePathComponents.length; + } + }); + // A common path can not be found when paths span multiple drives on windows, for example + if (failed) { + return ""; + } + if (!commonPathComponents) { // Can happen when all input files are .d.ts files + return currentDirectory; + } + return getPathFromPathComponents(commonPathComponents); +} +interface OutputFingerprint { + hash: string; + byteOrderMark: boolean; + mtime: Date; +} +export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost { + return createCompilerHostWorker(options, setParentNodes); +} +/*@internal*/ +// TODO(shkamat): update this after reworking ts build API +export function createCompilerHostWorker(options: CompilerOptions, setParentNodes?: boolean, system = sys): CompilerHost { + const existingDirectories = createMap(); + const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); + function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile | undefined { + let text: string | undefined; + try { + mark("beforeIORead"); + text = compilerHost.readFile(fileName); + mark("afterIORead"); + measure("I/O Read", "beforeIORead", "afterIORead"); + } + catch (e) { + if (onError) { + onError(e.message); + } + text = ""; + } + return text !== undefined ? createSourceFile(fileName, text, languageVersion, setParentNodes) : undefined; + } + function directoryExists(directoryPath: string): boolean { + if (existingDirectories.has(directoryPath)) { + return true; + } + if ((compilerHost.directoryExists || system.directoryExists)(directoryPath)) { + existingDirectories.set(directoryPath, true); + return true; + } + return false; + } + function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { + try { + mark("beforeIOWrite"); + // NOTE: If patchWriteFileEnsuringDirectory has been called, + // the system.writeFile will do its own directory creation and + // the ensureDirectoriesExist call will always be redundant. + writeFileEnsuringDirectories(fileName, data, writeByteOrderMark, (path, data, writeByteOrderMark) => writeFileWorker(path, data, writeByteOrderMark), path => (compilerHost.createDirectory || system.createDirectory)(path), path => directoryExists(path)); + mark("afterIOWrite"); + measure("I/O Write", "beforeIOWrite", "afterIOWrite"); + } + catch (e) { + if (onError) { + onError(e.message); } - return false; } - - function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { - try { - performance.mark("beforeIOWrite"); - - // NOTE: If patchWriteFileEnsuringDirectory has been called, - // the system.writeFile will do its own directory creation and - // the ensureDirectoriesExist call will always be redundant. - writeFileEnsuringDirectories( - fileName, - data, - writeByteOrderMark, - (path, data, writeByteOrderMark) => writeFileWorker(path, data, writeByteOrderMark), - path => (compilerHost.createDirectory || system.createDirectory)(path), - path => directoryExists(path)); - - performance.mark("afterIOWrite"); - performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); - } - catch (e) { - if (onError) { - onError(e.message); - } - } - } - - let outputFingerprints: Map; - function writeFileWorker(fileName: string, data: string, writeByteOrderMark: boolean) { - if (!isWatchSet(options) || !system.createHash || !system.getModifiedTime) { - system.writeFile(fileName, data, writeByteOrderMark); + } + let outputFingerprints: ts.Map; + function writeFileWorker(fileName: string, data: string, writeByteOrderMark: boolean) { + if (!isWatchSet(options) || !system.createHash || !system.getModifiedTime) { + system.writeFile(fileName, data, writeByteOrderMark); + return; + } + if (!outputFingerprints) { + outputFingerprints = createMap(); + } + const hash = system.createHash(data); + const mtimeBefore = system.getModifiedTime(fileName); + if (mtimeBefore) { + const fingerprint = outputFingerprints.get(fileName); + // If output has not been changed, and the file has no external modification + if (fingerprint && + fingerprint.byteOrderMark === writeByteOrderMark && + fingerprint.hash === hash && + fingerprint.mtime.getTime() === mtimeBefore.getTime()) { return; } - - if (!outputFingerprints) { - outputFingerprints = createMap(); - } - - const hash = system.createHash(data); - const mtimeBefore = system.getModifiedTime(fileName); - - if (mtimeBefore) { - const fingerprint = outputFingerprints.get(fileName); - // If output has not been changed, and the file has no external modification - if (fingerprint && - fingerprint.byteOrderMark === writeByteOrderMark && - fingerprint.hash === hash && - fingerprint.mtime.getTime() === mtimeBefore.getTime()) { - return; - } - } - - system.writeFile(fileName, data, writeByteOrderMark); - - const mtimeAfter = system.getModifiedTime(fileName) || missingFileModifiedTime; - - outputFingerprints.set(fileName, { - hash, - byteOrderMark: writeByteOrderMark, - mtime: mtimeAfter - }); } - - function getDefaultLibLocation(): string { - return getDirectoryPath(normalizePath(system.getExecutingFilePath())); - } - - const newLine = getNewLineCharacter(options, () => system.newLine); - const realpath = system.realpath && ((path: string) => system.realpath!(path)); - const compilerHost: CompilerHost = { - getSourceFile, - getDefaultLibLocation, - getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), - writeFile, - getCurrentDirectory: memoize(() => system.getCurrentDirectory()), - useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, - getCanonicalFileName, - getNewLine: () => newLine, - fileExists: fileName => system.fileExists(fileName), - readFile: fileName => system.readFile(fileName), - trace: (s: string) => system.write(s + newLine), - directoryExists: directoryName => system.directoryExists(directoryName), - getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", - getDirectories: (path: string) => system.getDirectories(path), - realpath, - readDirectory: (path, extensions, include, exclude, depth) => system.readDirectory(path, extensions, include, exclude, depth), - createDirectory: d => system.createDirectory(d), - createHash: maybeBind(system, system.createHash) - }; - return compilerHost; - } - - /*@internal*/ - interface CompilerHostLikeForCache { - fileExists(fileName: string): boolean; - readFile(fileName: string, encoding?: string): string | undefined; - directoryExists?(directory: string): boolean; - createDirectory?(directory: string): void; - writeFile?: WriteFileCallback; - } - - /*@internal*/ - export function changeCompilerHostLikeToUseCache( - host: CompilerHostLikeForCache, - toPath: (fileName: string) => Path, - getSourceFile?: CompilerHost["getSourceFile"] - ) { - const originalReadFile = host.readFile; - const originalFileExists = host.fileExists; - const originalDirectoryExists = host.directoryExists; - const originalCreateDirectory = host.createDirectory; - const originalWriteFile = host.writeFile; - const readFileCache = createMap(); - const fileExistsCache = createMap(); - const directoryExistsCache = createMap(); - const sourceFileCache = createMap(); - - const readFileWithCache = (fileName: string): string | undefined => { - const key = toPath(fileName); - const value = readFileCache.get(key); - if (value !== undefined) return value !== false ? value : undefined; - return setReadFileCache(key, fileName); - }; - const setReadFileCache = (key: Path, fileName: string) => { - const newValue = originalReadFile.call(host, fileName); - readFileCache.set(key, newValue !== undefined ? newValue : false); - return newValue; - }; - host.readFile = fileName => { + system.writeFile(fileName, data, writeByteOrderMark); + const mtimeAfter = system.getModifiedTime(fileName) || missingFileModifiedTime; + outputFingerprints.set(fileName, { + hash, + byteOrderMark: writeByteOrderMark, + mtime: mtimeAfter + }); + } + function getDefaultLibLocation(): string { + return getDirectoryPath(normalizePath(system.getExecutingFilePath())); + } + const newLine = getNewLineCharacter(options, () => system.newLine); + const realpath = system.realpath && ((path: string) => system.realpath!(path)); + const compilerHost: CompilerHost = { + getSourceFile, + getDefaultLibLocation, + getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), + writeFile, + getCurrentDirectory: memoize(() => system.getCurrentDirectory()), + useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, + getCanonicalFileName, + getNewLine: () => newLine, + fileExists: fileName => system.fileExists(fileName), + readFile: fileName => system.readFile(fileName), + trace: (s: string) => system.write(s + newLine), + directoryExists: directoryName => system.directoryExists(directoryName), + getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", + getDirectories: (path: string) => system.getDirectories(path), + realpath, + readDirectory: (path, extensions, include, exclude, depth) => system.readDirectory(path, extensions, include, exclude, depth), + createDirectory: d => system.createDirectory(d), + createHash: maybeBind(system, system.createHash) + }; + return compilerHost; +} +/*@internal*/ +interface CompilerHostLikeForCache { + fileExists(fileName: string): boolean; + readFile(fileName: string, encoding?: string): string | undefined; + directoryExists?(directory: string): boolean; + createDirectory?(directory: string): void; + writeFile?: WriteFileCallback; +} +/*@internal*/ +export function changeCompilerHostLikeToUseCache(host: CompilerHostLikeForCache, toPath: (fileName: string) => Path, getSourceFile?: CompilerHost["getSourceFile"]) { + const originalReadFile = host.readFile; + const originalFileExists = host.fileExists; + const originalDirectoryExists = host.directoryExists; + const originalCreateDirectory = host.createDirectory; + const originalWriteFile = host.writeFile; + const readFileCache = createMap(); + const fileExistsCache = createMap(); + const directoryExistsCache = createMap(); + const sourceFileCache = createMap(); + const readFileWithCache = (fileName: string): string | undefined => { + const key = toPath(fileName); + const value = readFileCache.get(key); + if (value !== undefined) + return value !== false ? value : undefined; + return setReadFileCache(key, fileName); + }; + const setReadFileCache = (key: Path, fileName: string) => { + const newValue = originalReadFile.call(host, fileName); + readFileCache.set(key, newValue !== undefined ? newValue : false); + return newValue; + }; + host.readFile = fileName => { + const key = toPath(fileName); + const value = readFileCache.get(key); + if (value !== undefined) + return value !== false ? value : undefined; // could be .d.ts from output + // Cache json or buildInfo + if (!fileExtensionIs(fileName, Extension.Json) && !isBuildInfoFile(fileName)) { + return originalReadFile.call(host, fileName); + } + return setReadFileCache(key, fileName); + }; + const getSourceFileWithCache: CompilerHost["getSourceFile"] | undefined = getSourceFile ? (fileName, languageVersion, onError, shouldCreateNewSourceFile) => { + const key = toPath(fileName); + const value = sourceFileCache.get(key); + if (value) + return value; + const sourceFile = getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile); + if (sourceFile && (isDeclarationFileName(fileName) || fileExtensionIs(fileName, Extension.Json))) { + sourceFileCache.set(key, sourceFile); + } + return sourceFile; + } : undefined; + // fileExists for any kind of extension + host.fileExists = fileName => { + const key = toPath(fileName); + const value = fileExistsCache.get(key); + if (value !== undefined) + return value; + const newValue = originalFileExists.call(host, fileName); + fileExistsCache.set(key, !!newValue); + return newValue; + }; + if (originalWriteFile) { + host.writeFile = (fileName, data, writeByteOrderMark, onError, sourceFiles) => { const key = toPath(fileName); + fileExistsCache.delete(key); const value = readFileCache.get(key); - if (value !== undefined) return value !== false ? value : undefined; // could be .d.ts from output - // Cache json or buildInfo - if (!fileExtensionIs(fileName, Extension.Json) && !isBuildInfoFile(fileName)) { - return originalReadFile.call(host, fileName); + if (value !== undefined && value !== data) { + readFileCache.delete(key); + sourceFileCache.delete(key); } - - return setReadFileCache(key, fileName); - }; - - const getSourceFileWithCache: CompilerHost["getSourceFile"] | undefined = getSourceFile ? (fileName, languageVersion, onError, shouldCreateNewSourceFile) => { - const key = toPath(fileName); - const value = sourceFileCache.get(key); - if (value) return value; - - const sourceFile = getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile); - if (sourceFile && (isDeclarationFileName(fileName) || fileExtensionIs(fileName, Extension.Json))) { - sourceFileCache.set(key, sourceFile); + else if (getSourceFileWithCache) { + const sourceFile = sourceFileCache.get(key); + if (sourceFile && sourceFile.text !== data) { + sourceFileCache.delete(key); + } } - return sourceFile; - } : undefined; - - // fileExists for any kind of extension - host.fileExists = fileName => { - const key = toPath(fileName); - const value = fileExistsCache.get(key); - if (value !== undefined) return value; - const newValue = originalFileExists.call(host, fileName); - fileExistsCache.set(key, !!newValue); + originalWriteFile.call(host, fileName, data, writeByteOrderMark, onError, sourceFiles); + }; + } + // directoryExists + if (originalDirectoryExists && originalCreateDirectory) { + host.directoryExists = directory => { + const key = toPath(directory); + const value = directoryExistsCache.get(key); + if (value !== undefined) + return value; + const newValue = originalDirectoryExists.call(host, directory); + directoryExistsCache.set(key, !!newValue); return newValue; }; - if (originalWriteFile) { - host.writeFile = (fileName, data, writeByteOrderMark, onError, sourceFiles) => { - const key = toPath(fileName); - fileExistsCache.delete(key); - - const value = readFileCache.get(key); - if (value !== undefined && value !== data) { - readFileCache.delete(key); - sourceFileCache.delete(key); - } - else if (getSourceFileWithCache) { - const sourceFile = sourceFileCache.get(key); - if (sourceFile && sourceFile.text !== data) { - sourceFileCache.delete(key); - } - } - originalWriteFile.call(host, fileName, data, writeByteOrderMark, onError, sourceFiles); - }; + host.createDirectory = directory => { + const key = toPath(directory); + directoryExistsCache.delete(key); + originalCreateDirectory.call(host, directory); + }; + } + return { + originalReadFile, + originalFileExists, + originalDirectoryExists, + originalCreateDirectory, + originalWriteFile, + getSourceFileWithCache, + readFileWithCache + }; +} +export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; +/*@internal*/ export function getPreEmitDiagnostics(program: BuilderProgram, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; // eslint-disable-line @typescript-eslint/unified-signatures +export function getPreEmitDiagnostics(program: Program | BuilderProgram, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { + let diagnostics: Diagnostic[] | undefined; + diagnostics = addRange(diagnostics, program.getConfigFileParsingDiagnostics()); + diagnostics = addRange(diagnostics, program.getOptionsDiagnostics(cancellationToken)); + diagnostics = addRange(diagnostics, program.getSyntacticDiagnostics(sourceFile, cancellationToken)); + diagnostics = addRange(diagnostics, program.getGlobalDiagnostics(cancellationToken)); + diagnostics = addRange(diagnostics, program.getSemanticDiagnostics(sourceFile, cancellationToken)); + if (getEmitDeclarations(program.getCompilerOptions())) { + diagnostics = addRange(diagnostics, program.getDeclarationDiagnostics(sourceFile, cancellationToken)); + } + return sortAndDeduplicateDiagnostics(diagnostics || emptyArray); +} +export interface FormatDiagnosticsHost { + getCurrentDirectory(): string; + getCanonicalFileName(fileName: string): string; + getNewLine(): string; +} +export function formatDiagnostics(diagnostics: readonly Diagnostic[], host: FormatDiagnosticsHost): string { + let output = ""; + for (const diagnostic of diagnostics) { + output += formatDiagnostic(diagnostic, host); + } + return output; +} +export function formatDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost): string { + const errorMessage = `${diagnosticCategoryName(diagnostic)} TS${diagnostic.code}: ${flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine())}${host.getNewLine()}`; + if (diagnostic.file) { + const { line, character } = getLineAndCharacterOfPosition(diagnostic.file, (diagnostic.start!)); // TODO: GH#18217 + const fileName = diagnostic.file.fileName; + const relativeFileName = convertToRelativePath(fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)); + return `${relativeFileName}(${line + 1},${character + 1}): ` + errorMessage; + } + return errorMessage; +} +/** @internal */ +export enum ForegroundColorEscapeSequences { + Grey = "\u001b[90m", + Red = "\u001b[91m", + Yellow = "\u001b[93m", + Blue = "\u001b[94m", + Cyan = "\u001b[96m" +} +const gutterStyleSequence = "\u001b[7m"; +const gutterSeparator = " "; +const resetEscapeSequence = "\u001b[0m"; +const ellipsis = "..."; +const halfIndent = " "; +const indent = " "; +function getCategoryFormat(category: DiagnosticCategory): ForegroundColorEscapeSequences { + switch (category) { + case DiagnosticCategory.Error: return ForegroundColorEscapeSequences.Red; + case DiagnosticCategory.Warning: return ForegroundColorEscapeSequences.Yellow; + case DiagnosticCategory.Suggestion: return Debug.fail("Should never get an Info diagnostic on the command line."); + case DiagnosticCategory.Message: return ForegroundColorEscapeSequences.Blue; + } +} +/** @internal */ +export function formatColorAndReset(text: string, formatStyle: string) { + return formatStyle + text + resetEscapeSequence; +} +function padLeft(s: string, length: number) { + while (s.length < length) { + s = " " + s; + } + return s; +} +function formatCodeSpan(file: SourceFile, start: number, length: number, indent: string, squiggleColor: ForegroundColorEscapeSequences, host: FormatDiagnosticsHost) { + const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start); + const { line: lastLine, character: lastLineChar } = getLineAndCharacterOfPosition(file, start + length); + const lastLineInFile = getLineAndCharacterOfPosition(file, file.text.length).line; + const hasMoreThanFiveLines = (lastLine - firstLine) >= 4; + let gutterWidth = (lastLine + 1 + "").length; + if (hasMoreThanFiveLines) { + gutterWidth = Math.max(ellipsis.length, gutterWidth); + } + let context = ""; + for (let i = firstLine; i <= lastLine; i++) { + context += host.getNewLine(); + // If the error spans over 5 lines, we'll only show the first 2 and last 2 lines, + // so we'll skip ahead to the second-to-last line. + if (hasMoreThanFiveLines && firstLine + 1 < i && i < lastLine - 1) { + context += indent + formatColorAndReset(padLeft(ellipsis, gutterWidth), gutterStyleSequence) + gutterSeparator + host.getNewLine(); + i = lastLine - 1; + } + const lineStart = getPositionOfLineAndCharacter(file, i, 0); + const lineEnd = i < lastLineInFile ? getPositionOfLineAndCharacter(file, i + 1, 0) : file.text.length; + let lineContent = file.text.slice(lineStart, lineEnd); + lineContent = lineContent.replace(/\s+$/g, ""); // trim from end + lineContent = lineContent.replace("\t", " "); // convert tabs to single spaces + // Output the gutter and the actual contents of the line. + context += indent + formatColorAndReset(padLeft(i + 1 + "", gutterWidth), gutterStyleSequence) + gutterSeparator; + context += lineContent + host.getNewLine(); + // Output the gutter and the error span for the line using tildes. + context += indent + formatColorAndReset(padLeft("", gutterWidth), gutterStyleSequence) + gutterSeparator; + context += squiggleColor; + if (i === firstLine) { + // If we're on the last line, then limit it to the last character of the last line. + // Otherwise, we'll just squiggle the rest of the line, giving 'slice' no end position. + const lastCharForLine = i === lastLine ? lastLineChar : undefined; + context += lineContent.slice(0, firstLineChar).replace(/\S/g, " "); + context += lineContent.slice(firstLineChar, lastCharForLine).replace(/./g, "~"); + } + else if (i === lastLine) { + context += lineContent.slice(0, lastLineChar).replace(/./g, "~"); } - - // directoryExists - if (originalDirectoryExists && originalCreateDirectory) { - host.directoryExists = directory => { - const key = toPath(directory); - const value = directoryExistsCache.get(key); - if (value !== undefined) return value; - const newValue = originalDirectoryExists.call(host, directory); - directoryExistsCache.set(key, !!newValue); - return newValue; - }; - host.createDirectory = directory => { - const key = toPath(directory); - directoryExistsCache.delete(key); - originalCreateDirectory.call(host, directory); - }; + else { + // Squiggle the entire line. + context += lineContent.replace(/./g, "~"); } - - return { - originalReadFile, - originalFileExists, - originalDirectoryExists, - originalCreateDirectory, - originalWriteFile, - getSourceFileWithCache, - readFileWithCache - }; + context += resetEscapeSequence; } - - export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; - /*@internal*/ export function getPreEmitDiagnostics(program: BuilderProgram, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; // eslint-disable-line @typescript-eslint/unified-signatures - export function getPreEmitDiagnostics(program: Program | BuilderProgram, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { - let diagnostics: Diagnostic[] | undefined; - diagnostics = addRange(diagnostics, program.getConfigFileParsingDiagnostics()); - diagnostics = addRange(diagnostics, program.getOptionsDiagnostics(cancellationToken)); - diagnostics = addRange(diagnostics, program.getSyntacticDiagnostics(sourceFile, cancellationToken)); - diagnostics = addRange(diagnostics, program.getGlobalDiagnostics(cancellationToken)); - diagnostics = addRange(diagnostics, program.getSemanticDiagnostics(sourceFile, cancellationToken)); - - if (getEmitDeclarations(program.getCompilerOptions())) { - diagnostics = addRange(diagnostics, program.getDeclarationDiagnostics(sourceFile, cancellationToken)); - } - - return sortAndDeduplicateDiagnostics(diagnostics || emptyArray); - } - - export interface FormatDiagnosticsHost { - getCurrentDirectory(): string; - getCanonicalFileName(fileName: string): string; - getNewLine(): string; - } - - export function formatDiagnostics(diagnostics: readonly Diagnostic[], host: FormatDiagnosticsHost): string { - let output = ""; - - for (const diagnostic of diagnostics) { - output += formatDiagnostic(diagnostic, host); - } - return output; - } - - export function formatDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost): string { - const errorMessage = `${diagnosticCategoryName(diagnostic)} TS${diagnostic.code}: ${flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine())}${host.getNewLine()}`; - + return context; +} +/* @internal */ +export function formatLocation(file: SourceFile, start: number, host: FormatDiagnosticsHost, color = formatColorAndReset) { + const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start); // TODO: GH#18217 + const relativeFileName = host ? convertToRelativePath(file.fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)) : file.fileName; + let output = ""; + output += color(relativeFileName, ForegroundColorEscapeSequences.Cyan); + output += ":"; + output += color(`${firstLine + 1}`, ForegroundColorEscapeSequences.Yellow); + output += ":"; + output += color(`${firstLineChar + 1}`, ForegroundColorEscapeSequences.Yellow); + return output; +} +export function formatDiagnosticsWithColorAndContext(diagnostics: readonly Diagnostic[], host: FormatDiagnosticsHost): string { + let output = ""; + for (const diagnostic of diagnostics) { if (diagnostic.file) { - const { line, character } = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start!); // TODO: GH#18217 - const fileName = diagnostic.file.fileName; - const relativeFileName = convertToRelativePath(fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)); - return `${relativeFileName}(${line + 1},${character + 1}): ` + errorMessage; - } - - return errorMessage; - } - - /** @internal */ - export enum ForegroundColorEscapeSequences { - Grey = "\u001b[90m", - Red = "\u001b[91m", - Yellow = "\u001b[93m", - Blue = "\u001b[94m", - Cyan = "\u001b[96m" - } - const gutterStyleSequence = "\u001b[7m"; - const gutterSeparator = " "; - const resetEscapeSequence = "\u001b[0m"; - const ellipsis = "..."; - const halfIndent = " "; - const indent = " "; - function getCategoryFormat(category: DiagnosticCategory): ForegroundColorEscapeSequences { - switch (category) { - case DiagnosticCategory.Error: return ForegroundColorEscapeSequences.Red; - case DiagnosticCategory.Warning: return ForegroundColorEscapeSequences.Yellow; - case DiagnosticCategory.Suggestion: return Debug.fail("Should never get an Info diagnostic on the command line."); - case DiagnosticCategory.Message: return ForegroundColorEscapeSequences.Blue; - } - } - - /** @internal */ - export function formatColorAndReset(text: string, formatStyle: string) { - return formatStyle + text + resetEscapeSequence; - } - - function padLeft(s: string, length: number) { - while (s.length < length) { - s = " " + s; - } - return s; - } - - function formatCodeSpan(file: SourceFile, start: number, length: number, indent: string, squiggleColor: ForegroundColorEscapeSequences, host: FormatDiagnosticsHost) { - const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start); - const { line: lastLine, character: lastLineChar } = getLineAndCharacterOfPosition(file, start + length); - const lastLineInFile = getLineAndCharacterOfPosition(file, file.text.length).line; - - const hasMoreThanFiveLines = (lastLine - firstLine) >= 4; - let gutterWidth = (lastLine + 1 + "").length; - if (hasMoreThanFiveLines) { - gutterWidth = Math.max(ellipsis.length, gutterWidth); - } - - let context = ""; - for (let i = firstLine; i <= lastLine; i++) { - context += host.getNewLine(); - // If the error spans over 5 lines, we'll only show the first 2 and last 2 lines, - // so we'll skip ahead to the second-to-last line. - if (hasMoreThanFiveLines && firstLine + 1 < i && i < lastLine - 1) { - context += indent + formatColorAndReset(padLeft(ellipsis, gutterWidth), gutterStyleSequence) + gutterSeparator + host.getNewLine(); - i = lastLine - 1; - } - - const lineStart = getPositionOfLineAndCharacter(file, i, 0); - const lineEnd = i < lastLineInFile ? getPositionOfLineAndCharacter(file, i + 1, 0) : file.text.length; - let lineContent = file.text.slice(lineStart, lineEnd); - lineContent = lineContent.replace(/\s+$/g, ""); // trim from end - lineContent = lineContent.replace("\t", " "); // convert tabs to single spaces - - // Output the gutter and the actual contents of the line. - context += indent + formatColorAndReset(padLeft(i + 1 + "", gutterWidth), gutterStyleSequence) + gutterSeparator; - context += lineContent + host.getNewLine(); - - // Output the gutter and the error span for the line using tildes. - context += indent + formatColorAndReset(padLeft("", gutterWidth), gutterStyleSequence) + gutterSeparator; - context += squiggleColor; - if (i === firstLine) { - // If we're on the last line, then limit it to the last character of the last line. - // Otherwise, we'll just squiggle the rest of the line, giving 'slice' no end position. - const lastCharForLine = i === lastLine ? lastLineChar : undefined; - - context += lineContent.slice(0, firstLineChar).replace(/\S/g, " "); - context += lineContent.slice(firstLineChar, lastCharForLine).replace(/./g, "~"); - } - else if (i === lastLine) { - context += lineContent.slice(0, lastLineChar).replace(/./g, "~"); - } - else { - // Squiggle the entire line. - context += lineContent.replace(/./g, "~"); - } - context += resetEscapeSequence; - } - return context; - } - - /* @internal */ - export function formatLocation(file: SourceFile, start: number, host: FormatDiagnosticsHost, color = formatColorAndReset) { - const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start); // TODO: GH#18217 - const relativeFileName = host ? convertToRelativePath(file.fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)) : file.fileName; - - let output = ""; - output += color(relativeFileName, ForegroundColorEscapeSequences.Cyan); - output += ":"; - output += color(`${firstLine + 1}`, ForegroundColorEscapeSequences.Yellow); - output += ":"; - output += color(`${firstLineChar + 1}`, ForegroundColorEscapeSequences.Yellow); - return output; - } - - export function formatDiagnosticsWithColorAndContext(diagnostics: readonly Diagnostic[], host: FormatDiagnosticsHost): string { - let output = ""; - for (const diagnostic of diagnostics) { - if (diagnostic.file) { - const { file, start } = diagnostic; - output += formatLocation(file, start!, host); // TODO: GH#18217 - output += " - "; - } - - output += formatColorAndReset(diagnosticCategoryName(diagnostic), getCategoryFormat(diagnostic.category)); - output += formatColorAndReset(` TS${diagnostic.code}: `, ForegroundColorEscapeSequences.Grey); - output += flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine()); - - if (diagnostic.file) { + const { file, start } = diagnostic; + output += formatLocation(file, start!, host); // TODO: GH#18217 + output += " - "; + } + output += formatColorAndReset(diagnosticCategoryName(diagnostic), getCategoryFormat(diagnostic.category)); + output += formatColorAndReset(` TS${diagnostic.code}: `, ForegroundColorEscapeSequences.Grey); + output += flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine()); + if (diagnostic.file) { + output += host.getNewLine(); + output += formatCodeSpan(diagnostic.file, diagnostic.start!, diagnostic.length!, "", getCategoryFormat(diagnostic.category), host); // TODO: GH#18217 + if (diagnostic.relatedInformation) { output += host.getNewLine(); - output += formatCodeSpan(diagnostic.file, diagnostic.start!, diagnostic.length!, "", getCategoryFormat(diagnostic.category), host); // TODO: GH#18217 - if (diagnostic.relatedInformation) { - output += host.getNewLine(); - for (const { file, start, length, messageText } of diagnostic.relatedInformation) { - if (file) { - output += host.getNewLine(); - output += halfIndent + formatLocation(file, start!, host); // TODO: GH#18217 - output += formatCodeSpan(file, start!, length!, indent, ForegroundColorEscapeSequences.Cyan, host); // TODO: GH#18217 - } + for (const { file, start, length, messageText } of diagnostic.relatedInformation) { + if (file) { output += host.getNewLine(); - output += indent + flattenDiagnosticMessageText(messageText, host.getNewLine()); + output += halfIndent + formatLocation(file, start!, host); // TODO: GH#18217 + output += formatCodeSpan(file, start!, length!, indent, ForegroundColorEscapeSequences.Cyan, host); // TODO: GH#18217 } + output += host.getNewLine(); + output += indent + flattenDiagnosticMessageText(messageText, host.getNewLine()); } } - - output += host.getNewLine(); } - return output; + output += host.getNewLine(); + } + return output; +} +export function flattenDiagnosticMessageText(diag: string | DiagnosticMessageChain | undefined, newLine: string, indent = 0): string { + if (isString(diag)) { + return diag; + } + else if (diag === undefined) { + return ""; } - - export function flattenDiagnosticMessageText(diag: string | DiagnosticMessageChain | undefined, newLine: string, indent = 0): string { - if (isString(diag)) { - return diag; + let result = ""; + if (indent) { + result += newLine; + for (let i = 0; i < indent; i++) { + result += " "; } - else if (diag === undefined) { - return ""; + } + result += diag.messageText; + indent++; + if (diag.next) { + for (const kid of diag.next) { + result += flattenDiagnosticMessageText(kid, newLine, indent); } - let result = ""; - if (indent) { - result += newLine; - - for (let i = 0; i < indent; i++) { - result += " "; - } + } + return result; +} +/* @internal */ +export function loadWithLocalCache(names: string[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, loader: (name: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => T): T[] { + if (names.length === 0) { + return []; + } + const resolutions: T[] = []; + const cache = createMap(); + for (const name of names) { + let result: T; + if (cache.has(name)) { + result = cache.get(name)!; } - result += diag.messageText; - indent++; - if (diag.next) { - for (const kid of diag.next) { - result += flattenDiagnosticMessageText(kid, newLine, indent); - } + else { + cache.set(name, result = loader(name, containingFile, redirectedReference)); } - return result; + resolutions.push(result); + } + return resolutions; +} +/* @internal */ +export const inferredTypesContainingFile = "__inferred type names__.ts"; +interface DiagnosticCache { + perFile?: ts.Map; + allDiagnostics?: readonly T[]; +} +interface RefFile extends TextRange { + kind: RefFileKind; + index: number; + file: SourceFile; +} +/** + * Determines if program structure is upto date or needs to be recreated + */ +/* @internal */ +export function isProgramUptoDate(program: Program | undefined, rootFileNames: string[], newOptions: CompilerOptions, getSourceVersion: (path: Path) => string | undefined, fileExists: (fileName: string) => boolean, hasInvalidatedResolution: HasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames: boolean, projectReferences: readonly ProjectReference[] | undefined): boolean { + // If we haven't created a program yet or have changed automatic type directives, then it is not up-to-date + if (!program || hasChangedAutomaticTypeDirectiveNames) { + return false; + } + // If number of files in the program do not match, it is not up-to-date + if (program.getRootFileNames().length !== rootFileNames.length) { + return false; + } + let seenResolvedRefs: ResolvedProjectReference[] | undefined; + // If project references dont match + if (!arrayIsEqualTo(program.getProjectReferences(), projectReferences, projectReferenceUptoDate)) { + return false; + } + // If any file is not up-to-date, then the whole program is not up-to-date + if (program.getSourceFiles().some(sourceFileNotUptoDate)) { + return false; } - - /* @internal */ - export function loadWithLocalCache(names: string[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, loader: (name: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => T): T[] { - if (names.length === 0) { - return []; + // If any of the missing file paths are now created + if (program.getMissingFilePaths().some(fileExists)) { + return false; + } + const currentOptions = program.getCompilerOptions(); + // If the compilation settings do no match, then the program is not up-to-date + if (!compareDataObjects(currentOptions, newOptions)) { + return false; + } + // If everything matches but the text of config file is changed, + // error locations can change for program options, so update the program + if (currentOptions.configFile && newOptions.configFile) { + return currentOptions.configFile.text === newOptions.configFile.text; + } + return true; + function sourceFileNotUptoDate(sourceFile: SourceFile) { + return !sourceFileVersionUptoDate(sourceFile) || + hasInvalidatedResolution(sourceFile.path); + } + function sourceFileVersionUptoDate(sourceFile: SourceFile) { + return sourceFile.version === getSourceVersion(sourceFile.resolvedPath); + } + function projectReferenceUptoDate(oldRef: ProjectReference, newRef: ProjectReference, index: number) { + if (!projectReferenceIsEqualTo(oldRef, newRef)) { + return false; } - const resolutions: T[] = []; - const cache = createMap(); - for (const name of names) { - let result: T; - if (cache.has(name)) { - result = cache.get(name)!; + return resolvedProjectReferenceUptoDate(program!.getResolvedProjectReferences()![index], oldRef); + } + function resolvedProjectReferenceUptoDate(oldResolvedRef: ResolvedProjectReference | undefined, oldRef: ProjectReference): boolean { + if (oldResolvedRef) { + if (contains(seenResolvedRefs, oldResolvedRef)) { + // Assume true + return true; } - else { - cache.set(name, result = loader(name, containingFile, redirectedReference)); + // If sourceFile for the oldResolvedRef existed, check the version for uptodate + if (!sourceFileVersionUptoDate(oldResolvedRef.sourceFile)) { + return false; } - resolutions.push(result); + // Add to seen before checking the referenced paths of this config file + (seenResolvedRefs || (seenResolvedRefs = [])).push(oldResolvedRef); + // If child project references are upto date, this project reference is uptodate + return !forEach(oldResolvedRef.references, (childResolvedRef, index) => !resolvedProjectReferenceUptoDate(childResolvedRef, oldResolvedRef.commandLine.projectReferences![index])); } - return resolutions; + // In old program, not able to resolve project reference path, + // so if config file doesnt exist, it is uptodate. + return !fileExists(resolveProjectReferencePath(oldRef)); + } +} +export function getConfigFileParsingDiagnostics(configFileParseResult: ParsedCommandLine): readonly Diagnostic[] { + return configFileParseResult.options.configFile ? + [...configFileParseResult.options.configFile.parseDiagnostics, ...configFileParseResult.errors] : + configFileParseResult.errors; +} +/** + * Determine if source file needs to be re-created even if its text hasn't changed + */ +function shouldProgramCreateNewSourceFiles(program: Program | undefined, newOptions: CompilerOptions): boolean { + if (!program) + return false; + // If any compiler options change, we can't reuse old source file even if version match + // The change in options like these could result in change in syntax tree or `sourceFile.bindDiagnostics`. + const oldOptions = program.getCompilerOptions(); + return !!sourceFileAffectingCompilerOptions.some(option => !isJsonEqual(getCompilerOptionValue(oldOptions, option), getCompilerOptionValue(newOptions, option))); +} +function createCreateProgramOptions(rootNames: readonly string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, configFileParsingDiagnostics?: readonly Diagnostic[]): CreateProgramOptions { + return { + rootNames, + options, + host, + oldProgram, + configFileParsingDiagnostics + }; +} +/** + * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' + * that represent a compilation unit. + * + * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and + * triple-slash-reference-path directives transitively. '@types' and triple-slash-reference-types are also pulled in. + * + * @param createProgramOptions - The options for creating a program. + * @returns A 'Program' object. + */ +export function createProgram(createProgramOptions: CreateProgramOptions): Program; +/** + * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' + * that represent a compilation unit. + * + * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and + * triple-slash-reference-path directives transitively. '@types' and triple-slash-reference-types are also pulled in. + * + * @param rootNames - A set of root files. + * @param options - The compiler options which should be used. + * @param host - The host interacts with the underlying file system. + * @param oldProgram - Reuses an old program structure. + * @param configFileParsingDiagnostics - error during config file parsing + * @returns A 'Program' object. + */ +export function createProgram(rootNames: readonly string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, configFileParsingDiagnostics?: readonly Diagnostic[]): Program; +export function createProgram(rootNamesOrOptions: readonly string[] | CreateProgramOptions, _options?: CompilerOptions, _host?: CompilerHost, _oldProgram?: Program, _configFileParsingDiagnostics?: readonly Diagnostic[]): Program { + const createProgramOptions = isArray(rootNamesOrOptions) ? createCreateProgramOptions(rootNamesOrOptions, _options!, _host, _oldProgram, _configFileParsingDiagnostics) : rootNamesOrOptions; // TODO: GH#18217 + const { rootNames, options, configFileParsingDiagnostics, projectReferences } = createProgramOptions; + let { oldProgram } = createProgramOptions; + let processingDefaultLibFiles: SourceFile[] | undefined; + let processingOtherFiles: SourceFile[] | undefined; + let files: SourceFile[]; + let symlinks: ts.ReadonlyMap | undefined; + let commonSourceDirectory: string; + let diagnosticsProducingTypeChecker: TypeChecker; + let noDiagnosticsTypeChecker: TypeChecker; + let classifiableNames: UnderscoreEscapedMap; + const ambientModuleNameToUnmodifiedFileName = createMap(); + // Todo:: Use this to report why file was included in --extendedDiagnostics + let refFileMap: MultiMap | undefined; + const cachedBindAndCheckDiagnosticsForFile: DiagnosticCache = {}; + const cachedDeclarationDiagnosticsForFile: DiagnosticCache = {}; + let resolvedTypeReferenceDirectives = createMap(); + let fileProcessingDiagnostics = createDiagnosticCollection(); + // The below settings are to track if a .js file should be add to the program if loaded via searching under node_modules. + // This works as imported modules are discovered recursively in a depth first manner, specifically: + // - For each root file, findSourceFile is called. + // - This calls processImportedModules for each module imported in the source file. + // - This calls resolveModuleNames, and then calls findSourceFile for each resolved module. + // As all these operations happen - and are nested - within the createProgram call, they close over the below variables. + // The current resolution depth is tracked by incrementing/decrementing as the depth first search progresses. + const maxNodeModuleJsDepth = typeof options.maxNodeModuleJsDepth === "number" ? options.maxNodeModuleJsDepth : 0; + let currentNodeModulesDepth = 0; + // If a module has some of its imports skipped due to being at the depth limit under node_modules, then track + // this, as it may be imported at a shallower depth later, and then it will need its skipped imports processed. + const modulesWithElidedImports = createMap(); + // Track source files that are source files found by searching under node_modules, as these shouldn't be compiled. + const sourceFilesFoundSearchingNodeModules = createMap(); + mark("beforeProgram"); + const host = createProgramOptions.host || createCompilerHost(options); + const configParsingHost = parseConfigHostFromCompilerHostLike(host); + let skipDefaultLib = options.noLib; + const getDefaultLibraryFileName = memoize(() => host.getDefaultLibFileName(options)); + const defaultLibraryPath = host.getDefaultLibLocation ? host.getDefaultLibLocation() : getDirectoryPath(getDefaultLibraryFileName()); + const programDiagnostics = createDiagnosticCollection(); + const currentDirectory = host.getCurrentDirectory(); + const supportedExtensions = getSupportedExtensions(options); + const supportedExtensionsWithJsonIfResolveJsonModule = getSuppoertedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions); + // Map storing if there is emit blocking diagnostics for given input + const hasEmitBlockingDiagnostics = createMap(); + let _compilerOptionsObjectLiteralSyntax: ObjectLiteralExpression | null | undefined; + let moduleResolutionCache: ModuleResolutionCache | undefined; + let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference) => ResolvedModuleFull[]; + const hasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse; + if (host.resolveModuleNames) { + resolveModuleNamesWorker = (moduleNames, containingFile, reusedNames, redirectedReference) => host.resolveModuleNames!(Debug.assertEachDefined(moduleNames), containingFile, reusedNames, redirectedReference, options).map(resolved => { + // An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName. + if (!resolved || (resolved as ResolvedModuleFull).extension !== undefined) { + return resolved as ResolvedModuleFull; + } + const withExtension = (clone(resolved) as ResolvedModuleFull); + withExtension.extension = extensionFromPath(resolved.resolvedFileName); + return withExtension; + }); + } + else { + moduleResolutionCache = createModuleResolutionCache(currentDirectory, x => host.getCanonicalFileName(x), options); + const loader = (moduleName: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFile, options, host, moduleResolutionCache, redirectedReference).resolvedModule!; // TODO: GH#18217 + resolveModuleNamesWorker = (moduleNames, containingFile, _reusedNames, redirectedReference) => loadWithLocalCache(Debug.assertEachDefined(moduleNames), containingFile, redirectedReference, loader); } - - /* @internal */ - export const inferredTypesContainingFile = "__inferred type names__.ts"; - - interface DiagnosticCache { - perFile?: Map; - allDiagnostics?: readonly T[]; + let resolveTypeReferenceDirectiveNamesWorker: (typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference) => (ResolvedTypeReferenceDirective | undefined)[]; + if (host.resolveTypeReferenceDirectives) { + resolveTypeReferenceDirectiveNamesWorker = (typeDirectiveNames, containingFile, redirectedReference) => host.resolveTypeReferenceDirectives!(Debug.assertEachDefined(typeDirectiveNames), containingFile, redirectedReference, options); } - - interface RefFile extends TextRange { - kind: RefFileKind; - index: number; - file: SourceFile; + else { + const loader = (typesRef: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveTypeReferenceDirective(typesRef, containingFile, options, host, redirectedReference).resolvedTypeReferenceDirective!; // TODO: GH#18217 + resolveTypeReferenceDirectiveNamesWorker = (typeReferenceDirectiveNames, containingFile, redirectedReference) => loadWithLocalCache(Debug.assertEachDefined(typeReferenceDirectiveNames), containingFile, redirectedReference, loader); } - + // Map from a stringified PackageId to the source file with that id. + // Only one source file may have a given packageId. Others become redirects (see createRedirectSourceFile). + // `packageIdToSourceFile` is only used while building the program, while `sourceFileToPackageName` and `isSourceFileTargetOfRedirect` are kept around. + const packageIdToSourceFile = createMap(); + // Maps from a SourceFile's `.path` to the name of the package it was imported with. + let sourceFileToPackageName = createMap(); + // Key is a file name. Value is the (non-empty, or undefined) list of files that redirect to it. + let redirectTargetsMap = createMultiMap(); /** - * Determines if program structure is upto date or needs to be recreated + * map with + * - SourceFile if present + * - false if sourceFile missing for source of project reference redirect + * - undefined otherwise */ - /* @internal */ - export function isProgramUptoDate( - program: Program | undefined, - rootFileNames: string[], - newOptions: CompilerOptions, - getSourceVersion: (path: Path) => string | undefined, - fileExists: (fileName: string) => boolean, - hasInvalidatedResolution: HasInvalidatedResolution, - hasChangedAutomaticTypeDirectiveNames: boolean, - projectReferences: readonly ProjectReference[] | undefined - ): boolean { - // If we haven't created a program yet or have changed automatic type directives, then it is not up-to-date - if (!program || hasChangedAutomaticTypeDirectiveNames) { - return false; - } - - // If number of files in the program do not match, it is not up-to-date - if (program.getRootFileNames().length !== rootFileNames.length) { - return false; - } - - let seenResolvedRefs: ResolvedProjectReference[] | undefined; - - // If project references dont match - if (!arrayIsEqualTo(program.getProjectReferences(), projectReferences, projectReferenceUptoDate)) { - return false; - } - - // If any file is not up-to-date, then the whole program is not up-to-date - if (program.getSourceFiles().some(sourceFileNotUptoDate)) { - return false; - } - - // If any of the missing file paths are now created - if (program.getMissingFilePaths().some(fileExists)) { - return false; - } - - const currentOptions = program.getCompilerOptions(); - // If the compilation settings do no match, then the program is not up-to-date - if (!compareDataObjects(currentOptions, newOptions)) { - return false; - } - - // If everything matches but the text of config file is changed, - // error locations can change for program options, so update the program - if (currentOptions.configFile && newOptions.configFile) { - return currentOptions.configFile.text === newOptions.configFile.text; + const filesByName = createMap(); + let missingFilePaths: readonly Path[] | undefined; + // stores 'filename -> file association' ignoring case + // used to track cases when two file names differ only in casing + const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createMap() : undefined; + // A parallel array to projectReferences storing the results of reading in the referenced tsconfig files + let resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined; + let projectReferenceRedirects: ts.Map | undefined; + let mapFromFileToProjectReferenceRedirects: ts.Map | undefined; + let mapFromToProjectReferenceRedirectSource: ts.Map | undefined; + const useSourceOfProjectReferenceRedirect = !!host.useSourceOfProjectReferenceRedirect && host.useSourceOfProjectReferenceRedirect(); + const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options); + // We set `structuralIsReused` to `undefined` because `tryReuseStructureFromOldProgram` calls `tryReuseStructureFromOldProgram` which checks + // `structuralIsReused`, which would be a TDZ violation if it was not set in advance to `undefined`. + let structuralIsReused: StructureIsReused | undefined; + structuralIsReused = tryReuseStructureFromOldProgram(); // eslint-disable-line prefer-const + if (structuralIsReused !== StructureIsReused.Completely) { + processingDefaultLibFiles = []; + processingOtherFiles = []; + if (projectReferences) { + if (!resolvedProjectReferences) { + resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile); + } + if (host.setResolvedProjectReferenceCallbacks) { + host.setResolvedProjectReferenceCallbacks({ + getSourceOfProjectReferenceRedirect, + forEachResolvedProjectReference + }); + } + if (rootNames.length) { + for (const parsedRef of resolvedProjectReferences) { + if (!parsedRef) + continue; + const out = parsedRef.commandLine.options.outFile || parsedRef.commandLine.options.out; + if (useSourceOfProjectReferenceRedirect) { + if (out || getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) { + for (const fileName of parsedRef.commandLine.fileNames) { + processSourceFile(fileName, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined); + } + } + } + else { + if (out) { + processSourceFile(changeExtension(out, ".d.ts"), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined); + } + else if (getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) { + for (const fileName of parsedRef.commandLine.fileNames) { + if (!fileExtensionIs(fileName, Extension.Dts)) { + processSourceFile(getOutputDeclarationFileName(fileName, parsedRef.commandLine, !host.useCaseSensitiveFileNames()), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined); + } + } + } + } + } + } } - - return true; - - function sourceFileNotUptoDate(sourceFile: SourceFile) { - return !sourceFileVersionUptoDate(sourceFile) || - hasInvalidatedResolution(sourceFile.path); - } - - function sourceFileVersionUptoDate(sourceFile: SourceFile) { - return sourceFile.version === getSourceVersion(sourceFile.resolvedPath); - } - - function projectReferenceUptoDate(oldRef: ProjectReference, newRef: ProjectReference, index: number) { - if (!projectReferenceIsEqualTo(oldRef, newRef)) { - return false; + forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false)); + // load type declarations specified via 'types' argument or implicitly from types/ and node_modules/@types folders + const typeReferences: string[] = rootNames.length ? getAutomaticTypeDirectiveNames(options, host) : emptyArray; + if (typeReferences.length) { + // This containingFilename needs to match with the one used in managed-side + const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : host.getCurrentDirectory(); + const containingFilename = combinePaths(containingDirectory, inferredTypesContainingFile); + const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeReferences, containingFilename); + for (let i = 0; i < typeReferences.length; i++) { + processTypeReferenceDirective(typeReferences[i], resolutions[i]); } - return resolvedProjectReferenceUptoDate(program!.getResolvedProjectReferences()![index], oldRef); } - - function resolvedProjectReferenceUptoDate(oldResolvedRef: ResolvedProjectReference | undefined, oldRef: ProjectReference): boolean { - if (oldResolvedRef) { - if (contains(seenResolvedRefs, oldResolvedRef)) { - // Assume true - return true; - } - - // If sourceFile for the oldResolvedRef existed, check the version for uptodate - if (!sourceFileVersionUptoDate(oldResolvedRef.sourceFile)) { - return false; - } - - // Add to seen before checking the referenced paths of this config file - (seenResolvedRefs || (seenResolvedRefs = [])).push(oldResolvedRef); - - // If child project references are upto date, this project reference is uptodate - return !forEach(oldResolvedRef.references, (childResolvedRef, index) => - !resolvedProjectReferenceUptoDate(childResolvedRef, oldResolvedRef.commandLine.projectReferences![index])); + // Do not process the default library if: + // - The '--noLib' flag is used. + // - A 'no-default-lib' reference comment is encountered in + // processing the root files. + if (rootNames.length && !skipDefaultLib) { + // If '--lib' is not specified, include default library file according to '--target' + // otherwise, using options specified in '--lib' instead of '--target' default library file + const defaultLibraryFileName = getDefaultLibraryFileName(); + if (!options.lib && defaultLibraryFileName) { + processRootFile(defaultLibraryFileName, /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ false); + } + else { + forEach(options.lib, libFileName => { + processRootFile(combinePaths(defaultLibraryPath, libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ false); + }); } - - // In old program, not able to resolve project reference path, - // so if config file doesnt exist, it is uptodate. - return !fileExists(resolveProjectReferencePath(oldRef)); } + missingFilePaths = arrayFrom(mapDefinedIterator(filesByName.entries(), ([path, file]) => file === undefined ? path as Path : undefined)); + files = stableSort(processingDefaultLibFiles, compareDefaultLibFiles).concat(processingOtherFiles); + processingDefaultLibFiles = undefined; + processingOtherFiles = undefined; } - - export function getConfigFileParsingDiagnostics(configFileParseResult: ParsedCommandLine): readonly Diagnostic[] { - return configFileParseResult.options.configFile ? - [...configFileParseResult.options.configFile.parseDiagnostics, ...configFileParseResult.errors] : - configFileParseResult.errors; + Debug.assert(!!missingFilePaths); + // Release any files we have acquired in the old program but are + // not part of the new program. + if (oldProgram && host.onReleaseOldSourceFile) { + const oldSourceFiles = oldProgram.getSourceFiles(); + for (const oldSourceFile of oldSourceFiles) { + const newFile = getSourceFileByPath(oldSourceFile.resolvedPath); + if (shouldCreateNewSourceFile || !newFile || + // old file wasnt redirect but new file is + (oldSourceFile.resolvedPath === oldSourceFile.path && newFile.resolvedPath !== oldSourceFile.path)) { + host.onReleaseOldSourceFile(oldSourceFile, oldProgram.getCompilerOptions(), !!getSourceFileByPath(oldSourceFile.path)); + } + } + oldProgram.forEachResolvedProjectReference((resolvedProjectReference, resolvedProjectReferencePath) => { + if (resolvedProjectReference && !getResolvedProjectReferenceByPath(resolvedProjectReferencePath)) { + host.onReleaseOldSourceFile!(resolvedProjectReference.sourceFile, oldProgram!.getCompilerOptions(), /*hasSourceFileByPath*/ false); + } + }); } - - /** - * Determine if source file needs to be re-created even if its text hasn't changed - */ - function shouldProgramCreateNewSourceFiles(program: Program | undefined, newOptions: CompilerOptions): boolean { - if (!program) return false; - // If any compiler options change, we can't reuse old source file even if version match - // The change in options like these could result in change in syntax tree or `sourceFile.bindDiagnostics`. - const oldOptions = program.getCompilerOptions(); - return !!sourceFileAffectingCompilerOptions.some(option => - !isJsonEqual(getCompilerOptionValue(oldOptions, option), getCompilerOptionValue(newOptions, option))); - } - - function createCreateProgramOptions(rootNames: readonly string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, configFileParsingDiagnostics?: readonly Diagnostic[]): CreateProgramOptions { - return { - rootNames, - options, - host, - oldProgram, - configFileParsingDiagnostics - }; + // unconditionally set oldProgram to undefined to prevent it from being captured in closure + oldProgram = undefined; + const program: Program = { + getRootFileNames: () => rootNames, + getSourceFile, + getSourceFileByPath, + getSourceFiles: () => files, + getMissingFilePaths: () => missingFilePaths!, + getRefFileMap: () => refFileMap, + getCompilerOptions: () => options, + getSyntacticDiagnostics, + getOptionsDiagnostics, + getGlobalDiagnostics, + getSemanticDiagnostics, + getSuggestionDiagnostics, + getDeclarationDiagnostics, + getBindAndCheckDiagnostics, + getProgramDiagnostics, + getTypeChecker, + getClassifiableNames, + getDiagnosticsProducingTypeChecker, + getCommonSourceDirectory, + emit, + getCurrentDirectory: () => currentDirectory, + getNodeCount: () => getDiagnosticsProducingTypeChecker().getNodeCount(), + getIdentifierCount: () => getDiagnosticsProducingTypeChecker().getIdentifierCount(), + getSymbolCount: () => getDiagnosticsProducingTypeChecker().getSymbolCount(), + getTypeCount: () => getDiagnosticsProducingTypeChecker().getTypeCount(), + getRelationCacheSizes: () => getDiagnosticsProducingTypeChecker().getRelationCacheSizes(), + getFileProcessingDiagnostics: () => fileProcessingDiagnostics, + getResolvedTypeReferenceDirectives: () => resolvedTypeReferenceDirectives, + isSourceFileFromExternalLibrary, + isSourceFileDefaultLibrary, + dropDiagnosticsProducingTypeChecker, + getSourceFileFromReference, + getLibFileFromReference, + sourceFileToPackageName, + redirectTargetsMap, + isEmittedFile, + getConfigFileParsingDiagnostics, + getResolvedModuleWithFailedLookupLocationsFromCache, + getProjectReferences, + getResolvedProjectReferences, + getProjectReferenceRedirect, + getResolvedProjectReferenceToRedirect, + getResolvedProjectReferenceByPath, + forEachResolvedProjectReference, + isSourceOfProjectReferenceRedirect, + emitBuildInfo, + getProbableSymlinks + }; + verifyCompilerOptions(); + mark("afterProgram"); + measure("Program", "beforeProgram", "afterProgram"); + return program; + function compareDefaultLibFiles(a: SourceFile, b: SourceFile) { + return compareValues(getDefaultLibFilePriority(a), getDefaultLibFilePriority(b)); } - - /** - * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' - * that represent a compilation unit. - * - * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and - * triple-slash-reference-path directives transitively. '@types' and triple-slash-reference-types are also pulled in. - * - * @param createProgramOptions - The options for creating a program. - * @returns A 'Program' object. - */ - export function createProgram(createProgramOptions: CreateProgramOptions): Program; - /** - * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' - * that represent a compilation unit. - * - * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and - * triple-slash-reference-path directives transitively. '@types' and triple-slash-reference-types are also pulled in. - * - * @param rootNames - A set of root files. - * @param options - The compiler options which should be used. - * @param host - The host interacts with the underlying file system. - * @param oldProgram - Reuses an old program structure. - * @param configFileParsingDiagnostics - error during config file parsing - * @returns A 'Program' object. - */ - export function createProgram(rootNames: readonly string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, configFileParsingDiagnostics?: readonly Diagnostic[]): Program; - export function createProgram(rootNamesOrOptions: readonly string[] | CreateProgramOptions, _options?: CompilerOptions, _host?: CompilerHost, _oldProgram?: Program, _configFileParsingDiagnostics?: readonly Diagnostic[]): Program { - const createProgramOptions = isArray(rootNamesOrOptions) ? createCreateProgramOptions(rootNamesOrOptions, _options!, _host, _oldProgram, _configFileParsingDiagnostics) : rootNamesOrOptions; // TODO: GH#18217 - const { rootNames, options, configFileParsingDiagnostics, projectReferences } = createProgramOptions; - let { oldProgram } = createProgramOptions; - - let processingDefaultLibFiles: SourceFile[] | undefined; - let processingOtherFiles: SourceFile[] | undefined; - let files: SourceFile[]; - let symlinks: ReadonlyMap | undefined; - let commonSourceDirectory: string; - let diagnosticsProducingTypeChecker: TypeChecker; - let noDiagnosticsTypeChecker: TypeChecker; - let classifiableNames: UnderscoreEscapedMap; - const ambientModuleNameToUnmodifiedFileName = createMap(); - // Todo:: Use this to report why file was included in --extendedDiagnostics - let refFileMap: MultiMap | undefined; - - const cachedBindAndCheckDiagnosticsForFile: DiagnosticCache = {}; - const cachedDeclarationDiagnosticsForFile: DiagnosticCache = {}; - - let resolvedTypeReferenceDirectives = createMap(); - let fileProcessingDiagnostics = createDiagnosticCollection(); - - // The below settings are to track if a .js file should be add to the program if loaded via searching under node_modules. - // This works as imported modules are discovered recursively in a depth first manner, specifically: - // - For each root file, findSourceFile is called. - // - This calls processImportedModules for each module imported in the source file. - // - This calls resolveModuleNames, and then calls findSourceFile for each resolved module. - // As all these operations happen - and are nested - within the createProgram call, they close over the below variables. - // The current resolution depth is tracked by incrementing/decrementing as the depth first search progresses. - const maxNodeModuleJsDepth = typeof options.maxNodeModuleJsDepth === "number" ? options.maxNodeModuleJsDepth : 0; - let currentNodeModulesDepth = 0; - - // If a module has some of its imports skipped due to being at the depth limit under node_modules, then track - // this, as it may be imported at a shallower depth later, and then it will need its skipped imports processed. - const modulesWithElidedImports = createMap(); - - // Track source files that are source files found by searching under node_modules, as these shouldn't be compiled. - const sourceFilesFoundSearchingNodeModules = createMap(); - - performance.mark("beforeProgram"); - - const host = createProgramOptions.host || createCompilerHost(options); - const configParsingHost = parseConfigHostFromCompilerHostLike(host); - - let skipDefaultLib = options.noLib; - const getDefaultLibraryFileName = memoize(() => host.getDefaultLibFileName(options)); - const defaultLibraryPath = host.getDefaultLibLocation ? host.getDefaultLibLocation() : getDirectoryPath(getDefaultLibraryFileName()); - const programDiagnostics = createDiagnosticCollection(); - const currentDirectory = host.getCurrentDirectory(); - const supportedExtensions = getSupportedExtensions(options); - const supportedExtensionsWithJsonIfResolveJsonModule = getSuppoertedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions); - - // Map storing if there is emit blocking diagnostics for given input - const hasEmitBlockingDiagnostics = createMap(); - let _compilerOptionsObjectLiteralSyntax: ObjectLiteralExpression | null | undefined; - - let moduleResolutionCache: ModuleResolutionCache | undefined; - let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference) => ResolvedModuleFull[]; - const hasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse; - if (host.resolveModuleNames) { - resolveModuleNamesWorker = (moduleNames, containingFile, reusedNames, redirectedReference) => host.resolveModuleNames!(Debug.assertEachDefined(moduleNames), containingFile, reusedNames, redirectedReference, options).map(resolved => { - // An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName. - if (!resolved || (resolved as ResolvedModuleFull).extension !== undefined) { - return resolved as ResolvedModuleFull; - } - const withExtension = clone(resolved) as ResolvedModuleFull; - withExtension.extension = extensionFromPath(resolved.resolvedFileName); - return withExtension; - }); + function getDefaultLibFilePriority(a: SourceFile) { + if (containsPath(defaultLibraryPath, a.fileName, /*ignoreCase*/ false)) { + const basename = getBaseFileName(a.fileName); + if (basename === "lib.d.ts" || basename === "lib.es6.d.ts") + return 0; + const name = removeSuffix(removePrefix(basename, "lib."), ".d.ts"); + const index = libs.indexOf(name); + if (index !== -1) + return index + 1; + } + return libs.length + 2; + } + function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): ResolvedModuleWithFailedLookupLocations | undefined { + return moduleResolutionCache && resolveModuleNameFromCache(moduleName, containingFile, moduleResolutionCache); + } + function toPath(fileName: string): Path { + return ts.toPath(fileName, currentDirectory, getCanonicalFileName); + } + function getCommonSourceDirectory() { + if (commonSourceDirectory === undefined) { + const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, program)); + if (options.rootDir && checkSourceFilesBelongToPath(emittedFiles, options.rootDir)) { + // If a rootDir is specified use it as the commonSourceDirectory + commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory); + } + else if (options.composite && options.configFilePath) { + // Project compilations never infer their root from the input source paths + commonSourceDirectory = getDirectoryPath(normalizeSlashes(options.configFilePath)); + checkSourceFilesBelongToPath(emittedFiles, commonSourceDirectory); + } + else { + commonSourceDirectory = computeCommonSourceDirectory(emittedFiles); + } + if (commonSourceDirectory && commonSourceDirectory[commonSourceDirectory.length - 1] !== directorySeparator) { + // Make sure directory path ends with directory separator so this string can directly + // used to replace with "" to get the relative path of the source file and the relative path doesn't + // start with / making it rooted path + commonSourceDirectory += directorySeparator; + } } - else { - moduleResolutionCache = createModuleResolutionCache(currentDirectory, x => host.getCanonicalFileName(x), options); - const loader = (moduleName: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFile, options, host, moduleResolutionCache, redirectedReference).resolvedModule!; // TODO: GH#18217 - resolveModuleNamesWorker = (moduleNames, containingFile, _reusedNames, redirectedReference) => loadWithLocalCache(Debug.assertEachDefined(moduleNames), containingFile, redirectedReference, loader); + return commonSourceDirectory; + } + function getClassifiableNames() { + if (!classifiableNames) { + // Initialize a checker so that all our files are bound. + getTypeChecker(); + classifiableNames = createUnderscoreEscapedMap(); + for (const sourceFile of files) { + copyEntries((sourceFile.classifiableNames!), classifiableNames); + } } - - let resolveTypeReferenceDirectiveNamesWorker: (typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference) => (ResolvedTypeReferenceDirective | undefined)[]; - if (host.resolveTypeReferenceDirectives) { - resolveTypeReferenceDirectiveNamesWorker = (typeDirectiveNames, containingFile, redirectedReference) => host.resolveTypeReferenceDirectives!(Debug.assertEachDefined(typeDirectiveNames), containingFile, redirectedReference, options); + return classifiableNames; + } + function resolveModuleNamesReusingOldState(moduleNames: string[], containingFile: string, file: SourceFile) { + if (structuralIsReused === StructureIsReused.Not && !file.ambientModuleNames.length) { + // If the old program state does not permit reusing resolutions and `file` does not contain locally defined ambient modules, + // the best we can do is fallback to the default logic. + return resolveModuleNamesWorker(moduleNames, containingFile, /*reusedNames*/ undefined, getResolvedProjectReferenceToRedirect(file.originalFileName)); + } + const oldSourceFile = oldProgram && oldProgram.getSourceFile(containingFile); + if (oldSourceFile !== file && file.resolvedModules) { + // `file` was created for the new program. + // + // We only set `file.resolvedModules` via work from the current function, + // so it is defined iff we already called the current function on `file`. + // That call happened no later than the creation of the `file` object, + // which per above occurred during the current program creation. + // Since we assume the filesystem does not change during program creation, + // it is safe to reuse resolutions from the earlier call. + const result: ResolvedModuleFull[] = []; + for (const moduleName of moduleNames) { + const resolvedModule = file.resolvedModules.get(moduleName)!; + result.push(resolvedModule); + } + return result; } - else { - const loader = (typesRef: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveTypeReferenceDirective(typesRef, containingFile, options, host, redirectedReference).resolvedTypeReferenceDirective!; // TODO: GH#18217 - resolveTypeReferenceDirectiveNamesWorker = (typeReferenceDirectiveNames, containingFile, redirectedReference) => loadWithLocalCache(Debug.assertEachDefined(typeReferenceDirectiveNames), containingFile, redirectedReference, loader); - } - - // Map from a stringified PackageId to the source file with that id. - // Only one source file may have a given packageId. Others become redirects (see createRedirectSourceFile). - // `packageIdToSourceFile` is only used while building the program, while `sourceFileToPackageName` and `isSourceFileTargetOfRedirect` are kept around. - const packageIdToSourceFile = createMap(); - // Maps from a SourceFile's `.path` to the name of the package it was imported with. - let sourceFileToPackageName = createMap(); - // Key is a file name. Value is the (non-empty, or undefined) list of files that redirect to it. - let redirectTargetsMap = createMultiMap(); - + // At this point, we know at least one of the following hold: + // - file has local declarations for ambient modules + // - old program state is available + // With this information, we can infer some module resolutions without performing resolution. + /** An ordered list of module names for which we cannot recover the resolution. */ + let unknownModuleNames: string[] | undefined; /** - * map with - * - SourceFile if present - * - false if sourceFile missing for source of project reference redirect - * - undefined otherwise + * The indexing of elements in this list matches that of `moduleNames`. + * + * Before combining results, result[i] is in one of the following states: + * * undefined: needs to be recomputed, + * * predictedToResolveToAmbientModuleMarker: known to be an ambient module. + * Needs to be reset to undefined before returning, + * * ResolvedModuleFull instance: can be reused. */ - const filesByName = createMap(); - let missingFilePaths: readonly Path[] | undefined; - // stores 'filename -> file association' ignoring case - // used to track cases when two file names differ only in casing - const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createMap() : undefined; - - // A parallel array to projectReferences storing the results of reading in the referenced tsconfig files - let resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined; - let projectReferenceRedirects: Map | undefined; - let mapFromFileToProjectReferenceRedirects: Map | undefined; - let mapFromToProjectReferenceRedirectSource: Map | undefined; - const useSourceOfProjectReferenceRedirect = !!host.useSourceOfProjectReferenceRedirect && host.useSourceOfProjectReferenceRedirect(); - - const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options); - // We set `structuralIsReused` to `undefined` because `tryReuseStructureFromOldProgram` calls `tryReuseStructureFromOldProgram` which checks - // `structuralIsReused`, which would be a TDZ violation if it was not set in advance to `undefined`. - let structuralIsReused: StructureIsReused | undefined; - structuralIsReused = tryReuseStructureFromOldProgram(); // eslint-disable-line prefer-const - if (structuralIsReused !== StructureIsReused.Completely) { - processingDefaultLibFiles = []; - processingOtherFiles = []; - - if (projectReferences) { - if (!resolvedProjectReferences) { - resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile); - } - if (host.setResolvedProjectReferenceCallbacks) { - host.setResolvedProjectReferenceCallbacks({ - getSourceOfProjectReferenceRedirect, - forEachResolvedProjectReference - }); - } - if (rootNames.length) { - for (const parsedRef of resolvedProjectReferences) { - if (!parsedRef) continue; - const out = parsedRef.commandLine.options.outFile || parsedRef.commandLine.options.out; - if (useSourceOfProjectReferenceRedirect) { - if (out || getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) { - for (const fileName of parsedRef.commandLine.fileNames) { - processSourceFile(fileName, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined); - } - } - } - else { - if (out) { - processSourceFile(changeExtension(out, ".d.ts"), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined); - } - else if (getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) { - for (const fileName of parsedRef.commandLine.fileNames) { - if (!fileExtensionIs(fileName, Extension.Dts)) { - processSourceFile(getOutputDeclarationFileName(fileName, parsedRef.commandLine, !host.useCaseSensitiveFileNames()), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined); - } - } - } - } + let result: ResolvedModuleFull[] | undefined; + let reusedNames: string[] | undefined; + /** A transient placeholder used to mark predicted resolution in the result list. */ + const predictedToResolveToAmbientModuleMarker: ResolvedModuleFull = ({}); + for (let i = 0; i < moduleNames.length; i++) { + const moduleName = moduleNames[i]; + // If the source file is unchanged and doesnt have invalidated resolution, reuse the module resolutions + if (file === oldSourceFile && !hasInvalidatedResolution(oldSourceFile.path)) { + const oldResolvedModule = oldSourceFile && oldSourceFile.resolvedModules!.get(moduleName); + if (oldResolvedModule) { + if (isTraceEnabled(options, host)) { + trace(host, Diagnostics.Reusing_resolution_of_module_0_to_file_1_from_old_program, moduleName, containingFile); } + (result || (result = new Array(moduleNames.length)))[i] = oldResolvedModule; + (reusedNames || (reusedNames = [])).push(moduleName); + continue; } } - - forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false)); - - // load type declarations specified via 'types' argument or implicitly from types/ and node_modules/@types folders - const typeReferences: string[] = rootNames.length ? getAutomaticTypeDirectiveNames(options, host) : emptyArray; - - if (typeReferences.length) { - // This containingFilename needs to match with the one used in managed-side - const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : host.getCurrentDirectory(); - const containingFilename = combinePaths(containingDirectory, inferredTypesContainingFile); - const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeReferences, containingFilename); - for (let i = 0; i < typeReferences.length; i++) { - processTypeReferenceDirective(typeReferences[i], resolutions[i]); - } - } - - // Do not process the default library if: - // - The '--noLib' flag is used. - // - A 'no-default-lib' reference comment is encountered in - // processing the root files. - if (rootNames.length && !skipDefaultLib) { - // If '--lib' is not specified, include default library file according to '--target' - // otherwise, using options specified in '--lib' instead of '--target' default library file - const defaultLibraryFileName = getDefaultLibraryFileName(); - if (!options.lib && defaultLibraryFileName) { - processRootFile(defaultLibraryFileName, /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ false); - } - else { - forEach(options.lib, libFileName => { - processRootFile(combinePaths(defaultLibraryPath, libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ false); - }); - } - } - - missingFilePaths = arrayFrom(mapDefinedIterator(filesByName.entries(), ([path, file]) => file === undefined ? path as Path : undefined)); - files = stableSort(processingDefaultLibFiles, compareDefaultLibFiles).concat(processingOtherFiles); - processingDefaultLibFiles = undefined; - processingOtherFiles = undefined; - } - - Debug.assert(!!missingFilePaths); - - // Release any files we have acquired in the old program but are - // not part of the new program. - if (oldProgram && host.onReleaseOldSourceFile) { - const oldSourceFiles = oldProgram.getSourceFiles(); - for (const oldSourceFile of oldSourceFiles) { - const newFile = getSourceFileByPath(oldSourceFile.resolvedPath); - if (shouldCreateNewSourceFile || !newFile || - // old file wasnt redirect but new file is - (oldSourceFile.resolvedPath === oldSourceFile.path && newFile.resolvedPath !== oldSourceFile.path)) { - host.onReleaseOldSourceFile(oldSourceFile, oldProgram.getCompilerOptions(), !!getSourceFileByPath(oldSourceFile.path)); - } - } - oldProgram.forEachResolvedProjectReference((resolvedProjectReference, resolvedProjectReferencePath) => { - if (resolvedProjectReference && !getResolvedProjectReferenceByPath(resolvedProjectReferencePath)) { - host.onReleaseOldSourceFile!(resolvedProjectReference.sourceFile, oldProgram!.getCompilerOptions(), /*hasSourceFileByPath*/ false); + // We know moduleName resolves to an ambient module provided that moduleName: + // - is in the list of ambient modules locally declared in the current source file. + // - resolved to an ambient module in the old program whose declaration is in an unmodified file + // (so the same module declaration will land in the new program) + let resolvesToAmbientModuleInNonModifiedFile = false; + if (contains(file.ambientModuleNames, moduleName)) { + resolvesToAmbientModuleInNonModifiedFile = true; + if (isTraceEnabled(options, host)) { + trace(host, Diagnostics.Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1, moduleName, containingFile); } - }); + } + else { + resolvesToAmbientModuleInNonModifiedFile = moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName); + } + if (resolvesToAmbientModuleInNonModifiedFile) { + (result || (result = new Array(moduleNames.length)))[i] = predictedToResolveToAmbientModuleMarker; + } + else { + // Resolution failed in the old program, or resolved to an ambient module for which we can't reuse the result. + (unknownModuleNames || (unknownModuleNames = [])).push(moduleName); + } } - - // unconditionally set oldProgram to undefined to prevent it from being captured in closure - oldProgram = undefined; - - const program: Program = { - getRootFileNames: () => rootNames, - getSourceFile, - getSourceFileByPath, - getSourceFiles: () => files, - getMissingFilePaths: () => missingFilePaths!, // TODO: GH#18217 - getRefFileMap: () => refFileMap, - getCompilerOptions: () => options, - getSyntacticDiagnostics, - getOptionsDiagnostics, - getGlobalDiagnostics, - getSemanticDiagnostics, - getSuggestionDiagnostics, - getDeclarationDiagnostics, - getBindAndCheckDiagnostics, - getProgramDiagnostics, - getTypeChecker, - getClassifiableNames, - getDiagnosticsProducingTypeChecker, - getCommonSourceDirectory, - emit, - getCurrentDirectory: () => currentDirectory, - getNodeCount: () => getDiagnosticsProducingTypeChecker().getNodeCount(), - getIdentifierCount: () => getDiagnosticsProducingTypeChecker().getIdentifierCount(), - getSymbolCount: () => getDiagnosticsProducingTypeChecker().getSymbolCount(), - getTypeCount: () => getDiagnosticsProducingTypeChecker().getTypeCount(), - getRelationCacheSizes: () => getDiagnosticsProducingTypeChecker().getRelationCacheSizes(), - getFileProcessingDiagnostics: () => fileProcessingDiagnostics, - getResolvedTypeReferenceDirectives: () => resolvedTypeReferenceDirectives, - isSourceFileFromExternalLibrary, - isSourceFileDefaultLibrary, - dropDiagnosticsProducingTypeChecker, - getSourceFileFromReference, - getLibFileFromReference, - sourceFileToPackageName, - redirectTargetsMap, - isEmittedFile, - getConfigFileParsingDiagnostics, - getResolvedModuleWithFailedLookupLocationsFromCache, - getProjectReferences, - getResolvedProjectReferences, - getProjectReferenceRedirect, - getResolvedProjectReferenceToRedirect, - getResolvedProjectReferenceByPath, - forEachResolvedProjectReference, - isSourceOfProjectReferenceRedirect, - emitBuildInfo, - getProbableSymlinks - }; - - verifyCompilerOptions(); - performance.mark("afterProgram"); - performance.measure("Program", "beforeProgram", "afterProgram"); - - return program; - - function compareDefaultLibFiles(a: SourceFile, b: SourceFile) { - return compareValues(getDefaultLibFilePriority(a), getDefaultLibFilePriority(b)); - } - - function getDefaultLibFilePriority(a: SourceFile) { - if (containsPath(defaultLibraryPath, a.fileName, /*ignoreCase*/ false)) { - const basename = getBaseFileName(a.fileName); - if (basename === "lib.d.ts" || basename === "lib.es6.d.ts") return 0; - const name = removeSuffix(removePrefix(basename, "lib."), ".d.ts"); - const index = libs.indexOf(name); - if (index !== -1) return index + 1; - } - return libs.length + 2; - } - - function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): ResolvedModuleWithFailedLookupLocations | undefined { - return moduleResolutionCache && resolveModuleNameFromCache(moduleName, containingFile, moduleResolutionCache); - } - - function toPath(fileName: string): Path { - return ts.toPath(fileName, currentDirectory, getCanonicalFileName); - } - - function getCommonSourceDirectory() { - if (commonSourceDirectory === undefined) { - const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, program)); - if (options.rootDir && checkSourceFilesBelongToPath(emittedFiles, options.rootDir)) { - // If a rootDir is specified use it as the commonSourceDirectory - commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory); - } - else if (options.composite && options.configFilePath) { - // Project compilations never infer their root from the input source paths - commonSourceDirectory = getDirectoryPath(normalizeSlashes(options.configFilePath)); - checkSourceFilesBelongToPath(emittedFiles, commonSourceDirectory); - } - else { - commonSourceDirectory = computeCommonSourceDirectory(emittedFiles); - } - - if (commonSourceDirectory && commonSourceDirectory[commonSourceDirectory.length - 1] !== directorySeparator) { - // Make sure directory path ends with directory separator so this string can directly - // used to replace with "" to get the relative path of the source file and the relative path doesn't - // start with / making it rooted path - commonSourceDirectory += directorySeparator; - } - } - return commonSourceDirectory; - } - - function getClassifiableNames() { - if (!classifiableNames) { - // Initialize a checker so that all our files are bound. - getTypeChecker(); - classifiableNames = createUnderscoreEscapedMap(); - - for (const sourceFile of files) { - copyEntries(sourceFile.classifiableNames!, classifiableNames); - } - } - - return classifiableNames; - } - - function resolveModuleNamesReusingOldState(moduleNames: string[], containingFile: string, file: SourceFile) { - if (structuralIsReused === StructureIsReused.Not && !file.ambientModuleNames.length) { - // If the old program state does not permit reusing resolutions and `file` does not contain locally defined ambient modules, - // the best we can do is fallback to the default logic. - return resolveModuleNamesWorker(moduleNames, containingFile, /*reusedNames*/ undefined, getResolvedProjectReferenceToRedirect(file.originalFileName)); - } - - const oldSourceFile = oldProgram && oldProgram.getSourceFile(containingFile); - if (oldSourceFile !== file && file.resolvedModules) { - // `file` was created for the new program. - // - // We only set `file.resolvedModules` via work from the current function, - // so it is defined iff we already called the current function on `file`. - // That call happened no later than the creation of the `file` object, - // which per above occurred during the current program creation. - // Since we assume the filesystem does not change during program creation, - // it is safe to reuse resolutions from the earlier call. - const result: ResolvedModuleFull[] = []; - for (const moduleName of moduleNames) { - const resolvedModule = file.resolvedModules.get(moduleName)!; - result.push(resolvedModule); + const resolutions = unknownModuleNames && unknownModuleNames.length + ? resolveModuleNamesWorker(unknownModuleNames, containingFile, reusedNames, getResolvedProjectReferenceToRedirect(file.originalFileName)) + : emptyArray; + // Combine results of resolutions and predicted results + if (!result) { + // There were no unresolved/ambient resolutions. + Debug.assert(resolutions.length === moduleNames.length); + return resolutions; + } + let j = 0; + for (let i = 0; i < result.length; i++) { + if (result[i]) { + // `result[i]` is either a `ResolvedModuleFull` or a marker. + // If it is the former, we can leave it as is. + if (result[i] === predictedToResolveToAmbientModuleMarker) { + result[i] = undefined!; // TODO: GH#18217 } - return result; } - // At this point, we know at least one of the following hold: - // - file has local declarations for ambient modules - // - old program state is available - // With this information, we can infer some module resolutions without performing resolution. - - /** An ordered list of module names for which we cannot recover the resolution. */ - let unknownModuleNames: string[] | undefined; - /** - * The indexing of elements in this list matches that of `moduleNames`. - * - * Before combining results, result[i] is in one of the following states: - * * undefined: needs to be recomputed, - * * predictedToResolveToAmbientModuleMarker: known to be an ambient module. - * Needs to be reset to undefined before returning, - * * ResolvedModuleFull instance: can be reused. - */ - let result: ResolvedModuleFull[] | undefined; - let reusedNames: string[] | undefined; - /** A transient placeholder used to mark predicted resolution in the result list. */ - const predictedToResolveToAmbientModuleMarker: ResolvedModuleFull = {}; - - for (let i = 0; i < moduleNames.length; i++) { - const moduleName = moduleNames[i]; - // If the source file is unchanged and doesnt have invalidated resolution, reuse the module resolutions - if (file === oldSourceFile && !hasInvalidatedResolution(oldSourceFile.path)) { - const oldResolvedModule = oldSourceFile && oldSourceFile.resolvedModules!.get(moduleName); - if (oldResolvedModule) { - if (isTraceEnabled(options, host)) { - trace(host, Diagnostics.Reusing_resolution_of_module_0_to_file_1_from_old_program, moduleName, containingFile); - } - (result || (result = new Array(moduleNames.length)))[i] = oldResolvedModule; - (reusedNames || (reusedNames = [])).push(moduleName); - continue; - } - } - // We know moduleName resolves to an ambient module provided that moduleName: - // - is in the list of ambient modules locally declared in the current source file. - // - resolved to an ambient module in the old program whose declaration is in an unmodified file - // (so the same module declaration will land in the new program) - let resolvesToAmbientModuleInNonModifiedFile = false; - if (contains(file.ambientModuleNames, moduleName)) { - resolvesToAmbientModuleInNonModifiedFile = true; - if (isTraceEnabled(options, host)) { - trace(host, Diagnostics.Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1, moduleName, containingFile); - } - } - else { - resolvesToAmbientModuleInNonModifiedFile = moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName); - } - - if (resolvesToAmbientModuleInNonModifiedFile) { - (result || (result = new Array(moduleNames.length)))[i] = predictedToResolveToAmbientModuleMarker; - } - else { - // Resolution failed in the old program, or resolved to an ambient module for which we can't reuse the result. - (unknownModuleNames || (unknownModuleNames = [])).push(moduleName); - } - } - - const resolutions = unknownModuleNames && unknownModuleNames.length - ? resolveModuleNamesWorker(unknownModuleNames, containingFile, reusedNames, getResolvedProjectReferenceToRedirect(file.originalFileName)) - : emptyArray; - - // Combine results of resolutions and predicted results - if (!result) { - // There were no unresolved/ambient resolutions. - Debug.assert(resolutions.length === moduleNames.length); - return resolutions; - } - - let j = 0; - for (let i = 0; i < result.length; i++) { - if (result[i]) { - // `result[i]` is either a `ResolvedModuleFull` or a marker. - // If it is the former, we can leave it as is. - if (result[i] === predictedToResolveToAmbientModuleMarker) { - result[i] = undefined!; // TODO: GH#18217 - } - } - else { - result[i] = resolutions[j]; - j++; - } + else { + result[i] = resolutions[j]; + j++; } - Debug.assert(j === resolutions.length); - - return result; - - // If we change our policy of rechecking failed lookups on each program create, - // we should adjust the value returned here. - function moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName: string): boolean { - const resolutionToFile = getResolvedModule(oldSourceFile, moduleName); - const resolvedFile = resolutionToFile && oldProgram!.getSourceFile(resolutionToFile.resolvedFileName); - if (resolutionToFile && resolvedFile) { - // In the old program, we resolved to an ambient module that was in the same - // place as we expected to find an actual module file. - // We actually need to return 'false' here even though this seems like a 'true' case - // because the normal module resolution algorithm will find this anyway. - return false; - } - - // at least one of declarations should come from non-modified source file - const unmodifiedFile = ambientModuleNameToUnmodifiedFileName.get(moduleName); - - if (!unmodifiedFile) { - return false; - } - - if (isTraceEnabled(options, host)) { - trace(host, Diagnostics.Module_0_was_resolved_as_ambient_module_declared_in_1_since_this_file_was_not_modified, moduleName, unmodifiedFile); - } - return true; + } + Debug.assert(j === resolutions.length); + return result; + // If we change our policy of rechecking failed lookups on each program create, + // we should adjust the value returned here. + function moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName: string): boolean { + const resolutionToFile = getResolvedModule(oldSourceFile, moduleName); + const resolvedFile = resolutionToFile && oldProgram!.getSourceFile(resolutionToFile.resolvedFileName); + if (resolutionToFile && resolvedFile) { + // In the old program, we resolved to an ambient module that was in the same + // place as we expected to find an actual module file. + // We actually need to return 'false' here even though this seems like a 'true' case + // because the normal module resolution algorithm will find this anyway. + return false; + } + // at least one of declarations should come from non-modified source file + const unmodifiedFile = ambientModuleNameToUnmodifiedFileName.get(moduleName); + if (!unmodifiedFile) { + return false; + } + if (isTraceEnabled(options, host)) { + trace(host, Diagnostics.Module_0_was_resolved_as_ambient_module_declared_in_1_since_this_file_was_not_modified, moduleName, unmodifiedFile); } + return true; } - - function canReuseProjectReferences(): boolean { - return !forEachProjectReference( - oldProgram!.getProjectReferences(), - oldProgram!.getResolvedProjectReferences(), - (oldResolvedRef, index, parent) => { - const newRef = (parent ? parent.commandLine.projectReferences : projectReferences)![index]; - const newResolvedRef = parseProjectReferenceConfigFile(newRef); - if (oldResolvedRef) { - // Resolved project reference has gone missing or changed - return !newResolvedRef || newResolvedRef.sourceFile !== oldResolvedRef.sourceFile; - } - else { - // A previously-unresolved reference may be resolved now - return newResolvedRef !== undefined; - } - }, - (oldProjectReferences, parent) => { - // If array of references is changed, we cant resue old program - const newReferences = parent ? getResolvedProjectReferenceByPath(parent.sourceFile.path)!.commandLine.projectReferences : projectReferences; - return !arrayIsEqualTo(oldProjectReferences, newReferences, projectReferenceIsEqualTo); - } - ); - } - - function tryReuseStructureFromOldProgram(): StructureIsReused { - if (!oldProgram) { - return StructureIsReused.Not; - } - - // check properties that can affect structure of the program or module resolution strategy - // if any of these properties has changed - structure cannot be reused - const oldOptions = oldProgram.getCompilerOptions(); - if (changesAffectModuleResolution(oldOptions, options)) { - return oldProgram.structureIsReused = StructureIsReused.Not; + } + function canReuseProjectReferences(): boolean { + return !forEachProjectReference(oldProgram!.getProjectReferences(), oldProgram!.getResolvedProjectReferences(), (oldResolvedRef, index, parent) => { + const newRef = (parent ? parent.commandLine.projectReferences : projectReferences)![index]; + const newResolvedRef = parseProjectReferenceConfigFile(newRef); + if (oldResolvedRef) { + // Resolved project reference has gone missing or changed + return !newResolvedRef || newResolvedRef.sourceFile !== oldResolvedRef.sourceFile; } - - Debug.assert(!(oldProgram.structureIsReused! & (StructureIsReused.Completely | StructureIsReused.SafeModules))); - - // there is an old program, check if we can reuse its structure - const oldRootNames = oldProgram.getRootFileNames(); - if (!arrayIsEqualTo(oldRootNames, rootNames)) { - return oldProgram.structureIsReused = StructureIsReused.Not; + else { + // A previously-unresolved reference may be resolved now + return newResolvedRef !== undefined; } - - if (!arrayIsEqualTo(options.types, oldOptions.types)) { - return oldProgram.structureIsReused = StructureIsReused.Not; + }, (oldProjectReferences, parent) => { + // If array of references is changed, we cant resue old program + const newReferences = parent ? getResolvedProjectReferenceByPath(parent.sourceFile.path)!.commandLine.projectReferences : projectReferences; + return !arrayIsEqualTo(oldProjectReferences, newReferences, projectReferenceIsEqualTo); + }); + } + function tryReuseStructureFromOldProgram(): StructureIsReused { + if (!oldProgram) { + return StructureIsReused.Not; + } + // check properties that can affect structure of the program or module resolution strategy + // if any of these properties has changed - structure cannot be reused + const oldOptions = oldProgram.getCompilerOptions(); + if (changesAffectModuleResolution(oldOptions, options)) { + return oldProgram.structureIsReused = StructureIsReused.Not; + } + Debug.assert(!((oldProgram.structureIsReused!) & (StructureIsReused.Completely | StructureIsReused.SafeModules))); + // there is an old program, check if we can reuse its structure + const oldRootNames = oldProgram.getRootFileNames(); + if (!arrayIsEqualTo(oldRootNames, rootNames)) { + return oldProgram.structureIsReused = StructureIsReused.Not; + } + if (!arrayIsEqualTo(options.types, oldOptions.types)) { + return oldProgram.structureIsReused = StructureIsReused.Not; + } + // Check if any referenced project tsconfig files are different + if (!canReuseProjectReferences()) { + return oldProgram.structureIsReused = StructureIsReused.Not; + } + if (projectReferences) { + resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile); + if (host.setResolvedProjectReferenceCallbacks) { + host.setResolvedProjectReferenceCallbacks({ + getSourceOfProjectReferenceRedirect, + forEachResolvedProjectReference + }); } - - // Check if any referenced project tsconfig files are different - if (!canReuseProjectReferences()) { + } + // check if program source files has changed in the way that can affect structure of the program + const newSourceFiles: SourceFile[] = []; + const modifiedSourceFiles: { + oldFile: SourceFile; + newFile: SourceFile; + }[] = []; + oldProgram.structureIsReused = StructureIsReused.Completely; + // If the missing file paths are now present, it can change the progam structure, + // and hence cant reuse the structure. + // This is same as how we dont reuse the structure if one of the file from old program is now missing + if (oldProgram.getMissingFilePaths().some(missingFilePath => host.fileExists(missingFilePath))) { + return oldProgram.structureIsReused = StructureIsReused.Not; + } + const oldSourceFiles = oldProgram.getSourceFiles(); + const enum SeenPackageName { + Exists, + Modified + } + const seenPackageNames = createMap(); + for (const oldSourceFile of oldSourceFiles) { + let newSourceFile = host.getSourceFileByPath + ? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.resolvedPath, options.target!, /*onError*/ undefined, shouldCreateNewSourceFile) + : host.getSourceFile(oldSourceFile.fileName, options.target!, /*onError*/ undefined, shouldCreateNewSourceFile); // TODO: GH#18217 + if (!newSourceFile) { return oldProgram.structureIsReused = StructureIsReused.Not; } - if (projectReferences) { - resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile); - if (host.setResolvedProjectReferenceCallbacks) { - host.setResolvedProjectReferenceCallbacks({ - getSourceOfProjectReferenceRedirect, - forEachResolvedProjectReference - }); - } - } - - // check if program source files has changed in the way that can affect structure of the program - const newSourceFiles: SourceFile[] = []; - const modifiedSourceFiles: { oldFile: SourceFile, newFile: SourceFile }[] = []; - oldProgram.structureIsReused = StructureIsReused.Completely; - - // If the missing file paths are now present, it can change the progam structure, - // and hence cant reuse the structure. - // This is same as how we dont reuse the structure if one of the file from old program is now missing - if (oldProgram.getMissingFilePaths().some(missingFilePath => host.fileExists(missingFilePath))) { - return oldProgram.structureIsReused = StructureIsReused.Not; + Debug.assert(!newSourceFile.redirectInfo, "Host should not return a redirect source file from `getSourceFile`"); + let fileChanged: boolean; + if (oldSourceFile.redirectInfo) { + // We got `newSourceFile` by path, so it is actually for the unredirected file. + // This lets us know if the unredirected file has changed. If it has we should break the redirect. + if (newSourceFile !== oldSourceFile.redirectInfo.unredirected) { + // Underlying file has changed. Might not redirect anymore. Must rebuild program. + return oldProgram.structureIsReused = StructureIsReused.Not; + } + fileChanged = false; + newSourceFile = oldSourceFile; // Use the redirect. } - - const oldSourceFiles = oldProgram.getSourceFiles(); - const enum SeenPackageName { Exists, Modified } - const seenPackageNames = createMap(); - - for (const oldSourceFile of oldSourceFiles) { - let newSourceFile = host.getSourceFileByPath - ? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.resolvedPath, options.target!, /*onError*/ undefined, shouldCreateNewSourceFile) - : host.getSourceFile(oldSourceFile.fileName, options.target!, /*onError*/ undefined, shouldCreateNewSourceFile); // TODO: GH#18217 - - if (!newSourceFile) { + else if (oldProgram.redirectTargetsMap.has(oldSourceFile.path)) { + // If a redirected-to source file changes, the redirect may be broken. + if (newSourceFile !== oldSourceFile) { return oldProgram.structureIsReused = StructureIsReused.Not; } - - Debug.assert(!newSourceFile.redirectInfo, "Host should not return a redirect source file from `getSourceFile`"); - - let fileChanged: boolean; - if (oldSourceFile.redirectInfo) { - // We got `newSourceFile` by path, so it is actually for the unredirected file. - // This lets us know if the unredirected file has changed. If it has we should break the redirect. - if (newSourceFile !== oldSourceFile.redirectInfo.unredirected) { - // Underlying file has changed. Might not redirect anymore. Must rebuild program. - return oldProgram.structureIsReused = StructureIsReused.Not; - } - fileChanged = false; - newSourceFile = oldSourceFile; // Use the redirect. + fileChanged = false; + } + else { + fileChanged = newSourceFile !== oldSourceFile; + } + // Since the project references havent changed, its right to set originalFileName and resolvedPath here + newSourceFile.path = oldSourceFile.path; + newSourceFile.originalFileName = oldSourceFile.originalFileName; + newSourceFile.resolvedPath = oldSourceFile.resolvedPath; + newSourceFile.fileName = oldSourceFile.fileName; + const packageName = oldProgram.sourceFileToPackageName.get(oldSourceFile.path); + if (packageName !== undefined) { + // If there are 2 different source files for the same package name and at least one of them changes, + // they might become redirects. So we must rebuild the program. + const prevKind = seenPackageNames.get(packageName); + const newKind = fileChanged ? SeenPackageName.Modified : SeenPackageName.Exists; + if ((prevKind !== undefined && newKind === SeenPackageName.Modified) || prevKind === SeenPackageName.Modified) { + return oldProgram.structureIsReused = StructureIsReused.Not; } - else if (oldProgram.redirectTargetsMap.has(oldSourceFile.path)) { - // If a redirected-to source file changes, the redirect may be broken. - if (newSourceFile !== oldSourceFile) { - return oldProgram.structureIsReused = StructureIsReused.Not; - } - fileChanged = false; + seenPackageNames.set(packageName, newKind); + } + if (fileChanged) { + // The `newSourceFile` object was created for the new program. + if (!arrayIsEqualTo(oldSourceFile.libReferenceDirectives, newSourceFile.libReferenceDirectives, fileReferenceIsEqualTo)) { + // 'lib' references has changed. Matches behavior in changesAffectModuleResolution + return oldProgram.structureIsReused = StructureIsReused.Not; } - else { - fileChanged = newSourceFile !== oldSourceFile; - } - - // Since the project references havent changed, its right to set originalFileName and resolvedPath here - newSourceFile.path = oldSourceFile.path; - newSourceFile.originalFileName = oldSourceFile.originalFileName; - newSourceFile.resolvedPath = oldSourceFile.resolvedPath; - newSourceFile.fileName = oldSourceFile.fileName; - - const packageName = oldProgram.sourceFileToPackageName.get(oldSourceFile.path); - if (packageName !== undefined) { - // If there are 2 different source files for the same package name and at least one of them changes, - // they might become redirects. So we must rebuild the program. - const prevKind = seenPackageNames.get(packageName); - const newKind = fileChanged ? SeenPackageName.Modified : SeenPackageName.Exists; - if ((prevKind !== undefined && newKind === SeenPackageName.Modified) || prevKind === SeenPackageName.Modified) { - return oldProgram.structureIsReused = StructureIsReused.Not; - } - seenPackageNames.set(packageName, newKind); - } - - if (fileChanged) { - // The `newSourceFile` object was created for the new program. - - if (!arrayIsEqualTo(oldSourceFile.libReferenceDirectives, newSourceFile.libReferenceDirectives, fileReferenceIsEqualTo)) { - // 'lib' references has changed. Matches behavior in changesAffectModuleResolution - return oldProgram.structureIsReused = StructureIsReused.Not; - } - - if (oldSourceFile.hasNoDefaultLib !== newSourceFile.hasNoDefaultLib) { - // value of no-default-lib has changed - // this will affect if default library is injected into the list of files - oldProgram.structureIsReused = StructureIsReused.SafeModules; - } - - // check tripleslash references - if (!arrayIsEqualTo(oldSourceFile.referencedFiles, newSourceFile.referencedFiles, fileReferenceIsEqualTo)) { - // tripleslash references has changed - oldProgram.structureIsReused = StructureIsReused.SafeModules; - } - - // check imports and module augmentations - collectExternalModuleReferences(newSourceFile); - if (!arrayIsEqualTo(oldSourceFile.imports, newSourceFile.imports, moduleNameIsEqualTo)) { - // imports has changed - oldProgram.structureIsReused = StructureIsReused.SafeModules; - } - if (!arrayIsEqualTo(oldSourceFile.moduleAugmentations, newSourceFile.moduleAugmentations, moduleNameIsEqualTo)) { - // moduleAugmentations has changed - oldProgram.structureIsReused = StructureIsReused.SafeModules; - } - if ((oldSourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags) !== (newSourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags)) { - // dynamicImport has changed - oldProgram.structureIsReused = StructureIsReused.SafeModules; - } - - if (!arrayIsEqualTo(oldSourceFile.typeReferenceDirectives, newSourceFile.typeReferenceDirectives, fileReferenceIsEqualTo)) { - // 'types' references has changed - oldProgram.structureIsReused = StructureIsReused.SafeModules; - } - - // tentatively approve the file - modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile }); + if (oldSourceFile.hasNoDefaultLib !== newSourceFile.hasNoDefaultLib) { + // value of no-default-lib has changed + // this will affect if default library is injected into the list of files + oldProgram.structureIsReused = StructureIsReused.SafeModules; } - else if (hasInvalidatedResolution(oldSourceFile.path)) { - // 'module/types' references could have changed + // check tripleslash references + if (!arrayIsEqualTo(oldSourceFile.referencedFiles, newSourceFile.referencedFiles, fileReferenceIsEqualTo)) { + // tripleslash references has changed oldProgram.structureIsReused = StructureIsReused.SafeModules; - - // add file to the modified list so that we will resolve it later - modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile }); - } - - // if file has passed all checks it should be safe to reuse it - newSourceFiles.push(newSourceFile); - } - - if (oldProgram.structureIsReused !== StructureIsReused.Completely) { - return oldProgram.structureIsReused; - } - - const modifiedFiles = modifiedSourceFiles.map(f => f.oldFile); - for (const oldFile of oldSourceFiles) { - if (!contains(modifiedFiles, oldFile)) { - for (const moduleName of oldFile.ambientModuleNames) { - ambientModuleNameToUnmodifiedFileName.set(moduleName, oldFile.fileName); - } } + // check imports and module augmentations + collectExternalModuleReferences(newSourceFile); + if (!arrayIsEqualTo(oldSourceFile.imports, newSourceFile.imports, moduleNameIsEqualTo)) { + // imports has changed + oldProgram.structureIsReused = StructureIsReused.SafeModules; + } + if (!arrayIsEqualTo(oldSourceFile.moduleAugmentations, newSourceFile.moduleAugmentations, moduleNameIsEqualTo)) { + // moduleAugmentations has changed + oldProgram.structureIsReused = StructureIsReused.SafeModules; + } + if ((oldSourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags) !== (newSourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags)) { + // dynamicImport has changed + oldProgram.structureIsReused = StructureIsReused.SafeModules; + } + if (!arrayIsEqualTo(oldSourceFile.typeReferenceDirectives, newSourceFile.typeReferenceDirectives, fileReferenceIsEqualTo)) { + // 'types' references has changed + oldProgram.structureIsReused = StructureIsReused.SafeModules; + } + // tentatively approve the file + modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile }); + } + else if (hasInvalidatedResolution(oldSourceFile.path)) { + // 'module/types' references could have changed + oldProgram.structureIsReused = StructureIsReused.SafeModules; + // add file to the modified list so that we will resolve it later + modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile }); + } + // if file has passed all checks it should be safe to reuse it + newSourceFiles.push(newSourceFile); + } + if (oldProgram.structureIsReused !== StructureIsReused.Completely) { + return oldProgram.structureIsReused; + } + const modifiedFiles = modifiedSourceFiles.map(f => f.oldFile); + for (const oldFile of oldSourceFiles) { + if (!contains(modifiedFiles, oldFile)) { + for (const moduleName of oldFile.ambientModuleNames) { + ambientModuleNameToUnmodifiedFileName.set(moduleName, oldFile.fileName); + } + } + } + // try to verify results of module resolution + for (const { oldFile: oldSourceFile, newFile: newSourceFile } of modifiedSourceFiles) { + const newSourceFilePath = getNormalizedAbsolutePath(newSourceFile.originalFileName, currentDirectory); + const moduleNames = getModuleNames(newSourceFile); + const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFilePath, newSourceFile); + // ensure that module resolution results are still correct + const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo); + if (resolutionsChanged) { + oldProgram.structureIsReused = StructureIsReused.SafeModules; + newSourceFile.resolvedModules = zipToMap(moduleNames, resolutions); } - // try to verify results of module resolution - for (const { oldFile: oldSourceFile, newFile: newSourceFile } of modifiedSourceFiles) { - const newSourceFilePath = getNormalizedAbsolutePath(newSourceFile.originalFileName, currentDirectory); - const moduleNames = getModuleNames(newSourceFile); - const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFilePath, newSourceFile); - // ensure that module resolution results are still correct - const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo); + else { + newSourceFile.resolvedModules = oldSourceFile.resolvedModules; + } + if (resolveTypeReferenceDirectiveNamesWorker) { + // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. + const typesReferenceDirectives = map(newSourceFile.typeReferenceDirectives, ref => ref.fileName.toLocaleLowerCase()); + const resolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFilePath, getResolvedProjectReferenceToRedirect(newSourceFile.originalFileName)); + // ensure that types resolutions are still correct + const resolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, resolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo); if (resolutionsChanged) { oldProgram.structureIsReused = StructureIsReused.SafeModules; - newSourceFile.resolvedModules = zipToMap(moduleNames, resolutions); + newSourceFile.resolvedTypeReferenceDirectiveNames = zipToMap(typesReferenceDirectives, resolutions); } else { - newSourceFile.resolvedModules = oldSourceFile.resolvedModules; - } - if (resolveTypeReferenceDirectiveNamesWorker) { - // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. - const typesReferenceDirectives = map(newSourceFile.typeReferenceDirectives, ref => ref.fileName.toLocaleLowerCase()); - const resolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFilePath, getResolvedProjectReferenceToRedirect(newSourceFile.originalFileName)); - // ensure that types resolutions are still correct - const resolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, resolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo); - if (resolutionsChanged) { - oldProgram.structureIsReused = StructureIsReused.SafeModules; - newSourceFile.resolvedTypeReferenceDirectiveNames = zipToMap(typesReferenceDirectives, resolutions); - } - else { - newSourceFile.resolvedTypeReferenceDirectiveNames = oldSourceFile.resolvedTypeReferenceDirectiveNames; - } + newSourceFile.resolvedTypeReferenceDirectiveNames = oldSourceFile.resolvedTypeReferenceDirectiveNames; + } + } + } + if (oldProgram.structureIsReused !== StructureIsReused.Completely) { + return oldProgram.structureIsReused; + } + if (host.hasChangedAutomaticTypeDirectiveNames) { + return oldProgram.structureIsReused = StructureIsReused.SafeModules; + } + missingFilePaths = oldProgram.getMissingFilePaths(); + refFileMap = oldProgram.getRefFileMap(); + // update fileName -> file mapping + for (const newSourceFile of newSourceFiles) { + const filePath = newSourceFile.path; + addFileToFilesByName(newSourceFile, filePath, newSourceFile.resolvedPath); + if (useSourceOfProjectReferenceRedirect) { + const redirectProject = getProjectReferenceRedirectProject(newSourceFile.fileName); + if (redirectProject && !(redirectProject.commandLine.options.outFile || redirectProject.commandLine.options.out)) { + const redirect = getProjectReferenceOutputName(redirectProject, newSourceFile.fileName); + addFileToFilesByName(newSourceFile, toPath(redirect), /*redirectedPath*/ undefined); } } - - if (oldProgram.structureIsReused !== StructureIsReused.Completely) { - return oldProgram.structureIsReused; - } - - if (host.hasChangedAutomaticTypeDirectiveNames) { - return oldProgram.structureIsReused = StructureIsReused.SafeModules; - } - - missingFilePaths = oldProgram.getMissingFilePaths(); - refFileMap = oldProgram.getRefFileMap(); - - // update fileName -> file mapping - for (const newSourceFile of newSourceFiles) { - const filePath = newSourceFile.path; - addFileToFilesByName(newSourceFile, filePath, newSourceFile.resolvedPath); - if (useSourceOfProjectReferenceRedirect) { - const redirectProject = getProjectReferenceRedirectProject(newSourceFile.fileName); - if (redirectProject && !(redirectProject.commandLine.options.outFile || redirectProject.commandLine.options.out)) { - const redirect = getProjectReferenceOutputName(redirectProject, newSourceFile.fileName); - addFileToFilesByName(newSourceFile, toPath(redirect), /*redirectedPath*/ undefined); + // Set the file as found during node modules search if it was found that way in old progra, + if (oldProgram.isSourceFileFromExternalLibrary(oldProgram.getSourceFileByPath(newSourceFile.resolvedPath)!)) { + sourceFilesFoundSearchingNodeModules.set(filePath, true); + } + } + files = newSourceFiles; + fileProcessingDiagnostics = oldProgram.getFileProcessingDiagnostics(); + for (const modifiedFile of modifiedSourceFiles) { + fileProcessingDiagnostics.reattachFileDiagnostics(modifiedFile.newFile); + } + resolvedTypeReferenceDirectives = oldProgram.getResolvedTypeReferenceDirectives(); + sourceFileToPackageName = oldProgram.sourceFileToPackageName; + redirectTargetsMap = oldProgram.redirectTargetsMap; + return oldProgram.structureIsReused = StructureIsReused.Completely; + } + function getEmitHost(writeFileCallback?: WriteFileCallback): EmitHost { + return { + getPrependNodes, + getCanonicalFileName, + getCommonSourceDirectory: program.getCommonSourceDirectory, + getCompilerOptions: program.getCompilerOptions, + getCurrentDirectory: () => currentDirectory, + getNewLine: () => host.getNewLine(), + getSourceFile: program.getSourceFile, + getSourceFileByPath: program.getSourceFileByPath, + getSourceFiles: program.getSourceFiles, + getLibFileFromReference: program.getLibFileFromReference, + isSourceFileFromExternalLibrary, + getResolvedProjectReferenceToRedirect, + isSourceOfProjectReferenceRedirect, + getProbableSymlinks, + writeFile: writeFileCallback || ((fileName, data, writeByteOrderMark, onError, sourceFiles) => host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles)), + isEmitBlocked, + readFile: f => host.readFile(f), + fileExists: f => { + // Use local caches + const path = toPath(f); + if (getSourceFileByPath(path)) + return true; + if (contains(missingFilePaths, path)) + return false; + // Before falling back to the host + return host.fileExists(f); + }, + ...(host.directoryExists ? { directoryExists: f => host.directoryExists!(f) } : {}), + useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), + getProgramBuildInfo: () => program.getProgramBuildInfo && program.getProgramBuildInfo(), + getSourceFileFromReference: (file, ref) => program.getSourceFileFromReference(file, ref), + redirectTargetsMap, + }; + } + function emitBuildInfo(writeFileCallback?: WriteFileCallback): EmitResult { + Debug.assert(!options.out && !options.outFile); + mark("beforeEmit"); + const emitResult = emitFiles(notImplementedResolver, getEmitHost(writeFileCallback), + /*targetSourceFile*/ undefined, noTransformers, + /*emitOnlyDtsFiles*/ false, + /*onlyBuildInfo*/ true); + mark("afterEmit"); + measure("Emit", "beforeEmit", "afterEmit"); + return emitResult; + } + function getResolvedProjectReferences() { + return resolvedProjectReferences; + } + function getProjectReferences() { + return projectReferences; + } + function getPrependNodes() { + return createPrependNodes(projectReferences, (_ref, index) => resolvedProjectReferences![index]!.commandLine, fileName => { + const path = toPath(fileName); + const sourceFile = getSourceFileByPath(path); + return sourceFile ? sourceFile.text : filesByName.has(path) ? undefined : host.readFile(path); + }); + } + function isSourceFileFromExternalLibrary(file: SourceFile): boolean { + return !!sourceFilesFoundSearchingNodeModules.get(file.path); + } + function isSourceFileDefaultLibrary(file: SourceFile): boolean { + if (file.hasNoDefaultLib) { + return true; + } + if (!options.noLib) { + return false; + } + // If '--lib' is not specified, include default library file according to '--target' + // otherwise, using options specified in '--lib' instead of '--target' default library file + const equalityComparer = host.useCaseSensitiveFileNames() ? equateStringsCaseSensitive : equateStringsCaseInsensitive; + if (!options.lib) { + return equalityComparer(file.fileName, getDefaultLibraryFileName()); + } + else { + return some(options.lib, libFileName => equalityComparer(file.fileName, combinePaths(defaultLibraryPath, libFileName))); + } + } + function getDiagnosticsProducingTypeChecker() { + return diagnosticsProducingTypeChecker || (diagnosticsProducingTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ true)); + } + function dropDiagnosticsProducingTypeChecker() { + diagnosticsProducingTypeChecker = undefined!; + } + function getTypeChecker() { + return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ false)); + } + function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, transformers?: CustomTransformers, forceDtsEmit?: boolean): EmitResult { + return runWithCancellationToken(() => emitWorker(program, sourceFile, writeFileCallback, cancellationToken, emitOnlyDtsFiles, transformers, forceDtsEmit)); + } + function isEmitBlocked(emitFileName: string): boolean { + return hasEmitBlockingDiagnostics.has(toPath(emitFileName)); + } + function emitWorker(program: Program, sourceFile: SourceFile | undefined, writeFileCallback: WriteFileCallback | undefined, cancellationToken: CancellationToken | undefined, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitResult { + if (!forceDtsEmit) { + const result = handleNoEmitOptions(program, sourceFile, cancellationToken); + if (result) + return result; + } + // Create the emit resolver outside of the "emitTime" tracking code below. That way + // any cost associated with it (like type checking) are appropriate associated with + // the type-checking counter. + // + // If the -out option is specified, we should not pass the source file to getEmitResolver. + // This is because in the -out scenario all files need to be emitted, and therefore all + // files need to be type checked. And the way to specify that all files need to be type + // checked is to not pass the file to getEmitResolver. + const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver((options.outFile || options.out) ? undefined : sourceFile, cancellationToken); + mark("beforeEmit"); + const emitResult = emitFiles(emitResolver, getEmitHost(writeFileCallback), sourceFile, getTransformers(options, customTransformers, emitOnlyDtsFiles), emitOnlyDtsFiles, + /*onlyBuildInfo*/ false, forceDtsEmit); + mark("afterEmit"); + measure("Emit", "beforeEmit", "afterEmit"); + return emitResult; + } + function getSourceFile(fileName: string): SourceFile | undefined { + return getSourceFileByPath(toPath(fileName)); + } + function getSourceFileByPath(path: Path): SourceFile | undefined { + return filesByName.get(path) || undefined; + } + function getDiagnosticsHelper(sourceFile: SourceFile | undefined, getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationToken | undefined) => readonly T[], cancellationToken: CancellationToken | undefined): readonly T[] { + if (sourceFile) { + return getDiagnostics(sourceFile, cancellationToken); + } + return sortAndDeduplicateDiagnostics(flatMap(program.getSourceFiles(), sourceFile => { + if (cancellationToken) { + cancellationToken.throwIfCancellationRequested(); + } + return getDiagnostics(sourceFile, cancellationToken); + })); + } + function getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[] { + return getDiagnosticsHelper(sourceFile, getSyntacticDiagnosticsForFile, cancellationToken); + } + function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { + return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile, cancellationToken); + } + function getBindAndCheckDiagnostics(sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { + return getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken); + } + function getProgramDiagnostics(sourceFile: SourceFile): readonly Diagnostic[] { + if (skipTypeChecking(sourceFile, options, program)) { + return emptyArray; + } + const fileProcessingDiagnosticsInFile = fileProcessingDiagnostics.getDiagnostics(sourceFile.fileName); + const programDiagnosticsInFile = programDiagnostics.getDiagnostics(sourceFile.fileName); + let diagnostics: Diagnostic[] | undefined; + for (const diags of [fileProcessingDiagnosticsInFile, programDiagnosticsInFile]) { + if (diags) { + for (const diag of diags) { + if (shouldReportDiagnostic(diag)) { + diagnostics = append(diagnostics, diag); } } - // Set the file as found during node modules search if it was found that way in old progra, - if (oldProgram.isSourceFileFromExternalLibrary(oldProgram.getSourceFileByPath(newSourceFile.resolvedPath)!)) { - sourceFilesFoundSearchingNodeModules.set(filePath, true); - } - } - - files = newSourceFiles; - fileProcessingDiagnostics = oldProgram.getFileProcessingDiagnostics(); - - for (const modifiedFile of modifiedSourceFiles) { - fileProcessingDiagnostics.reattachFileDiagnostics(modifiedFile.newFile); - } - resolvedTypeReferenceDirectives = oldProgram.getResolvedTypeReferenceDirectives(); - - sourceFileToPackageName = oldProgram.sourceFileToPackageName; - redirectTargetsMap = oldProgram.redirectTargetsMap; - - return oldProgram.structureIsReused = StructureIsReused.Completely; - } - - function getEmitHost(writeFileCallback?: WriteFileCallback): EmitHost { - return { - getPrependNodes, - getCanonicalFileName, - getCommonSourceDirectory: program.getCommonSourceDirectory, - getCompilerOptions: program.getCompilerOptions, - getCurrentDirectory: () => currentDirectory, - getNewLine: () => host.getNewLine(), - getSourceFile: program.getSourceFile, - getSourceFileByPath: program.getSourceFileByPath, - getSourceFiles: program.getSourceFiles, - getLibFileFromReference: program.getLibFileFromReference, - isSourceFileFromExternalLibrary, - getResolvedProjectReferenceToRedirect, - isSourceOfProjectReferenceRedirect, - getProbableSymlinks, - writeFile: writeFileCallback || ( - (fileName, data, writeByteOrderMark, onError, sourceFiles) => host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles)), - isEmitBlocked, - readFile: f => host.readFile(f), - fileExists: f => { - // Use local caches - const path = toPath(f); - if (getSourceFileByPath(path)) return true; - if (contains(missingFilePaths, path)) return false; - // Before falling back to the host - return host.fileExists(f); - }, - ...(host.directoryExists ? { directoryExists: f => host.directoryExists!(f) } : {}), - useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), - getProgramBuildInfo: () => program.getProgramBuildInfo && program.getProgramBuildInfo(), - getSourceFileFromReference: (file, ref) => program.getSourceFileFromReference(file, ref), - redirectTargetsMap, - }; - } - - function emitBuildInfo(writeFileCallback?: WriteFileCallback): EmitResult { - Debug.assert(!options.out && !options.outFile); - performance.mark("beforeEmit"); - const emitResult = emitFiles( - notImplementedResolver, - getEmitHost(writeFileCallback), - /*targetSourceFile*/ undefined, - /*transformers*/ noTransformers, - /*emitOnlyDtsFiles*/ false, - /*onlyBuildInfo*/ true - ); - - performance.mark("afterEmit"); - performance.measure("Emit", "beforeEmit", "afterEmit"); - return emitResult; - } - - function getResolvedProjectReferences() { - return resolvedProjectReferences; - } - - function getProjectReferences() { - return projectReferences; - } - - function getPrependNodes() { - return createPrependNodes( - projectReferences, - (_ref, index) => resolvedProjectReferences![index]!.commandLine, - fileName => { - const path = toPath(fileName); - const sourceFile = getSourceFileByPath(path); - return sourceFile ? sourceFile.text : filesByName.has(path) ? undefined : host.readFile(path); - } - ); - } - - function isSourceFileFromExternalLibrary(file: SourceFile): boolean { - return !!sourceFilesFoundSearchingNodeModules.get(file.path); - } - - function isSourceFileDefaultLibrary(file: SourceFile): boolean { - if (file.hasNoDefaultLib) { - return true; - } - - if (!options.noLib) { - return false; - } - - // If '--lib' is not specified, include default library file according to '--target' - // otherwise, using options specified in '--lib' instead of '--target' default library file - const equalityComparer = host.useCaseSensitiveFileNames() ? equateStringsCaseSensitive : equateStringsCaseInsensitive; - if (!options.lib) { - return equalityComparer(file.fileName, getDefaultLibraryFileName()); - } - else { - return some(options.lib, libFileName => equalityComparer(file.fileName, combinePaths(defaultLibraryPath, libFileName))); } } - - function getDiagnosticsProducingTypeChecker() { - return diagnosticsProducingTypeChecker || (diagnosticsProducingTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ true)); - } - - function dropDiagnosticsProducingTypeChecker() { - diagnosticsProducingTypeChecker = undefined!; - } - - function getTypeChecker() { - return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ false)); + return diagnostics || emptyArray; + } + function getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[] { + const options = program.getCompilerOptions(); + // collect diagnostics from the program only once if either no source file was specified or out/outFile is set (bundled emit) + if (!sourceFile || options.out || options.outFile) { + return getDeclarationDiagnosticsWorker(sourceFile, cancellationToken); } - - function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, transformers?: CustomTransformers, forceDtsEmit?: boolean): EmitResult { - return runWithCancellationToken(() => emitWorker(program, sourceFile, writeFileCallback, cancellationToken, emitOnlyDtsFiles, transformers, forceDtsEmit)); + else { + return getDiagnosticsHelper(sourceFile, getDeclarationDiagnosticsForFile, cancellationToken); } - - function isEmitBlocked(emitFileName: string): boolean { - return hasEmitBlockingDiagnostics.has(toPath(emitFileName)); + } + function getSyntacticDiagnosticsForFile(sourceFile: SourceFile): readonly DiagnosticWithLocation[] { + // For JavaScript files, we report semantic errors for using TypeScript-only + // constructs from within a JavaScript file as syntactic errors. + if (isSourceFileJS(sourceFile)) { + if (!sourceFile.additionalSyntacticDiagnostics) { + sourceFile.additionalSyntacticDiagnostics = getJSSyntacticDiagnosticsForFile(sourceFile); + } + return concatenate(sourceFile.additionalSyntacticDiagnostics, sourceFile.parseDiagnostics); } - - function emitWorker(program: Program, sourceFile: SourceFile | undefined, writeFileCallback: WriteFileCallback | undefined, cancellationToken: CancellationToken | undefined, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitResult { - if (!forceDtsEmit) { - const result = handleNoEmitOptions(program, sourceFile, cancellationToken); - if (result) return result; + return sourceFile.parseDiagnostics; + } + function runWithCancellationToken(func: () => T): T { + try { + return func(); + } + catch (e) { + if (e instanceof OperationCanceledException) { + // We were canceled while performing the operation. Because our type checker + // might be a bad state, we need to throw it away. + // + // Note: we are overly aggressive here. We do not actually *have* to throw away + // the "noDiagnosticsTypeChecker". However, for simplicity, i'd like to keep + // the lifetimes of these two TypeCheckers the same. Also, we generally only + // cancel when the user has made a change anyways. And, in that case, we (the + // program instance) will get thrown away anyways. So trying to keep one of + // these type checkers alive doesn't serve much purpose. + noDiagnosticsTypeChecker = undefined!; + diagnosticsProducingTypeChecker = undefined!; } - - // Create the emit resolver outside of the "emitTime" tracking code below. That way - // any cost associated with it (like type checking) are appropriate associated with - // the type-checking counter. - // - // If the -out option is specified, we should not pass the source file to getEmitResolver. - // This is because in the -out scenario all files need to be emitted, and therefore all - // files need to be type checked. And the way to specify that all files need to be type - // checked is to not pass the file to getEmitResolver. - const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver((options.outFile || options.out) ? undefined : sourceFile, cancellationToken); - - performance.mark("beforeEmit"); - - const emitResult = emitFiles( - emitResolver, - getEmitHost(writeFileCallback), - sourceFile, - getTransformers(options, customTransformers, emitOnlyDtsFiles), - emitOnlyDtsFiles, - /*onlyBuildInfo*/ false, - forceDtsEmit - ); - - performance.mark("afterEmit"); - performance.measure("Emit", "beforeEmit", "afterEmit"); - return emitResult; - } - - function getSourceFile(fileName: string): SourceFile | undefined { - return getSourceFileByPath(toPath(fileName)); - } - - function getSourceFileByPath(path: Path): SourceFile | undefined { - return filesByName.get(path) || undefined; - } - - function getDiagnosticsHelper( - sourceFile: SourceFile | undefined, - getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationToken | undefined) => readonly T[], - cancellationToken: CancellationToken | undefined): readonly T[] { - if (sourceFile) { - return getDiagnostics(sourceFile, cancellationToken); - } - return sortAndDeduplicateDiagnostics(flatMap(program.getSourceFiles(), sourceFile => { - if (cancellationToken) { - cancellationToken.throwIfCancellationRequested(); - } - return getDiagnostics(sourceFile, cancellationToken); - })); - } - - function getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[] { - return getDiagnosticsHelper(sourceFile, getSyntacticDiagnosticsForFile, cancellationToken); - } - - function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { - return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile, cancellationToken); - } - - function getBindAndCheckDiagnostics(sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { - return getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken); - } - - function getProgramDiagnostics(sourceFile: SourceFile): readonly Diagnostic[] { + throw e; + } + } + function getSemanticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] { + return concatenate(getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken), getProgramDiagnostics(sourceFile)); + } + function getBindAndCheckDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] { + return getAndCacheDiagnostics(sourceFile, cancellationToken, cachedBindAndCheckDiagnosticsForFile, getBindAndCheckDiagnosticsForFileNoCache); + } + function getBindAndCheckDiagnosticsForFileNoCache(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] { + return runWithCancellationToken(() => { if (skipTypeChecking(sourceFile, options, program)) { return emptyArray; } - - const fileProcessingDiagnosticsInFile = fileProcessingDiagnostics.getDiagnostics(sourceFile.fileName); - const programDiagnosticsInFile = programDiagnostics.getDiagnostics(sourceFile.fileName); - + const typeChecker = getDiagnosticsProducingTypeChecker(); + Debug.assert(!!sourceFile.bindDiagnostics); + const isCheckJs = isCheckJsEnabledForFile(sourceFile, options); + const isTsNoCheck = !!sourceFile.checkJsDirective && sourceFile.checkJsDirective.enabled === false; + // By default, only type-check .ts, .tsx, 'Deferred' and 'External' files (external files are added by plugins) + const includeBindAndCheckDiagnostics = !isTsNoCheck && (sourceFile.scriptKind === ScriptKind.TS || sourceFile.scriptKind === ScriptKind.TSX || + sourceFile.scriptKind === ScriptKind.External || isCheckJs || sourceFile.scriptKind === ScriptKind.Deferred); + const bindDiagnostics: readonly Diagnostic[] = includeBindAndCheckDiagnostics ? sourceFile.bindDiagnostics : emptyArray; + const checkDiagnostics = includeBindAndCheckDiagnostics ? typeChecker.getDiagnostics(sourceFile, cancellationToken) : emptyArray; let diagnostics: Diagnostic[] | undefined; - for (const diags of [fileProcessingDiagnosticsInFile, programDiagnosticsInFile]) { + for (const diags of [bindDiagnostics, checkDiagnostics, isCheckJs ? sourceFile.jsDocDiagnostics : undefined]) { if (diags) { for (const diag of diags) { if (shouldReportDiagnostic(diag)) { @@ -1656,1894 +1503,1520 @@ namespace ts { } } return diagnostics || emptyArray; - } - - function getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[] { - const options = program.getCompilerOptions(); - // collect diagnostics from the program only once if either no source file was specified or out/outFile is set (bundled emit) - if (!sourceFile || options.out || options.outFile) { - return getDeclarationDiagnosticsWorker(sourceFile, cancellationToken); - } - else { - return getDiagnosticsHelper(sourceFile, getDeclarationDiagnosticsForFile, cancellationToken); - } - } - - function getSyntacticDiagnosticsForFile(sourceFile: SourceFile): readonly DiagnosticWithLocation[] { - // For JavaScript files, we report semantic errors for using TypeScript-only - // constructs from within a JavaScript file as syntactic errors. - if (isSourceFileJS(sourceFile)) { - if (!sourceFile.additionalSyntacticDiagnostics) { - sourceFile.additionalSyntacticDiagnostics = getJSSyntacticDiagnosticsForFile(sourceFile); - } - return concatenate(sourceFile.additionalSyntacticDiagnostics, sourceFile.parseDiagnostics); - } - return sourceFile.parseDiagnostics; - } - - function runWithCancellationToken(func: () => T): T { - try { - return func(); - } - catch (e) { - if (e instanceof OperationCanceledException) { - // We were canceled while performing the operation. Because our type checker - // might be a bad state, we need to throw it away. - // - // Note: we are overly aggressive here. We do not actually *have* to throw away - // the "noDiagnosticsTypeChecker". However, for simplicity, i'd like to keep - // the lifetimes of these two TypeCheckers the same. Also, we generally only - // cancel when the user has made a change anyways. And, in that case, we (the - // program instance) will get thrown away anyways. So trying to keep one of - // these type checkers alive doesn't serve much purpose. - noDiagnosticsTypeChecker = undefined!; - diagnosticsProducingTypeChecker = undefined!; - } - - throw e; - } - } - - function getSemanticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] { - return concatenate( - getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken), - getProgramDiagnostics(sourceFile) - ); - } - - function getBindAndCheckDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] { - return getAndCacheDiagnostics(sourceFile, cancellationToken, cachedBindAndCheckDiagnosticsForFile, getBindAndCheckDiagnosticsForFileNoCache); - } - - function getBindAndCheckDiagnosticsForFileNoCache(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] { - return runWithCancellationToken(() => { - if (skipTypeChecking(sourceFile, options, program)) { - return emptyArray; - } - - const typeChecker = getDiagnosticsProducingTypeChecker(); - - Debug.assert(!!sourceFile.bindDiagnostics); - - const isCheckJs = isCheckJsEnabledForFile(sourceFile, options); - const isTsNoCheck = !!sourceFile.checkJsDirective && sourceFile.checkJsDirective.enabled === false; - // By default, only type-check .ts, .tsx, 'Deferred' and 'External' files (external files are added by plugins) - const includeBindAndCheckDiagnostics = !isTsNoCheck && (sourceFile.scriptKind === ScriptKind.TS || sourceFile.scriptKind === ScriptKind.TSX || - sourceFile.scriptKind === ScriptKind.External || isCheckJs || sourceFile.scriptKind === ScriptKind.Deferred); - const bindDiagnostics: readonly Diagnostic[] = includeBindAndCheckDiagnostics ? sourceFile.bindDiagnostics : emptyArray; - const checkDiagnostics = includeBindAndCheckDiagnostics ? typeChecker.getDiagnostics(sourceFile, cancellationToken) : emptyArray; - - let diagnostics: Diagnostic[] | undefined; - for (const diags of [bindDiagnostics, checkDiagnostics, isCheckJs ? sourceFile.jsDocDiagnostics : undefined]) { - if (diags) { - for (const diag of diags) { - if (shouldReportDiagnostic(diag)) { - diagnostics = append(diagnostics, diag); - } - } - } + }); + } + function getSuggestionDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): readonly DiagnosticWithLocation[] { + return runWithCancellationToken(() => { + return getDiagnosticsProducingTypeChecker().getSuggestionDiagnostics(sourceFile, cancellationToken); + }); + } + /** + * Skip errors if previous line start with '// @ts-ignore' comment, not counting non-empty non-comment lines + */ + function shouldReportDiagnostic(diagnostic: Diagnostic) { + const { file, start } = diagnostic; + if (file) { + const lineStarts = getLineStarts(file); + let { line } = computeLineAndCharacterOfPosition(lineStarts, (start!)); // TODO: GH#18217 + while (line > 0) { + const previousLineText = file.text.slice(lineStarts[line - 1], lineStarts[line]); + const result = ignoreDiagnosticCommentRegEx.exec(previousLineText); + if (!result) { + // non-empty line + return true; } - return diagnostics || emptyArray; - }); - } - - function getSuggestionDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): readonly DiagnosticWithLocation[] { - return runWithCancellationToken(() => { - return getDiagnosticsProducingTypeChecker().getSuggestionDiagnostics(sourceFile, cancellationToken); - }); - } - - /** - * Skip errors if previous line start with '// @ts-ignore' comment, not counting non-empty non-comment lines - */ - function shouldReportDiagnostic(diagnostic: Diagnostic) { - const { file, start } = diagnostic; - if (file) { - const lineStarts = getLineStarts(file); - let { line } = computeLineAndCharacterOfPosition(lineStarts, start!); // TODO: GH#18217 - while (line > 0) { - const previousLineText = file.text.slice(lineStarts[line - 1], lineStarts[line]); - const result = ignoreDiagnosticCommentRegEx.exec(previousLineText); - if (!result) { - // non-empty line - return true; - } - if (result[3]) { - // @ts-ignore - return false; - } - line--; + if (result[3]) { + // @ts-ignore + return false; } + line--; } - return true; } - - function getJSSyntacticDiagnosticsForFile(sourceFile: SourceFile): DiagnosticWithLocation[] { - return runWithCancellationToken(() => { - const diagnostics: DiagnosticWithLocation[] = []; - let parent: Node = sourceFile; - walk(sourceFile); - - return diagnostics; - - function walk(node: Node) { - // Return directly from the case if the given node doesnt want to visit each child - // Otherwise break to visit each child - - switch (parent.kind) { - case SyntaxKind.Parameter: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - if ((parent).questionToken === node) { - diagnostics.push(createDiagnosticForNode(node, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, "?")); - return; - } - // falls through - case SyntaxKind.MethodSignature: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ArrowFunction: - case SyntaxKind.VariableDeclaration: - // type annotation - if ((parent).type === node) { - diagnostics.push(createDiagnosticForNode(node, Diagnostics.Type_annotations_can_only_be_used_in_TypeScript_files)); - return; - } - } - - switch (node.kind) { - case SyntaxKind.ImportEqualsDeclaration: - diagnostics.push(createDiagnosticForNode(node, Diagnostics.import_can_only_be_used_in_TypeScript_files)); - return; - case SyntaxKind.ExportAssignment: - if ((node).isExportEquals) { - diagnostics.push(createDiagnosticForNode(node, Diagnostics.export_can_only_be_used_in_TypeScript_files)); - return; - } - break; - case SyntaxKind.HeritageClause: - const heritageClause = node; - if (heritageClause.token === SyntaxKind.ImplementsKeyword) { - diagnostics.push(createDiagnosticForNode(node, Diagnostics.implements_clauses_can_only_be_used_in_TypeScript_files)); - return; - } - break; - case SyntaxKind.InterfaceDeclaration: - diagnostics.push(createDiagnosticForNode(node, Diagnostics.Interface_declaration_cannot_have_implements_clause)); - return; - case SyntaxKind.ModuleDeclaration: - const moduleKeyword = node.flags & NodeFlags.Namespace ? tokenToString(SyntaxKind.NamespaceKeyword) : tokenToString(SyntaxKind.ModuleKeyword); - Debug.assertDefined(moduleKeyword); - diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, moduleKeyword)); + return true; + } + function getJSSyntacticDiagnosticsForFile(sourceFile: SourceFile): DiagnosticWithLocation[] { + return runWithCancellationToken(() => { + const diagnostics: DiagnosticWithLocation[] = []; + let parent: Node = sourceFile; + walk(sourceFile); + return diagnostics; + function walk(node: Node) { + // Return directly from the case if the given node doesnt want to visit each child + // Otherwise break to visit each child + switch (parent.kind) { + case SyntaxKind.Parameter: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + if ((parent).questionToken === node) { + diagnostics.push(createDiagnosticForNode(node, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, "?")); return; - case SyntaxKind.TypeAliasDeclaration: - diagnostics.push(createDiagnosticForNode(node, Diagnostics.Type_aliases_can_only_be_used_in_TypeScript_files)); + } + // falls through + case SyntaxKind.MethodSignature: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ArrowFunction: + case SyntaxKind.VariableDeclaration: + // type annotation + if ((parent).type === node) { + diagnostics.push(createDiagnosticForNode(node, Diagnostics.Type_annotations_can_only_be_used_in_TypeScript_files)); return; - case SyntaxKind.EnumDeclaration: - const enumKeyword = Debug.assertDefined(tokenToString(SyntaxKind.EnumKeyword)); - diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, enumKeyword)); + } + } + switch (node.kind) { + case SyntaxKind.ImportEqualsDeclaration: + diagnostics.push(createDiagnosticForNode(node, Diagnostics.import_can_only_be_used_in_TypeScript_files)); + return; + case SyntaxKind.ExportAssignment: + if ((node).isExportEquals) { + diagnostics.push(createDiagnosticForNode(node, Diagnostics.export_can_only_be_used_in_TypeScript_files)); return; - case SyntaxKind.NonNullExpression: - diagnostics.push(createDiagnosticForNode(node, Diagnostics.Non_null_assertions_can_only_be_used_in_TypeScript_files)); + } + break; + case SyntaxKind.HeritageClause: + const heritageClause = (node); + if (heritageClause.token === SyntaxKind.ImplementsKeyword) { + diagnostics.push(createDiagnosticForNode(node, Diagnostics.implements_clauses_can_only_be_used_in_TypeScript_files)); return; - case SyntaxKind.AsExpression: - diagnostics.push(createDiagnosticForNode((node as AsExpression).type, Diagnostics.Type_assertion_expressions_can_only_be_used_in_TypeScript_files)); + } + break; + case SyntaxKind.InterfaceDeclaration: + diagnostics.push(createDiagnosticForNode(node, Diagnostics.Interface_declaration_cannot_have_implements_clause)); + return; + case SyntaxKind.ModuleDeclaration: + const moduleKeyword = node.flags & NodeFlags.Namespace ? tokenToString(SyntaxKind.NamespaceKeyword) : tokenToString(SyntaxKind.ModuleKeyword); + Debug.assertDefined(moduleKeyword); + diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, moduleKeyword)); + return; + case SyntaxKind.TypeAliasDeclaration: + diagnostics.push(createDiagnosticForNode(node, Diagnostics.Type_aliases_can_only_be_used_in_TypeScript_files)); + return; + case SyntaxKind.EnumDeclaration: + const enumKeyword = Debug.assertDefined(tokenToString(SyntaxKind.EnumKeyword)); + diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, enumKeyword)); + return; + case SyntaxKind.NonNullExpression: + diagnostics.push(createDiagnosticForNode(node, Diagnostics.Non_null_assertions_can_only_be_used_in_TypeScript_files)); + return; + case SyntaxKind.AsExpression: + diagnostics.push(createDiagnosticForNode((node as AsExpression).type, Diagnostics.Type_assertion_expressions_can_only_be_used_in_TypeScript_files)); + return; + case SyntaxKind.TypeAssertionExpression: + Debug.fail(); // Won't parse these in a JS file anyway, as they are interpreted as JSX. + } + const prevParent = parent; + parent = node; + forEachChild(node, walk, walkArray); + parent = prevParent; + } + function walkArray(nodes: NodeArray) { + if (parent.decorators === nodes && !options.experimentalDecorators) { + diagnostics.push(createDiagnosticForNode(parent, Diagnostics.Experimental_support_for_decorators_is_a_feature_that_is_subject_to_change_in_a_future_release_Set_the_experimentalDecorators_option_in_your_tsconfig_or_jsconfig_to_remove_this_warning)); + } + switch (parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ArrowFunction: + // Check type parameters + if (nodes === (parent).typeParameters) { + diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Type_parameter_declarations_can_only_be_used_in_TypeScript_files)); return; - case SyntaxKind.TypeAssertionExpression: - Debug.fail(); // Won't parse these in a JS file anyway, as they are interpreted as JSX. - } - - const prevParent = parent; - parent = node; - forEachChild(node, walk, walkArray); - parent = prevParent; - } - - function walkArray(nodes: NodeArray) { - if (parent.decorators === nodes && !options.experimentalDecorators) { - diagnostics.push(createDiagnosticForNode(parent, Diagnostics.Experimental_support_for_decorators_is_a_feature_that_is_subject_to_change_in_a_future_release_Set_the_experimentalDecorators_option_in_your_tsconfig_or_jsconfig_to_remove_this_warning)); - } - - switch (parent.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ArrowFunction: - // Check type parameters - if (nodes === (parent).typeParameters) { - diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Type_parameter_declarations_can_only_be_used_in_TypeScript_files)); - return; - } - // falls through - - case SyntaxKind.VariableStatement: - // Check modifiers - if (nodes === parent.modifiers) { - return checkModifiers(parent.modifiers, parent.kind === SyntaxKind.VariableStatement); - } - break; - case SyntaxKind.PropertyDeclaration: - // Check modifiers of property declaration - if (nodes === (parent).modifiers) { - for (const modifier of >nodes) { - if (modifier.kind !== SyntaxKind.StaticKeyword) { - diagnostics.push(createDiagnosticForNode(modifier, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, tokenToString(modifier.kind))); - } + } + // falls through + case SyntaxKind.VariableStatement: + // Check modifiers + if (nodes === parent.modifiers) { + return checkModifiers(parent.modifiers, parent.kind === SyntaxKind.VariableStatement); + } + break; + case SyntaxKind.PropertyDeclaration: + // Check modifiers of property declaration + if (nodes === (parent).modifiers) { + for (const modifier of >nodes) { + if (modifier.kind !== SyntaxKind.StaticKeyword) { + diagnostics.push(createDiagnosticForNode(modifier, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, tokenToString(modifier.kind))); } - return; - } - break; - case SyntaxKind.Parameter: - // Check modifiers of parameter declaration - if (nodes === (parent).modifiers) { - diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Parameter_modifiers_can_only_be_used_in_TypeScript_files)); - return; } - break; - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - case SyntaxKind.ExpressionWithTypeArguments: - case SyntaxKind.JsxSelfClosingElement: - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.TaggedTemplateExpression: - // Check type arguments - if (nodes === (parent).typeArguments) { - diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Type_arguments_can_only_be_used_in_TypeScript_files)); - return; - } - break; - } - - for (const node of nodes) { - walk(node); - } - } - - function checkModifiers(modifiers: NodeArray, isConstValid: boolean) { - for (const modifier of modifiers) { - switch (modifier.kind) { - case SyntaxKind.ConstKeyword: - if (isConstValid) { - continue; - } - // to report error, - // falls through - case SyntaxKind.PublicKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.ProtectedKeyword: - case SyntaxKind.ReadonlyKeyword: - case SyntaxKind.DeclareKeyword: - case SyntaxKind.AbstractKeyword: - diagnostics.push(createDiagnosticForNode(modifier, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, tokenToString(modifier.kind))); - break; - - // These are all legal modifiers. - case SyntaxKind.StaticKeyword: - case SyntaxKind.ExportKeyword: - case SyntaxKind.DefaultKeyword: + return; } - } + break; + case SyntaxKind.Parameter: + // Check modifiers of parameter declaration + if (nodes === (parent).modifiers) { + diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Parameter_modifiers_can_only_be_used_in_TypeScript_files)); + return; + } + break; + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + case SyntaxKind.ExpressionWithTypeArguments: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.TaggedTemplateExpression: + // Check type arguments + if (nodes === (parent).typeArguments) { + diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Type_arguments_can_only_be_used_in_TypeScript_files)); + return; + } + break; } - - function createDiagnosticForNodeArray(nodes: NodeArray, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): DiagnosticWithLocation { - const start = nodes.pos; - return createFileDiagnostic(sourceFile, start, nodes.end - start, message, arg0, arg1, arg2); + for (const node of nodes) { + walk(node); } - - // Since these are syntactic diagnostics, parent might not have been set - // this means the sourceFile cannot be infered from the node - function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): DiagnosticWithLocation { - return createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0, arg1, arg2); + } + function checkModifiers(modifiers: NodeArray, isConstValid: boolean) { + for (const modifier of modifiers) { + switch (modifier.kind) { + case SyntaxKind.ConstKeyword: + if (isConstValid) { + continue; + } + // to report error, + // falls through + case SyntaxKind.PublicKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.ReadonlyKeyword: + case SyntaxKind.DeclareKeyword: + case SyntaxKind.AbstractKeyword: + diagnostics.push(createDiagnosticForNode(modifier, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, tokenToString(modifier.kind))); + break; + // These are all legal modifiers. + case SyntaxKind.StaticKeyword: + case SyntaxKind.ExportKeyword: + case SyntaxKind.DefaultKeyword: + } } - }); - } - - function getDeclarationDiagnosticsWorker(sourceFile: SourceFile | undefined, cancellationToken: CancellationToken | undefined): readonly DiagnosticWithLocation[] { - return getAndCacheDiagnostics(sourceFile, cancellationToken, cachedDeclarationDiagnosticsForFile, getDeclarationDiagnosticsForFileNoCache); - } - - function getDeclarationDiagnosticsForFileNoCache(sourceFile: SourceFile | undefined, cancellationToken: CancellationToken | undefined): readonly DiagnosticWithLocation[] { - return runWithCancellationToken(() => { - const resolver = getDiagnosticsProducingTypeChecker().getEmitResolver(sourceFile, cancellationToken); - // Don't actually write any files since we're just getting diagnostics. - return ts.getDeclarationDiagnostics(getEmitHost(noop), resolver, sourceFile) || emptyArray; - }); - } - - function getAndCacheDiagnostics( - sourceFile: T, - cancellationToken: CancellationToken | undefined, - cache: DiagnosticCache, - getDiagnostics: (sourceFile: T, cancellationToken: CancellationToken | undefined) => readonly U[], - ): readonly U[] { - - const cachedResult = sourceFile - ? cache.perFile && cache.perFile.get(sourceFile.path) - : cache.allDiagnostics; - - if (cachedResult) { - return cachedResult; - } - const result = getDiagnostics(sourceFile, cancellationToken); - if (sourceFile) { - if (!cache.perFile) { - cache.perFile = createMap(); - } - cache.perFile.set(sourceFile.path, result); } - else { - cache.allDiagnostics = result; + function createDiagnosticForNodeArray(nodes: NodeArray, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): DiagnosticWithLocation { + const start = nodes.pos; + return createFileDiagnostic(sourceFile, start, nodes.end - start, message, arg0, arg1, arg2); } - return result; - } - - function getDeclarationDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): readonly DiagnosticWithLocation[] { - return sourceFile.isDeclarationFile ? [] : getDeclarationDiagnosticsWorker(sourceFile, cancellationToken); - } - - function getOptionsDiagnostics(): SortedReadonlyArray { - return sortAndDeduplicateDiagnostics(concatenate( - fileProcessingDiagnostics.getGlobalDiagnostics(), - concatenate( - programDiagnostics.getGlobalDiagnostics(), - getOptionsDiagnosticsOfConfigFile() - ) - )); - } - - function getOptionsDiagnosticsOfConfigFile() { - if (!options.configFile) { return emptyArray; } - let diagnostics = programDiagnostics.getDiagnostics(options.configFile.fileName); - forEachResolvedProjectReference(resolvedRef => { - if (resolvedRef) { - diagnostics = concatenate(diagnostics, programDiagnostics.getDiagnostics(resolvedRef.sourceFile.fileName)); - } - }); - return diagnostics; - } - - function getGlobalDiagnostics(): SortedReadonlyArray { - return rootNames.length ? sortAndDeduplicateDiagnostics(getDiagnosticsProducingTypeChecker().getGlobalDiagnostics().slice()) : emptyArray as any as SortedReadonlyArray; - } - - function getConfigFileParsingDiagnostics(): readonly Diagnostic[] { - return configFileParsingDiagnostics || emptyArray; + // Since these are syntactic diagnostics, parent might not have been set + // this means the sourceFile cannot be infered from the node + function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): DiagnosticWithLocation { + return createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0, arg1, arg2); + } + }); + } + function getDeclarationDiagnosticsWorker(sourceFile: SourceFile | undefined, cancellationToken: CancellationToken | undefined): readonly DiagnosticWithLocation[] { + return getAndCacheDiagnostics(sourceFile, cancellationToken, cachedDeclarationDiagnosticsForFile, getDeclarationDiagnosticsForFileNoCache); + } + function getDeclarationDiagnosticsForFileNoCache(sourceFile: SourceFile | undefined, cancellationToken: CancellationToken | undefined): readonly DiagnosticWithLocation[] { + return runWithCancellationToken(() => { + const resolver = getDiagnosticsProducingTypeChecker().getEmitResolver(sourceFile, cancellationToken); + // Don't actually write any files since we're just getting diagnostics. + return ts.getDeclarationDiagnostics(getEmitHost(noop), resolver, sourceFile) || emptyArray; + }); + } + function getAndCacheDiagnostics(sourceFile: T, cancellationToken: CancellationToken | undefined, cache: DiagnosticCache, getDiagnostics: (sourceFile: T, cancellationToken: CancellationToken | undefined) => readonly U[]): readonly U[] { + const cachedResult = sourceFile + ? cache.perFile && cache.perFile.get(sourceFile.path) + : cache.allDiagnostics; + if (cachedResult) { + return cachedResult; } - - function processRootFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean) { - processSourceFile(normalizePath(fileName), isDefaultLib, ignoreNoDefaultLib, /*packageId*/ undefined); + const result = getDiagnostics(sourceFile, cancellationToken); + if (sourceFile) { + if (!cache.perFile) { + cache.perFile = createMap(); + } + cache.perFile.set(sourceFile.path, result); } - - function fileReferenceIsEqualTo(a: FileReference, b: FileReference): boolean { - return a.fileName === b.fileName; + else { + cache.allDiagnostics = result; } - - function moduleNameIsEqualTo(a: StringLiteralLike | Identifier, b: StringLiteralLike | Identifier): boolean { - return a.kind === SyntaxKind.Identifier - ? b.kind === SyntaxKind.Identifier && a.escapedText === b.escapedText - : b.kind === SyntaxKind.StringLiteral && a.text === b.text; + return result; + } + function getDeclarationDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): readonly DiagnosticWithLocation[] { + return sourceFile.isDeclarationFile ? [] : getDeclarationDiagnosticsWorker(sourceFile, cancellationToken); + } + function getOptionsDiagnostics(): SortedReadonlyArray { + return sortAndDeduplicateDiagnostics(concatenate(fileProcessingDiagnostics.getGlobalDiagnostics(), concatenate(programDiagnostics.getGlobalDiagnostics(), getOptionsDiagnosticsOfConfigFile()))); + } + function getOptionsDiagnosticsOfConfigFile() { + if (!options.configFile) { + return emptyArray; } - - function collectExternalModuleReferences(file: SourceFile): void { - if (file.imports) { - return; + let diagnostics = programDiagnostics.getDiagnostics(options.configFile.fileName); + forEachResolvedProjectReference(resolvedRef => { + if (resolvedRef) { + diagnostics = concatenate(diagnostics, programDiagnostics.getDiagnostics(resolvedRef.sourceFile.fileName)); } - - const isJavaScriptFile = isSourceFileJS(file); - const isExternalModuleFile = isExternalModule(file); - - // file.imports may not be undefined if there exists dynamic import - let imports: StringLiteralLike[] | undefined; - let moduleAugmentations: (StringLiteral | Identifier)[] | undefined; - let ambientModules: string[] | undefined; - - // If we are importing helpers, we need to add a synthetic reference to resolve the - // helpers library. - if (options.importHelpers - && (options.isolatedModules || isExternalModuleFile) - && !file.isDeclarationFile) { - // synthesize 'import "tslib"' declaration - const externalHelpersModuleReference = createLiteral(externalHelpersModuleNameText); - const importDecl = createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*importClause*/ undefined, externalHelpersModuleReference); - addEmitFlags(importDecl, EmitFlags.NeverApplyImportHelper); - externalHelpersModuleReference.parent = importDecl; - importDecl.parent = file; - imports = [externalHelpersModuleReference]; - } - - for (const node of file.statements) { - collectModuleReferences(node, /*inAmbientModule*/ false); - } - if ((file.flags & NodeFlags.PossiblyContainsDynamicImport) || isJavaScriptFile) { - collectDynamicImportOrRequireCalls(file); - } - - file.imports = imports || emptyArray; - file.moduleAugmentations = moduleAugmentations || emptyArray; - file.ambientModuleNames = ambientModules || emptyArray; - + }); + return diagnostics; + } + function getGlobalDiagnostics(): SortedReadonlyArray { + return rootNames.length ? sortAndDeduplicateDiagnostics(getDiagnosticsProducingTypeChecker().getGlobalDiagnostics().slice()) : emptyArray as any as SortedReadonlyArray; + } + function getConfigFileParsingDiagnostics(): readonly Diagnostic[] { + return configFileParsingDiagnostics || emptyArray; + } + function processRootFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean) { + processSourceFile(normalizePath(fileName), isDefaultLib, ignoreNoDefaultLib, /*packageId*/ undefined); + } + function fileReferenceIsEqualTo(a: FileReference, b: FileReference): boolean { + return a.fileName === b.fileName; + } + function moduleNameIsEqualTo(a: StringLiteralLike | Identifier, b: StringLiteralLike | Identifier): boolean { + return a.kind === SyntaxKind.Identifier + ? b.kind === SyntaxKind.Identifier && a.escapedText === b.escapedText + : b.kind === SyntaxKind.StringLiteral && a.text === b.text; + } + function collectExternalModuleReferences(file: SourceFile): void { + if (file.imports) { return; - - function collectModuleReferences(node: Statement, inAmbientModule: boolean): void { - if (isAnyImportOrReExport(node)) { - const moduleNameExpr = getExternalModuleName(node); - // TypeScript 1.0 spec (April 2014): 12.1.6 - // An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference other external modules - // only through top - level external module names. Relative external module names are not permitted. - if (moduleNameExpr && isStringLiteral(moduleNameExpr) && moduleNameExpr.text && (!inAmbientModule || !isExternalModuleNameRelative(moduleNameExpr.text))) { - imports = append(imports, moduleNameExpr); - } - } - else if (isModuleDeclaration(node)) { - if (isAmbientModule(node) && (inAmbientModule || hasModifier(node, ModifierFlags.Ambient) || file.isDeclarationFile)) { - const nameText = getTextOfIdentifierOrLiteral(node.name); - // Ambient module declarations can be interpreted as augmentations for some existing external modules. - // This will happen in two cases: - // - if current file is external module then module augmentation is a ambient module declaration defined in the top level scope - // - if current file is not external module then module augmentation is an ambient module declaration with non-relative module name - // immediately nested in top level ambient module declaration . - if (isExternalModuleFile || (inAmbientModule && !isExternalModuleNameRelative(nameText))) { - (moduleAugmentations || (moduleAugmentations = [])).push(node.name); + } + const isJavaScriptFile = isSourceFileJS(file); + const isExternalModuleFile = isExternalModule(file); + // file.imports may not be undefined if there exists dynamic import + let imports: StringLiteralLike[] | undefined; + let moduleAugmentations: (StringLiteral | Identifier)[] | undefined; + let ambientModules: string[] | undefined; + // If we are importing helpers, we need to add a synthetic reference to resolve the + // helpers library. + if (options.importHelpers + && (options.isolatedModules || isExternalModuleFile) + && !file.isDeclarationFile) { + // synthesize 'import "tslib"' declaration + const externalHelpersModuleReference = createLiteral(externalHelpersModuleNameText); + const importDecl = createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*importClause*/ undefined, externalHelpersModuleReference); + addEmitFlags(importDecl, EmitFlags.NeverApplyImportHelper); + externalHelpersModuleReference.parent = importDecl; + importDecl.parent = file; + imports = [externalHelpersModuleReference]; + } + for (const node of file.statements) { + collectModuleReferences(node, /*inAmbientModule*/ false); + } + if ((file.flags & NodeFlags.PossiblyContainsDynamicImport) || isJavaScriptFile) { + collectDynamicImportOrRequireCalls(file); + } + file.imports = imports || emptyArray; + file.moduleAugmentations = moduleAugmentations || emptyArray; + file.ambientModuleNames = ambientModules || emptyArray; + return; + function collectModuleReferences(node: Statement, inAmbientModule: boolean): void { + if (isAnyImportOrReExport(node)) { + const moduleNameExpr = getExternalModuleName(node); + // TypeScript 1.0 spec (April 2014): 12.1.6 + // An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference other external modules + // only through top - level external module names. Relative external module names are not permitted. + if (moduleNameExpr && isStringLiteral(moduleNameExpr) && moduleNameExpr.text && (!inAmbientModule || !isExternalModuleNameRelative(moduleNameExpr.text))) { + imports = append(imports, moduleNameExpr); + } + } + else if (isModuleDeclaration(node)) { + if (isAmbientModule(node) && (inAmbientModule || hasModifier(node, ModifierFlags.Ambient) || file.isDeclarationFile)) { + const nameText = getTextOfIdentifierOrLiteral(node.name); + // Ambient module declarations can be interpreted as augmentations for some existing external modules. + // This will happen in two cases: + // - if current file is external module then module augmentation is a ambient module declaration defined in the top level scope + // - if current file is not external module then module augmentation is an ambient module declaration with non-relative module name + // immediately nested in top level ambient module declaration . + if (isExternalModuleFile || (inAmbientModule && !isExternalModuleNameRelative(nameText))) { + (moduleAugmentations || (moduleAugmentations = [])).push(node.name); + } + else if (!inAmbientModule) { + if (file.isDeclarationFile) { + // for global .d.ts files record name of ambient module + (ambientModules || (ambientModules = [])).push(nameText); } - else if (!inAmbientModule) { - if (file.isDeclarationFile) { - // for global .d.ts files record name of ambient module - (ambientModules || (ambientModules = [])).push(nameText); - } - // An AmbientExternalModuleDeclaration declares an external module. - // This type of declaration is permitted only in the global module. - // The StringLiteral must specify a top - level external module name. - // Relative external module names are not permitted - - // NOTE: body of ambient module is always a module block, if it exists - const body = (node).body; - if (body) { - for (const statement of body.statements) { - collectModuleReferences(statement, /*inAmbientModule*/ true); - } + // An AmbientExternalModuleDeclaration declares an external module. + // This type of declaration is permitted only in the global module. + // The StringLiteral must specify a top - level external module name. + // Relative external module names are not permitted + // NOTE: body of ambient module is always a module block, if it exists + const body = ((node).body); + if (body) { + for (const statement of body.statements) { + collectModuleReferences(statement, /*inAmbientModule*/ true); } } } } } - - function collectDynamicImportOrRequireCalls(file: SourceFile) { - const r = /import|require/g; - while (r.exec(file.text) !== null) { // eslint-disable-line no-null/no-null - const node = getNodeAtPosition(file, r.lastIndex); - if (isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) { - imports = append(imports, node.arguments[0]); - } - // we have to check the argument list has length of 1. We will still have to process these even though we have parsing error. - else if (isImportCall(node) && node.arguments.length === 1 && isStringLiteralLike(node.arguments[0])) { - imports = append(imports, node.arguments[0] as StringLiteralLike); + } + function collectDynamicImportOrRequireCalls(file: SourceFile) { + const r = /import|require/g; + while (r.exec(file.text) !== null) { // eslint-disable-line no-null/no-null + const node = getNodeAtPosition(file, r.lastIndex); + if (isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) { + imports = append(imports, node.arguments[0]); + } + // we have to check the argument list has length of 1. We will still have to process these even though we have parsing error. + else if (isImportCall(node) && node.arguments.length === 1 && isStringLiteralLike(node.arguments[0])) { + imports = append(imports, (node.arguments[0] as StringLiteralLike)); + } + else if (isLiteralImportTypeNode(node)) { + imports = append(imports, node.argument.literal); + } + } + } + /** Returns a token if position is in [start-of-leading-trivia, end), includes JSDoc only in JS files */ + function getNodeAtPosition(sourceFile: SourceFile, position: number): Node { + let current: Node = sourceFile; + const getContainingChild = (child: Node) => { + if (child.pos <= position && (position < child.end || (position === child.end && (child.kind === SyntaxKind.EndOfFileToken)))) { + return child; + } + }; + while (true) { + const child = isJavaScriptFile && hasJSDocNodes(current) && forEach(current.jsDoc, getContainingChild) || forEachChild(current, getContainingChild); + if (!child) { + return current; + } + current = child; + } + } + } + function getLibFileFromReference(ref: FileReference) { + const libName = ref.fileName.toLocaleLowerCase(); + const libFileName = libMap.get(libName); + if (libFileName) { + return getSourceFile(combinePaths(defaultLibraryPath, libFileName)); + } + } + /** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */ + function getSourceFileFromReference(referencingFile: SourceFile | UnparsedSource, ref: FileReference): SourceFile | undefined { + return getSourceFileFromReferenceWorker(resolveTripleslashReference(ref.fileName, referencingFile.fileName), fileName => filesByName.get(toPath(fileName)) || undefined); + } + function getSourceFileFromReferenceWorker(fileName: string, getSourceFile: (fileName: string) => SourceFile | undefined, fail?: (diagnostic: DiagnosticMessage, ...argument: string[]) => void, refFile?: SourceFile): SourceFile | undefined { + if (hasExtension(fileName)) { + const canonicalFileName = host.getCanonicalFileName(fileName); + if (!options.allowNonTsExtensions && !forEach(supportedExtensionsWithJsonIfResolveJsonModule, extension => fileExtensionIs(canonicalFileName, extension))) { + if (fail) { + if (hasJSFileExtension(canonicalFileName)) { + fail(Diagnostics.File_0_is_a_JavaScript_file_Did_you_mean_to_enable_the_allowJs_option, fileName); } - else if (isLiteralImportTypeNode(node)) { - imports = append(imports, node.argument.literal); + else { + fail(Diagnostics.File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1, fileName, "'" + supportedExtensions.join("', '") + "'"); } } + return undefined; } - - /** Returns a token if position is in [start-of-leading-trivia, end), includes JSDoc only in JS files */ - function getNodeAtPosition(sourceFile: SourceFile, position: number): Node { - let current: Node = sourceFile; - const getContainingChild = (child: Node) => { - if (child.pos <= position && (position < child.end || (position === child.end && (child.kind === SyntaxKind.EndOfFileToken)))) { - return child; + const sourceFile = getSourceFile(fileName); + if (fail) { + if (!sourceFile) { + const redirect = getProjectReferenceRedirect(fileName); + if (redirect) { + fail(Diagnostics.Output_file_0_has_not_been_built_from_source_file_1, redirect, fileName); } - }; - while (true) { - const child = isJavaScriptFile && hasJSDocNodes(current) && forEach(current.jsDoc, getContainingChild) || forEachChild(current, getContainingChild); - if (!child) { - return current; + else { + fail(Diagnostics.File_0_not_found, fileName); } - current = child; + } + else if (refFile && canonicalFileName === host.getCanonicalFileName(refFile.fileName)) { + fail(Diagnostics.A_file_cannot_have_a_reference_to_itself); } } + return sourceFile; } - - function getLibFileFromReference(ref: FileReference) { - const libName = ref.fileName.toLocaleLowerCase(); - const libFileName = libMap.get(libName); - if (libFileName) { - return getSourceFile(combinePaths(defaultLibraryPath, libFileName)); - } - } - - /** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */ - function getSourceFileFromReference(referencingFile: SourceFile | UnparsedSource, ref: FileReference): SourceFile | undefined { - return getSourceFileFromReferenceWorker(resolveTripleslashReference(ref.fileName, referencingFile.fileName), fileName => filesByName.get(toPath(fileName)) || undefined); - } - - function getSourceFileFromReferenceWorker( - fileName: string, - getSourceFile: (fileName: string) => SourceFile | undefined, - fail?: (diagnostic: DiagnosticMessage, ...argument: string[]) => void, - refFile?: SourceFile): SourceFile | undefined { - - if (hasExtension(fileName)) { - const canonicalFileName = host.getCanonicalFileName(fileName); - if (!options.allowNonTsExtensions && !forEach(supportedExtensionsWithJsonIfResolveJsonModule, extension => fileExtensionIs(canonicalFileName, extension))) { - if (fail) { - if (hasJSFileExtension(canonicalFileName)) { - fail(Diagnostics.File_0_is_a_JavaScript_file_Did_you_mean_to_enable_the_allowJs_option, fileName); - } - else { - fail(Diagnostics.File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1, fileName, "'" + supportedExtensions.join("', '") + "'"); - } - } - return undefined; + else { + const sourceFileNoExtension = options.allowNonTsExtensions && getSourceFile(fileName); + if (sourceFileNoExtension) + return sourceFileNoExtension; + if (fail && options.allowNonTsExtensions) { + fail(Diagnostics.File_0_not_found, fileName); + return undefined; + } + const sourceFileWithAddedExtension = forEach(supportedExtensions, extension => getSourceFile(fileName + extension)); + if (fail && !sourceFileWithAddedExtension) + fail(Diagnostics.File_0_not_found, fileName + Extension.Ts); + return sourceFileWithAddedExtension; + } + } + /** This has side effects through `findSourceFile`. */ + function processSourceFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, packageId: PackageId | undefined, refFile?: RefFile): void { + getSourceFileFromReferenceWorker(fileName, fileName => findSourceFile(fileName, toPath(fileName), isDefaultLib, ignoreNoDefaultLib, refFile, packageId), // TODO: GH#18217 + (diagnostic, ...args) => fileProcessingDiagnostics.add(createRefFileDiagnostic(refFile, diagnostic, ...args)), refFile && refFile.file); + } + function reportFileNamesDifferOnlyInCasingError(fileName: string, existingFile: SourceFile, refFile: RefFile | undefined): void { + const refs = !refFile ? refFileMap && refFileMap.get(existingFile.path) : undefined; + const refToReportErrorOn = refs && find(refs, ref => ref.referencedFileName === existingFile.fileName); + fileProcessingDiagnostics.add(refToReportErrorOn ? + createFileDiagnosticAtReference(refToReportErrorOn, Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, existingFile.fileName, fileName) : + createRefFileDiagnostic(refFile, Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, fileName, existingFile.fileName)); + } + function createRedirectSourceFile(redirectTarget: SourceFile, unredirected: SourceFile, fileName: string, path: Path, resolvedPath: Path, originalFileName: string): SourceFile { + const redirect: SourceFile = Object.create(redirectTarget); + redirect.fileName = fileName; + redirect.path = path; + redirect.resolvedPath = resolvedPath; + redirect.originalFileName = originalFileName; + redirect.redirectInfo = { redirectTarget, unredirected }; + sourceFilesFoundSearchingNodeModules.set(path, currentNodeModulesDepth > 0); + Object.defineProperties(redirect, { + id: { + get(this: SourceFile) { return this.redirectInfo!.redirectTarget.id; }, + set(this: SourceFile, value: SourceFile["id"]) { this.redirectInfo!.redirectTarget.id = value; }, + }, + symbol: { + get(this: SourceFile) { return this.redirectInfo!.redirectTarget.symbol; }, + set(this: SourceFile, value: SourceFile["symbol"]) { this.redirectInfo!.redirectTarget.symbol = value; }, + }, + }); + return redirect; + } + // Get source file from normalized fileName + function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, refFile: RefFile | undefined, packageId: PackageId | undefined): SourceFile | undefined { + if (useSourceOfProjectReferenceRedirect) { + let source = getSourceOfProjectReferenceRedirect(fileName); + // If preserveSymlinks is true, module resolution wont jump the symlink + // but the resolved real path may be the .d.ts from project reference + // Note:: Currently we try the real path only if the + // file is from node_modules to avoid having to run real path on all file paths + if (!source && + host.realpath && + options.preserveSymlinks && + isDeclarationFileName(fileName) && + stringContains(fileName, nodeModulesPathPart)) { + const realPath = host.realpath(fileName); + if (realPath !== fileName) + source = getSourceOfProjectReferenceRedirect(realPath); + } + if (source) { + const file = isString(source) ? + findSourceFile(source, toPath(source), isDefaultLib, ignoreNoDefaultLib, refFile, packageId) : + undefined; + if (file) + addFileToFilesByName(file, path, /*redirectedPath*/ undefined); + return file; + } + } + const originalFileName = fileName; + if (filesByName.has(path)) { + const file = filesByName.get(path); + addFileToRefFileMap(fileName, file || undefined, refFile); + // try to check if we've already seen this file but with a different casing in path + // NOTE: this only makes sense for case-insensitive file systems, and only on files which are not redirected + if (file && options.forceConsistentCasingInFileNames) { + const checkedName = file.fileName; + const isRedirect = toPath(checkedName) !== toPath(fileName); + if (isRedirect) { + fileName = getProjectReferenceRedirect(fileName) || fileName; + } + // Check if it differs only in drive letters its ok to ignore that error: + const checkedAbsolutePath = getNormalizedAbsolutePathWithoutRoot(checkedName, currentDirectory); + const inputAbsolutePath = getNormalizedAbsolutePathWithoutRoot(fileName, currentDirectory); + if (checkedAbsolutePath !== inputAbsolutePath) { + reportFileNamesDifferOnlyInCasingError(fileName, file, refFile); + } + } + // If the file was previously found via a node_modules search, but is now being processed as a root file, + // then everything it sucks in may also be marked incorrectly, and needs to be checked again. + if (file && sourceFilesFoundSearchingNodeModules.get(file.path) && currentNodeModulesDepth === 0) { + sourceFilesFoundSearchingNodeModules.set(file.path, false); + if (!options.noResolve) { + processReferencedFiles(file, isDefaultLib); + processTypeReferenceDirectives(file); } - - const sourceFile = getSourceFile(fileName); - if (fail) { - if (!sourceFile) { - const redirect = getProjectReferenceRedirect(fileName); - if (redirect) { - fail(Diagnostics.Output_file_0_has_not_been_built_from_source_file_1, redirect, fileName); - } - else { - fail(Diagnostics.File_0_not_found, fileName); - } - } - else if (refFile && canonicalFileName === host.getCanonicalFileName(refFile.fileName)) { - fail(Diagnostics.A_file_cannot_have_a_reference_to_itself); - } + if (!options.noLib) { + processLibReferenceDirectives(file); } - return sourceFile; + modulesWithElidedImports.set(file.path, false); + processImportedModules(file); } - else { - const sourceFileNoExtension = options.allowNonTsExtensions && getSourceFile(fileName); - if (sourceFileNoExtension) return sourceFileNoExtension; - - if (fail && options.allowNonTsExtensions) { - fail(Diagnostics.File_0_not_found, fileName); + // See if we need to reprocess the imports due to prior skipped imports + else if (file && modulesWithElidedImports.get(file.path)) { + if (currentNodeModulesDepth < maxNodeModuleJsDepth) { + modulesWithElidedImports.set(file.path, false); + processImportedModules(file); + } + } + return file || undefined; + } + let redirectedPath: Path | undefined; + if (refFile && !useSourceOfProjectReferenceRedirect) { + const redirectProject = getProjectReferenceRedirectProject(fileName); + if (redirectProject) { + if (redirectProject.commandLine.options.outFile || redirectProject.commandLine.options.out) { + // Shouldnt create many to 1 mapping file in --out scenario return undefined; } - - const sourceFileWithAddedExtension = forEach(supportedExtensions, extension => getSourceFile(fileName + extension)); - if (fail && !sourceFileWithAddedExtension) fail(Diagnostics.File_0_not_found, fileName + Extension.Ts); - return sourceFileWithAddedExtension; - } - } - - /** This has side effects through `findSourceFile`. */ - function processSourceFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, packageId: PackageId | undefined, refFile?: RefFile): void { - getSourceFileFromReferenceWorker( - fileName, - fileName => findSourceFile(fileName, toPath(fileName), isDefaultLib, ignoreNoDefaultLib, refFile, packageId), // TODO: GH#18217 - (diagnostic, ...args) => fileProcessingDiagnostics.add( - createRefFileDiagnostic(refFile, diagnostic, ...args) - ), - refFile && refFile.file - ); - } - - function reportFileNamesDifferOnlyInCasingError(fileName: string, existingFile: SourceFile, refFile: RefFile | undefined): void { - const refs = !refFile ? refFileMap && refFileMap.get(existingFile.path) : undefined; - const refToReportErrorOn = refs && find(refs, ref => ref.referencedFileName === existingFile.fileName); - fileProcessingDiagnostics.add(refToReportErrorOn ? - createFileDiagnosticAtReference( - refToReportErrorOn, - Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, - existingFile.fileName, - fileName, - ) : - createRefFileDiagnostic( - refFile, - Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, - fileName, - existingFile.fileName - ) - ); - } - - function createRedirectSourceFile(redirectTarget: SourceFile, unredirected: SourceFile, fileName: string, path: Path, resolvedPath: Path, originalFileName: string): SourceFile { - const redirect: SourceFile = Object.create(redirectTarget); - redirect.fileName = fileName; - redirect.path = path; - redirect.resolvedPath = resolvedPath; - redirect.originalFileName = originalFileName; - redirect.redirectInfo = { redirectTarget, unredirected }; + const redirect = getProjectReferenceOutputName(redirectProject, fileName); + fileName = redirect; + // Once we start redirecting to a file, we can potentially come back to it + // via a back-reference from another file in the .d.ts folder. If that happens we'll + // end up trying to add it to the program *again* because we were tracking it via its + // original (un-redirected) name. So we have to map both the original path and the redirected path + // to the source file we're about to find/create + redirectedPath = toPath(redirect); + } + } + // We haven't looked for this file, do so now and cache result + const file = host.getSourceFile(fileName, (options.target!), hostErrorMessage => fileProcessingDiagnostics.add(createRefFileDiagnostic(refFile, Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage)), shouldCreateNewSourceFile); + if (packageId) { + const packageIdKey = packageIdToString(packageId); + const fileFromPackageId = packageIdToSourceFile.get(packageIdKey); + if (fileFromPackageId) { + // Some other SourceFile already exists with this package name and version. + // Instead of creating a duplicate, just redirect to the existing one. + const dupFile = createRedirectSourceFile(fileFromPackageId, file!, fileName, path, toPath(fileName), originalFileName); // TODO: GH#18217 + redirectTargetsMap.add(fileFromPackageId.path, fileName); + addFileToFilesByName(dupFile, path, redirectedPath); + sourceFileToPackageName.set(path, packageId.name); + processingOtherFiles!.push(dupFile); + return dupFile; + } + else if (file) { + // This is the first source file to have this packageId. + packageIdToSourceFile.set(packageIdKey, file); + sourceFileToPackageName.set(path, packageId.name); + } + } + addFileToFilesByName(file, path, redirectedPath); + if (file) { sourceFilesFoundSearchingNodeModules.set(path, currentNodeModulesDepth > 0); - Object.defineProperties(redirect, { - id: { - get(this: SourceFile) { return this.redirectInfo!.redirectTarget.id; }, - set(this: SourceFile, value: SourceFile["id"]) { this.redirectInfo!.redirectTarget.id = value; }, - }, - symbol: { - get(this: SourceFile) { return this.redirectInfo!.redirectTarget.symbol; }, - set(this: SourceFile, value: SourceFile["symbol"]) { this.redirectInfo!.redirectTarget.symbol = value; }, - }, - }); - return redirect; - } - - // Get source file from normalized fileName - function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, refFile: RefFile | undefined, packageId: PackageId | undefined): SourceFile | undefined { - if (useSourceOfProjectReferenceRedirect) { - let source = getSourceOfProjectReferenceRedirect(fileName); - // If preserveSymlinks is true, module resolution wont jump the symlink - // but the resolved real path may be the .d.ts from project reference - // Note:: Currently we try the real path only if the - // file is from node_modules to avoid having to run real path on all file paths - if (!source && - host.realpath && - options.preserveSymlinks && - isDeclarationFileName(fileName) && - stringContains(fileName, nodeModulesPathPart)) { - const realPath = host.realpath(fileName); - if (realPath !== fileName) source = getSourceOfProjectReferenceRedirect(realPath); - } - if (source) { - const file = isString(source) ? - findSourceFile(source, toPath(source), isDefaultLib, ignoreNoDefaultLib, refFile, packageId) : - undefined; - if (file) addFileToFilesByName(file, path, /*redirectedPath*/ undefined); - return file; - } - } - const originalFileName = fileName; - if (filesByName.has(path)) { - const file = filesByName.get(path); - addFileToRefFileMap(fileName, file || undefined, refFile); - // try to check if we've already seen this file but with a different casing in path - // NOTE: this only makes sense for case-insensitive file systems, and only on files which are not redirected - if (file && options.forceConsistentCasingInFileNames) { - const checkedName = file.fileName; - const isRedirect = toPath(checkedName) !== toPath(fileName); - if (isRedirect) { - fileName = getProjectReferenceRedirect(fileName) || fileName; - } - // Check if it differs only in drive letters its ok to ignore that error: - const checkedAbsolutePath = getNormalizedAbsolutePathWithoutRoot(checkedName, currentDirectory); - const inputAbsolutePath = getNormalizedAbsolutePathWithoutRoot(fileName, currentDirectory); - if (checkedAbsolutePath !== inputAbsolutePath) { - reportFileNamesDifferOnlyInCasingError(fileName, file, refFile); - } + file.fileName = fileName; // Ensure that source file has same name as what we were looking for + file.path = path; + file.resolvedPath = toPath(fileName); + file.originalFileName = originalFileName; + addFileToRefFileMap(fileName, file, refFile); + if (host.useCaseSensitiveFileNames()) { + const pathLowerCase = path.toLowerCase(); + // for case-sensitive file systems check if we've already seen some file with similar filename ignoring case + const existingFile = filesByNameIgnoreCase!.get(pathLowerCase); + if (existingFile) { + reportFileNamesDifferOnlyInCasingError(fileName, existingFile, refFile); } - - // If the file was previously found via a node_modules search, but is now being processed as a root file, - // then everything it sucks in may also be marked incorrectly, and needs to be checked again. - if (file && sourceFilesFoundSearchingNodeModules.get(file.path) && currentNodeModulesDepth === 0) { - sourceFilesFoundSearchingNodeModules.set(file.path, false); - if (!options.noResolve) { - processReferencedFiles(file, isDefaultLib); - processTypeReferenceDirectives(file); - } - if (!options.noLib) { - processLibReferenceDirectives(file); - } - - modulesWithElidedImports.set(file.path, false); - processImportedModules(file); + else { + filesByNameIgnoreCase!.set(pathLowerCase, file); } - // See if we need to reprocess the imports due to prior skipped imports - else if (file && modulesWithElidedImports.get(file.path)) { - if (currentNodeModulesDepth < maxNodeModuleJsDepth) { - modulesWithElidedImports.set(file.path, false); - processImportedModules(file); - } + } + skipDefaultLib = skipDefaultLib || (file.hasNoDefaultLib && !ignoreNoDefaultLib); + if (!options.noResolve) { + processReferencedFiles(file, isDefaultLib); + processTypeReferenceDirectives(file); + } + if (!options.noLib) { + processLibReferenceDirectives(file); + } + // always process imported modules to record module name resolutions + processImportedModules(file); + if (isDefaultLib) { + processingDefaultLibFiles!.push(file); + } + else { + processingOtherFiles!.push(file); + } + } + return file; + } + function addFileToRefFileMap(referencedFileName: string, file: SourceFile | undefined, refFile: RefFile | undefined) { + if (refFile && file) { + (refFileMap || (refFileMap = createMultiMap())).add(file.path, { + referencedFileName, + kind: refFile.kind, + index: refFile.index, + file: refFile.file.path + }); + } + } + function addFileToFilesByName(file: SourceFile | undefined, path: Path, redirectedPath: Path | undefined) { + if (redirectedPath) { + filesByName.set(redirectedPath, file); + filesByName.set(path, file || false); + } + else { + filesByName.set(path, file); + } + } + function getProjectReferenceRedirect(fileName: string): string | undefined { + const referencedProject = getProjectReferenceRedirectProject(fileName); + return referencedProject && getProjectReferenceOutputName(referencedProject, fileName); + } + function getProjectReferenceRedirectProject(fileName: string) { + // Ignore dts + if (!resolvedProjectReferences || !resolvedProjectReferences.length || fileExtensionIs(fileName, Extension.Dts)) { + return undefined; + } + // If this file is produced by a referenced project, we need to rewrite it to + // look in the output folder of the referenced project rather than the input + return getResolvedProjectReferenceToRedirect(fileName); + } + function getProjectReferenceOutputName(referencedProject: ResolvedProjectReference, fileName: string) { + const out = referencedProject.commandLine.options.outFile || referencedProject.commandLine.options.out; + return out ? + changeExtension(out, Extension.Dts) : + getOutputDeclarationFileName(fileName, referencedProject.commandLine, !host.useCaseSensitiveFileNames()); + } + /** + * Get the referenced project if the file is input file from that reference project + */ + function getResolvedProjectReferenceToRedirect(fileName: string) { + if (mapFromFileToProjectReferenceRedirects === undefined) { + mapFromFileToProjectReferenceRedirects = createMap(); + forEachResolvedProjectReference((referencedProject, referenceProjectPath) => { + // not input file from the referenced project, ignore + if (referencedProject && + toPath(options.configFilePath!) !== referenceProjectPath) { + referencedProject.commandLine.fileNames.forEach(f => mapFromFileToProjectReferenceRedirects!.set(toPath(f), referenceProjectPath)); } - - return file || undefined; - } - - let redirectedPath: Path | undefined; - if (refFile && !useSourceOfProjectReferenceRedirect) { - const redirectProject = getProjectReferenceRedirectProject(fileName); - if (redirectProject) { - if (redirectProject.commandLine.options.outFile || redirectProject.commandLine.options.out) { - // Shouldnt create many to 1 mapping file in --out scenario - return undefined; - } - const redirect = getProjectReferenceOutputName(redirectProject, fileName); - fileName = redirect; - // Once we start redirecting to a file, we can potentially come back to it - // via a back-reference from another file in the .d.ts folder. If that happens we'll - // end up trying to add it to the program *again* because we were tracking it via its - // original (un-redirected) name. So we have to map both the original path and the redirected path - // to the source file we're about to find/create - redirectedPath = toPath(redirect); - } - } - - // We haven't looked for this file, do so now and cache result - const file = host.getSourceFile( - fileName, - options.target!, - hostErrorMessage => fileProcessingDiagnostics.add(createRefFileDiagnostic( - refFile, - Diagnostics.Cannot_read_file_0_Colon_1, - fileName, - hostErrorMessage - )), - shouldCreateNewSourceFile - ); - - if (packageId) { - const packageIdKey = packageIdToString(packageId); - const fileFromPackageId = packageIdToSourceFile.get(packageIdKey); - if (fileFromPackageId) { - // Some other SourceFile already exists with this package name and version. - // Instead of creating a duplicate, just redirect to the existing one. - const dupFile = createRedirectSourceFile(fileFromPackageId, file!, fileName, path, toPath(fileName), originalFileName); // TODO: GH#18217 - redirectTargetsMap.add(fileFromPackageId.path, fileName); - addFileToFilesByName(dupFile, path, redirectedPath); - sourceFileToPackageName.set(path, packageId.name); - processingOtherFiles!.push(dupFile); - return dupFile; - } - else if (file) { - // This is the first source file to have this packageId. - packageIdToSourceFile.set(packageIdKey, file); - sourceFileToPackageName.set(path, packageId.name); - } - } - addFileToFilesByName(file, path, redirectedPath); - - if (file) { - sourceFilesFoundSearchingNodeModules.set(path, currentNodeModulesDepth > 0); - file.fileName = fileName; // Ensure that source file has same name as what we were looking for - file.path = path; - file.resolvedPath = toPath(fileName); - file.originalFileName = originalFileName; - addFileToRefFileMap(fileName, file, refFile); - - if (host.useCaseSensitiveFileNames()) { - const pathLowerCase = path.toLowerCase(); - // for case-sensitive file systems check if we've already seen some file with similar filename ignoring case - const existingFile = filesByNameIgnoreCase!.get(pathLowerCase); - if (existingFile) { - reportFileNamesDifferOnlyInCasingError(fileName, existingFile, refFile); + }); + } + const referencedProjectPath = mapFromFileToProjectReferenceRedirects.get(toPath(fileName)); + return referencedProjectPath && getResolvedProjectReferenceByPath(referencedProjectPath); + } + function forEachResolvedProjectReference(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined { + return forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, index, parent) => { + const ref = (parent ? parent.commandLine.projectReferences : projectReferences)![index]; + const resolvedRefPath = toPath(resolveProjectReferencePath(ref)); + return cb(resolvedRef, resolvedRefPath); + }); + } + function getSourceOfProjectReferenceRedirect(file: string) { + if (!isDeclarationFileName(file)) + return undefined; + if (mapFromToProjectReferenceRedirectSource === undefined) { + mapFromToProjectReferenceRedirectSource = createMap(); + forEachResolvedProjectReference(resolvedRef => { + if (resolvedRef) { + const out = resolvedRef.commandLine.options.outFile || resolvedRef.commandLine.options.out; + if (out) { + // Dont know which source file it means so return true? + const outputDts = changeExtension(out, Extension.Dts); + mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), true); } else { - filesByNameIgnoreCase!.set(pathLowerCase, file); + forEach(resolvedRef.commandLine.fileNames, fileName => { + if (!fileExtensionIs(fileName, Extension.Dts)) { + const outputDts = getOutputDeclarationFileName(fileName, resolvedRef.commandLine, host.useCaseSensitiveFileNames()); + mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), fileName); + } + }); } } - - skipDefaultLib = skipDefaultLib || (file.hasNoDefaultLib && !ignoreNoDefaultLib); - - if (!options.noResolve) { - processReferencedFiles(file, isDefaultLib); - processTypeReferenceDirectives(file); + }); + } + return mapFromToProjectReferenceRedirectSource.get(toPath(file)); + } + function isSourceOfProjectReferenceRedirect(fileName: string) { + return useSourceOfProjectReferenceRedirect && !!getResolvedProjectReferenceToRedirect(fileName); + } + function forEachProjectReference(projectReferences: readonly ProjectReference[] | undefined, resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined, cbResolvedRef: (resolvedRef: ResolvedProjectReference | undefined, index: number, parent: ResolvedProjectReference | undefined) => T | undefined, cbRef?: (projectReferences: readonly ProjectReference[] | undefined, parent: ResolvedProjectReference | undefined) => T | undefined): T | undefined { + let seenResolvedRefs: ResolvedProjectReference[] | undefined; + return worker(projectReferences, resolvedProjectReferences, /*parent*/ undefined, cbResolvedRef, cbRef); + function worker(projectReferences: readonly ProjectReference[] | undefined, resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined, parent: ResolvedProjectReference | undefined, cbResolvedRef: (resolvedRef: ResolvedProjectReference | undefined, index: number, parent: ResolvedProjectReference | undefined) => T | undefined, cbRef?: (projectReferences: readonly ProjectReference[] | undefined, parent: ResolvedProjectReference | undefined) => T | undefined): T | undefined { + // Visit project references first + if (cbRef) { + const result = cbRef(projectReferences, parent); + if (result) { + return result; + } + } + return forEach(resolvedProjectReferences, (resolvedRef, index) => { + if (contains(seenResolvedRefs, resolvedRef)) { + // ignore recursives + return undefined; } - if (!options.noLib) { - processLibReferenceDirectives(file); + const result = cbResolvedRef(resolvedRef, index, parent); + if (result) { + return result; } - - - // always process imported modules to record module name resolutions - processImportedModules(file); - - if (isDefaultLib) { - processingDefaultLibFiles!.push(file); + if (!resolvedRef) + return undefined; + (seenResolvedRefs || (seenResolvedRefs = [])).push(resolvedRef); + return worker(resolvedRef.commandLine.projectReferences, resolvedRef.references, resolvedRef, cbResolvedRef, cbRef); + }); + } + } + function getResolvedProjectReferenceByPath(projectReferencePath: Path): ResolvedProjectReference | undefined { + if (!projectReferenceRedirects) { + return undefined; + } + return projectReferenceRedirects.get(projectReferencePath) || undefined; + } + function processReferencedFiles(file: SourceFile, isDefaultLib: boolean) { + forEach(file.referencedFiles, (ref, index) => { + const referencedFileName = resolveTripleslashReference(ref.fileName, file.originalFileName); + processSourceFile(referencedFileName, isDefaultLib, + /*ignoreNoDefaultLib*/ false, + /*packageId*/ undefined, { + kind: RefFileKind.ReferenceFile, + index, + file, + pos: ref.pos, + end: ref.end + }); + }); + } + function processTypeReferenceDirectives(file: SourceFile) { + // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. + const typeDirectives = map(file.typeReferenceDirectives, ref => ref.fileName.toLocaleLowerCase()); + if (!typeDirectives) { + return; + } + const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeDirectives, file.originalFileName, getResolvedProjectReferenceToRedirect(file.originalFileName)); + for (let i = 0; i < typeDirectives.length; i++) { + const ref = file.typeReferenceDirectives[i]; + const resolvedTypeReferenceDirective = resolutions[i]; + // store resolved type directive on the file + const fileName = ref.fileName.toLocaleLowerCase(); + setResolvedTypeReferenceDirective(file, fileName, resolvedTypeReferenceDirective); + processTypeReferenceDirective(fileName, resolvedTypeReferenceDirective, { + kind: RefFileKind.TypeReferenceDirective, + index: i, + file, + pos: ref.pos, + end: ref.end + }); + } + } + function processTypeReferenceDirective(typeReferenceDirective: string, resolvedTypeReferenceDirective?: ResolvedTypeReferenceDirective, refFile?: RefFile): void { + // If we already found this library as a primary reference - nothing to do + const previousResolution = resolvedTypeReferenceDirectives.get(typeReferenceDirective); + if (previousResolution && previousResolution.primary) { + return; + } + let saveResolution = true; + if (resolvedTypeReferenceDirective) { + if (resolvedTypeReferenceDirective.isExternalLibraryImport) + currentNodeModulesDepth++; + if (resolvedTypeReferenceDirective.primary) { + // resolved from the primary path + processSourceFile(resolvedTypeReferenceDirective.resolvedFileName!, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, resolvedTypeReferenceDirective.packageId, refFile); // TODO: GH#18217 + } + else { + // If we already resolved to this file, it must have been a secondary reference. Check file contents + // for sameness and possibly issue an error + if (previousResolution) { + // Don't bother reading the file again if it's the same file. + if (resolvedTypeReferenceDirective.resolvedFileName !== previousResolution.resolvedFileName) { + const otherFileText = host.readFile(resolvedTypeReferenceDirective.resolvedFileName!); + const existingFile = getSourceFile(previousResolution.resolvedFileName!)!; + if (otherFileText !== existingFile.text) { + // Try looking up ref for original file + const refs = !refFile ? refFileMap && refFileMap.get(existingFile.path) : undefined; + const refToReportErrorOn = refs && find(refs, ref => ref.referencedFileName === existingFile.fileName); + fileProcessingDiagnostics.add(refToReportErrorOn ? + createFileDiagnosticAtReference(refToReportErrorOn, Diagnostics.Conflicting_definitions_for_0_found_at_1_and_2_Consider_installing_a_specific_version_of_this_library_to_resolve_the_conflict, typeReferenceDirective, resolvedTypeReferenceDirective.resolvedFileName, previousResolution.resolvedFileName) : + createRefFileDiagnostic(refFile, Diagnostics.Conflicting_definitions_for_0_found_at_1_and_2_Consider_installing_a_specific_version_of_this_library_to_resolve_the_conflict, typeReferenceDirective, resolvedTypeReferenceDirective.resolvedFileName, previousResolution.resolvedFileName)); + } + } + // don't overwrite previous resolution result + saveResolution = false; } else { - processingOtherFiles!.push(file); + // First resolution of this library + processSourceFile(resolvedTypeReferenceDirective.resolvedFileName!, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, resolvedTypeReferenceDirective.packageId, refFile); } } - return file; + if (resolvedTypeReferenceDirective.isExternalLibraryImport) + currentNodeModulesDepth--; } - - function addFileToRefFileMap(referencedFileName: string, file: SourceFile | undefined, refFile: RefFile | undefined) { - if (refFile && file) { - (refFileMap || (refFileMap = createMultiMap())).add(file.path, { - referencedFileName, - kind: refFile.kind, - index: refFile.index, - file: refFile.file.path - }); - } + else { + fileProcessingDiagnostics.add(createRefFileDiagnostic(refFile, Diagnostics.Cannot_find_type_definition_file_for_0, typeReferenceDirective)); + } + if (saveResolution) { + resolvedTypeReferenceDirectives.set(typeReferenceDirective, resolvedTypeReferenceDirective); } - - function addFileToFilesByName(file: SourceFile | undefined, path: Path, redirectedPath: Path | undefined) { - if (redirectedPath) { - filesByName.set(redirectedPath, file); - filesByName.set(path, file || false); + } + function processLibReferenceDirectives(file: SourceFile) { + forEach(file.libReferenceDirectives, libReference => { + const libName = libReference.fileName.toLocaleLowerCase(); + const libFileName = libMap.get(libName); + if (libFileName) { + // we ignore any 'no-default-lib' reference set on this file. + processRootFile(combinePaths(defaultLibraryPath, libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ true); } else { - filesByName.set(path, file); + const unqualifiedLibName = removeSuffix(removePrefix(libName, "lib."), ".d.ts"); + const suggestion = getSpellingSuggestion(unqualifiedLibName, libs, identity); + const message = suggestion ? Diagnostics.Cannot_find_lib_definition_for_0_Did_you_mean_1 : Diagnostics.Cannot_find_lib_definition_for_0; + fileProcessingDiagnostics.add(createFileDiagnostic(file, libReference.pos, libReference.end - libReference.pos, message, libName, suggestion)); } + }); + } + function createRefFileDiagnostic(refFile: RefFile | undefined, message: DiagnosticMessage, ...args: any[]): Diagnostic { + if (!refFile) { + return createCompilerDiagnostic(message, ...args); } - - function getProjectReferenceRedirect(fileName: string): string | undefined { - const referencedProject = getProjectReferenceRedirectProject(fileName); - return referencedProject && getProjectReferenceOutputName(referencedProject, fileName); + else { + return createFileDiagnostic(refFile.file, refFile.pos, refFile.end - refFile.pos, message, ...args); } - - function getProjectReferenceRedirectProject(fileName: string) { - // Ignore dts - if (!resolvedProjectReferences || !resolvedProjectReferences.length || fileExtensionIs(fileName, Extension.Dts)) { - return undefined; + } + function getCanonicalFileName(fileName: string): string { + return host.getCanonicalFileName(fileName); + } + function processImportedModules(file: SourceFile) { + collectExternalModuleReferences(file); + if (file.imports.length || file.moduleAugmentations.length) { + // Because global augmentation doesn't have string literal name, we can check for global augmentation as such. + const moduleNames = getModuleNames(file); + const resolutions = resolveModuleNamesReusingOldState(moduleNames, getNormalizedAbsolutePath(file.originalFileName, currentDirectory), file); + Debug.assert(resolutions.length === moduleNames.length); + for (let i = 0; i < moduleNames.length; i++) { + const resolution = resolutions[i]; + setResolvedModule(file, moduleNames[i], resolution); + if (!resolution) { + continue; + } + const isFromNodeModulesSearch = resolution.isExternalLibraryImport; + const isJsFile = !resolutionExtensionIsTSOrJson(resolution.extension); + const isJsFileFromNodeModules = isFromNodeModulesSearch && isJsFile; + const resolvedFileName = resolution.resolvedFileName; + if (isFromNodeModulesSearch) { + currentNodeModulesDepth++; + } + // add file to program only if: + // - resolution was successful + // - noResolve is falsy + // - module name comes from the list of imports + // - it's not a top level JavaScript module that exceeded the search max + const elideImport = isJsFileFromNodeModules && currentNodeModulesDepth > maxNodeModuleJsDepth; + // Don't add the file if it has a bad extension (e.g. 'tsx' if we don't have '--allowJs') + // This may still end up being an untyped module -- the file won't be included but imports will be allowed. + const shouldAddFile = resolvedFileName + && !getResolutionDiagnostic(options, resolution) + && !options.noResolve + && i < file.imports.length + && !elideImport + && !(isJsFile && !options.allowJs) + && (isInJSFile(file.imports[i]) || !(file.imports[i].flags & NodeFlags.JSDoc)); + if (elideImport) { + modulesWithElidedImports.set(file.path, true); + } + else if (shouldAddFile) { + const path = toPath(resolvedFileName); + const pos = skipTrivia(file.text, file.imports[i].pos); + findSourceFile(resolvedFileName, path, + /*isDefaultLib*/ false, + /*ignoreNoDefaultLib*/ false, { + kind: RefFileKind.Import, + index: i, + file, + pos, + end: file.imports[i].end + }, resolution.packageId); + } + if (isFromNodeModulesSearch) { + currentNodeModulesDepth--; + } } - - // If this file is produced by a referenced project, we need to rewrite it to - // look in the output folder of the referenced project rather than the input - return getResolvedProjectReferenceToRedirect(fileName); } - - - function getProjectReferenceOutputName(referencedProject: ResolvedProjectReference, fileName: string) { - const out = referencedProject.commandLine.options.outFile || referencedProject.commandLine.options.out; - return out ? - changeExtension(out, Extension.Dts) : - getOutputDeclarationFileName(fileName, referencedProject.commandLine, !host.useCaseSensitiveFileNames()); - } - - /** - * Get the referenced project if the file is input file from that reference project - */ - function getResolvedProjectReferenceToRedirect(fileName: string) { - if (mapFromFileToProjectReferenceRedirects === undefined) { - mapFromFileToProjectReferenceRedirects = createMap(); - forEachResolvedProjectReference((referencedProject, referenceProjectPath) => { - // not input file from the referenced project, ignore - if (referencedProject && - toPath(options.configFilePath!) !== referenceProjectPath) { - referencedProject.commandLine.fileNames.forEach(f => - mapFromFileToProjectReferenceRedirects!.set(toPath(f), referenceProjectPath)); - } - }); - } - - const referencedProjectPath = mapFromFileToProjectReferenceRedirects.get(toPath(fileName)); - return referencedProjectPath && getResolvedProjectReferenceByPath(referencedProjectPath); - } - - function forEachResolvedProjectReference( - cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined - ): T | undefined { - return forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, index, parent) => { - const ref = (parent ? parent.commandLine.projectReferences : projectReferences)![index]; - const resolvedRefPath = toPath(resolveProjectReferencePath(ref)); - return cb(resolvedRef, resolvedRefPath); - }); + else { + // no imports - drop cached module resolutions + file.resolvedModules = undefined; } - - function getSourceOfProjectReferenceRedirect(file: string) { - if (!isDeclarationFileName(file)) return undefined; - if (mapFromToProjectReferenceRedirectSource === undefined) { - mapFromToProjectReferenceRedirectSource = createMap(); - forEachResolvedProjectReference(resolvedRef => { - if (resolvedRef) { - const out = resolvedRef.commandLine.options.outFile || resolvedRef.commandLine.options.out; - if (out) { - // Dont know which source file it means so return true? - const outputDts = changeExtension(out, Extension.Dts); - mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), true); - } - else { - forEach(resolvedRef.commandLine.fileNames, fileName => { - if (!fileExtensionIs(fileName, Extension.Dts)) { - const outputDts = getOutputDeclarationFileName(fileName, resolvedRef.commandLine, host.useCaseSensitiveFileNames()); - mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), fileName); - } - }); - } - } - }); - } - return mapFromToProjectReferenceRedirectSource.get(toPath(file)); - } - - function isSourceOfProjectReferenceRedirect(fileName: string) { - return useSourceOfProjectReferenceRedirect && !!getResolvedProjectReferenceToRedirect(fileName); - } - - function forEachProjectReference( - projectReferences: readonly ProjectReference[] | undefined, - resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined, - cbResolvedRef: (resolvedRef: ResolvedProjectReference | undefined, index: number, parent: ResolvedProjectReference | undefined) => T | undefined, - cbRef?: (projectReferences: readonly ProjectReference[] | undefined, parent: ResolvedProjectReference | undefined) => T | undefined - ): T | undefined { - let seenResolvedRefs: ResolvedProjectReference[] | undefined; - - return worker(projectReferences, resolvedProjectReferences, /*parent*/ undefined, cbResolvedRef, cbRef); - - function worker( - projectReferences: readonly ProjectReference[] | undefined, - resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined, - parent: ResolvedProjectReference | undefined, - cbResolvedRef: (resolvedRef: ResolvedProjectReference | undefined, index: number, parent: ResolvedProjectReference | undefined) => T | undefined, - cbRef?: (projectReferences: readonly ProjectReference[] | undefined, parent: ResolvedProjectReference | undefined) => T | undefined, - ): T | undefined { - - // Visit project references first - if (cbRef) { - const result = cbRef(projectReferences, parent); - if (result) { return result; } - } - - return forEach(resolvedProjectReferences, (resolvedRef, index) => { - if (contains(seenResolvedRefs, resolvedRef)) { - // ignore recursives - return undefined; - } - - const result = cbResolvedRef(resolvedRef, index, parent); - if (result) { - return result; - } - - if (!resolvedRef) return undefined; - - (seenResolvedRefs || (seenResolvedRefs = [])).push(resolvedRef); - return worker(resolvedRef.commandLine.projectReferences, resolvedRef.references, resolvedRef, cbResolvedRef, cbRef); - }); + } + function computeCommonSourceDirectory(sourceFiles: SourceFile[]): string { + const fileNames = mapDefined(sourceFiles, file => file.isDeclarationFile ? undefined : file.fileName); + return computeCommonSourceDirectoryOfFilenames(fileNames, currentDirectory, getCanonicalFileName); + } + function checkSourceFilesBelongToPath(sourceFiles: readonly SourceFile[], rootDirectory: string): boolean { + let allFilesBelongToPath = true; + const absoluteRootDirectoryPath = host.getCanonicalFileName(getNormalizedAbsolutePath(rootDirectory, currentDirectory)); + let rootPaths: ts.Map | undefined; + for (const sourceFile of sourceFiles) { + if (!sourceFile.isDeclarationFile) { + const absoluteSourceFilePath = host.getCanonicalFileName(getNormalizedAbsolutePath(sourceFile.fileName, currentDirectory)); + if (absoluteSourceFilePath.indexOf(absoluteRootDirectoryPath) !== 0) { + if (!rootPaths) + rootPaths = arrayToSet(rootNames, toPath); + addProgramDiagnosticAtRefPath(sourceFile, rootPaths, Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, sourceFile.fileName, rootDirectory); + allFilesBelongToPath = false; + } + } + } + return allFilesBelongToPath; + } + function parseProjectReferenceConfigFile(ref: ProjectReference): ResolvedProjectReference | undefined { + if (!projectReferenceRedirects) { + projectReferenceRedirects = createMap(); + } + // The actual filename (i.e. add "/tsconfig.json" if necessary) + const refPath = resolveProjectReferencePath(ref); + const sourceFilePath = toPath(refPath); + const fromCache = projectReferenceRedirects.get(sourceFilePath); + if (fromCache !== undefined) { + return fromCache || undefined; + } + let commandLine: ParsedCommandLine | undefined; + let sourceFile: JsonSourceFile | undefined; + if (host.getParsedCommandLine) { + commandLine = host.getParsedCommandLine(refPath); + if (!commandLine) { + addFileToFilesByName(/*sourceFile*/ undefined, sourceFilePath, /*redirectedPath*/ undefined); + projectReferenceRedirects.set(sourceFilePath, false); + return undefined; } + sourceFile = Debug.assertDefined(commandLine.options.configFile); + Debug.assert(!sourceFile.path || sourceFile.path === sourceFilePath); + addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined); } - - function getResolvedProjectReferenceByPath(projectReferencePath: Path): ResolvedProjectReference | undefined { - if (!projectReferenceRedirects) { + else { + // An absolute path pointing to the containing directory of the config file + const basePath = getNormalizedAbsolutePath(getDirectoryPath(refPath), host.getCurrentDirectory()); + sourceFile = (host.getSourceFile(refPath, ScriptTarget.JSON) as JsonSourceFile | undefined); + addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined); + if (sourceFile === undefined) { + projectReferenceRedirects.set(sourceFilePath, false); return undefined; } - - return projectReferenceRedirects.get(projectReferencePath) || undefined; - } - - function processReferencedFiles(file: SourceFile, isDefaultLib: boolean) { - forEach(file.referencedFiles, (ref, index) => { - const referencedFileName = resolveTripleslashReference(ref.fileName, file.originalFileName); - processSourceFile( - referencedFileName, - isDefaultLib, - /*ignoreNoDefaultLib*/ false, - /*packageId*/ undefined, - { - kind: RefFileKind.ReferenceFile, - index, - file, - pos: ref.pos, - end: ref.end - } - ); - }); + commandLine = parseJsonSourceFileConfigFileContent(sourceFile, configParsingHost, basePath, /*existingOptions*/ undefined, refPath); } - - function processTypeReferenceDirectives(file: SourceFile) { - // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. - const typeDirectives = map(file.typeReferenceDirectives, ref => ref.fileName.toLocaleLowerCase()); - if (!typeDirectives) { - return; + sourceFile.fileName = refPath; + sourceFile.path = sourceFilePath; + sourceFile.resolvedPath = sourceFilePath; + sourceFile.originalFileName = refPath; + const resolvedRef: ResolvedProjectReference = { commandLine, sourceFile }; + projectReferenceRedirects.set(sourceFilePath, resolvedRef); + if (commandLine.projectReferences) { + resolvedRef.references = commandLine.projectReferences.map(parseProjectReferenceConfigFile); + } + return resolvedRef; + } + function verifyCompilerOptions() { + if (options.strictPropertyInitialization && !getStrictOptionValue(options, "strictNullChecks")) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "strictPropertyInitialization", "strictNullChecks"); + } + if (options.isolatedModules) { + if (options.out) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "isolatedModules"); } - - const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeDirectives, file.originalFileName, getResolvedProjectReferenceToRedirect(file.originalFileName)); - - for (let i = 0; i < typeDirectives.length; i++) { - const ref = file.typeReferenceDirectives[i]; - const resolvedTypeReferenceDirective = resolutions[i]; - // store resolved type directive on the file - const fileName = ref.fileName.toLocaleLowerCase(); - setResolvedTypeReferenceDirective(file, fileName, resolvedTypeReferenceDirective); - processTypeReferenceDirective( - fileName, - resolvedTypeReferenceDirective, - { - kind: RefFileKind.TypeReferenceDirective, - index: i, - file, - pos: ref.pos, - end: ref.end - } - ); - } - } - - function processTypeReferenceDirective( - typeReferenceDirective: string, - resolvedTypeReferenceDirective?: ResolvedTypeReferenceDirective, - refFile?: RefFile - ): void { - - // If we already found this library as a primary reference - nothing to do - const previousResolution = resolvedTypeReferenceDirectives.get(typeReferenceDirective); - if (previousResolution && previousResolution.primary) { - return; + if (options.outFile) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "outFile", "isolatedModules"); } - let saveResolution = true; - if (resolvedTypeReferenceDirective) { - if (resolvedTypeReferenceDirective.isExternalLibraryImport) currentNodeModulesDepth++; - - if (resolvedTypeReferenceDirective.primary) { - // resolved from the primary path - processSourceFile(resolvedTypeReferenceDirective.resolvedFileName!, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, resolvedTypeReferenceDirective.packageId, refFile); // TODO: GH#18217 - } - else { - // If we already resolved to this file, it must have been a secondary reference. Check file contents - // for sameness and possibly issue an error - if (previousResolution) { - // Don't bother reading the file again if it's the same file. - if (resolvedTypeReferenceDirective.resolvedFileName !== previousResolution.resolvedFileName) { - const otherFileText = host.readFile(resolvedTypeReferenceDirective.resolvedFileName!); - const existingFile = getSourceFile(previousResolution.resolvedFileName!)!; - if (otherFileText !== existingFile.text) { - // Try looking up ref for original file - const refs = !refFile ? refFileMap && refFileMap.get(existingFile.path) : undefined; - const refToReportErrorOn = refs && find(refs, ref => ref.referencedFileName === existingFile.fileName); - fileProcessingDiagnostics.add( - refToReportErrorOn ? - createFileDiagnosticAtReference( - refToReportErrorOn, - Diagnostics.Conflicting_definitions_for_0_found_at_1_and_2_Consider_installing_a_specific_version_of_this_library_to_resolve_the_conflict, - typeReferenceDirective, - resolvedTypeReferenceDirective.resolvedFileName, - previousResolution.resolvedFileName - ) : - createRefFileDiagnostic( - refFile, - Diagnostics.Conflicting_definitions_for_0_found_at_1_and_2_Consider_installing_a_specific_version_of_this_library_to_resolve_the_conflict, - typeReferenceDirective, - resolvedTypeReferenceDirective.resolvedFileName, - previousResolution.resolvedFileName - ) - ); - } - } - // don't overwrite previous resolution result - saveResolution = false; - } - else { - // First resolution of this library - processSourceFile(resolvedTypeReferenceDirective.resolvedFileName!, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, resolvedTypeReferenceDirective.packageId, refFile); - } - } - - if (resolvedTypeReferenceDirective.isExternalLibraryImport) currentNodeModulesDepth--; + } + if (options.inlineSourceMap) { + if (options.sourceMap) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "sourceMap", "inlineSourceMap"); + } + if (options.mapRoot) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "mapRoot", "inlineSourceMap"); } - else { - fileProcessingDiagnostics.add(createRefFileDiagnostic( - refFile, - Diagnostics.Cannot_find_type_definition_file_for_0, - typeReferenceDirective - )); - } - - if (saveResolution) { - resolvedTypeReferenceDirectives.set(typeReferenceDirective, resolvedTypeReferenceDirective); - } - } - - function processLibReferenceDirectives(file: SourceFile) { - forEach(file.libReferenceDirectives, libReference => { - const libName = libReference.fileName.toLocaleLowerCase(); - const libFileName = libMap.get(libName); - if (libFileName) { - // we ignore any 'no-default-lib' reference set on this file. - processRootFile(combinePaths(defaultLibraryPath, libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ true); - } - else { - const unqualifiedLibName = removeSuffix(removePrefix(libName, "lib."), ".d.ts"); - const suggestion = getSpellingSuggestion(unqualifiedLibName, libs, identity); - const message = suggestion ? Diagnostics.Cannot_find_lib_definition_for_0_Did_you_mean_1 : Diagnostics.Cannot_find_lib_definition_for_0; - fileProcessingDiagnostics.add(createFileDiagnostic( - file, - libReference.pos, - libReference.end - libReference.pos, - message, - libName, - suggestion - )); - } - }); } - - function createRefFileDiagnostic(refFile: RefFile | undefined, message: DiagnosticMessage, ...args: any[]): Diagnostic { - if (!refFile) { - return createCompilerDiagnostic(message, ...args); + if (options.paths && options.baseUrl === undefined) { + createDiagnosticForOptionName(Diagnostics.Option_paths_cannot_be_used_without_specifying_baseUrl_option, "paths"); + } + if (options.composite) { + if (options.declaration === false) { + createDiagnosticForOptionName(Diagnostics.Composite_projects_may_not_disable_declaration_emit, "declaration"); } - else { - return createFileDiagnostic(refFile.file, refFile.pos, refFile.end - refFile.pos, message, ...args); - } - } - - function getCanonicalFileName(fileName: string): string { - return host.getCanonicalFileName(fileName); - } - - function processImportedModules(file: SourceFile) { - collectExternalModuleReferences(file); - if (file.imports.length || file.moduleAugmentations.length) { - // Because global augmentation doesn't have string literal name, we can check for global augmentation as such. - const moduleNames = getModuleNames(file); - const resolutions = resolveModuleNamesReusingOldState(moduleNames, getNormalizedAbsolutePath(file.originalFileName, currentDirectory), file); - Debug.assert(resolutions.length === moduleNames.length); - for (let i = 0; i < moduleNames.length; i++) { - const resolution = resolutions[i]; - setResolvedModule(file, moduleNames[i], resolution); - - if (!resolution) { - continue; - } - - const isFromNodeModulesSearch = resolution.isExternalLibraryImport; - const isJsFile = !resolutionExtensionIsTSOrJson(resolution.extension); - const isJsFileFromNodeModules = isFromNodeModulesSearch && isJsFile; - const resolvedFileName = resolution.resolvedFileName; - - if (isFromNodeModulesSearch) { - currentNodeModulesDepth++; - } - - // add file to program only if: - // - resolution was successful - // - noResolve is falsy - // - module name comes from the list of imports - // - it's not a top level JavaScript module that exceeded the search max - const elideImport = isJsFileFromNodeModules && currentNodeModulesDepth > maxNodeModuleJsDepth; - // Don't add the file if it has a bad extension (e.g. 'tsx' if we don't have '--allowJs') - // This may still end up being an untyped module -- the file won't be included but imports will be allowed. - const shouldAddFile = resolvedFileName - && !getResolutionDiagnostic(options, resolution) - && !options.noResolve - && i < file.imports.length - && !elideImport - && !(isJsFile && !options.allowJs) - && (isInJSFile(file.imports[i]) || !(file.imports[i].flags & NodeFlags.JSDoc)); - - if (elideImport) { - modulesWithElidedImports.set(file.path, true); - } - else if (shouldAddFile) { - const path = toPath(resolvedFileName); - const pos = skipTrivia(file.text, file.imports[i].pos); - findSourceFile( - resolvedFileName, - path, - /*isDefaultLib*/ false, - /*ignoreNoDefaultLib*/ false, - { - kind: RefFileKind.Import, - index: i, - file, - pos, - end: file.imports[i].end - }, - resolution.packageId - ); - } - - if (isFromNodeModulesSearch) { - currentNodeModulesDepth--; - } - } + if (options.incremental === false) { + createDiagnosticForOptionName(Diagnostics.Composite_projects_may_not_disable_incremental_compilation, "declaration"); } - else { - // no imports - drop cached module resolutions - file.resolvedModules = undefined; - } - } - - function computeCommonSourceDirectory(sourceFiles: SourceFile[]): string { - const fileNames = mapDefined(sourceFiles, file => file.isDeclarationFile ? undefined : file.fileName); - return computeCommonSourceDirectoryOfFilenames(fileNames, currentDirectory, getCanonicalFileName); - } - - function checkSourceFilesBelongToPath(sourceFiles: readonly SourceFile[], rootDirectory: string): boolean { - let allFilesBelongToPath = true; - const absoluteRootDirectoryPath = host.getCanonicalFileName(getNormalizedAbsolutePath(rootDirectory, currentDirectory)); - let rootPaths: Map | undefined; - - for (const sourceFile of sourceFiles) { - if (!sourceFile.isDeclarationFile) { - const absoluteSourceFilePath = host.getCanonicalFileName(getNormalizedAbsolutePath(sourceFile.fileName, currentDirectory)); - if (absoluteSourceFilePath.indexOf(absoluteRootDirectoryPath) !== 0) { - if (!rootPaths) rootPaths = arrayToSet(rootNames, toPath); - addProgramDiagnosticAtRefPath( - sourceFile, - rootPaths, - Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, - sourceFile.fileName, - rootDirectory - ); - allFilesBelongToPath = false; - } - } + } + if (options.tsBuildInfoFile) { + if (!isIncrementalCompilation(options)) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "tsBuildInfoFile", "incremental", "composite"); } - - return allFilesBelongToPath; - } - - function parseProjectReferenceConfigFile(ref: ProjectReference): ResolvedProjectReference | undefined { - if (!projectReferenceRedirects) { - projectReferenceRedirects = createMap(); - } - - // The actual filename (i.e. add "/tsconfig.json" if necessary) - const refPath = resolveProjectReferencePath(ref); - const sourceFilePath = toPath(refPath); - const fromCache = projectReferenceRedirects.get(sourceFilePath); - if (fromCache !== undefined) { - return fromCache || undefined; - } - - let commandLine: ParsedCommandLine | undefined; - let sourceFile: JsonSourceFile | undefined; - if (host.getParsedCommandLine) { - commandLine = host.getParsedCommandLine(refPath); - if (!commandLine) { - addFileToFilesByName(/*sourceFile*/ undefined, sourceFilePath, /*redirectedPath*/ undefined); - projectReferenceRedirects.set(sourceFilePath, false); - return undefined; + } + else if (options.incremental && !options.outFile && !options.out && !options.configFilePath) { + programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_incremental_can_only_be_specified_using_tsconfig_emitting_to_single_file_or_when_option_tsBuildInfoFile_is_specified)); + } + verifyProjectReferences(); + // List of collected files is complete; validate exhautiveness if this is a project with a file list + if (options.composite) { + const rootPaths = arrayToSet(rootNames, toPath); + for (const file of files) { + // Ignore file that is not emitted + if (sourceFileMayBeEmitted(file, program) && !rootPaths.has(file.path)) { + addProgramDiagnosticAtRefPath(file, rootPaths, Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern, file.fileName, options.configFilePath || ""); } - sourceFile = Debug.assertDefined(commandLine.options.configFile); - Debug.assert(!sourceFile.path || sourceFile.path === sourceFilePath); - addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined); } - else { - // An absolute path pointing to the containing directory of the config file - const basePath = getNormalizedAbsolutePath(getDirectoryPath(refPath), host.getCurrentDirectory()); - sourceFile = host.getSourceFile(refPath, ScriptTarget.JSON) as JsonSourceFile | undefined; - addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined); - if (sourceFile === undefined) { - projectReferenceRedirects.set(sourceFilePath, false); - return undefined; + } + if (options.paths) { + for (const key in options.paths) { + if (!hasProperty(options.paths, key)) { + continue; } - commandLine = parseJsonSourceFileConfigFileContent(sourceFile, configParsingHost, basePath, /*existingOptions*/ undefined, refPath); - } - sourceFile.fileName = refPath; - sourceFile.path = sourceFilePath; - sourceFile.resolvedPath = sourceFilePath; - sourceFile.originalFileName = refPath; - - const resolvedRef: ResolvedProjectReference = { commandLine, sourceFile }; - projectReferenceRedirects.set(sourceFilePath, resolvedRef); - if (commandLine.projectReferences) { - resolvedRef.references = commandLine.projectReferences.map(parseProjectReferenceConfigFile); - } - return resolvedRef; - } - - function verifyCompilerOptions() { - if (options.strictPropertyInitialization && !getStrictOptionValue(options, "strictNullChecks")) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "strictPropertyInitialization", "strictNullChecks"); - } - - if (options.isolatedModules) { - if (options.out) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "isolatedModules"); - } - - if (options.outFile) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "outFile", "isolatedModules"); - } - } - - if (options.inlineSourceMap) { - if (options.sourceMap) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "sourceMap", "inlineSourceMap"); - } - if (options.mapRoot) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "mapRoot", "inlineSourceMap"); - } - } - - if (options.paths && options.baseUrl === undefined) { - createDiagnosticForOptionName(Diagnostics.Option_paths_cannot_be_used_without_specifying_baseUrl_option, "paths"); - } - - if (options.composite) { - if (options.declaration === false) { - createDiagnosticForOptionName(Diagnostics.Composite_projects_may_not_disable_declaration_emit, "declaration"); - } - if (options.incremental === false) { - createDiagnosticForOptionName(Diagnostics.Composite_projects_may_not_disable_incremental_compilation, "declaration"); - } - } - - if (options.tsBuildInfoFile) { - if (!isIncrementalCompilation(options)) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "tsBuildInfoFile", "incremental", "composite"); - } - } - else if (options.incremental && !options.outFile && !options.out && !options.configFilePath) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_incremental_can_only_be_specified_using_tsconfig_emitting_to_single_file_or_when_option_tsBuildInfoFile_is_specified)); - } - - verifyProjectReferences(); - - // List of collected files is complete; validate exhautiveness if this is a project with a file list - if (options.composite) { - const rootPaths = arrayToSet(rootNames, toPath); - for (const file of files) { - // Ignore file that is not emitted - if (sourceFileMayBeEmitted(file, program) && !rootPaths.has(file.path)) { - addProgramDiagnosticAtRefPath( - file, - rootPaths, - Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern, - file.fileName, - options.configFilePath || "" - ); - } + if (!hasZeroOrOneAsteriskCharacter(key)) { + createDiagnosticForOptionPaths(/*onKey*/ true, key, Diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, key); } - } - - if (options.paths) { - for (const key in options.paths) { - if (!hasProperty(options.paths, key)) { - continue; + if (isArray(options.paths[key])) { + const len = options.paths[key].length; + if (len === 0) { + createDiagnosticForOptionPaths(/*onKey*/ false, key, Diagnostics.Substitutions_for_pattern_0_shouldn_t_be_an_empty_array, key); } - if (!hasZeroOrOneAsteriskCharacter(key)) { - createDiagnosticForOptionPaths(/*onKey*/ true, key, Diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, key); - } - if (isArray(options.paths[key])) { - const len = options.paths[key].length; - if (len === 0) { - createDiagnosticForOptionPaths(/*onKey*/ false, key, Diagnostics.Substitutions_for_pattern_0_shouldn_t_be_an_empty_array, key); - } - for (let i = 0; i < len; i++) { - const subst = options.paths[key][i]; - const typeOfSubst = typeof subst; - if (typeOfSubst === "string") { - if (!hasZeroOrOneAsteriskCharacter(subst)) { - createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Substitution_0_in_pattern_1_can_have_at_most_one_Asterisk_character, subst, key); - } - } - else { - createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Substitution_0_for_pattern_1_has_incorrect_type_expected_string_got_2, subst, key, typeOfSubst); + for (let i = 0; i < len; i++) { + const subst = options.paths[key][i]; + const typeOfSubst = typeof subst; + if (typeOfSubst === "string") { + if (!hasZeroOrOneAsteriskCharacter(subst)) { + createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Substitution_0_in_pattern_1_can_have_at_most_one_Asterisk_character, subst, key); } } + else { + createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Substitution_0_for_pattern_1_has_incorrect_type_expected_string_got_2, subst, key, typeOfSubst); + } } - else { - createDiagnosticForOptionPaths(/*onKey*/ false, key, Diagnostics.Substitutions_for_pattern_0_should_be_an_array, key); - } - } - } - - if (!options.sourceMap && !options.inlineSourceMap) { - if (options.inlineSources) { - createDiagnosticForOptionName(Diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "inlineSources"); } - if (options.sourceRoot) { - createDiagnosticForOptionName(Diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "sourceRoot"); + else { + createDiagnosticForOptionPaths(/*onKey*/ false, key, Diagnostics.Substitutions_for_pattern_0_should_be_an_array, key); } } - - if (options.out && options.outFile) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "outFile"); + } + if (!options.sourceMap && !options.inlineSourceMap) { + if (options.inlineSources) { + createDiagnosticForOptionName(Diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "inlineSources"); } - - if (options.mapRoot && !(options.sourceMap || options.declarationMap)) { - // Error to specify --mapRoot without --sourcemap - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "mapRoot", "sourceMap", "declarationMap"); + if (options.sourceRoot) { + createDiagnosticForOptionName(Diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "sourceRoot"); } - - if (options.declarationDir) { - if (!getEmitDeclarations(options)) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "declarationDir", "declaration", "composite"); - } - if (options.out || options.outFile) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "declarationDir", options.out ? "out" : "outFile"); - } + } + if (options.out && options.outFile) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "outFile"); + } + if (options.mapRoot && !(options.sourceMap || options.declarationMap)) { + // Error to specify --mapRoot without --sourcemap + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "mapRoot", "sourceMap", "declarationMap"); + } + if (options.declarationDir) { + if (!getEmitDeclarations(options)) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "declarationDir", "declaration", "composite"); } - - if (options.declarationMap && !getEmitDeclarations(options)) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "declarationMap", "declaration", "composite"); + if (options.out || options.outFile) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "declarationDir", options.out ? "out" : "outFile"); } - - if (options.lib && options.noLib) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "lib", "noLib"); + } + if (options.declarationMap && !getEmitDeclarations(options)) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "declarationMap", "declaration", "composite"); + } + if (options.lib && options.noLib) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "lib", "noLib"); + } + if (options.noImplicitUseStrict && getStrictOptionValue(options, "alwaysStrict")) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noImplicitUseStrict", "alwaysStrict"); + } + const languageVersion = options.target || ScriptTarget.ES3; + const outFile = options.outFile || options.out; + const firstNonAmbientExternalModuleSourceFile = find(files, f => isExternalModule(f) && !f.isDeclarationFile); + if (options.isolatedModules) { + if (options.module === ModuleKind.None && languageVersion < ScriptTarget.ES2015) { + createDiagnosticForOptionName(Diagnostics.Option_isolatedModules_can_only_be_used_when_either_option_module_is_provided_or_option_target_is_ES2015_or_higher, "isolatedModules", "target"); } - - if (options.noImplicitUseStrict && getStrictOptionValue(options, "alwaysStrict")) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noImplicitUseStrict", "alwaysStrict"); + const firstNonExternalModuleSourceFile = find(files, f => !isExternalModule(f) && !isSourceFileJS(f) && !f.isDeclarationFile && f.scriptKind !== ScriptKind.JSON); + if (firstNonExternalModuleSourceFile) { + const span = getErrorSpanForNode(firstNonExternalModuleSourceFile, firstNonExternalModuleSourceFile); + programDiagnostics.add(createFileDiagnostic(firstNonExternalModuleSourceFile, span.start, span.length, Diagnostics.All_files_must_be_modules_when_the_isolatedModules_flag_is_provided)); } - - const languageVersion = options.target || ScriptTarget.ES3; - const outFile = options.outFile || options.out; - - const firstNonAmbientExternalModuleSourceFile = find(files, f => isExternalModule(f) && !f.isDeclarationFile); - if (options.isolatedModules) { - if (options.module === ModuleKind.None && languageVersion < ScriptTarget.ES2015) { - createDiagnosticForOptionName(Diagnostics.Option_isolatedModules_can_only_be_used_when_either_option_module_is_provided_or_option_target_is_ES2015_or_higher, "isolatedModules", "target"); - } - - const firstNonExternalModuleSourceFile = find(files, f => !isExternalModule(f) && !isSourceFileJS(f) && !f.isDeclarationFile && f.scriptKind !== ScriptKind.JSON); - if (firstNonExternalModuleSourceFile) { - const span = getErrorSpanForNode(firstNonExternalModuleSourceFile, firstNonExternalModuleSourceFile); - programDiagnostics.add(createFileDiagnostic(firstNonExternalModuleSourceFile, span.start, span.length, Diagnostics.All_files_must_be_modules_when_the_isolatedModules_flag_is_provided)); - } + } + else if (firstNonAmbientExternalModuleSourceFile && languageVersion < ScriptTarget.ES2015 && options.module === ModuleKind.None) { + // We cannot use createDiagnosticFromNode because nodes do not have parents yet + const span = getErrorSpanForNode(firstNonAmbientExternalModuleSourceFile, (firstNonAmbientExternalModuleSourceFile.externalModuleIndicator!)); + programDiagnostics.add(createFileDiagnostic(firstNonAmbientExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_use_imports_exports_or_module_augmentations_when_module_is_none)); + } + // Cannot specify module gen that isn't amd or system with --out + if (outFile && !options.emitDeclarationOnly) { + if (options.module && !(options.module === ModuleKind.AMD || options.module === ModuleKind.System)) { + createDiagnosticForOptionName(Diagnostics.Only_amd_and_system_modules_are_supported_alongside_0, options.out ? "out" : "outFile", "module"); } - else if (firstNonAmbientExternalModuleSourceFile && languageVersion < ScriptTarget.ES2015 && options.module === ModuleKind.None) { - // We cannot use createDiagnosticFromNode because nodes do not have parents yet - const span = getErrorSpanForNode(firstNonAmbientExternalModuleSourceFile, firstNonAmbientExternalModuleSourceFile.externalModuleIndicator!); - programDiagnostics.add(createFileDiagnostic(firstNonAmbientExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_use_imports_exports_or_module_augmentations_when_module_is_none)); + else if (options.module === undefined && firstNonAmbientExternalModuleSourceFile) { + const span = getErrorSpanForNode(firstNonAmbientExternalModuleSourceFile, (firstNonAmbientExternalModuleSourceFile.externalModuleIndicator!)); + programDiagnostics.add(createFileDiagnostic(firstNonAmbientExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_compile_modules_using_option_0_unless_the_module_flag_is_amd_or_system, options.out ? "out" : "outFile")); } - - // Cannot specify module gen that isn't amd or system with --out - if (outFile && !options.emitDeclarationOnly) { - if (options.module && !(options.module === ModuleKind.AMD || options.module === ModuleKind.System)) { - createDiagnosticForOptionName(Diagnostics.Only_amd_and_system_modules_are_supported_alongside_0, options.out ? "out" : "outFile", "module"); - } - else if (options.module === undefined && firstNonAmbientExternalModuleSourceFile) { - const span = getErrorSpanForNode(firstNonAmbientExternalModuleSourceFile, firstNonAmbientExternalModuleSourceFile.externalModuleIndicator!); - programDiagnostics.add(createFileDiagnostic(firstNonAmbientExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_compile_modules_using_option_0_unless_the_module_flag_is_amd_or_system, options.out ? "out" : "outFile")); - } + } + if (options.resolveJsonModule) { + if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs) { + createDiagnosticForOptionName(Diagnostics.Option_resolveJsonModule_cannot_be_specified_without_node_module_resolution_strategy, "resolveJsonModule"); } - - if (options.resolveJsonModule) { - if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs) { - createDiagnosticForOptionName(Diagnostics.Option_resolveJsonModule_cannot_be_specified_without_node_module_resolution_strategy, "resolveJsonModule"); - } - // Any emit other than common js, amd, es2015 or esnext is error - else if (!hasJsonModuleEmitEnabled(options)) { - createDiagnosticForOptionName(Diagnostics.Option_resolveJsonModule_can_only_be_specified_when_module_code_generation_is_commonjs_amd_es2015_or_esNext, "resolveJsonModule", "module"); - } + // Any emit other than common js, amd, es2015 or esnext is error + else if (!hasJsonModuleEmitEnabled(options)) { + createDiagnosticForOptionName(Diagnostics.Option_resolveJsonModule_can_only_be_specified_when_module_code_generation_is_commonjs_amd_es2015_or_esNext, "resolveJsonModule", "module"); } - - // there has to be common source directory if user specified --outdir || --sourceRoot - // if user specified --mapRoot, there needs to be common source directory if there would be multiple files being emitted - if (options.outDir || // there is --outDir specified - options.sourceRoot || // there is --sourceRoot specified - options.mapRoot) { // there is --mapRoot specified - - // Precalculate and cache the common source directory - const dir = getCommonSourceDirectory(); - - // If we failed to find a good common directory, but outDir is specified and at least one of our files is on a windows drive/URL/other resource, add a failure - if (options.outDir && dir === "" && files.some(file => getRootLength(file.fileName) > 1)) { - createDiagnosticForOptionName(Diagnostics.Cannot_find_the_common_subdirectory_path_for_the_input_files, "outDir"); - } + } + // there has to be common source directory if user specified --outdir || --sourceRoot + // if user specified --mapRoot, there needs to be common source directory if there would be multiple files being emitted + if (options.outDir || // there is --outDir specified + options.sourceRoot || // there is --sourceRoot specified + options.mapRoot) { // there is --mapRoot specified + // Precalculate and cache the common source directory + const dir = getCommonSourceDirectory(); + // If we failed to find a good common directory, but outDir is specified and at least one of our files is on a windows drive/URL/other resource, add a failure + if (options.outDir && dir === "" && files.some(file => getRootLength(file.fileName) > 1)) { + createDiagnosticForOptionName(Diagnostics.Cannot_find_the_common_subdirectory_path_for_the_input_files, "outDir"); } - - if (options.useDefineForClassFields && languageVersion === ScriptTarget.ES3) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_target_is_ES3, "useDefineForClassFields"); - } - - if (options.checkJs && !options.allowJs) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "checkJs", "allowJs")); - } - - if (options.emitDeclarationOnly) { - if (!getEmitDeclarations(options)) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "emitDeclarationOnly", "declaration", "composite"); - } - - if (options.noEmit) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "emitDeclarationOnly", "noEmit"); - } - } - - if (options.emitDecoratorMetadata && - !options.experimentalDecorators) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "emitDecoratorMetadata", "experimentalDecorators"); - } - - if (options.jsxFactory) { - if (options.reactNamespace) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "reactNamespace", "jsxFactory"); - } - if (!parseIsolatedEntityName(options.jsxFactory, languageVersion)) { - createOptionValueDiagnostic("jsxFactory", Diagnostics.Invalid_value_for_jsxFactory_0_is_not_a_valid_identifier_or_qualified_name, options.jsxFactory); - } - } - else if (options.reactNamespace && !isIdentifierText(options.reactNamespace, languageVersion)) { - createOptionValueDiagnostic("reactNamespace", Diagnostics.Invalid_value_for_reactNamespace_0_is_not_a_valid_identifier, options.reactNamespace); - } - - // If the emit is enabled make sure that every output file is unique and not overwriting any of the input files - if (!options.noEmit && !options.suppressOutputPathCheck) { - const emitHost = getEmitHost(); - const emitFilesSeen = createMap(); - forEachEmittedFile(emitHost, (emitFileNames) => { - if (!options.emitDeclarationOnly) { - verifyEmitFilePath(emitFileNames.jsFilePath, emitFilesSeen); - } - verifyEmitFilePath(emitFileNames.declarationFilePath, emitFilesSeen); - }); + } + if (options.useDefineForClassFields && languageVersion === ScriptTarget.ES3) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_target_is_ES3, "useDefineForClassFields"); + } + if (options.checkJs && !options.allowJs) { + programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "checkJs", "allowJs")); + } + if (options.emitDeclarationOnly) { + if (!getEmitDeclarations(options)) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "emitDeclarationOnly", "declaration", "composite"); } - - // Verify that all the emit files are unique and don't overwrite input files - function verifyEmitFilePath(emitFileName: string | undefined, emitFilesSeen: Map) { - if (emitFileName) { - const emitFilePath = toPath(emitFileName); - // Report error if the output overwrites input file - if (filesByName.has(emitFilePath)) { - let chain: DiagnosticMessageChain | undefined; - if (!options.configFilePath) { - // The program is from either an inferred project or an external project - chain = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Adding_a_tsconfig_json_file_will_help_organize_projects_that_contain_both_TypeScript_and_JavaScript_files_Learn_more_at_https_Colon_Slash_Slashaka_ms_Slashtsconfig); - } - chain = chainDiagnosticMessages(chain, Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file, emitFileName); - blockEmittingOfFile(emitFileName, createCompilerDiagnosticFromMessageChain(chain)); - } - - const emitFileKey = !host.useCaseSensitiveFileNames() ? emitFilePath.toLocaleLowerCase() : emitFilePath; - // Report error if multiple files write into same file - if (emitFilesSeen.has(emitFileKey)) { - // Already seen the same emit file - report error - blockEmittingOfFile(emitFileName, createCompilerDiagnostic(Diagnostics.Cannot_write_file_0_because_it_would_be_overwritten_by_multiple_input_files, emitFileName)); - } - else { - emitFilesSeen.set(emitFileKey, true); - } - } + if (options.noEmit) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "emitDeclarationOnly", "noEmit"); } } - - function createFileDiagnosticAtReference(refPathToReportErrorOn: ts.RefFile, message: DiagnosticMessage, ...args: (string | number | undefined)[]) { - const refFile = Debug.assertDefined(getSourceFileByPath(refPathToReportErrorOn.file)); - const { kind, index } = refPathToReportErrorOn; - let pos: number, end: number; - switch (kind) { - case RefFileKind.Import: - pos = skipTrivia(refFile.text, refFile.imports[index].pos); - end = refFile.imports[index].end; - break; - case RefFileKind.ReferenceFile: - ({ pos, end } = refFile.referencedFiles[index]); - break; - case RefFileKind.TypeReferenceDirective: - ({ pos, end } = refFile.typeReferenceDirectives[index]); - break; - default: - return Debug.assertNever(kind); - } - return createFileDiagnostic(refFile, pos, end - pos, message, ...args); - } - - function addProgramDiagnosticAtRefPath(file: SourceFile, rootPaths: Map, message: DiagnosticMessage, ...args: (string | number | undefined)[]) { - const refPaths = refFileMap && refFileMap.get(file.path); - const refPathToReportErrorOn = forEach(refPaths, refPath => rootPaths.has(refPath.file) ? refPath : undefined) || - elementAt(refPaths, 0); - programDiagnostics.add( - refPathToReportErrorOn ? - createFileDiagnosticAtReference(refPathToReportErrorOn, message, ...args) : - createCompilerDiagnostic(message, ...args) - ); - } - - function verifyProjectReferences() { - const buildInfoPath = !options.noEmit && !options.suppressOutputPathCheck ? getTsBuildInfoEmitOutputFilePath(options) : undefined; - forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, index, parent) => { - const ref = (parent ? parent.commandLine.projectReferences : projectReferences)![index]; - const parentFile = parent && parent.sourceFile as JsonSourceFile; - if (!resolvedRef) { - createDiagnosticForReference(parentFile, index, Diagnostics.File_0_not_found, ref.path); - return; - } - const options = resolvedRef.commandLine.options; - if (!options.composite) { - // ok to not have composite if the current program is container only - const inputs = parent ? parent.commandLine.fileNames : rootNames; - if (inputs.length) { - createDiagnosticForReference(parentFile, index, Diagnostics.Referenced_project_0_must_have_setting_composite_Colon_true, ref.path); - } + if (options.emitDecoratorMetadata && + !options.experimentalDecorators) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "emitDecoratorMetadata", "experimentalDecorators"); + } + if (options.jsxFactory) { + if (options.reactNamespace) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "reactNamespace", "jsxFactory"); + } + if (!parseIsolatedEntityName(options.jsxFactory, languageVersion)) { + createOptionValueDiagnostic("jsxFactory", Diagnostics.Invalid_value_for_jsxFactory_0_is_not_a_valid_identifier_or_qualified_name, options.jsxFactory); + } + } + else if (options.reactNamespace && !isIdentifierText(options.reactNamespace, languageVersion)) { + createOptionValueDiagnostic("reactNamespace", Diagnostics.Invalid_value_for_reactNamespace_0_is_not_a_valid_identifier, options.reactNamespace); + } + // If the emit is enabled make sure that every output file is unique and not overwriting any of the input files + if (!options.noEmit && !options.suppressOutputPathCheck) { + const emitHost = getEmitHost(); + const emitFilesSeen = createMap(); + forEachEmittedFile(emitHost, (emitFileNames) => { + if (!options.emitDeclarationOnly) { + verifyEmitFilePath(emitFileNames.jsFilePath, emitFilesSeen); } - if (ref.prepend) { - const out = options.outFile || options.out; - if (out) { - if (!host.fileExists(out)) { - createDiagnosticForReference(parentFile, index, Diagnostics.Output_file_0_from_project_1_does_not_exist, out, ref.path); - } - } - else { - createDiagnosticForReference(parentFile, index, Diagnostics.Cannot_prepend_project_0_because_it_does_not_have_outFile_set, ref.path); - } + verifyEmitFilePath(emitFileNames.declarationFilePath, emitFilesSeen); + }); + } + // Verify that all the emit files are unique and don't overwrite input files + function verifyEmitFilePath(emitFileName: string | undefined, emitFilesSeen: ts.Map) { + if (emitFileName) { + const emitFilePath = toPath(emitFileName); + // Report error if the output overwrites input file + if (filesByName.has(emitFilePath)) { + let chain: DiagnosticMessageChain | undefined; + if (!options.configFilePath) { + // The program is from either an inferred project or an external project + chain = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Adding_a_tsconfig_json_file_will_help_organize_projects_that_contain_both_TypeScript_and_JavaScript_files_Learn_more_at_https_Colon_Slash_Slashaka_ms_Slashtsconfig); + } + chain = chainDiagnosticMessages(chain, Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file, emitFileName); + blockEmittingOfFile(emitFileName, createCompilerDiagnosticFromMessageChain(chain)); + } + const emitFileKey = !host.useCaseSensitiveFileNames() ? emitFilePath.toLocaleLowerCase() : emitFilePath; + // Report error if multiple files write into same file + if (emitFilesSeen.has(emitFileKey)) { + // Already seen the same emit file - report error + blockEmittingOfFile(emitFileName, createCompilerDiagnostic(Diagnostics.Cannot_write_file_0_because_it_would_be_overwritten_by_multiple_input_files, emitFileName)); } - if (!parent && buildInfoPath && buildInfoPath === getTsBuildInfoEmitOutputFilePath(options)) { - createDiagnosticForReference(parentFile, index, Diagnostics.Cannot_write_file_0_because_it_will_overwrite_tsbuildinfo_file_generated_by_referenced_project_1, buildInfoPath, ref.path); - hasEmitBlockingDiagnostics.set(toPath(buildInfoPath), true); + else { + emitFilesSeen.set(emitFileKey, true); } - }); + } } - - function createDiagnosticForOptionPathKeyValue(key: string, valueIndex: number, message: DiagnosticMessage, arg0: string | number, arg1: string | number, arg2?: string | number) { - let needCompilerDiagnostic = true; - const pathsSyntax = getOptionPathsSyntax(); - for (const pathProp of pathsSyntax) { - if (isObjectLiteralExpression(pathProp.initializer)) { - for (const keyProps of getPropertyAssignment(pathProp.initializer, key)) { - const initializer = keyProps.initializer; - if (isArrayLiteralExpression(initializer) && initializer.elements.length > valueIndex) { - programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, initializer.elements[valueIndex], message, arg0, arg1, arg2)); - needCompilerDiagnostic = false; - } + } + function createFileDiagnosticAtReference(refPathToReportErrorOn: ts.RefFile, message: DiagnosticMessage, ...args: (string | number | undefined)[]) { + const refFile = Debug.assertDefined(getSourceFileByPath(refPathToReportErrorOn.file)); + const { kind, index } = refPathToReportErrorOn; + let pos: number, end: number; + switch (kind) { + case RefFileKind.Import: + pos = skipTrivia(refFile.text, refFile.imports[index].pos); + end = refFile.imports[index].end; + break; + case RefFileKind.ReferenceFile: + ({ pos, end } = refFile.referencedFiles[index]); + break; + case RefFileKind.TypeReferenceDirective: + ({ pos, end } = refFile.typeReferenceDirectives[index]); + break; + default: + return Debug.assertNever(kind); + } + return createFileDiagnostic(refFile, pos, end - pos, message, ...args); + } + function addProgramDiagnosticAtRefPath(file: SourceFile, rootPaths: ts.Map, message: DiagnosticMessage, ...args: (string | number | undefined)[]) { + const refPaths = refFileMap && refFileMap.get(file.path); + const refPathToReportErrorOn = forEach(refPaths, refPath => rootPaths.has(refPath.file) ? refPath : undefined) || + elementAt(refPaths, 0); + programDiagnostics.add(refPathToReportErrorOn ? + createFileDiagnosticAtReference(refPathToReportErrorOn, message, ...args) : + createCompilerDiagnostic(message, ...args)); + } + function verifyProjectReferences() { + const buildInfoPath = !options.noEmit && !options.suppressOutputPathCheck ? getTsBuildInfoEmitOutputFilePath(options) : undefined; + forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, index, parent) => { + const ref = (parent ? parent.commandLine.projectReferences : projectReferences)![index]; + const parentFile = parent && (parent.sourceFile as JsonSourceFile); + if (!resolvedRef) { + createDiagnosticForReference(parentFile, index, Diagnostics.File_0_not_found, ref.path); + return; + } + const options = resolvedRef.commandLine.options; + if (!options.composite) { + // ok to not have composite if the current program is container only + const inputs = parent ? parent.commandLine.fileNames : rootNames; + if (inputs.length) { + createDiagnosticForReference(parentFile, index, Diagnostics.Referenced_project_0_must_have_setting_composite_Colon_true, ref.path); + } + } + if (ref.prepend) { + const out = options.outFile || options.out; + if (out) { + if (!host.fileExists(out)) { + createDiagnosticForReference(parentFile, index, Diagnostics.Output_file_0_from_project_1_does_not_exist, out, ref.path); } } + else { + createDiagnosticForReference(parentFile, index, Diagnostics.Cannot_prepend_project_0_because_it_does_not_have_outFile_set, ref.path); + } } - - if (needCompilerDiagnostic) { - programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1, arg2)); + if (!parent && buildInfoPath && buildInfoPath === getTsBuildInfoEmitOutputFilePath(options)) { + createDiagnosticForReference(parentFile, index, Diagnostics.Cannot_write_file_0_because_it_will_overwrite_tsbuildinfo_file_generated_by_referenced_project_1, buildInfoPath, ref.path); + hasEmitBlockingDiagnostics.set(toPath(buildInfoPath), true); } - } - - function createDiagnosticForOptionPaths(onKey: boolean, key: string, message: DiagnosticMessage, arg0: string | number) { - let needCompilerDiagnostic = true; - const pathsSyntax = getOptionPathsSyntax(); - for (const pathProp of pathsSyntax) { - if (isObjectLiteralExpression(pathProp.initializer) && - createOptionDiagnosticInObjectLiteralSyntax( - pathProp.initializer, onKey, key, /*key2*/ undefined, - message, arg0)) { - needCompilerDiagnostic = false; + }); + } + function createDiagnosticForOptionPathKeyValue(key: string, valueIndex: number, message: DiagnosticMessage, arg0: string | number, arg1: string | number, arg2?: string | number) { + let needCompilerDiagnostic = true; + const pathsSyntax = getOptionPathsSyntax(); + for (const pathProp of pathsSyntax) { + if (isObjectLiteralExpression(pathProp.initializer)) { + for (const keyProps of getPropertyAssignment(pathProp.initializer, key)) { + const initializer = keyProps.initializer; + if (isArrayLiteralExpression(initializer) && initializer.elements.length > valueIndex) { + programDiagnostics.add(createDiagnosticForNodeInSourceFile((options.configFile!), initializer.elements[valueIndex], message, arg0, arg1, arg2)); + needCompilerDiagnostic = false; + } } } - if (needCompilerDiagnostic) { - programDiagnostics.add(createCompilerDiagnostic(message, arg0)); - } } - - function getOptionsSyntaxByName(name: string): object | undefined { - const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax(); - if (compilerOptionsObjectLiteralSyntax) { - return getPropertyAssignment(compilerOptionsObjectLiteralSyntax, name); + if (needCompilerDiagnostic) { + programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1, arg2)); + } + } + function createDiagnosticForOptionPaths(onKey: boolean, key: string, message: DiagnosticMessage, arg0: string | number) { + let needCompilerDiagnostic = true; + const pathsSyntax = getOptionPathsSyntax(); + for (const pathProp of pathsSyntax) { + if (isObjectLiteralExpression(pathProp.initializer) && + createOptionDiagnosticInObjectLiteralSyntax(pathProp.initializer, onKey, key, /*key2*/ undefined, message, arg0)) { + needCompilerDiagnostic = false; } - return undefined; } - - function getOptionPathsSyntax(): PropertyAssignment[] { - return getOptionsSyntaxByName("paths") as PropertyAssignment[] || emptyArray; + if (needCompilerDiagnostic) { + programDiagnostics.add(createCompilerDiagnostic(message, arg0)); } - - function createDiagnosticForOptionName(message: DiagnosticMessage, option1: string, option2?: string, option3?: string) { - createDiagnosticForOption(/*onKey*/ true, option1, option2, message, option1, option2, option3); + } + function getOptionsSyntaxByName(name: string): object | undefined { + const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax(); + if (compilerOptionsObjectLiteralSyntax) { + return getPropertyAssignment(compilerOptionsObjectLiteralSyntax, name); } - - function createOptionValueDiagnostic(option1: string, message: DiagnosticMessage, arg0: string) { - createDiagnosticForOption(/*onKey*/ false, option1, /*option2*/ undefined, message, arg0); + return undefined; + } + function getOptionPathsSyntax(): PropertyAssignment[] { + return (getOptionsSyntaxByName("paths") as PropertyAssignment[]) || emptyArray; + } + function createDiagnosticForOptionName(message: DiagnosticMessage, option1: string, option2?: string, option3?: string) { + createDiagnosticForOption(/*onKey*/ true, option1, option2, message, option1, option2, option3); + } + function createOptionValueDiagnostic(option1: string, message: DiagnosticMessage, arg0: string) { + createDiagnosticForOption(/*onKey*/ false, option1, /*option2*/ undefined, message, arg0); + } + function createDiagnosticForReference(sourceFile: JsonSourceFile | undefined, index: number, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number) { + const referencesSyntax = firstDefined(getTsConfigPropArray(sourceFile || options.configFile, "references"), property => isArrayLiteralExpression(property.initializer) ? property.initializer : undefined); + if (referencesSyntax && referencesSyntax.elements.length > index) { + programDiagnostics.add(createDiagnosticForNodeInSourceFile(sourceFile || options.configFile!, referencesSyntax.elements[index], message, arg0, arg1)); } - - function createDiagnosticForReference(sourceFile: JsonSourceFile | undefined, index: number, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number) { - const referencesSyntax = firstDefined(getTsConfigPropArray(sourceFile || options.configFile, "references"), - property => isArrayLiteralExpression(property.initializer) ? property.initializer : undefined); - if (referencesSyntax && referencesSyntax.elements.length > index) { - programDiagnostics.add(createDiagnosticForNodeInSourceFile(sourceFile || options.configFile!, referencesSyntax.elements[index], message, arg0, arg1)); - } - else { - programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1)); - } - } - - function createDiagnosticForOption(onKey: boolean, option1: string, option2: string | undefined, message: DiagnosticMessage, arg0: string | number, arg1?: string | number, arg2?: string | number) { - const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax(); - const needCompilerDiagnostic = !compilerOptionsObjectLiteralSyntax || - !createOptionDiagnosticInObjectLiteralSyntax(compilerOptionsObjectLiteralSyntax, onKey, option1, option2, message, arg0, arg1, arg2); - - if (needCompilerDiagnostic) { - programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1, arg2)); - } - } - - function getCompilerOptionsObjectLiteralSyntax() { - if (_compilerOptionsObjectLiteralSyntax === undefined) { - _compilerOptionsObjectLiteralSyntax = null; // eslint-disable-line no-null/no-null - const jsonObjectLiteral = getTsConfigObjectLiteralExpression(options.configFile); - if (jsonObjectLiteral) { - for (const prop of getPropertyAssignment(jsonObjectLiteral, "compilerOptions")) { - if (isObjectLiteralExpression(prop.initializer)) { - _compilerOptionsObjectLiteralSyntax = prop.initializer; - break; - } + else { + programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1)); + } + } + function createDiagnosticForOption(onKey: boolean, option1: string, option2: string | undefined, message: DiagnosticMessage, arg0: string | number, arg1?: string | number, arg2?: string | number) { + const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax(); + const needCompilerDiagnostic = !compilerOptionsObjectLiteralSyntax || + !createOptionDiagnosticInObjectLiteralSyntax(compilerOptionsObjectLiteralSyntax, onKey, option1, option2, message, arg0, arg1, arg2); + if (needCompilerDiagnostic) { + programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1, arg2)); + } + } + function getCompilerOptionsObjectLiteralSyntax() { + if (_compilerOptionsObjectLiteralSyntax === undefined) { + _compilerOptionsObjectLiteralSyntax = null; // eslint-disable-line no-null/no-null + const jsonObjectLiteral = getTsConfigObjectLiteralExpression(options.configFile); + if (jsonObjectLiteral) { + for (const prop of getPropertyAssignment(jsonObjectLiteral, "compilerOptions")) { + if (isObjectLiteralExpression(prop.initializer)) { + _compilerOptionsObjectLiteralSyntax = prop.initializer; + break; } } } - return _compilerOptionsObjectLiteralSyntax; } - - function createOptionDiagnosticInObjectLiteralSyntax(objectLiteral: ObjectLiteralExpression, onKey: boolean, key1: string, key2: string | undefined, message: DiagnosticMessage, arg0: string | number, arg1?: string | number, arg2?: string | number): boolean { - const props = getPropertyAssignment(objectLiteral, key1, key2); - for (const prop of props) { - programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, onKey ? prop.name : prop.initializer, message, arg0, arg1, arg2)); - } - return !!props.length; + return _compilerOptionsObjectLiteralSyntax; + } + function createOptionDiagnosticInObjectLiteralSyntax(objectLiteral: ObjectLiteralExpression, onKey: boolean, key1: string, key2: string | undefined, message: DiagnosticMessage, arg0: string | number, arg1?: string | number, arg2?: string | number): boolean { + const props = getPropertyAssignment(objectLiteral, key1, key2); + for (const prop of props) { + programDiagnostics.add(createDiagnosticForNodeInSourceFile((options.configFile!), onKey ? prop.name : prop.initializer, message, arg0, arg1, arg2)); } - - function blockEmittingOfFile(emitFileName: string, diag: Diagnostic) { - hasEmitBlockingDiagnostics.set(toPath(emitFileName), true); - programDiagnostics.add(diag); + return !!props.length; + } + function blockEmittingOfFile(emitFileName: string, diag: Diagnostic) { + hasEmitBlockingDiagnostics.set(toPath(emitFileName), true); + programDiagnostics.add(diag); + } + function isEmittedFile(file: string): boolean { + if (options.noEmit) { + return false; } - - function isEmittedFile(file: string): boolean { - if (options.noEmit) { - return false; - } - - // If this is source file, its not emitted file - const filePath = toPath(file); - if (getSourceFileByPath(filePath)) { - return false; - } - - // If options have --outFile or --out just check that - const out = options.outFile || options.out; - if (out) { - return isSameFile(filePath, out) || isSameFile(filePath, removeFileExtension(out) + Extension.Dts); - } - - // If declarationDir is specified, return if its a file in that directory - if (options.declarationDir && containsPath(options.declarationDir, filePath, currentDirectory, !host.useCaseSensitiveFileNames())) { - return true; - } - - // If --outDir, check if file is in that directory - if (options.outDir) { - return containsPath(options.outDir, filePath, currentDirectory, !host.useCaseSensitiveFileNames()); - } - - if (fileExtensionIsOneOf(filePath, supportedJSExtensions) || fileExtensionIs(filePath, Extension.Dts)) { - // Otherwise just check if sourceFile with the name exists - const filePathWithoutExtension = removeFileExtension(filePath); - return !!getSourceFileByPath((filePathWithoutExtension + Extension.Ts) as Path) || - !!getSourceFileByPath((filePathWithoutExtension + Extension.Tsx) as Path); - } + // If this is source file, its not emitted file + const filePath = toPath(file); + if (getSourceFileByPath(filePath)) { return false; } - - function isSameFile(file1: string, file2: string) { - return comparePaths(file1, file2, currentDirectory, !host.useCaseSensitiveFileNames()) === Comparison.EqualTo; + // If options have --outFile or --out just check that + const out = options.outFile || options.out; + if (out) { + return isSameFile(filePath, out) || isSameFile(filePath, removeFileExtension(out) + Extension.Dts); } - - function getProbableSymlinks(): ReadonlyMap { - if (host.getSymlinks) { - return host.getSymlinks(); - } - return symlinks || (symlinks = discoverProbableSymlinks( - files, - getCanonicalFileName, - host.getCurrentDirectory())); + // If declarationDir is specified, return if its a file in that directory + if (options.declarationDir && containsPath(options.declarationDir, filePath, currentDirectory, !host.useCaseSensitiveFileNames())) { + return true; } - } - - /*@internal*/ - export function handleNoEmitOptions(program: ProgramToEmitFilesAndReportErrors, sourceFile: SourceFile | undefined, cancellationToken: CancellationToken | undefined): EmitResult | undefined { - const options = program.getCompilerOptions(); - if (options.noEmit) { - return { diagnostics: emptyArray, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true }; - } - - // If the noEmitOnError flag is set, then check if we have any errors so far. If so, - // immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we - // get any preEmit diagnostics, not just the ones - if (!options.noEmitOnError) return undefined; - let diagnostics: readonly Diagnostic[] = [ - ...program.getOptionsDiagnostics(cancellationToken), - ...program.getSyntacticDiagnostics(sourceFile, cancellationToken), - ...program.getGlobalDiagnostics(cancellationToken), - ...program.getSemanticDiagnostics(sourceFile, cancellationToken) - ]; - - if (diagnostics.length === 0 && getEmitDeclarations(program.getCompilerOptions())) { - diagnostics = program.getDeclarationDiagnostics(/*sourceFile*/ undefined, cancellationToken); - } - - return diagnostics.length > 0 ? - { diagnostics, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true } : - undefined; - } - - /*@internal*/ - interface CompilerHostLike { - useCaseSensitiveFileNames(): boolean; - getCurrentDirectory(): string; - fileExists(fileName: string): boolean; - readFile(fileName: string): string | undefined; - readDirectory?(rootDir: string, extensions: readonly string[], excludes: readonly string[] | undefined, includes: readonly string[], depth?: number): string[]; - trace?(s: string): void; - onUnRecoverableConfigFileDiagnostic?: DiagnosticReporter; - } - - /* @internal */ - export function parseConfigHostFromCompilerHostLike(host: CompilerHostLike, directoryStructureHost: DirectoryStructureHost = host): ParseConfigFileHost { - return { - fileExists: f => directoryStructureHost.fileExists(f), - readDirectory(root, extensions, excludes, includes, depth) { - Debug.assertDefined(directoryStructureHost.readDirectory, "'CompilerHost.readDirectory' must be implemented to correctly process 'projectReferences'"); - return directoryStructureHost.readDirectory!(root, extensions, excludes, includes, depth); - }, - readFile: f => directoryStructureHost.readFile(f), - useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(), - getCurrentDirectory: () => host.getCurrentDirectory(), - onUnRecoverableConfigFileDiagnostic: host.onUnRecoverableConfigFileDiagnostic || returnUndefined, - trace: host.trace ? (s) => host.trace!(s) : undefined - }; - } - - // For backward compatibility - /** @deprecated */ export interface ResolveProjectReferencePathHost { - fileExists(fileName: string): boolean; - } - - /* @internal */ - export function createPrependNodes(projectReferences: readonly ProjectReference[] | undefined, getCommandLine: (ref: ProjectReference, index: number) => ParsedCommandLine | undefined, readFile: (path: string) => string | undefined) { - if (!projectReferences) return emptyArray; - let nodes: InputFiles[] | undefined; - for (let i = 0; i < projectReferences.length; i++) { - const ref = projectReferences[i]; - const resolvedRefOpts = getCommandLine(ref, i); - if (ref.prepend && resolvedRefOpts && resolvedRefOpts.options) { - const out = resolvedRefOpts.options.outFile || resolvedRefOpts.options.out; - // Upstream project didn't have outFile set -- skip (error will have been issued earlier) - if (!out) continue; - - const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(resolvedRefOpts.options, /*forceDtsPaths*/ true); - const node = createInputFiles(readFile, jsFilePath!, sourceMapFilePath, declarationFilePath!, declarationMapPath, buildInfoPath); - (nodes || (nodes = [])).push(node); - } - } - return nodes || emptyArray; - } - /** - * Returns the target config filename of a project reference. - * Note: The file might not exist. - */ - export function resolveProjectReferencePath(ref: ProjectReference): ResolvedConfigFileName; - /** @deprecated */ export function resolveProjectReferencePath(host: ResolveProjectReferencePathHost, ref: ProjectReference): ResolvedConfigFileName; - export function resolveProjectReferencePath(hostOrRef: ResolveProjectReferencePathHost | ProjectReference, ref?: ProjectReference): ResolvedConfigFileName { - const passedInRef = ref ? ref : hostOrRef as ProjectReference; - return resolveConfigFileProjectName(passedInRef.path); - } - - /* @internal */ - /** - * Returns a DiagnosticMessage if we won't include a resolved module due to its extension. - * The DiagnosticMessage's parameters are the imported module name, and the filename it resolved to. - * This returns a diagnostic even if the module will be an untyped module. - */ - export function getResolutionDiagnostic(options: CompilerOptions, { extension }: ResolvedModuleFull): DiagnosticMessage | undefined { - switch (extension) { - case Extension.Ts: - case Extension.Dts: - // These are always allowed. - return undefined; - case Extension.Tsx: - return needJsx(); - case Extension.Jsx: - return needJsx() || needAllowJs(); - case Extension.Js: - return needAllowJs(); - case Extension.Json: - return needResolveJsonModule(); + // If --outDir, check if file is in that directory + if (options.outDir) { + return containsPath(options.outDir, filePath, currentDirectory, !host.useCaseSensitiveFileNames()); } - - function needJsx() { - return options.jsx ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_jsx_is_not_set; + if (fileExtensionIsOneOf(filePath, supportedJSExtensions) || fileExtensionIs(filePath, Extension.Dts)) { + // Otherwise just check if sourceFile with the name exists + const filePathWithoutExtension = removeFileExtension(filePath); + return !!getSourceFileByPath(((filePathWithoutExtension + Extension.Ts) as Path)) || + !!getSourceFileByPath(((filePathWithoutExtension + Extension.Tsx) as Path)); } - function needAllowJs() { - return options.allowJs || !getStrictOptionValue(options, "noImplicitAny") ? undefined : Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type; + return false; + } + function isSameFile(file1: string, file2: string) { + return comparePaths(file1, file2, currentDirectory, !host.useCaseSensitiveFileNames()) === Comparison.EqualTo; + } + function getProbableSymlinks(): ts.ReadonlyMap { + if (host.getSymlinks) { + return host.getSymlinks(); } - function needResolveJsonModule() { - return options.resolveJsonModule ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_resolveJsonModule_is_not_used; + return symlinks || (symlinks = discoverProbableSymlinks(files, getCanonicalFileName, host.getCurrentDirectory())); + } +} +/*@internal*/ +export function handleNoEmitOptions(program: ProgramToEmitFilesAndReportErrors, sourceFile: SourceFile | undefined, cancellationToken: CancellationToken | undefined): EmitResult | undefined { + const options = program.getCompilerOptions(); + if (options.noEmit) { + return { diagnostics: emptyArray, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true }; + } + // If the noEmitOnError flag is set, then check if we have any errors so far. If so, + // immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we + // get any preEmit diagnostics, not just the ones + if (!options.noEmitOnError) + return undefined; + let diagnostics: readonly Diagnostic[] = [ + ...program.getOptionsDiagnostics(cancellationToken), + ...program.getSyntacticDiagnostics(sourceFile, cancellationToken), + ...program.getGlobalDiagnostics(cancellationToken), + ...program.getSemanticDiagnostics(sourceFile, cancellationToken) + ]; + if (diagnostics.length === 0 && getEmitDeclarations(program.getCompilerOptions())) { + diagnostics = program.getDeclarationDiagnostics(/*sourceFile*/ undefined, cancellationToken); + } + return diagnostics.length > 0 ? + { diagnostics, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true } : + undefined; +} +/*@internal*/ +interface CompilerHostLike { + useCaseSensitiveFileNames(): boolean; + getCurrentDirectory(): string; + fileExists(fileName: string): boolean; + readFile(fileName: string): string | undefined; + readDirectory?(rootDir: string, extensions: readonly string[], excludes: readonly string[] | undefined, includes: readonly string[], depth?: number): string[]; + trace?(s: string): void; + onUnRecoverableConfigFileDiagnostic?: DiagnosticReporter; +} +/* @internal */ +export function parseConfigHostFromCompilerHostLike(host: CompilerHostLike, directoryStructureHost: DirectoryStructureHost = host): ParseConfigFileHost { + return { + fileExists: f => directoryStructureHost.fileExists(f), + readDirectory(root, extensions, excludes, includes, depth) { + Debug.assertDefined(directoryStructureHost.readDirectory, "'CompilerHost.readDirectory' must be implemented to correctly process 'projectReferences'"); + return directoryStructureHost.readDirectory!(root, extensions, excludes, includes, depth); + }, + readFile: f => directoryStructureHost.readFile(f), + useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(), + getCurrentDirectory: () => host.getCurrentDirectory(), + onUnRecoverableConfigFileDiagnostic: host.onUnRecoverableConfigFileDiagnostic || returnUndefined, + trace: host.trace ? (s) => host.trace!(s) : undefined + }; +} +// For backward compatibility +/** @deprecated */ export interface ResolveProjectReferencePathHost { + fileExists(fileName: string): boolean; +} +/* @internal */ +export function createPrependNodes(projectReferences: readonly ProjectReference[] | undefined, getCommandLine: (ref: ProjectReference, index: number) => ParsedCommandLine | undefined, readFile: (path: string) => string | undefined) { + if (!projectReferences) + return emptyArray; + let nodes: InputFiles[] | undefined; + for (let i = 0; i < projectReferences.length; i++) { + const ref = projectReferences[i]; + const resolvedRefOpts = getCommandLine(ref, i); + if (ref.prepend && resolvedRefOpts && resolvedRefOpts.options) { + const out = resolvedRefOpts.options.outFile || resolvedRefOpts.options.out; + // Upstream project didn't have outFile set -- skip (error will have been issued earlier) + if (!out) + continue; + const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(resolvedRefOpts.options, /*forceDtsPaths*/ true); + const node = createInputFiles(readFile, (jsFilePath!), sourceMapFilePath, (declarationFilePath!), declarationMapPath, buildInfoPath); + (nodes || (nodes = [])).push(node); } } - - function getModuleNames({ imports, moduleAugmentations }: SourceFile): string[] { - const res = imports.map(i => i.text); - for (const aug of moduleAugmentations) { - if (aug.kind === SyntaxKind.StringLiteral) { - res.push(aug.text); - } - // Do nothing if it's an Identifier; we don't need to do module resolution for `declare global`. + return nodes || emptyArray; +} +/** + * Returns the target config filename of a project reference. + * Note: The file might not exist. + */ +export function resolveProjectReferencePath(ref: ProjectReference): ResolvedConfigFileName; +/** @deprecated */ export function resolveProjectReferencePath(host: ResolveProjectReferencePathHost, ref: ProjectReference): ResolvedConfigFileName; +export function resolveProjectReferencePath(hostOrRef: ResolveProjectReferencePathHost | ProjectReference, ref?: ProjectReference): ResolvedConfigFileName { + const passedInRef = ref ? ref : hostOrRef as ProjectReference; + return resolveConfigFileProjectName(passedInRef.path); +} +/* @internal */ +/** + * Returns a DiagnosticMessage if we won't include a resolved module due to its extension. + * The DiagnosticMessage's parameters are the imported module name, and the filename it resolved to. + * This returns a diagnostic even if the module will be an untyped module. + */ +export function getResolutionDiagnostic(options: CompilerOptions, { extension }: ResolvedModuleFull): DiagnosticMessage | undefined { + switch (extension) { + case Extension.Ts: + case Extension.Dts: + // These are always allowed. + return undefined; + case Extension.Tsx: + return needJsx(); + case Extension.Jsx: + return needJsx() || needAllowJs(); + case Extension.Js: + return needAllowJs(); + case Extension.Json: + return needResolveJsonModule(); + } + function needJsx() { + return options.jsx ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_jsx_is_not_set; + } + function needAllowJs() { + return options.allowJs || !getStrictOptionValue(options, "noImplicitAny") ? undefined : Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type; + } + function needResolveJsonModule() { + return options.resolveJsonModule ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_resolveJsonModule_is_not_used; + } +} +function getModuleNames({ imports, moduleAugmentations }: SourceFile): string[] { + const res = imports.map(i => i.text); + for (const aug of moduleAugmentations) { + if (aug.kind === SyntaxKind.StringLiteral) { + res.push(aug.text); } - return res; + // Do nothing if it's an Identifier; we don't need to do module resolution for `declare global`. } + return res; } diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 5af7919b6d620..163ef36b0244e 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -1,845 +1,718 @@ +import { Path, ResolvedProjectReference, ResolvedModuleFull, ResolvedTypeReferenceDirective, HasInvalidatedResolution, ResolvedModuleWithFailedLookupLocations, ResolvedTypeReferenceDirectiveWithFailedLookupLocations, ModuleResolutionHost, GetCanonicalFileName, CompilerOptions, DirectoryWatcherCallback, WatchDirectoryFlags, FileWatcher, CachedDirectoryStructureHost, Program, some, ignoredPaths, stringContains, getRootLength, directorySeparator, CharacterCodes, createMultiMap, memoize, createMap, CacheWithRedirects, createCacheWithRedirects, PerModuleNameCache, createModuleResolutionCacheWithMaps, Extension, removeTrailingDirectorySeparator, getNormalizedAbsolutePath, startsWith, clearMap, closeFileWatcherOf, returnTrue, isExternalModuleNameRelative, extensionIsTS, loadModuleFromGlobalCache, Debug, addRange, getDirectoryPath, contains, resolveTypeReferenceDirective, resolutionExtensionIsTSOrJson, endsWith, isRootedDiskPath, normalizePath, pathContainsNodeModules, fileExtensionIsOneOf, fileExtensionIs, inferredTypesContainingFile, isEmittedFileOfProgram, closeFileWatcher, getEffectiveTypeRoots, mutateMap, arrayToMap } from "./ts"; +import * as ts from "./ts"; /*@internal*/ -namespace ts { - /** This is the cache of module/typedirectives resolution that can be retained across program */ - export interface ResolutionCache { - startRecordingFilesWithChangedResolutions(): void; - finishRecordingFilesWithChangedResolutions(): Path[] | undefined; - - resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference): (ResolvedModuleFull | undefined)[]; - getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): CachedResolvedModuleWithFailedLookupLocations | undefined; - resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[]; - - invalidateResolutionOfFile(filePath: Path): void; - removeResolutionsOfFile(filePath: Path): void; - removeResolutionsFromProjectReferenceRedirects(filePath: Path): void; - setFilesWithInvalidatedNonRelativeUnresolvedImports(filesWithUnresolvedImports: Map): void; - createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution; - - startCachingPerDirectoryResolution(): void; - finishCachingPerDirectoryResolution(): void; - - updateTypeRootsWatch(): void; - closeTypeRootsWatch(): void; - - clear(): void; - } - - interface ResolutionWithFailedLookupLocations { - readonly failedLookupLocations: readonly string[]; - isInvalidated?: boolean; - refCount?: number; - } - - interface ResolutionWithResolvedFileName { - resolvedFileName: string | undefined; - } - - interface CachedResolvedModuleWithFailedLookupLocations extends ResolvedModuleWithFailedLookupLocations, ResolutionWithFailedLookupLocations { - } - - interface CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations extends ResolvedTypeReferenceDirectiveWithFailedLookupLocations, ResolutionWithFailedLookupLocations { - } - - export interface ResolutionCacheHost extends ModuleResolutionHost { - toPath(fileName: string): Path; - getCanonicalFileName: GetCanonicalFileName; - getCompilationSettings(): CompilerOptions; - watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; - onInvalidatedResolution(): void; - watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; - onChangedAutomaticTypeDirectiveNames(): void; - getCachedDirectoryStructureHost(): CachedDirectoryStructureHost | undefined; - projectName?: string; - getGlobalCache?(): string | undefined; - globalCacheResolutionModuleName?(externalModuleName: string): string; - writeLog(s: string): void; - maxNumberOfFilesToIterateForInvalidation?: number; - getCurrentProgram(): Program | undefined; - fileIsOpen(filePath: Path): boolean; - } - - interface DirectoryWatchesOfFailedLookup { - /** watcher for the directory of failed lookup */ - watcher: FileWatcher; - /** ref count keeping this directory watch alive */ - refCount: number; - /** is the directory watched being non recursive */ - nonRecursive?: boolean; - } - - interface DirectoryOfFailedLookupWatch { - dir: string; - dirPath: Path; - nonRecursive?: boolean; - } - - export function isPathIgnored(path: Path) { - return some(ignoredPaths, searchPath => stringContains(path, searchPath)); - } - - /** - * Filter out paths like - * "/", "/user", "/user/username", "/user/username/folderAtRoot", - * "c:/", "c:/users", "c:/users/username", "c:/users/username/folderAtRoot", "c:/folderAtRoot" - * @param dirPath - */ - export function canWatchDirectory(dirPath: Path) { - const rootLength = getRootLength(dirPath); - if (dirPath.length === rootLength) { - // Ignore "/", "c:/" +/** This is the cache of module/typedirectives resolution that can be retained across program */ +export interface ResolutionCache { + startRecordingFilesWithChangedResolutions(): void; + finishRecordingFilesWithChangedResolutions(): Path[] | undefined; + resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference): (ResolvedModuleFull | undefined)[]; + getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): CachedResolvedModuleWithFailedLookupLocations | undefined; + resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[]; + invalidateResolutionOfFile(filePath: Path): void; + removeResolutionsOfFile(filePath: Path): void; + removeResolutionsFromProjectReferenceRedirects(filePath: Path): void; + setFilesWithInvalidatedNonRelativeUnresolvedImports(filesWithUnresolvedImports: ts.Map): void; + createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution; + startCachingPerDirectoryResolution(): void; + finishCachingPerDirectoryResolution(): void; + updateTypeRootsWatch(): void; + closeTypeRootsWatch(): void; + clear(): void; +} +/* @internal */ +interface ResolutionWithFailedLookupLocations { + readonly failedLookupLocations: readonly string[]; + isInvalidated?: boolean; + refCount?: number; +} +/* @internal */ +interface ResolutionWithResolvedFileName { + resolvedFileName: string | undefined; +} +/* @internal */ +interface CachedResolvedModuleWithFailedLookupLocations extends ResolvedModuleWithFailedLookupLocations, ResolutionWithFailedLookupLocations { +} +/* @internal */ +interface CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations extends ResolvedTypeReferenceDirectiveWithFailedLookupLocations, ResolutionWithFailedLookupLocations { +} +/* @internal */ +export interface ResolutionCacheHost extends ModuleResolutionHost { + toPath(fileName: string): Path; + getCanonicalFileName: GetCanonicalFileName; + getCompilationSettings(): CompilerOptions; + watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; + onInvalidatedResolution(): void; + watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; + onChangedAutomaticTypeDirectiveNames(): void; + getCachedDirectoryStructureHost(): CachedDirectoryStructureHost | undefined; + projectName?: string; + getGlobalCache?(): string | undefined; + globalCacheResolutionModuleName?(externalModuleName: string): string; + writeLog(s: string): void; + maxNumberOfFilesToIterateForInvalidation?: number; + getCurrentProgram(): Program | undefined; + fileIsOpen(filePath: Path): boolean; +} +/* @internal */ +interface DirectoryWatchesOfFailedLookup { + /** watcher for the directory of failed lookup */ + watcher: FileWatcher; + /** ref count keeping this directory watch alive */ + refCount: number; + /** is the directory watched being non recursive */ + nonRecursive?: boolean; +} +/* @internal */ +interface DirectoryOfFailedLookupWatch { + dir: string; + dirPath: Path; + nonRecursive?: boolean; +} +/* @internal */ +export function isPathIgnored(path: Path) { + return some(ignoredPaths, searchPath => stringContains(path, searchPath)); +} +/** + * Filter out paths like + * "/", "/user", "/user/username", "/user/username/folderAtRoot", + * "c:/", "c:/users", "c:/users/username", "c:/users/username/folderAtRoot", "c:/folderAtRoot" + * @param dirPath + */ +/* @internal */ +export function canWatchDirectory(dirPath: Path) { + const rootLength = getRootLength(dirPath); + if (dirPath.length === rootLength) { + // Ignore "/", "c:/" + return false; + } + let nextDirectorySeparator = dirPath.indexOf(directorySeparator, rootLength); + if (nextDirectorySeparator === -1) { + // ignore "/user", "c:/users" or "c:/folderAtRoot" + return false; + } + let pathPartForUserCheck = dirPath.substring(rootLength, nextDirectorySeparator + 1); + const isNonDirectorySeparatorRoot = rootLength > 1 || dirPath.charCodeAt(0) !== CharacterCodes.slash; + if (isNonDirectorySeparatorRoot && + dirPath.search(/[a-zA-Z]:/) !== 0 && // Non dos style paths + pathPartForUserCheck.search(/[a-zA-z]\$\//) === 0) { // Dos style nextPart + nextDirectorySeparator = dirPath.indexOf(directorySeparator, nextDirectorySeparator + 1); + if (nextDirectorySeparator === -1) { + // ignore "//vda1cs4850/c$/folderAtRoot" return false; } - - let nextDirectorySeparator = dirPath.indexOf(directorySeparator, rootLength); - if (nextDirectorySeparator === -1) { - // ignore "/user", "c:/users" or "c:/folderAtRoot" + pathPartForUserCheck = dirPath.substring(rootLength + pathPartForUserCheck.length, nextDirectorySeparator + 1); + } + if (isNonDirectorySeparatorRoot && + pathPartForUserCheck.search(/users\//i) !== 0) { + // Paths like c:/folderAtRoot/subFolder are allowed + return true; + } + for (let searchIndex = nextDirectorySeparator + 1, searchLevels = 2; searchLevels > 0; searchLevels--) { + searchIndex = dirPath.indexOf(directorySeparator, searchIndex) + 1; + if (searchIndex === 0) { + // Folder isnt at expected minimum levels return false; } - - let pathPartForUserCheck = dirPath.substring(rootLength, nextDirectorySeparator + 1); - const isNonDirectorySeparatorRoot = rootLength > 1 || dirPath.charCodeAt(0) !== CharacterCodes.slash; - if (isNonDirectorySeparatorRoot && - dirPath.search(/[a-zA-Z]:/) !== 0 && // Non dos style paths - pathPartForUserCheck.search(/[a-zA-z]\$\//) === 0) { // Dos style nextPart - nextDirectorySeparator = dirPath.indexOf(directorySeparator, nextDirectorySeparator + 1); - if (nextDirectorySeparator === -1) { - // ignore "//vda1cs4850/c$/folderAtRoot" - return false; - } - - pathPartForUserCheck = dirPath.substring(rootLength + pathPartForUserCheck.length, nextDirectorySeparator + 1); - } - - if (isNonDirectorySeparatorRoot && - pathPartForUserCheck.search(/users\//i) !== 0) { - // Paths like c:/folderAtRoot/subFolder are allowed - return true; - } - - for (let searchIndex = nextDirectorySeparator + 1, searchLevels = 2; searchLevels > 0; searchLevels--) { - searchIndex = dirPath.indexOf(directorySeparator, searchIndex) + 1; - if (searchIndex === 0) { - // Folder isnt at expected minimum levels - return false; - } + } + return true; +} +/* @internal */ +export const maxNumberOfFilesToIterateForInvalidation = 256; +/* @internal */ +type GetResolutionWithResolvedFileName = (resolution: T) => R | undefined; +/* @internal */ +export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string | undefined, logChangesWhenResolvingModule: boolean): ResolutionCache { + let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; + let filesWithInvalidatedResolutions: ts.Map | undefined; + let filesWithInvalidatedNonRelativeUnresolvedImports: ts.ReadonlyMap | undefined; + let allFilesHaveInvalidatedResolution = false; + const nonRelativeExternalModuleResolutions = createMultiMap(); + const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory!()); // TODO: GH#18217 + const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost(); + // The resolvedModuleNames and resolvedTypeReferenceDirectives are the cache of resolutions per file. + // The key in the map is source file's path. + // The values are Map of resolutions with key being name lookedup. + const resolvedModuleNames = createMap>(); + const perDirectoryResolvedModuleNames: CacheWithRedirects> = createCacheWithRedirects(); + const nonRelativeModuleNameCache: CacheWithRedirects = createCacheWithRedirects(); + const moduleResolutionCache = createModuleResolutionCacheWithMaps(perDirectoryResolvedModuleNames, nonRelativeModuleNameCache, getCurrentDirectory(), resolutionHost.getCanonicalFileName); + const resolvedTypeReferenceDirectives = createMap>(); + const perDirectoryResolvedTypeReferenceDirectives: CacheWithRedirects> = createCacheWithRedirects(); + /** + * These are the extensions that failed lookup files will have by default, + * any other extension of failed lookup will be store that path in custom failed lookup path + * This helps in not having to comb through all resolutions when files are added/removed + * Note that .d.ts file also has .d.ts extension hence will be part of default extensions + */ + const failedLookupDefaultExtensions = [Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx, Extension.Json]; + const customFailedLookupPaths = createMap(); + const directoryWatchesOfFailedLookups = createMap(); + const rootDir = rootDirForResolution && removeTrailingDirectorySeparator(getNormalizedAbsolutePath(rootDirForResolution, getCurrentDirectory())); + const rootPath = ((rootDir && resolutionHost.toPath(rootDir)) as Path); // TODO: GH#18217 + // TypeRoot watches for the types that get added as part of getAutomaticTypeDirectiveNames + const typeRootsWatches = createMap(); + return { + startRecordingFilesWithChangedResolutions, + finishRecordingFilesWithChangedResolutions, + // perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update + // (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution) + startCachingPerDirectoryResolution: clearPerDirectoryResolutions, + finishCachingPerDirectoryResolution, + resolveModuleNames, + getResolvedModuleWithFailedLookupLocationsFromCache, + resolveTypeReferenceDirectives, + removeResolutionsFromProjectReferenceRedirects, + removeResolutionsOfFile, + invalidateResolutionOfFile, + setFilesWithInvalidatedNonRelativeUnresolvedImports, + createHasInvalidatedResolution, + updateTypeRootsWatch, + closeTypeRootsWatch, + clear + }; + function getResolvedModule(resolution: CachedResolvedModuleWithFailedLookupLocations) { + return resolution.resolvedModule; + } + function getResolvedTypeReferenceDirective(resolution: CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations) { + return resolution.resolvedTypeReferenceDirective; + } + function isInDirectoryPath(dir: Path | undefined, file: Path) { + if (dir === undefined || file.length <= dir.length) { + return false; } - return true; + return startsWith(file, dir) && file[dir.length] === directorySeparator; } - - export const maxNumberOfFilesToIterateForInvalidation = 256; - - type GetResolutionWithResolvedFileName = - (resolution: T) => R | undefined; - - export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string | undefined, logChangesWhenResolvingModule: boolean): ResolutionCache { - let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; - let filesWithInvalidatedResolutions: Map | undefined; - let filesWithInvalidatedNonRelativeUnresolvedImports: ReadonlyMap | undefined; - let allFilesHaveInvalidatedResolution = false; - const nonRelativeExternalModuleResolutions = createMultiMap(); - - const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory!()); // TODO: GH#18217 - const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost(); - - // The resolvedModuleNames and resolvedTypeReferenceDirectives are the cache of resolutions per file. - // The key in the map is source file's path. - // The values are Map of resolutions with key being name lookedup. - const resolvedModuleNames = createMap>(); - const perDirectoryResolvedModuleNames: CacheWithRedirects> = createCacheWithRedirects(); - const nonRelativeModuleNameCache: CacheWithRedirects = createCacheWithRedirects(); - const moduleResolutionCache = createModuleResolutionCacheWithMaps( - perDirectoryResolvedModuleNames, - nonRelativeModuleNameCache, - getCurrentDirectory(), - resolutionHost.getCanonicalFileName - ); - - const resolvedTypeReferenceDirectives = createMap>(); - const perDirectoryResolvedTypeReferenceDirectives: CacheWithRedirects> = createCacheWithRedirects(); - - /** - * These are the extensions that failed lookup files will have by default, - * any other extension of failed lookup will be store that path in custom failed lookup path - * This helps in not having to comb through all resolutions when files are added/removed - * Note that .d.ts file also has .d.ts extension hence will be part of default extensions - */ - const failedLookupDefaultExtensions = [Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx, Extension.Json]; - const customFailedLookupPaths = createMap(); - - const directoryWatchesOfFailedLookups = createMap(); - const rootDir = rootDirForResolution && removeTrailingDirectorySeparator(getNormalizedAbsolutePath(rootDirForResolution, getCurrentDirectory())); - const rootPath = (rootDir && resolutionHost.toPath(rootDir)) as Path; // TODO: GH#18217 - - // TypeRoot watches for the types that get added as part of getAutomaticTypeDirectiveNames - const typeRootsWatches = createMap(); - - return { - startRecordingFilesWithChangedResolutions, - finishRecordingFilesWithChangedResolutions, - // perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update - // (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution) - startCachingPerDirectoryResolution: clearPerDirectoryResolutions, - finishCachingPerDirectoryResolution, - resolveModuleNames, - getResolvedModuleWithFailedLookupLocationsFromCache, - resolveTypeReferenceDirectives, - removeResolutionsFromProjectReferenceRedirects, - removeResolutionsOfFile, - invalidateResolutionOfFile, - setFilesWithInvalidatedNonRelativeUnresolvedImports, - createHasInvalidatedResolution, - updateTypeRootsWatch, - closeTypeRootsWatch, - clear - }; - - function getResolvedModule(resolution: CachedResolvedModuleWithFailedLookupLocations) { - return resolution.resolvedModule; - } - - function getResolvedTypeReferenceDirective(resolution: CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations) { - return resolution.resolvedTypeReferenceDirective; - } - - function isInDirectoryPath(dir: Path | undefined, file: Path) { - if (dir === undefined || file.length <= dir.length) { - return false; - } - return startsWith(file, dir) && file[dir.length] === directorySeparator; + function clear() { + clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf); + customFailedLookupPaths.clear(); + nonRelativeExternalModuleResolutions.clear(); + closeTypeRootsWatch(); + resolvedModuleNames.clear(); + resolvedTypeReferenceDirectives.clear(); + allFilesHaveInvalidatedResolution = false; + // perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update + // (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution) + clearPerDirectoryResolutions(); + } + function startRecordingFilesWithChangedResolutions() { + filesWithChangedSetOfUnresolvedImports = []; + } + function finishRecordingFilesWithChangedResolutions() { + const collected = filesWithChangedSetOfUnresolvedImports; + filesWithChangedSetOfUnresolvedImports = undefined; + return collected; + } + function isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path): boolean { + if (!filesWithInvalidatedNonRelativeUnresolvedImports) { + return false; } - - function clear() { - clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf); - customFailedLookupPaths.clear(); - nonRelativeExternalModuleResolutions.clear(); - closeTypeRootsWatch(); - resolvedModuleNames.clear(); - resolvedTypeReferenceDirectives.clear(); - allFilesHaveInvalidatedResolution = false; - // perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update - // (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution) - clearPerDirectoryResolutions(); - } - - function startRecordingFilesWithChangedResolutions() { - filesWithChangedSetOfUnresolvedImports = []; - } - - function finishRecordingFilesWithChangedResolutions() { - const collected = filesWithChangedSetOfUnresolvedImports; - filesWithChangedSetOfUnresolvedImports = undefined; - return collected; - } - - function isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path): boolean { - if (!filesWithInvalidatedNonRelativeUnresolvedImports) { - return false; - } - - // Invalidated if file has unresolved imports - const value = filesWithInvalidatedNonRelativeUnresolvedImports.get(path); - return !!value && !!value.length; - } - - function createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution { - if (allFilesHaveInvalidatedResolution || forceAllFilesAsInvalidated) { - // Any file asked would have invalidated resolution - filesWithInvalidatedResolutions = undefined; - return returnTrue; - } - const collected = filesWithInvalidatedResolutions; + // Invalidated if file has unresolved imports + const value = filesWithInvalidatedNonRelativeUnresolvedImports.get(path); + return !!value && !!value.length; + } + function createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution { + if (allFilesHaveInvalidatedResolution || forceAllFilesAsInvalidated) { + // Any file asked would have invalidated resolution filesWithInvalidatedResolutions = undefined; - return path => (!!collected && collected.has(path)) || - isFileWithInvalidatedNonRelativeUnresolvedImports(path); - } - - function clearPerDirectoryResolutions() { - perDirectoryResolvedModuleNames.clear(); - nonRelativeModuleNameCache.clear(); - perDirectoryResolvedTypeReferenceDirectives.clear(); - nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions); - nonRelativeExternalModuleResolutions.clear(); - } - - function finishCachingPerDirectoryResolution() { - allFilesHaveInvalidatedResolution = false; - filesWithInvalidatedNonRelativeUnresolvedImports = undefined; - clearPerDirectoryResolutions(); - directoryWatchesOfFailedLookups.forEach((watcher, path) => { - if (watcher.refCount === 0) { - directoryWatchesOfFailedLookups.delete(path); - watcher.watcher.close(); - } - }); + return returnTrue; } - - function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference): CachedResolvedModuleWithFailedLookupLocations { - const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host, moduleResolutionCache, redirectedReference); - // return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts - if (!resolutionHost.getGlobalCache) { - return primaryResult; - } - - // otherwise try to load typings from @types - const globalCache = resolutionHost.getGlobalCache(); - if (globalCache !== undefined && !isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTS(primaryResult.resolvedModule.extension))) { - // create different collection of failed lookup locations for second pass - // if it will fail and we've already found something during the first pass - we don't want to pollute its results - const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache( - Debug.assertDefined(resolutionHost.globalCacheResolutionModuleName)(moduleName), - resolutionHost.projectName, - compilerOptions, - host, - globalCache); - if (resolvedModule) { - return { resolvedModule, failedLookupLocations: addRange(primaryResult.failedLookupLocations as string[], failedLookupLocations) }; - } - } - - // Default return the result from the first pass + const collected = filesWithInvalidatedResolutions; + filesWithInvalidatedResolutions = undefined; + return path => (!!collected && collected.has(path)) || + isFileWithInvalidatedNonRelativeUnresolvedImports(path); + } + function clearPerDirectoryResolutions() { + perDirectoryResolvedModuleNames.clear(); + nonRelativeModuleNameCache.clear(); + perDirectoryResolvedTypeReferenceDirectives.clear(); + nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions); + nonRelativeExternalModuleResolutions.clear(); + } + function finishCachingPerDirectoryResolution() { + allFilesHaveInvalidatedResolution = false; + filesWithInvalidatedNonRelativeUnresolvedImports = undefined; + clearPerDirectoryResolutions(); + directoryWatchesOfFailedLookups.forEach((watcher, path) => { + if (watcher.refCount === 0) { + directoryWatchesOfFailedLookups.delete(path); + watcher.watcher.close(); + } + }); + } + function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference): CachedResolvedModuleWithFailedLookupLocations { + const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host, moduleResolutionCache, redirectedReference); + // return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts + if (!resolutionHost.getGlobalCache) { return primaryResult; } - - function resolveNamesWithLocalCache( - names: readonly string[], - containingFile: string, - redirectedReference: ResolvedProjectReference | undefined, - cache: Map>, - perDirectoryCacheWithRedirects: CacheWithRedirects>, - loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference) => T, - getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, - shouldRetryResolution: (t: T) => boolean, - reusedNames: readonly string[] | undefined, - logChanges: boolean): (R | undefined)[] { - - const path = resolutionHost.toPath(containingFile); - const resolutionsInFile = cache.get(path) || cache.set(path, createMap()).get(path)!; - const dirPath = getDirectoryPath(path); - const perDirectoryCache = perDirectoryCacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference); - let perDirectoryResolution = perDirectoryCache.get(dirPath); - if (!perDirectoryResolution) { - perDirectoryResolution = createMap(); - perDirectoryCache.set(dirPath, perDirectoryResolution); + // otherwise try to load typings from @types + const globalCache = resolutionHost.getGlobalCache(); + if (globalCache !== undefined && !isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTS(primaryResult.resolvedModule.extension))) { + // create different collection of failed lookup locations for second pass + // if it will fail and we've already found something during the first pass - we don't want to pollute its results + const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache(Debug.assertDefined(resolutionHost.globalCacheResolutionModuleName)(moduleName), resolutionHost.projectName, compilerOptions, host, globalCache); + if (resolvedModule) { + return { resolvedModule, failedLookupLocations: addRange((primaryResult.failedLookupLocations as string[]), failedLookupLocations) }; } - const resolvedModules: (R | undefined)[] = []; - const compilerOptions = resolutionHost.getCompilationSettings(); - const hasInvalidatedNonRelativeUnresolvedImport = logChanges && isFileWithInvalidatedNonRelativeUnresolvedImports(path); - - // All the resolutions in this file are invalidated if this file wasnt resolved using same redirect - const program = resolutionHost.getCurrentProgram(); - const oldRedirect = program && program.getResolvedProjectReferenceToRedirect(containingFile); - const unmatchedRedirects = oldRedirect ? - !redirectedReference || redirectedReference.sourceFile.path !== oldRedirect.sourceFile.path : - !!redirectedReference; - - const seenNamesInFile = createMap(); - for (const name of names) { - let resolution = resolutionsInFile.get(name); - // Resolution is valid if it is present and not invalidated - if (!seenNamesInFile.has(name) && - allFilesHaveInvalidatedResolution || unmatchedRedirects || !resolution || resolution.isInvalidated || - // If the name is unresolved import that was invalidated, recalculate - (hasInvalidatedNonRelativeUnresolvedImport && !isExternalModuleNameRelative(name) && shouldRetryResolution(resolution))) { - const existingResolution = resolution; - const resolutionInDirectory = perDirectoryResolution.get(name); - if (resolutionInDirectory) { - resolution = resolutionInDirectory; - } - else { - resolution = loader(name, containingFile, compilerOptions, resolutionHost, redirectedReference); - perDirectoryResolution.set(name, resolution); - } - resolutionsInFile.set(name, resolution); - watchFailedLookupLocationsOfExternalModuleResolutions(name, resolution); - if (existingResolution) { - stopWatchFailedLookupLocationOfResolution(existingResolution); - } - - if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) { - filesWithChangedSetOfUnresolvedImports.push(path); - // reset log changes to avoid recording the same file multiple times - logChanges = false; - } - } - Debug.assert(resolution !== undefined && !resolution.isInvalidated); - seenNamesInFile.set(name, true); - resolvedModules.push(getResolutionWithResolvedFileName(resolution)); - } - - // Stop watching and remove the unused name - resolutionsInFile.forEach((resolution, name) => { - if (!seenNamesInFile.has(name) && !contains(reusedNames, name)) { - stopWatchFailedLookupLocationOfResolution(resolution); - resolutionsInFile.delete(name); - } - }); - - return resolvedModules; - - function resolutionIsEqualTo(oldResolution: T | undefined, newResolution: T | undefined): boolean { - if (oldResolution === newResolution) { - return true; + } + // Default return the result from the first pass + return primaryResult; + } + function resolveNamesWithLocalCache(names: readonly string[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, cache: ts.Map>, perDirectoryCacheWithRedirects: CacheWithRedirects>, loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference) => T, getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, shouldRetryResolution: (t: T) => boolean, reusedNames: readonly string[] | undefined, logChanges: boolean): (R | undefined)[] { + const path = resolutionHost.toPath(containingFile); + const resolutionsInFile = cache.get(path) || (cache.set(path, createMap()).get(path)!); + const dirPath = getDirectoryPath(path); + const perDirectoryCache = perDirectoryCacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference); + let perDirectoryResolution = perDirectoryCache.get(dirPath); + if (!perDirectoryResolution) { + perDirectoryResolution = createMap(); + perDirectoryCache.set(dirPath, perDirectoryResolution); + } + const resolvedModules: (R | undefined)[] = []; + const compilerOptions = resolutionHost.getCompilationSettings(); + const hasInvalidatedNonRelativeUnresolvedImport = logChanges && isFileWithInvalidatedNonRelativeUnresolvedImports(path); + // All the resolutions in this file are invalidated if this file wasnt resolved using same redirect + const program = resolutionHost.getCurrentProgram(); + const oldRedirect = program && program.getResolvedProjectReferenceToRedirect(containingFile); + const unmatchedRedirects = oldRedirect ? + !redirectedReference || redirectedReference.sourceFile.path !== oldRedirect.sourceFile.path : + !!redirectedReference; + const seenNamesInFile = createMap(); + for (const name of names) { + let resolution = resolutionsInFile.get(name); + // Resolution is valid if it is present and not invalidated + if (!seenNamesInFile.has(name) && + allFilesHaveInvalidatedResolution || unmatchedRedirects || !resolution || resolution.isInvalidated || + // If the name is unresolved import that was invalidated, recalculate + (hasInvalidatedNonRelativeUnresolvedImport && !isExternalModuleNameRelative(name) && shouldRetryResolution(resolution))) { + const existingResolution = resolution; + const resolutionInDirectory = perDirectoryResolution.get(name); + if (resolutionInDirectory) { + resolution = resolutionInDirectory; } - if (!oldResolution || !newResolution) { - return false; + else { + resolution = loader(name, containingFile, compilerOptions, resolutionHost, redirectedReference); + perDirectoryResolution.set(name, resolution); } - const oldResult = getResolutionWithResolvedFileName(oldResolution); - const newResult = getResolutionWithResolvedFileName(newResolution); - if (oldResult === newResult) { - return true; + resolutionsInFile.set(name, resolution); + watchFailedLookupLocationsOfExternalModuleResolutions(name, resolution); + if (existingResolution) { + stopWatchFailedLookupLocationOfResolution(existingResolution); } - if (!oldResult || !newResult) { - return false; + if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) { + filesWithChangedSetOfUnresolvedImports.push(path); + // reset log changes to avoid recording the same file multiple times + logChanges = false; } - return oldResult.resolvedFileName === newResult.resolvedFileName; } + Debug.assert(resolution !== undefined && !resolution.isInvalidated); + seenNamesInFile.set(name, true); + resolvedModules.push(getResolutionWithResolvedFileName(resolution)); } - - function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[] { - return resolveNamesWithLocalCache( - typeDirectiveNames, containingFile, redirectedReference, - resolvedTypeReferenceDirectives, perDirectoryResolvedTypeReferenceDirectives, - resolveTypeReferenceDirective, getResolvedTypeReferenceDirective, - /*shouldRetryResolution*/ resolution => resolution.resolvedTypeReferenceDirective === undefined, - /*reusedNames*/ undefined, /*logChanges*/ false - ); - } - - function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference): (ResolvedModuleFull | undefined)[] { - return resolveNamesWithLocalCache( - moduleNames, containingFile, redirectedReference, - resolvedModuleNames, perDirectoryResolvedModuleNames, - resolveModuleName, getResolvedModule, - /*shouldRetryResolution*/ resolution => !resolution.resolvedModule || !resolutionExtensionIsTSOrJson(resolution.resolvedModule.extension), - reusedNames, logChangesWhenResolvingModule - ); - } - - function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): CachedResolvedModuleWithFailedLookupLocations | undefined { - const cache = resolvedModuleNames.get(resolutionHost.toPath(containingFile)); - return cache && cache.get(moduleName); - } - - function isNodeModulesDirectory(dirPath: Path) { - return endsWith(dirPath, "/node_modules"); - } - - function isNodeModulesAtTypesDirectory(dirPath: Path) { - return endsWith(dirPath, "/node_modules/@types"); - } - - function getDirectoryToWatchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path): DirectoryOfFailedLookupWatch | undefined { - if (isInDirectoryPath(rootPath, failedLookupLocationPath)) { - // Ensure failed look up is normalized path - failedLookupLocation = isRootedDiskPath(failedLookupLocation) ? normalizePath(failedLookupLocation) : getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory()); - Debug.assert(failedLookupLocation.length === failedLookupLocationPath.length, `FailedLookup: ${failedLookupLocation} failedLookupLocationPath: ${failedLookupLocationPath}`); - const subDirectoryInRoot = failedLookupLocationPath.indexOf(directorySeparator, rootPath.length + 1); - if (subDirectoryInRoot !== -1) { - // Instead of watching root, watch directory in root to avoid watching excluded directories not needed for module resolution - return { dir: failedLookupLocation.substr(0, subDirectoryInRoot), dirPath: failedLookupLocationPath.substr(0, subDirectoryInRoot) as Path }; - } - else { - // Always watch root directory non recursively - return { dir: rootDir!, dirPath: rootPath, nonRecursive: false }; // TODO: GH#18217 - } + // Stop watching and remove the unused name + resolutionsInFile.forEach((resolution, name) => { + if (!seenNamesInFile.has(name) && !contains(reusedNames, name)) { + stopWatchFailedLookupLocationOfResolution(resolution); + resolutionsInFile.delete(name); } - - return getDirectoryToWatchFromFailedLookupLocationDirectory( - getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory())), - getDirectoryPath(failedLookupLocationPath) - ); - } - - function getDirectoryToWatchFromFailedLookupLocationDirectory(dir: string, dirPath: Path): DirectoryOfFailedLookupWatch | undefined { - // If directory path contains node module, get the most parent node_modules directory for watching - while (pathContainsNodeModules(dirPath)) { - dir = getDirectoryPath(dir); - dirPath = getDirectoryPath(dirPath); - } - - // If the directory is node_modules use it to watch, always watch it recursively - if (isNodeModulesDirectory(dirPath)) { - return canWatchDirectory(getDirectoryPath(dirPath)) ? { dir, dirPath } : undefined; + }); + return resolvedModules; + function resolutionIsEqualTo(oldResolution: T | undefined, newResolution: T | undefined): boolean { + if (oldResolution === newResolution) { + return true; } - - let nonRecursive = true; - // Use some ancestor of the root directory - let subDirectoryPath: Path | undefined, subDirectory: string | undefined; - if (rootPath !== undefined) { - while (!isInDirectoryPath(dirPath, rootPath)) { - const parentPath = getDirectoryPath(dirPath); - if (parentPath === dirPath) { - break; - } - nonRecursive = false; - subDirectoryPath = dirPath; - subDirectory = dir; - dirPath = parentPath; - dir = getDirectoryPath(dir); - } - } - - return canWatchDirectory(dirPath) ? { dir: subDirectory || dir, dirPath: subDirectoryPath || dirPath, nonRecursive } : undefined; - } - - function isPathWithDefaultFailedLookupExtension(path: Path) { - return fileExtensionIsOneOf(path, failedLookupDefaultExtensions); - } - - function watchFailedLookupLocationsOfExternalModuleResolutions(name: string, resolution: ResolutionWithFailedLookupLocations) { - // No need to set the resolution refCount - if (resolution.failedLookupLocations && resolution.failedLookupLocations.length) { - if (resolution.refCount) { - resolution.refCount++; - } - else { - resolution.refCount = 1; - if (isExternalModuleNameRelative(name)) { - watchFailedLookupLocationOfResolution(resolution); - } - else { - nonRelativeExternalModuleResolutions.add(name, resolution); - } - } + if (!oldResolution || !newResolution) { + return false; } - } - - function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) { - Debug.assert(!!resolution.refCount); - - const { failedLookupLocations } = resolution; - let setAtRoot = false; - for (const failedLookupLocation of failedLookupLocations) { - const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); - const toWatch = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); - if (toWatch) { - const { dir, dirPath, nonRecursive } = toWatch; - // If the failed lookup location path is not one of the supported extensions, - // store it in the custom path - if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) { - const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0; - customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1); - } - if (dirPath === rootPath) { - Debug.assert(!nonRecursive); - setAtRoot = true; - } - else { - setDirectoryWatcher(dir, dirPath, nonRecursive); - } - } + const oldResult = getResolutionWithResolvedFileName(oldResolution); + const newResult = getResolutionWithResolvedFileName(newResolution); + if (oldResult === newResult) { + return true; } - - if (setAtRoot) { - // This is always non recursive - setDirectoryWatcher(rootDir!, rootPath, /*nonRecursive*/ true); // TODO: GH#18217 + if (!oldResult || !newResult) { + return false; } + return oldResult.resolvedFileName === newResult.resolvedFileName; } - - function setRefCountToUndefined(resolution: ResolutionWithFailedLookupLocations) { - resolution.refCount = undefined; - } - - function watchFailedLookupLocationOfNonRelativeModuleResolutions(resolutions: ResolutionWithFailedLookupLocations[], name: string) { - const program = resolutionHost.getCurrentProgram(); - const updateResolution = program && program.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(name) ? - setRefCountToUndefined : watchFailedLookupLocationOfResolution; - resolutions.forEach(updateResolution); - } - - function setDirectoryWatcher(dir: string, dirPath: Path, nonRecursive?: boolean) { - const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); - if (dirWatcher) { - Debug.assert(!!nonRecursive === !!dirWatcher.nonRecursive); - dirWatcher.refCount++; + } + function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[] { + return resolveNamesWithLocalCache(typeDirectiveNames, containingFile, redirectedReference, resolvedTypeReferenceDirectives, perDirectoryResolvedTypeReferenceDirectives, resolveTypeReferenceDirective, getResolvedTypeReferenceDirective, + /*shouldRetryResolution*/ resolution => resolution.resolvedTypeReferenceDirective === undefined, + /*reusedNames*/ undefined, /*logChanges*/ false); + } + function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference): (ResolvedModuleFull | undefined)[] { + return resolveNamesWithLocalCache(moduleNames, containingFile, redirectedReference, resolvedModuleNames, perDirectoryResolvedModuleNames, resolveModuleName, getResolvedModule, + /*shouldRetryResolution*/ resolution => !resolution.resolvedModule || !resolutionExtensionIsTSOrJson(resolution.resolvedModule.extension), reusedNames, logChangesWhenResolvingModule); + } + function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): CachedResolvedModuleWithFailedLookupLocations | undefined { + const cache = resolvedModuleNames.get(resolutionHost.toPath(containingFile)); + return cache && cache.get(moduleName); + } + function isNodeModulesDirectory(dirPath: Path) { + return endsWith(dirPath, "/node_modules"); + } + function isNodeModulesAtTypesDirectory(dirPath: Path) { + return endsWith(dirPath, "/node_modules/@types"); + } + function getDirectoryToWatchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path): DirectoryOfFailedLookupWatch | undefined { + if (isInDirectoryPath(rootPath, failedLookupLocationPath)) { + // Ensure failed look up is normalized path + failedLookupLocation = isRootedDiskPath(failedLookupLocation) ? normalizePath(failedLookupLocation) : getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory()); + Debug.assert(failedLookupLocation.length === failedLookupLocationPath.length, `FailedLookup: ${failedLookupLocation} failedLookupLocationPath: ${failedLookupLocationPath}`); + const subDirectoryInRoot = failedLookupLocationPath.indexOf(directorySeparator, rootPath.length + 1); + if (subDirectoryInRoot !== -1) { + // Instead of watching root, watch directory in root to avoid watching excluded directories not needed for module resolution + return { dir: failedLookupLocation.substr(0, subDirectoryInRoot), dirPath: (failedLookupLocationPath.substr(0, subDirectoryInRoot) as Path) }; } else { - directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath, nonRecursive), refCount: 1, nonRecursive }); + // Always watch root directory non recursively + return { dir: rootDir!, dirPath: rootPath, nonRecursive: false }; // TODO: GH#18217 } } - - function stopWatchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) { - if (!resolution.refCount) { - return; + return getDirectoryToWatchFromFailedLookupLocationDirectory(getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory())), getDirectoryPath(failedLookupLocationPath)); + } + function getDirectoryToWatchFromFailedLookupLocationDirectory(dir: string, dirPath: Path): DirectoryOfFailedLookupWatch | undefined { + // If directory path contains node module, get the most parent node_modules directory for watching + while (pathContainsNodeModules(dirPath)) { + dir = getDirectoryPath(dir); + dirPath = getDirectoryPath(dirPath); + } + // If the directory is node_modules use it to watch, always watch it recursively + if (isNodeModulesDirectory(dirPath)) { + return canWatchDirectory(getDirectoryPath(dirPath)) ? { dir, dirPath } : undefined; + } + let nonRecursive = true; + // Use some ancestor of the root directory + let subDirectoryPath: Path | undefined, subDirectory: string | undefined; + if (rootPath !== undefined) { + while (!isInDirectoryPath(dirPath, rootPath)) { + const parentPath = getDirectoryPath(dirPath); + if (parentPath === dirPath) { + break; + } + nonRecursive = false; + subDirectoryPath = dirPath; + subDirectory = dir; + dirPath = parentPath; + dir = getDirectoryPath(dir); } - - resolution.refCount--; + } + return canWatchDirectory(dirPath) ? { dir: subDirectory || dir, dirPath: subDirectoryPath || dirPath, nonRecursive } : undefined; + } + function isPathWithDefaultFailedLookupExtension(path: Path) { + return fileExtensionIsOneOf(path, failedLookupDefaultExtensions); + } + function watchFailedLookupLocationsOfExternalModuleResolutions(name: string, resolution: ResolutionWithFailedLookupLocations) { + // No need to set the resolution refCount + if (resolution.failedLookupLocations && resolution.failedLookupLocations.length) { if (resolution.refCount) { - return; + resolution.refCount++; } - - const { failedLookupLocations } = resolution; - let removeAtRoot = false; - for (const failedLookupLocation of failedLookupLocations) { - const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); - const toWatch = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); - if (toWatch) { - const { dirPath } = toWatch; - const refCount = customFailedLookupPaths.get(failedLookupLocationPath); - if (refCount) { - if (refCount === 1) { - customFailedLookupPaths.delete(failedLookupLocationPath); - } - else { - Debug.assert(refCount > 1); - customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1); - } - } - - if (dirPath === rootPath) { - removeAtRoot = true; - } - else { - removeDirectoryWatcher(dirPath); - } + else { + resolution.refCount = 1; + if (isExternalModuleNameRelative(name)) { + watchFailedLookupLocationOfResolution(resolution); + } + else { + nonRelativeExternalModuleResolutions.add(name, resolution); } - } - if (removeAtRoot) { - removeDirectoryWatcher(rootPath); } } - - function removeDirectoryWatcher(dirPath: string) { - const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath)!; - // Do not close the watcher yet since it might be needed by other failed lookup locations. - dirWatcher.refCount--; - } - - function createDirectoryWatcher(directory: string, dirPath: Path, nonRecursive: boolean | undefined) { - return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrDirectory => { - const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); - if (cachedDirectoryStructureHost) { - // Since the file existence changed, update the sourceFiles cache - cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + } + function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) { + Debug.assert(!!resolution.refCount); + const { failedLookupLocations } = resolution; + let setAtRoot = false; + for (const failedLookupLocation of failedLookupLocations) { + const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); + const toWatch = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); + if (toWatch) { + const { dir, dirPath, nonRecursive } = toWatch; + // If the failed lookup location path is not one of the supported extensions, + // store it in the custom path + if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) { + const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0; + customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1); + } + if (dirPath === rootPath) { + Debug.assert(!nonRecursive); + setAtRoot = true; } - - if (!allFilesHaveInvalidatedResolution && invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath)) { - resolutionHost.onInvalidatedResolution(); + else { + setDirectoryWatcher(dir, dirPath, nonRecursive); } - }, nonRecursive ? WatchDirectoryFlags.None : WatchDirectoryFlags.Recursive); - } - - function removeResolutionsOfFileFromCache(cache: Map>, filePath: Path) { - // Deleted file, stop watching failed lookups for all the resolutions in the file - const resolutions = cache.get(filePath); - if (resolutions) { - resolutions.forEach(stopWatchFailedLookupLocationOfResolution); - cache.delete(filePath); } } - - function removeResolutionsFromProjectReferenceRedirects(filePath: Path) { - if (!fileExtensionIs(filePath, Extension.Json)) { return; } - - const program = resolutionHost.getCurrentProgram(); - if (!program) { return; } - - // If this file is input file for the referenced project, get it - const resolvedProjectReference = program.getResolvedProjectReferenceByPath(filePath); - if (!resolvedProjectReference) { return; } - - // filePath is for the projectReference and the containing file is from this project reference, invalidate the resolution - resolvedProjectReference.commandLine.fileNames.forEach(f => removeResolutionsOfFile(resolutionHost.toPath(f))); - } - - function removeResolutionsOfFile(filePath: Path) { - removeResolutionsOfFileFromCache(resolvedModuleNames, filePath); - removeResolutionsOfFileFromCache(resolvedTypeReferenceDirectives, filePath); - } - - function invalidateResolutionCache( - cache: Map>, - isInvalidatedResolution: (resolution: T, getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName) => boolean, - getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName - ) { - const seen = createMap>(); - cache.forEach((resolutions, containingFilePath) => { - const dirPath = getDirectoryPath(containingFilePath); - let seenInDir = seen.get(dirPath); - if (!seenInDir) { - seenInDir = createMap(); - seen.set(dirPath, seenInDir); - } - resolutions.forEach((resolution, name) => { - if (seenInDir!.has(name)) { - return; + if (setAtRoot) { + // This is always non recursive + setDirectoryWatcher(rootDir!, rootPath, /*nonRecursive*/ true); // TODO: GH#18217 + } + } + function setRefCountToUndefined(resolution: ResolutionWithFailedLookupLocations) { + resolution.refCount = undefined; + } + function watchFailedLookupLocationOfNonRelativeModuleResolutions(resolutions: ResolutionWithFailedLookupLocations[], name: string) { + const program = resolutionHost.getCurrentProgram(); + const updateResolution = program && program.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(name) ? + setRefCountToUndefined : watchFailedLookupLocationOfResolution; + resolutions.forEach(updateResolution); + } + function setDirectoryWatcher(dir: string, dirPath: Path, nonRecursive?: boolean) { + const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); + if (dirWatcher) { + Debug.assert(!!nonRecursive === !!dirWatcher.nonRecursive); + dirWatcher.refCount++; + } + else { + directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath, nonRecursive), refCount: 1, nonRecursive }); + } + } + function stopWatchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) { + if (!resolution.refCount) { + return; + } + resolution.refCount--; + if (resolution.refCount) { + return; + } + const { failedLookupLocations } = resolution; + let removeAtRoot = false; + for (const failedLookupLocation of failedLookupLocations) { + const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); + const toWatch = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); + if (toWatch) { + const { dirPath } = toWatch; + const refCount = customFailedLookupPaths.get(failedLookupLocationPath); + if (refCount) { + if (refCount === 1) { + customFailedLookupPaths.delete(failedLookupLocationPath); } - seenInDir!.set(name, true); - if (!resolution.isInvalidated && isInvalidatedResolution(resolution, getResolutionWithResolvedFileName)) { - // Mark the file as needing re-evaluation of module resolution instead of using it blindly. - resolution.isInvalidated = true; - (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap())).set(containingFilePath, true); - - // When its a file with inferred types resolution, invalidate type reference directive resolution - if (containingFilePath.endsWith(inferredTypesContainingFile)) { - resolutionHost.onChangedAutomaticTypeDirectiveNames(); - } + else { + Debug.assert(refCount > 1); + customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1); } - }); - }); - } - - function hasReachedResolutionIterationLimit() { - const maxSize = resolutionHost.maxNumberOfFilesToIterateForInvalidation || maxNumberOfFilesToIterateForInvalidation; - return resolvedModuleNames.size > maxSize || resolvedTypeReferenceDirectives.size > maxSize; - } - - function invalidateResolutions( - isInvalidatedResolution: (resolution: ResolutionWithFailedLookupLocations, getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName) => boolean, - ) { - // If more than maxNumberOfFilesToIterateForInvalidation present, - // just invalidated all files and recalculate the resolutions for files instead - if (hasReachedResolutionIterationLimit()) { - allFilesHaveInvalidatedResolution = true; - return; - } - invalidateResolutionCache(resolvedModuleNames, isInvalidatedResolution, getResolvedModule); - invalidateResolutionCache(resolvedTypeReferenceDirectives, isInvalidatedResolution, getResolvedTypeReferenceDirective); - } - - function invalidateResolutionOfFile(filePath: Path) { - removeResolutionsOfFile(filePath); - invalidateResolutions( - // Resolution is invalidated if the resulting file name is same as the deleted file path - (resolution, getResolutionWithResolvedFileName) => { - const result = getResolutionWithResolvedFileName(resolution); - return !!result && resolutionHost.toPath(result.resolvedFileName!) === filePath; // TODO: GH#18217 - } - ); - } - - function setFilesWithInvalidatedNonRelativeUnresolvedImports(filesMap: ReadonlyMap) { - Debug.assert(filesWithInvalidatedNonRelativeUnresolvedImports === filesMap || filesWithInvalidatedNonRelativeUnresolvedImports === undefined); - filesWithInvalidatedNonRelativeUnresolvedImports = filesMap; - } - - function invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) { - let isChangedFailedLookupLocation: (location: string) => boolean; - if (isCreatingWatchedDirectory) { - // Watching directory is created - // Invalidate any resolution has failed lookup in this directory - isChangedFailedLookupLocation = location => isInDirectoryPath(fileOrDirectoryPath, resolutionHost.toPath(location)); - } - else { - // If something to do with folder/file starting with "." in node_modules folder, skip it - if (isPathIgnored(fileOrDirectoryPath)) return false; - - // prevent saving an open file from over-eagerly triggering invalidation - if (resolutionHost.fileIsOpen(fileOrDirectoryPath)) { - return false; } - - // Some file or directory in the watching directory is created - // Return early if it does not have any of the watching extension or not the custom failed lookup path - const dirOfFileOrDirectory = getDirectoryPath(fileOrDirectoryPath); - if (isNodeModulesAtTypesDirectory(fileOrDirectoryPath) || isNodeModulesDirectory(fileOrDirectoryPath) || - isNodeModulesAtTypesDirectory(dirOfFileOrDirectory) || isNodeModulesDirectory(dirOfFileOrDirectory)) { - // Invalidate any resolution from this directory - isChangedFailedLookupLocation = location => { - const locationPath = resolutionHost.toPath(location); - return locationPath === fileOrDirectoryPath || startsWith(resolutionHost.toPath(location), fileOrDirectoryPath); - }; + if (dirPath === rootPath) { + removeAtRoot = true; } else { - if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) { - return false; - } - // Ignore emits from the program - if (isEmittedFileOfProgram(resolutionHost.getCurrentProgram(), fileOrDirectoryPath)) { - return false; - } - // Resolution need to be invalidated if failed lookup location is same as the file or directory getting created - isChangedFailedLookupLocation = location => resolutionHost.toPath(location) === fileOrDirectoryPath; + removeDirectoryWatcher(dirPath); } } - const hasChangedFailedLookupLocation = (resolution: ResolutionWithFailedLookupLocations) => some(resolution.failedLookupLocations, isChangedFailedLookupLocation); - const invalidatedFilesCount = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size; - invalidateResolutions( - // Resolution is invalidated if the resulting file name is same as the deleted file path - hasChangedFailedLookupLocation - ); - return allFilesHaveInvalidatedResolution || filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size !== invalidatedFilesCount; - } - - function closeTypeRootsWatch() { - clearMap(typeRootsWatches, closeFileWatcher); - } - - function getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot: string, typeRootPath: Path): Path | undefined { - if (allFilesHaveInvalidatedResolution) { - return undefined; + } + if (removeAtRoot) { + removeDirectoryWatcher(rootPath); + } + } + function removeDirectoryWatcher(dirPath: string) { + const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath)!; + // Do not close the watcher yet since it might be needed by other failed lookup locations. + dirWatcher.refCount--; + } + function createDirectoryWatcher(directory: string, dirPath: Path, nonRecursive: boolean | undefined) { + return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrDirectory => { + const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); + if (cachedDirectoryStructureHost) { + // Since the file existence changed, update the sourceFiles cache + cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); } - - if (isInDirectoryPath(rootPath, typeRootPath)) { - return rootPath; + if (!allFilesHaveInvalidatedResolution && invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath)) { + resolutionHost.onInvalidatedResolution(); } - const toWatch = getDirectoryToWatchFromFailedLookupLocationDirectory(typeRoot, typeRootPath); - return toWatch && directoryWatchesOfFailedLookups.has(toWatch.dirPath) ? toWatch.dirPath : undefined; - } - - function createTypeRootsWatch(typeRootPath: Path, typeRoot: string): FileWatcher { - // Create new watch and recursive info - return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => { - const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); - if (cachedDirectoryStructureHost) { - // Since the file existence changed, update the sourceFiles cache - cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + }, nonRecursive ? WatchDirectoryFlags.None : WatchDirectoryFlags.Recursive); + } + function removeResolutionsOfFileFromCache(cache: ts.Map>, filePath: Path) { + // Deleted file, stop watching failed lookups for all the resolutions in the file + const resolutions = cache.get(filePath); + if (resolutions) { + resolutions.forEach(stopWatchFailedLookupLocationOfResolution); + cache.delete(filePath); + } + } + function removeResolutionsFromProjectReferenceRedirects(filePath: Path) { + if (!fileExtensionIs(filePath, Extension.Json)) { + return; + } + const program = resolutionHost.getCurrentProgram(); + if (!program) { + return; + } + // If this file is input file for the referenced project, get it + const resolvedProjectReference = program.getResolvedProjectReferenceByPath(filePath); + if (!resolvedProjectReference) { + return; + } + // filePath is for the projectReference and the containing file is from this project reference, invalidate the resolution + resolvedProjectReference.commandLine.fileNames.forEach(f => removeResolutionsOfFile(resolutionHost.toPath(f))); + } + function removeResolutionsOfFile(filePath: Path) { + removeResolutionsOfFileFromCache(resolvedModuleNames, filePath); + removeResolutionsOfFileFromCache(resolvedTypeReferenceDirectives, filePath); + } + function invalidateResolutionCache(cache: ts.Map>, isInvalidatedResolution: (resolution: T, getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName) => boolean, getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName) { + const seen = createMap>(); + cache.forEach((resolutions, containingFilePath) => { + const dirPath = getDirectoryPath(containingFilePath); + let seenInDir = seen.get(dirPath); + if (!seenInDir) { + seenInDir = createMap(); + seen.set(dirPath, seenInDir); + } + resolutions.forEach((resolution, name) => { + if (seenInDir!.has(name)) { + return; } - - // For now just recompile - // We could potentially store more data here about whether it was/would be really be used or not - // and with that determine to trigger compilation but for now this is enough - resolutionHost.onChangedAutomaticTypeDirectiveNames(); - - // Since directory watchers invoked are flaky, the failed lookup location events might not be triggered - // So handle to failed lookup locations here as well to ensure we are invalidating resolutions - const dirPath = getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot, typeRootPath); - if (dirPath && invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath)) { - resolutionHost.onInvalidatedResolution(); + seenInDir!.set(name, true); + if (!resolution.isInvalidated && isInvalidatedResolution(resolution, getResolutionWithResolvedFileName)) { + // Mark the file as needing re-evaluation of module resolution instead of using it blindly. + resolution.isInvalidated = true; + (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap())).set(containingFilePath, true); + // When its a file with inferred types resolution, invalidate type reference directive resolution + if (containingFilePath.endsWith(inferredTypesContainingFile)) { + resolutionHost.onChangedAutomaticTypeDirectiveNames(); + } } - }, WatchDirectoryFlags.Recursive); - } - - /** - * Watches the types that would get added as part of getAutomaticTypeDirectiveNames - * To be called when compiler options change - */ - function updateTypeRootsWatch() { - const options = resolutionHost.getCompilationSettings(); - if (options.types) { - // No need to do any watch since resolution cache is going to handle the failed lookups - // for the types added by this - closeTypeRootsWatch(); - return; + }); + }); + } + function hasReachedResolutionIterationLimit() { + const maxSize = resolutionHost.maxNumberOfFilesToIterateForInvalidation || maxNumberOfFilesToIterateForInvalidation; + return resolvedModuleNames.size > maxSize || resolvedTypeReferenceDirectives.size > maxSize; + } + function invalidateResolutions(isInvalidatedResolution: (resolution: ResolutionWithFailedLookupLocations, getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName) => boolean) { + // If more than maxNumberOfFilesToIterateForInvalidation present, + // just invalidated all files and recalculate the resolutions for files instead + if (hasReachedResolutionIterationLimit()) { + allFilesHaveInvalidatedResolution = true; + return; + } + invalidateResolutionCache(resolvedModuleNames, isInvalidatedResolution, getResolvedModule); + invalidateResolutionCache(resolvedTypeReferenceDirectives, isInvalidatedResolution, getResolvedTypeReferenceDirective); + } + function invalidateResolutionOfFile(filePath: Path) { + removeResolutionsOfFile(filePath); + invalidateResolutions( + // Resolution is invalidated if the resulting file name is same as the deleted file path + (resolution, getResolutionWithResolvedFileName) => { + const result = getResolutionWithResolvedFileName(resolution); + return !!result && resolutionHost.toPath(result.resolvedFileName!) === filePath; // TODO: GH#18217 + }); + } + function setFilesWithInvalidatedNonRelativeUnresolvedImports(filesMap: ts.ReadonlyMap) { + Debug.assert(filesWithInvalidatedNonRelativeUnresolvedImports === filesMap || filesWithInvalidatedNonRelativeUnresolvedImports === undefined); + filesWithInvalidatedNonRelativeUnresolvedImports = filesMap; + } + function invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) { + let isChangedFailedLookupLocation: (location: string) => boolean; + if (isCreatingWatchedDirectory) { + // Watching directory is created + // Invalidate any resolution has failed lookup in this directory + isChangedFailedLookupLocation = location => isInDirectoryPath(fileOrDirectoryPath, resolutionHost.toPath(location)); + } + else { + // If something to do with folder/file starting with "." in node_modules folder, skip it + if (isPathIgnored(fileOrDirectoryPath)) + return false; + // prevent saving an open file from over-eagerly triggering invalidation + if (resolutionHost.fileIsOpen(fileOrDirectoryPath)) { + return false; } - - // we need to assume the directories exist to ensure that we can get all the type root directories that get included - // But filter directories that are at root level to say directory doesnt exist, so that we arent watching them - const typeRoots = getEffectiveTypeRoots(options, { directoryExists: directoryExistsForTypeRootWatch, getCurrentDirectory }); - if (typeRoots) { - mutateMap( - typeRootsWatches, - arrayToMap(typeRoots, tr => resolutionHost.toPath(tr)), - { - createNewValue: createTypeRootsWatch, - onDeleteValue: closeFileWatcher - } - ); + // Some file or directory in the watching directory is created + // Return early if it does not have any of the watching extension or not the custom failed lookup path + const dirOfFileOrDirectory = getDirectoryPath(fileOrDirectoryPath); + if (isNodeModulesAtTypesDirectory(fileOrDirectoryPath) || isNodeModulesDirectory(fileOrDirectoryPath) || + isNodeModulesAtTypesDirectory(dirOfFileOrDirectory) || isNodeModulesDirectory(dirOfFileOrDirectory)) { + // Invalidate any resolution from this directory + isChangedFailedLookupLocation = location => { + const locationPath = resolutionHost.toPath(location); + return locationPath === fileOrDirectoryPath || startsWith(resolutionHost.toPath(location), fileOrDirectoryPath); + }; } else { - closeTypeRootsWatch(); + if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) { + return false; + } + // Ignore emits from the program + if (isEmittedFileOfProgram(resolutionHost.getCurrentProgram(), fileOrDirectoryPath)) { + return false; + } + // Resolution need to be invalidated if failed lookup location is same as the file or directory getting created + isChangedFailedLookupLocation = location => resolutionHost.toPath(location) === fileOrDirectoryPath; } } - - /** - * Use this function to return if directory exists to get type roots to watch - * If we return directory exists then only the paths will be added to type roots - * Hence return true for all directories except root directories which are filtered from watching - */ - function directoryExistsForTypeRootWatch(nodeTypesDirectory: string) { - const dir = getDirectoryPath(getDirectoryPath(nodeTypesDirectory)); - const dirPath = resolutionHost.toPath(dir); - return dirPath === rootPath || canWatchDirectory(dirPath); + const hasChangedFailedLookupLocation = (resolution: ResolutionWithFailedLookupLocations) => some(resolution.failedLookupLocations, isChangedFailedLookupLocation); + const invalidatedFilesCount = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size; + invalidateResolutions( + // Resolution is invalidated if the resulting file name is same as the deleted file path + hasChangedFailedLookupLocation); + return allFilesHaveInvalidatedResolution || filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size !== invalidatedFilesCount; + } + function closeTypeRootsWatch() { + clearMap(typeRootsWatches, closeFileWatcher); + } + function getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot: string, typeRootPath: Path): Path | undefined { + if (allFilesHaveInvalidatedResolution) { + return undefined; + } + if (isInDirectoryPath(rootPath, typeRootPath)) { + return rootPath; + } + const toWatch = getDirectoryToWatchFromFailedLookupLocationDirectory(typeRoot, typeRootPath); + return toWatch && directoryWatchesOfFailedLookups.has(toWatch.dirPath) ? toWatch.dirPath : undefined; + } + function createTypeRootsWatch(typeRootPath: Path, typeRoot: string): FileWatcher { + // Create new watch and recursive info + return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => { + const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); + if (cachedDirectoryStructureHost) { + // Since the file existence changed, update the sourceFiles cache + cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + } + // For now just recompile + // We could potentially store more data here about whether it was/would be really be used or not + // and with that determine to trigger compilation but for now this is enough + resolutionHost.onChangedAutomaticTypeDirectiveNames(); + // Since directory watchers invoked are flaky, the failed lookup location events might not be triggered + // So handle to failed lookup locations here as well to ensure we are invalidating resolutions + const dirPath = getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot, typeRootPath); + if (dirPath && invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath)) { + resolutionHost.onInvalidatedResolution(); + } + }, WatchDirectoryFlags.Recursive); + } + /** + * Watches the types that would get added as part of getAutomaticTypeDirectiveNames + * To be called when compiler options change + */ + function updateTypeRootsWatch() { + const options = resolutionHost.getCompilationSettings(); + if (options.types) { + // No need to do any watch since resolution cache is going to handle the failed lookups + // for the types added by this + closeTypeRootsWatch(); + return; + } + // we need to assume the directories exist to ensure that we can get all the type root directories that get included + // But filter directories that are at root level to say directory doesnt exist, so that we arent watching them + const typeRoots = getEffectiveTypeRoots(options, { directoryExists: directoryExistsForTypeRootWatch, getCurrentDirectory }); + if (typeRoots) { + mutateMap(typeRootsWatches, arrayToMap(typeRoots, tr => resolutionHost.toPath(tr)), { + createNewValue: createTypeRootsWatch, + onDeleteValue: closeFileWatcher + }); + } + else { + closeTypeRootsWatch(); } } + /** + * Use this function to return if directory exists to get type roots to watch + * If we return directory exists then only the paths will be added to type roots + * Hence return true for all directories except root directories which are filtered from watching + */ + function directoryExistsForTypeRootWatch(nodeTypesDirectory: string) { + const dir = getDirectoryPath(getDirectoryPath(nodeTypesDirectory)); + const dirPath = resolutionHost.toPath(dir); + return dirPath === rootPath || canWatchDirectory(dirPath); + } } diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 09a3215b0f50a..4489f95d1be0e 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -1,548 +1,681 @@ -namespace ts { - export type ErrorCallback = (message: DiagnosticMessage, length: number) => void; - +import { DiagnosticMessage, SyntaxKind, TokenFlags, JsxTokenSyntaxKind, JSDocSyntaxKind, ScriptTarget, LanguageVariant, MapLike, KeywordSyntaxKind, createMapFromTemplate, CharacterCodes, SourceFileLike, Debug, arraysEqual, LineAndCharacter, binarySearch, identity, compareValues, positionIsSynthesized, Diagnostics, CommentKind, CommentRange, parsePseudoBigInt } from "./ts"; +import * as ts from "./ts"; +export type ErrorCallback = (message: DiagnosticMessage, length: number) => void; +/* @internal */ +export function tokenIsIdentifierOrKeyword(token: SyntaxKind): boolean { + return token >= SyntaxKind.Identifier; +} +/* @internal */ +export function tokenIsIdentifierOrKeywordOrGreaterThan(token: SyntaxKind): boolean { + return token === SyntaxKind.GreaterThanToken || tokenIsIdentifierOrKeyword(token); +} +export interface Scanner { + getStartPos(): number; + getToken(): SyntaxKind; + getTextPos(): number; + getTokenPos(): number; + getTokenText(): string; + getTokenValue(): string; + hasUnicodeEscape(): boolean; + hasExtendedUnicodeEscape(): boolean; + hasPrecedingLineBreak(): boolean; + isIdentifier(): boolean; + isReservedWord(): boolean; + isUnterminated(): boolean; /* @internal */ - export function tokenIsIdentifierOrKeyword(token: SyntaxKind): boolean { - return token >= SyntaxKind.Identifier; - } - + getTokenFlags(): TokenFlags; + reScanGreaterToken(): SyntaxKind; + reScanSlashToken(): SyntaxKind; + reScanTemplateToken(): SyntaxKind; + scanJsxIdentifier(): SyntaxKind; + scanJsxAttributeValue(): SyntaxKind; + reScanJsxAttributeValue(): SyntaxKind; + reScanJsxToken(): JsxTokenSyntaxKind; + reScanLessThanToken(): SyntaxKind; + reScanQuestionToken(): SyntaxKind; + scanJsxToken(): JsxTokenSyntaxKind; + scanJsDocToken(): JSDocSyntaxKind; + scan(): SyntaxKind; + getText(): string; + // Sets the text for the scanner to scan. An optional subrange starting point and length + // can be provided to have the scanner only scan a portion of the text. + setText(text: string | undefined, start?: number, length?: number): void; + setOnError(onError: ErrorCallback | undefined): void; + setScriptTarget(scriptTarget: ScriptTarget): void; + setLanguageVariant(variant: LanguageVariant): void; + setTextPos(textPos: number): void; /* @internal */ - export function tokenIsIdentifierOrKeywordOrGreaterThan(token: SyntaxKind): boolean { - return token === SyntaxKind.GreaterThanToken || tokenIsIdentifierOrKeyword(token); - } - - export interface Scanner { - getStartPos(): number; - getToken(): SyntaxKind; - getTextPos(): number; - getTokenPos(): number; - getTokenText(): string; - getTokenValue(): string; - hasUnicodeEscape(): boolean; - hasExtendedUnicodeEscape(): boolean; - hasPrecedingLineBreak(): boolean; - isIdentifier(): boolean; - isReservedWord(): boolean; - isUnterminated(): boolean; - /* @internal */ - getTokenFlags(): TokenFlags; - reScanGreaterToken(): SyntaxKind; - reScanSlashToken(): SyntaxKind; - reScanTemplateToken(): SyntaxKind; - scanJsxIdentifier(): SyntaxKind; - scanJsxAttributeValue(): SyntaxKind; - reScanJsxAttributeValue(): SyntaxKind; - reScanJsxToken(): JsxTokenSyntaxKind; - reScanLessThanToken(): SyntaxKind; - reScanQuestionToken(): SyntaxKind; - scanJsxToken(): JsxTokenSyntaxKind; - scanJsDocToken(): JSDocSyntaxKind; - scan(): SyntaxKind; - getText(): string; - // Sets the text for the scanner to scan. An optional subrange starting point and length - // can be provided to have the scanner only scan a portion of the text. - setText(text: string | undefined, start?: number, length?: number): void; - setOnError(onError: ErrorCallback | undefined): void; - setScriptTarget(scriptTarget: ScriptTarget): void; - setLanguageVariant(variant: LanguageVariant): void; - setTextPos(textPos: number): void; - /* @internal */ - setInJSDocType(inType: boolean): void; - // Invokes the provided callback then unconditionally restores the scanner to the state it - // was in immediately prior to invoking the callback. The result of invoking the callback - // is returned from this function. - lookAhead(callback: () => T): T; - - // Invokes the callback with the scanner set to scan the specified range. When the callback - // returns, the scanner is restored to the state it was in before scanRange was called. - scanRange(start: number, length: number, callback: () => T): T; - - // Invokes the provided callback. If the callback returns something falsy, then it restores - // the scanner to the state it was in immediately prior to invoking the callback. If the - // callback returns something truthy, then the scanner state is not rolled back. The result - // of invoking the callback is returned from this function. - tryScan(callback: () => T): T; + setInJSDocType(inType: boolean): void; + // Invokes the provided callback then unconditionally restores the scanner to the state it + // was in immediately prior to invoking the callback. The result of invoking the callback + // is returned from this function. + lookAhead(callback: () => T): T; + // Invokes the callback with the scanner set to scan the specified range. When the callback + // returns, the scanner is restored to the state it was in before scanRange was called. + scanRange(start: number, length: number, callback: () => T): T; + // Invokes the provided callback. If the callback returns something falsy, then it restores + // the scanner to the state it was in immediately prior to invoking the callback. If the + // callback returns something truthy, then the scanner state is not rolled back. The result + // of invoking the callback is returned from this function. + tryScan(callback: () => T): T; +} +const textToKeywordObj: MapLike = { + abstract: SyntaxKind.AbstractKeyword, + any: SyntaxKind.AnyKeyword, + as: SyntaxKind.AsKeyword, + asserts: SyntaxKind.AssertsKeyword, + bigint: SyntaxKind.BigIntKeyword, + boolean: SyntaxKind.BooleanKeyword, + break: SyntaxKind.BreakKeyword, + case: SyntaxKind.CaseKeyword, + catch: SyntaxKind.CatchKeyword, + class: SyntaxKind.ClassKeyword, + continue: SyntaxKind.ContinueKeyword, + const: SyntaxKind.ConstKeyword, + ["" + "constructor"]: SyntaxKind.ConstructorKeyword, + debugger: SyntaxKind.DebuggerKeyword, + declare: SyntaxKind.DeclareKeyword, + default: SyntaxKind.DefaultKeyword, + delete: SyntaxKind.DeleteKeyword, + do: SyntaxKind.DoKeyword, + else: SyntaxKind.ElseKeyword, + enum: SyntaxKind.EnumKeyword, + export: SyntaxKind.ExportKeyword, + extends: SyntaxKind.ExtendsKeyword, + false: SyntaxKind.FalseKeyword, + finally: SyntaxKind.FinallyKeyword, + for: SyntaxKind.ForKeyword, + from: SyntaxKind.FromKeyword, + function: SyntaxKind.FunctionKeyword, + get: SyntaxKind.GetKeyword, + if: SyntaxKind.IfKeyword, + implements: SyntaxKind.ImplementsKeyword, + import: SyntaxKind.ImportKeyword, + in: SyntaxKind.InKeyword, + infer: SyntaxKind.InferKeyword, + instanceof: SyntaxKind.InstanceOfKeyword, + interface: SyntaxKind.InterfaceKeyword, + is: SyntaxKind.IsKeyword, + keyof: SyntaxKind.KeyOfKeyword, + let: SyntaxKind.LetKeyword, + module: SyntaxKind.ModuleKeyword, + namespace: SyntaxKind.NamespaceKeyword, + never: SyntaxKind.NeverKeyword, + new: SyntaxKind.NewKeyword, + null: SyntaxKind.NullKeyword, + number: SyntaxKind.NumberKeyword, + object: SyntaxKind.ObjectKeyword, + package: SyntaxKind.PackageKeyword, + private: SyntaxKind.PrivateKeyword, + protected: SyntaxKind.ProtectedKeyword, + public: SyntaxKind.PublicKeyword, + readonly: SyntaxKind.ReadonlyKeyword, + require: SyntaxKind.RequireKeyword, + global: SyntaxKind.GlobalKeyword, + return: SyntaxKind.ReturnKeyword, + set: SyntaxKind.SetKeyword, + static: SyntaxKind.StaticKeyword, + string: SyntaxKind.StringKeyword, + super: SyntaxKind.SuperKeyword, + switch: SyntaxKind.SwitchKeyword, + symbol: SyntaxKind.SymbolKeyword, + this: SyntaxKind.ThisKeyword, + throw: SyntaxKind.ThrowKeyword, + true: SyntaxKind.TrueKeyword, + try: SyntaxKind.TryKeyword, + type: SyntaxKind.TypeKeyword, + typeof: SyntaxKind.TypeOfKeyword, + undefined: SyntaxKind.UndefinedKeyword, + unique: SyntaxKind.UniqueKeyword, + unknown: SyntaxKind.UnknownKeyword, + var: SyntaxKind.VarKeyword, + void: SyntaxKind.VoidKeyword, + while: SyntaxKind.WhileKeyword, + with: SyntaxKind.WithKeyword, + yield: SyntaxKind.YieldKeyword, + async: SyntaxKind.AsyncKeyword, + await: SyntaxKind.AwaitKeyword, + of: SyntaxKind.OfKeyword, +}; +const textToKeyword = createMapFromTemplate(textToKeywordObj); +const textToToken = createMapFromTemplate({ + ...textToKeywordObj, + "{": SyntaxKind.OpenBraceToken, + "}": SyntaxKind.CloseBraceToken, + "(": SyntaxKind.OpenParenToken, + ")": SyntaxKind.CloseParenToken, + "[": SyntaxKind.OpenBracketToken, + "]": SyntaxKind.CloseBracketToken, + ".": SyntaxKind.DotToken, + "...": SyntaxKind.DotDotDotToken, + ";": SyntaxKind.SemicolonToken, + ",": SyntaxKind.CommaToken, + "<": SyntaxKind.LessThanToken, + ">": SyntaxKind.GreaterThanToken, + "<=": SyntaxKind.LessThanEqualsToken, + ">=": SyntaxKind.GreaterThanEqualsToken, + "==": SyntaxKind.EqualsEqualsToken, + "!=": SyntaxKind.ExclamationEqualsToken, + "===": SyntaxKind.EqualsEqualsEqualsToken, + "!==": SyntaxKind.ExclamationEqualsEqualsToken, + "=>": SyntaxKind.EqualsGreaterThanToken, + "+": SyntaxKind.PlusToken, + "-": SyntaxKind.MinusToken, + "**": SyntaxKind.AsteriskAsteriskToken, + "*": SyntaxKind.AsteriskToken, + "/": SyntaxKind.SlashToken, + "%": SyntaxKind.PercentToken, + "++": SyntaxKind.PlusPlusToken, + "--": SyntaxKind.MinusMinusToken, + "<<": SyntaxKind.LessThanLessThanToken, + ">": SyntaxKind.GreaterThanGreaterThanToken, + ">>>": SyntaxKind.GreaterThanGreaterThanGreaterThanToken, + "&": SyntaxKind.AmpersandToken, + "|": SyntaxKind.BarToken, + "^": SyntaxKind.CaretToken, + "!": SyntaxKind.ExclamationToken, + "~": SyntaxKind.TildeToken, + "&&": SyntaxKind.AmpersandAmpersandToken, + "||": SyntaxKind.BarBarToken, + "?": SyntaxKind.QuestionToken, + "??": SyntaxKind.QuestionQuestionToken, + "?.": SyntaxKind.QuestionDotToken, + ":": SyntaxKind.ColonToken, + "=": SyntaxKind.EqualsToken, + "+=": SyntaxKind.PlusEqualsToken, + "-=": SyntaxKind.MinusEqualsToken, + "*=": SyntaxKind.AsteriskEqualsToken, + "**=": SyntaxKind.AsteriskAsteriskEqualsToken, + "/=": SyntaxKind.SlashEqualsToken, + "%=": SyntaxKind.PercentEqualsToken, + "<<=": SyntaxKind.LessThanLessThanEqualsToken, + ">>=": SyntaxKind.GreaterThanGreaterThanEqualsToken, + ">>>=": SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, + "&=": SyntaxKind.AmpersandEqualsToken, + "|=": SyntaxKind.BarEqualsToken, + "^=": SyntaxKind.CaretEqualsToken, + "@": SyntaxKind.AtToken, + "`": SyntaxKind.BacktickToken +}); +/* + As per ECMAScript Language Specification 3th Edition, Section 7.6: Identifiers + IdentifierStart :: + Can contain Unicode 3.0.0 categories: + Uppercase letter (Lu), + Lowercase letter (Ll), + Titlecase letter (Lt), + Modifier letter (Lm), + Other letter (Lo), or + Letter number (Nl). + IdentifierPart :: = + Can contain IdentifierStart + Unicode 3.0.0 categories: + Non-spacing mark (Mn), + Combining spacing mark (Mc), + Decimal number (Nd), or + Connector punctuation (Pc). + + Codepoint ranges for ES3 Identifiers are extracted from the Unicode 3.0.0 specification at: + http://www.unicode.org/Public/3.0-Update/UnicodeData-3.0.0.txt +*/ +const unicodeES3IdentifierStart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 543, 546, 563, 592, 685, 688, 696, 699, 705, 720, 721, 736, 740, 750, 750, 890, 890, 902, 902, 904, 906, 908, 908, 910, 929, 931, 974, 976, 983, 986, 1011, 1024, 1153, 1164, 1220, 1223, 1224, 1227, 1228, 1232, 1269, 1272, 1273, 1329, 1366, 1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1569, 1594, 1600, 1610, 1649, 1747, 1749, 1749, 1765, 1766, 1786, 1788, 1808, 1808, 1810, 1836, 1920, 1957, 2309, 2361, 2365, 2365, 2384, 2384, 2392, 2401, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2524, 2525, 2527, 2529, 2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2699, 2701, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2784, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2870, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 2997, 2999, 3001, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3168, 3169, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3294, 3294, 3296, 3297, 3333, 3340, 3342, 3344, 3346, 3368, 3370, 3385, 3424, 3425, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760, 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3805, 3840, 3840, 3904, 3911, 3913, 3946, 3976, 3979, 4096, 4129, 4131, 4135, 4137, 4138, 4176, 4181, 4256, 4293, 4304, 4342, 4352, 4441, 4447, 4514, 4520, 4601, 4608, 4614, 4616, 4678, 4680, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4742, 4744, 4744, 4746, 4749, 4752, 4782, 4784, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4814, 4816, 4822, 4824, 4846, 4848, 4878, 4880, 4880, 4882, 4885, 4888, 4894, 4896, 4934, 4936, 4954, 5024, 5108, 5121, 5740, 5743, 5750, 5761, 5786, 5792, 5866, 6016, 6067, 6176, 6263, 6272, 6312, 7680, 7835, 7840, 7929, 7936, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8319, 8319, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8497, 8499, 8505, 8544, 8579, 12293, 12295, 12321, 12329, 12337, 12341, 12344, 12346, 12353, 12436, 12445, 12446, 12449, 12538, 12540, 12542, 12549, 12588, 12593, 12686, 12704, 12727, 13312, 19893, 19968, 40869, 40960, 42124, 44032, 55203, 63744, 64045, 64256, 64262, 64275, 64279, 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65136, 65138, 65140, 65140, 65142, 65276, 65313, 65338, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500,]; +const unicodeES3IdentifierPart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 543, 546, 563, 592, 685, 688, 696, 699, 705, 720, 721, 736, 740, 750, 750, 768, 846, 864, 866, 890, 890, 902, 902, 904, 906, 908, 908, 910, 929, 931, 974, 976, 983, 986, 1011, 1024, 1153, 1155, 1158, 1164, 1220, 1223, 1224, 1227, 1228, 1232, 1269, 1272, 1273, 1329, 1366, 1369, 1369, 1377, 1415, 1425, 1441, 1443, 1465, 1467, 1469, 1471, 1471, 1473, 1474, 1476, 1476, 1488, 1514, 1520, 1522, 1569, 1594, 1600, 1621, 1632, 1641, 1648, 1747, 1749, 1756, 1759, 1768, 1770, 1773, 1776, 1788, 1808, 1836, 1840, 1866, 1920, 1968, 2305, 2307, 2309, 2361, 2364, 2381, 2384, 2388, 2392, 2403, 2406, 2415, 2433, 2435, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2492, 2492, 2494, 2500, 2503, 2504, 2507, 2509, 2519, 2519, 2524, 2525, 2527, 2531, 2534, 2545, 2562, 2562, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2649, 2652, 2654, 2654, 2662, 2676, 2689, 2691, 2693, 2699, 2701, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765, 2768, 2768, 2784, 2784, 2790, 2799, 2817, 2819, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2870, 2873, 2876, 2883, 2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2913, 2918, 2927, 2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 2997, 2999, 3001, 3006, 3010, 3014, 3016, 3018, 3021, 3031, 3031, 3047, 3055, 3073, 3075, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3134, 3140, 3142, 3144, 3146, 3149, 3157, 3158, 3168, 3169, 3174, 3183, 3202, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3262, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3297, 3302, 3311, 3330, 3331, 3333, 3340, 3342, 3344, 3346, 3368, 3370, 3385, 3390, 3395, 3398, 3400, 3402, 3405, 3415, 3415, 3424, 3425, 3430, 3439, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551, 3570, 3571, 3585, 3642, 3648, 3662, 3664, 3673, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3769, 3771, 3773, 3776, 3780, 3782, 3782, 3784, 3789, 3792, 3801, 3804, 3805, 3840, 3840, 3864, 3865, 3872, 3881, 3893, 3893, 3895, 3895, 3897, 3897, 3902, 3911, 3913, 3946, 3953, 3972, 3974, 3979, 3984, 3991, 3993, 4028, 4038, 4038, 4096, 4129, 4131, 4135, 4137, 4138, 4140, 4146, 4150, 4153, 4160, 4169, 4176, 4185, 4256, 4293, 4304, 4342, 4352, 4441, 4447, 4514, 4520, 4601, 4608, 4614, 4616, 4678, 4680, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4742, 4744, 4744, 4746, 4749, 4752, 4782, 4784, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4814, 4816, 4822, 4824, 4846, 4848, 4878, 4880, 4880, 4882, 4885, 4888, 4894, 4896, 4934, 4936, 4954, 4969, 4977, 5024, 5108, 5121, 5740, 5743, 5750, 5761, 5786, 5792, 5866, 6016, 6099, 6112, 6121, 6160, 6169, 6176, 6263, 6272, 6313, 7680, 7835, 7840, 7929, 7936, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8255, 8256, 8319, 8319, 8400, 8412, 8417, 8417, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8497, 8499, 8505, 8544, 8579, 12293, 12295, 12321, 12335, 12337, 12341, 12344, 12346, 12353, 12436, 12441, 12442, 12445, 12446, 12449, 12542, 12549, 12588, 12593, 12686, 12704, 12727, 13312, 19893, 19968, 40869, 40960, 42124, 44032, 55203, 63744, 64045, 64256, 64262, 64275, 64279, 64285, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65056, 65059, 65075, 65076, 65101, 65103, 65136, 65138, 65140, 65140, 65142, 65276, 65296, 65305, 65313, 65338, 65343, 65343, 65345, 65370, 65381, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500,]; +/* + As per ECMAScript Language Specification 5th Edition, Section 7.6: ISyntaxToken Names and Identifiers + IdentifierStart :: + Can contain Unicode 6.2 categories: + Uppercase letter (Lu), + Lowercase letter (Ll), + Titlecase letter (Lt), + Modifier letter (Lm), + Other letter (Lo), or + Letter number (Nl). + IdentifierPart :: + Can contain IdentifierStart + Unicode 6.2 categories: + Non-spacing mark (Mn), + Combining spacing mark (Mc), + Decimal number (Nd), + Connector punctuation (Pc), + , or + . + + Codepoint ranges for ES5 Identifiers are extracted from the Unicode 6.2 specification at: + http://www.unicode.org/Public/6.2.0/ucd/UnicodeData.txt +*/ +const unicodeES5IdentifierStart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 880, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1162, 1319, 1329, 1366, 1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1568, 1610, 1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775, 1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957, 1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2048, 2069, 2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2208, 2208, 2210, 2220, 2308, 2361, 2365, 2365, 2384, 2384, 2392, 2401, 2417, 2423, 2425, 2431, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529, 2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929, 2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3133, 3133, 3160, 3161, 3168, 3169, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261, 3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344, 3346, 3386, 3389, 3389, 3406, 3406, 3424, 3425, 3450, 3455, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760, 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3807, 3840, 3840, 3904, 3911, 3913, 3948, 3976, 3980, 4096, 4138, 4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198, 4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4992, 5007, 5024, 5108, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900, 5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000, 6016, 6067, 6103, 6103, 6108, 6108, 6176, 6263, 6272, 6312, 6314, 6314, 6320, 6389, 6400, 6428, 6480, 6509, 6512, 6516, 6528, 6571, 6593, 6599, 6656, 6678, 6688, 6740, 6823, 6823, 6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7098, 7141, 7168, 7203, 7245, 7247, 7258, 7293, 7401, 7404, 7406, 7409, 7413, 7414, 7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8305, 8305, 8319, 8319, 8336, 8348, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11502, 11506, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11823, 11823, 12293, 12295, 12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438, 12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40908, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42527, 42538, 42539, 42560, 42606, 42623, 42647, 42656, 42735, 42775, 42783, 42786, 42888, 42891, 42894, 42896, 42899, 42912, 42922, 43000, 43009, 43011, 43013, 43015, 43018, 43020, 43042, 43072, 43123, 43138, 43187, 43250, 43255, 43259, 43259, 43274, 43301, 43312, 43334, 43360, 43388, 43396, 43442, 43471, 43471, 43520, 43560, 43584, 43586, 43588, 43595, 43616, 43638, 43642, 43642, 43648, 43695, 43697, 43697, 43701, 43702, 43705, 43709, 43712, 43712, 43714, 43714, 43739, 43741, 43744, 43754, 43762, 43764, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43968, 44002, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65136, 65140, 65142, 65276, 65313, 65338, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500,]; +const unicodeES5IdentifierPart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 768, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1155, 1159, 1162, 1319, 1329, 1366, 1369, 1369, 1377, 1415, 1425, 1469, 1471, 1471, 1473, 1474, 1476, 1477, 1479, 1479, 1488, 1514, 1520, 1522, 1552, 1562, 1568, 1641, 1646, 1747, 1749, 1756, 1759, 1768, 1770, 1788, 1791, 1791, 1808, 1866, 1869, 1969, 1984, 2037, 2042, 2042, 2048, 2093, 2112, 2139, 2208, 2208, 2210, 2220, 2276, 2302, 2304, 2403, 2406, 2415, 2417, 2423, 2425, 2431, 2433, 2435, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2492, 2500, 2503, 2504, 2507, 2510, 2519, 2519, 2524, 2525, 2527, 2531, 2534, 2545, 2561, 2563, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2641, 2641, 2649, 2652, 2654, 2654, 2662, 2677, 2689, 2691, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765, 2768, 2768, 2784, 2787, 2790, 2799, 2817, 2819, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2876, 2884, 2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2915, 2918, 2927, 2929, 2929, 2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3006, 3010, 3014, 3016, 3018, 3021, 3024, 3024, 3031, 3031, 3046, 3055, 3073, 3075, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3133, 3140, 3142, 3144, 3146, 3149, 3157, 3158, 3160, 3161, 3168, 3171, 3174, 3183, 3202, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3260, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3299, 3302, 3311, 3313, 3314, 3330, 3331, 3333, 3340, 3342, 3344, 3346, 3386, 3389, 3396, 3398, 3400, 3402, 3406, 3415, 3415, 3424, 3427, 3430, 3439, 3450, 3455, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551, 3570, 3571, 3585, 3642, 3648, 3662, 3664, 3673, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3769, 3771, 3773, 3776, 3780, 3782, 3782, 3784, 3789, 3792, 3801, 3804, 3807, 3840, 3840, 3864, 3865, 3872, 3881, 3893, 3893, 3895, 3895, 3897, 3897, 3902, 3911, 3913, 3948, 3953, 3972, 3974, 3991, 3993, 4028, 4038, 4038, 4096, 4169, 4176, 4253, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4957, 4959, 4992, 5007, 5024, 5108, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900, 5902, 5908, 5920, 5940, 5952, 5971, 5984, 5996, 5998, 6000, 6002, 6003, 6016, 6099, 6103, 6103, 6108, 6109, 6112, 6121, 6155, 6157, 6160, 6169, 6176, 6263, 6272, 6314, 6320, 6389, 6400, 6428, 6432, 6443, 6448, 6459, 6470, 6509, 6512, 6516, 6528, 6571, 6576, 6601, 6608, 6617, 6656, 6683, 6688, 6750, 6752, 6780, 6783, 6793, 6800, 6809, 6823, 6823, 6912, 6987, 6992, 7001, 7019, 7027, 7040, 7155, 7168, 7223, 7232, 7241, 7245, 7293, 7376, 7378, 7380, 7414, 7424, 7654, 7676, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8204, 8205, 8255, 8256, 8276, 8276, 8305, 8305, 8319, 8319, 8336, 8348, 8400, 8412, 8417, 8417, 8421, 8432, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11647, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11744, 11775, 11823, 11823, 12293, 12295, 12321, 12335, 12337, 12341, 12344, 12348, 12353, 12438, 12441, 12442, 12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40908, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42539, 42560, 42607, 42612, 42621, 42623, 42647, 42655, 42737, 42775, 42783, 42786, 42888, 42891, 42894, 42896, 42899, 42912, 42922, 43000, 43047, 43072, 43123, 43136, 43204, 43216, 43225, 43232, 43255, 43259, 43259, 43264, 43309, 43312, 43347, 43360, 43388, 43392, 43456, 43471, 43481, 43520, 43574, 43584, 43597, 43600, 43609, 43616, 43638, 43642, 43643, 43648, 43714, 43739, 43741, 43744, 43759, 43762, 43766, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43968, 44010, 44012, 44013, 44016, 44025, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65024, 65039, 65056, 65062, 65075, 65076, 65101, 65103, 65136, 65140, 65142, 65276, 65296, 65305, 65313, 65338, 65343, 65343, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500,]; +/** + * Generated by scripts/regenerate-unicode-identifier-parts.js on node v12.4.0 with unicode 12.1 + * based on http://www.unicode.org/reports/tr31/ and https://www.ecma-international.org/ecma-262/6.0/#sec-names-and-keywords + * unicodeESNextIdentifierStart corresponds to the ID_Start and Other_ID_Start property, and + * unicodeESNextIdentifierPart corresponds to ID_Continue, Other_ID_Continue, plus ID_Start and Other_ID_Start + */ +const unicodeESNextIdentifierStart = [65, 90, 97, 122, 170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 880, 884, 886, 887, 890, 893, 895, 895, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1162, 1327, 1329, 1366, 1369, 1369, 1376, 1416, 1488, 1514, 1519, 1522, 1568, 1610, 1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775, 1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957, 1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2048, 2069, 2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2144, 2154, 2208, 2228, 2230, 2237, 2308, 2361, 2365, 2365, 2384, 2384, 2392, 2401, 2417, 2432, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529, 2544, 2545, 2556, 2556, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785, 2809, 2809, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929, 2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3129, 3133, 3133, 3160, 3162, 3168, 3169, 3200, 3200, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261, 3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344, 3346, 3386, 3389, 3389, 3406, 3406, 3412, 3414, 3423, 3425, 3450, 3455, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, 3718, 3722, 3724, 3747, 3749, 3749, 3751, 3760, 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3807, 3840, 3840, 3904, 3911, 3913, 3948, 3976, 3980, 4096, 4138, 4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198, 4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4992, 5007, 5024, 5109, 5112, 5117, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5880, 5888, 5900, 5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000, 6016, 6067, 6103, 6103, 6108, 6108, 6176, 6264, 6272, 6312, 6314, 6314, 6320, 6389, 6400, 6430, 6480, 6509, 6512, 6516, 6528, 6571, 6576, 6601, 6656, 6678, 6688, 6740, 6823, 6823, 6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7098, 7141, 7168, 7203, 7245, 7247, 7258, 7293, 7296, 7304, 7312, 7354, 7357, 7359, 7401, 7404, 7406, 7411, 7413, 7414, 7418, 7418, 7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8305, 8305, 8319, 8319, 8336, 8348, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8472, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11502, 11506, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 12293, 12295, 12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438, 12443, 12447, 12449, 12538, 12540, 12543, 12549, 12591, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40943, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42527, 42538, 42539, 42560, 42606, 42623, 42653, 42656, 42735, 42775, 42783, 42786, 42888, 42891, 42943, 42946, 42950, 42999, 43009, 43011, 43013, 43015, 43018, 43020, 43042, 43072, 43123, 43138, 43187, 43250, 43255, 43259, 43259, 43261, 43262, 43274, 43301, 43312, 43334, 43360, 43388, 43396, 43442, 43471, 43471, 43488, 43492, 43494, 43503, 43514, 43518, 43520, 43560, 43584, 43586, 43588, 43595, 43616, 43638, 43642, 43642, 43646, 43695, 43697, 43697, 43701, 43702, 43705, 43709, 43712, 43712, 43714, 43714, 43739, 43741, 43744, 43754, 43762, 43764, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43824, 43866, 43868, 43879, 43888, 44002, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65136, 65140, 65142, 65276, 65313, 65338, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, 65536, 65547, 65549, 65574, 65576, 65594, 65596, 65597, 65599, 65613, 65616, 65629, 65664, 65786, 65856, 65908, 66176, 66204, 66208, 66256, 66304, 66335, 66349, 66378, 66384, 66421, 66432, 66461, 66464, 66499, 66504, 66511, 66513, 66517, 66560, 66717, 66736, 66771, 66776, 66811, 66816, 66855, 66864, 66915, 67072, 67382, 67392, 67413, 67424, 67431, 67584, 67589, 67592, 67592, 67594, 67637, 67639, 67640, 67644, 67644, 67647, 67669, 67680, 67702, 67712, 67742, 67808, 67826, 67828, 67829, 67840, 67861, 67872, 67897, 67968, 68023, 68030, 68031, 68096, 68096, 68112, 68115, 68117, 68119, 68121, 68149, 68192, 68220, 68224, 68252, 68288, 68295, 68297, 68324, 68352, 68405, 68416, 68437, 68448, 68466, 68480, 68497, 68608, 68680, 68736, 68786, 68800, 68850, 68864, 68899, 69376, 69404, 69415, 69415, 69424, 69445, 69600, 69622, 69635, 69687, 69763, 69807, 69840, 69864, 69891, 69926, 69956, 69956, 69968, 70002, 70006, 70006, 70019, 70066, 70081, 70084, 70106, 70106, 70108, 70108, 70144, 70161, 70163, 70187, 70272, 70278, 70280, 70280, 70282, 70285, 70287, 70301, 70303, 70312, 70320, 70366, 70405, 70412, 70415, 70416, 70419, 70440, 70442, 70448, 70450, 70451, 70453, 70457, 70461, 70461, 70480, 70480, 70493, 70497, 70656, 70708, 70727, 70730, 70751, 70751, 70784, 70831, 70852, 70853, 70855, 70855, 71040, 71086, 71128, 71131, 71168, 71215, 71236, 71236, 71296, 71338, 71352, 71352, 71424, 71450, 71680, 71723, 71840, 71903, 71935, 71935, 72096, 72103, 72106, 72144, 72161, 72161, 72163, 72163, 72192, 72192, 72203, 72242, 72250, 72250, 72272, 72272, 72284, 72329, 72349, 72349, 72384, 72440, 72704, 72712, 72714, 72750, 72768, 72768, 72818, 72847, 72960, 72966, 72968, 72969, 72971, 73008, 73030, 73030, 73056, 73061, 73063, 73064, 73066, 73097, 73112, 73112, 73440, 73458, 73728, 74649, 74752, 74862, 74880, 75075, 77824, 78894, 82944, 83526, 92160, 92728, 92736, 92766, 92880, 92909, 92928, 92975, 92992, 92995, 93027, 93047, 93053, 93071, 93760, 93823, 93952, 94026, 94032, 94032, 94099, 94111, 94176, 94177, 94179, 94179, 94208, 100343, 100352, 101106, 110592, 110878, 110928, 110930, 110948, 110951, 110960, 111355, 113664, 113770, 113776, 113788, 113792, 113800, 113808, 113817, 119808, 119892, 119894, 119964, 119966, 119967, 119970, 119970, 119973, 119974, 119977, 119980, 119982, 119993, 119995, 119995, 119997, 120003, 120005, 120069, 120071, 120074, 120077, 120084, 120086, 120092, 120094, 120121, 120123, 120126, 120128, 120132, 120134, 120134, 120138, 120144, 120146, 120485, 120488, 120512, 120514, 120538, 120540, 120570, 120572, 120596, 120598, 120628, 120630, 120654, 120656, 120686, 120688, 120712, 120714, 120744, 120746, 120770, 120772, 120779, 123136, 123180, 123191, 123197, 123214, 123214, 123584, 123627, 124928, 125124, 125184, 125251, 125259, 125259, 126464, 126467, 126469, 126495, 126497, 126498, 126500, 126500, 126503, 126503, 126505, 126514, 126516, 126519, 126521, 126521, 126523, 126523, 126530, 126530, 126535, 126535, 126537, 126537, 126539, 126539, 126541, 126543, 126545, 126546, 126548, 126548, 126551, 126551, 126553, 126553, 126555, 126555, 126557, 126557, 126559, 126559, 126561, 126562, 126564, 126564, 126567, 126570, 126572, 126578, 126580, 126583, 126585, 126588, 126590, 126590, 126592, 126601, 126603, 126619, 126625, 126627, 126629, 126633, 126635, 126651, 131072, 173782, 173824, 177972, 177984, 178205, 178208, 183969, 183984, 191456, 194560, 195101]; +const unicodeESNextIdentifierPart = [48, 57, 65, 90, 95, 95, 97, 122, 170, 170, 181, 181, 183, 183, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 768, 884, 886, 887, 890, 893, 895, 895, 902, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1155, 1159, 1162, 1327, 1329, 1366, 1369, 1369, 1376, 1416, 1425, 1469, 1471, 1471, 1473, 1474, 1476, 1477, 1479, 1479, 1488, 1514, 1519, 1522, 1552, 1562, 1568, 1641, 1646, 1747, 1749, 1756, 1759, 1768, 1770, 1788, 1791, 1791, 1808, 1866, 1869, 1969, 1984, 2037, 2042, 2042, 2045, 2045, 2048, 2093, 2112, 2139, 2144, 2154, 2208, 2228, 2230, 2237, 2259, 2273, 2275, 2403, 2406, 2415, 2417, 2435, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2492, 2500, 2503, 2504, 2507, 2510, 2519, 2519, 2524, 2525, 2527, 2531, 2534, 2545, 2556, 2556, 2558, 2558, 2561, 2563, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2641, 2641, 2649, 2652, 2654, 2654, 2662, 2677, 2689, 2691, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765, 2768, 2768, 2784, 2787, 2790, 2799, 2809, 2815, 2817, 2819, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2876, 2884, 2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2915, 2918, 2927, 2929, 2929, 2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3006, 3010, 3014, 3016, 3018, 3021, 3024, 3024, 3031, 3031, 3046, 3055, 3072, 3084, 3086, 3088, 3090, 3112, 3114, 3129, 3133, 3140, 3142, 3144, 3146, 3149, 3157, 3158, 3160, 3162, 3168, 3171, 3174, 3183, 3200, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3260, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3299, 3302, 3311, 3313, 3314, 3328, 3331, 3333, 3340, 3342, 3344, 3346, 3396, 3398, 3400, 3402, 3406, 3412, 3415, 3423, 3427, 3430, 3439, 3450, 3455, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551, 3558, 3567, 3570, 3571, 3585, 3642, 3648, 3662, 3664, 3673, 3713, 3714, 3716, 3716, 3718, 3722, 3724, 3747, 3749, 3749, 3751, 3773, 3776, 3780, 3782, 3782, 3784, 3789, 3792, 3801, 3804, 3807, 3840, 3840, 3864, 3865, 3872, 3881, 3893, 3893, 3895, 3895, 3897, 3897, 3902, 3911, 3913, 3948, 3953, 3972, 3974, 3991, 3993, 4028, 4038, 4038, 4096, 4169, 4176, 4253, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4957, 4959, 4969, 4977, 4992, 5007, 5024, 5109, 5112, 5117, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5880, 5888, 5900, 5902, 5908, 5920, 5940, 5952, 5971, 5984, 5996, 5998, 6000, 6002, 6003, 6016, 6099, 6103, 6103, 6108, 6109, 6112, 6121, 6155, 6157, 6160, 6169, 6176, 6264, 6272, 6314, 6320, 6389, 6400, 6430, 6432, 6443, 6448, 6459, 6470, 6509, 6512, 6516, 6528, 6571, 6576, 6601, 6608, 6618, 6656, 6683, 6688, 6750, 6752, 6780, 6783, 6793, 6800, 6809, 6823, 6823, 6832, 6845, 6912, 6987, 6992, 7001, 7019, 7027, 7040, 7155, 7168, 7223, 7232, 7241, 7245, 7293, 7296, 7304, 7312, 7354, 7357, 7359, 7376, 7378, 7380, 7418, 7424, 7673, 7675, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8255, 8256, 8276, 8276, 8305, 8305, 8319, 8319, 8336, 8348, 8400, 8412, 8417, 8417, 8421, 8432, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8472, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11647, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11744, 11775, 12293, 12295, 12321, 12335, 12337, 12341, 12344, 12348, 12353, 12438, 12441, 12447, 12449, 12538, 12540, 12543, 12549, 12591, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40943, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42539, 42560, 42607, 42612, 42621, 42623, 42737, 42775, 42783, 42786, 42888, 42891, 42943, 42946, 42950, 42999, 43047, 43072, 43123, 43136, 43205, 43216, 43225, 43232, 43255, 43259, 43259, 43261, 43309, 43312, 43347, 43360, 43388, 43392, 43456, 43471, 43481, 43488, 43518, 43520, 43574, 43584, 43597, 43600, 43609, 43616, 43638, 43642, 43714, 43739, 43741, 43744, 43759, 43762, 43766, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43824, 43866, 43868, 43879, 43888, 44010, 44012, 44013, 44016, 44025, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65024, 65039, 65056, 65071, 65075, 65076, 65101, 65103, 65136, 65140, 65142, 65276, 65296, 65305, 65313, 65338, 65343, 65343, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, 65536, 65547, 65549, 65574, 65576, 65594, 65596, 65597, 65599, 65613, 65616, 65629, 65664, 65786, 65856, 65908, 66045, 66045, 66176, 66204, 66208, 66256, 66272, 66272, 66304, 66335, 66349, 66378, 66384, 66426, 66432, 66461, 66464, 66499, 66504, 66511, 66513, 66517, 66560, 66717, 66720, 66729, 66736, 66771, 66776, 66811, 66816, 66855, 66864, 66915, 67072, 67382, 67392, 67413, 67424, 67431, 67584, 67589, 67592, 67592, 67594, 67637, 67639, 67640, 67644, 67644, 67647, 67669, 67680, 67702, 67712, 67742, 67808, 67826, 67828, 67829, 67840, 67861, 67872, 67897, 67968, 68023, 68030, 68031, 68096, 68099, 68101, 68102, 68108, 68115, 68117, 68119, 68121, 68149, 68152, 68154, 68159, 68159, 68192, 68220, 68224, 68252, 68288, 68295, 68297, 68326, 68352, 68405, 68416, 68437, 68448, 68466, 68480, 68497, 68608, 68680, 68736, 68786, 68800, 68850, 68864, 68903, 68912, 68921, 69376, 69404, 69415, 69415, 69424, 69456, 69600, 69622, 69632, 69702, 69734, 69743, 69759, 69818, 69840, 69864, 69872, 69881, 69888, 69940, 69942, 69951, 69956, 69958, 69968, 70003, 70006, 70006, 70016, 70084, 70089, 70092, 70096, 70106, 70108, 70108, 70144, 70161, 70163, 70199, 70206, 70206, 70272, 70278, 70280, 70280, 70282, 70285, 70287, 70301, 70303, 70312, 70320, 70378, 70384, 70393, 70400, 70403, 70405, 70412, 70415, 70416, 70419, 70440, 70442, 70448, 70450, 70451, 70453, 70457, 70459, 70468, 70471, 70472, 70475, 70477, 70480, 70480, 70487, 70487, 70493, 70499, 70502, 70508, 70512, 70516, 70656, 70730, 70736, 70745, 70750, 70751, 70784, 70853, 70855, 70855, 70864, 70873, 71040, 71093, 71096, 71104, 71128, 71133, 71168, 71232, 71236, 71236, 71248, 71257, 71296, 71352, 71360, 71369, 71424, 71450, 71453, 71467, 71472, 71481, 71680, 71738, 71840, 71913, 71935, 71935, 72096, 72103, 72106, 72151, 72154, 72161, 72163, 72164, 72192, 72254, 72263, 72263, 72272, 72345, 72349, 72349, 72384, 72440, 72704, 72712, 72714, 72758, 72760, 72768, 72784, 72793, 72818, 72847, 72850, 72871, 72873, 72886, 72960, 72966, 72968, 72969, 72971, 73014, 73018, 73018, 73020, 73021, 73023, 73031, 73040, 73049, 73056, 73061, 73063, 73064, 73066, 73102, 73104, 73105, 73107, 73112, 73120, 73129, 73440, 73462, 73728, 74649, 74752, 74862, 74880, 75075, 77824, 78894, 82944, 83526, 92160, 92728, 92736, 92766, 92768, 92777, 92880, 92909, 92912, 92916, 92928, 92982, 92992, 92995, 93008, 93017, 93027, 93047, 93053, 93071, 93760, 93823, 93952, 94026, 94031, 94087, 94095, 94111, 94176, 94177, 94179, 94179, 94208, 100343, 100352, 101106, 110592, 110878, 110928, 110930, 110948, 110951, 110960, 111355, 113664, 113770, 113776, 113788, 113792, 113800, 113808, 113817, 113821, 113822, 119141, 119145, 119149, 119154, 119163, 119170, 119173, 119179, 119210, 119213, 119362, 119364, 119808, 119892, 119894, 119964, 119966, 119967, 119970, 119970, 119973, 119974, 119977, 119980, 119982, 119993, 119995, 119995, 119997, 120003, 120005, 120069, 120071, 120074, 120077, 120084, 120086, 120092, 120094, 120121, 120123, 120126, 120128, 120132, 120134, 120134, 120138, 120144, 120146, 120485, 120488, 120512, 120514, 120538, 120540, 120570, 120572, 120596, 120598, 120628, 120630, 120654, 120656, 120686, 120688, 120712, 120714, 120744, 120746, 120770, 120772, 120779, 120782, 120831, 121344, 121398, 121403, 121452, 121461, 121461, 121476, 121476, 121499, 121503, 121505, 121519, 122880, 122886, 122888, 122904, 122907, 122913, 122915, 122916, 122918, 122922, 123136, 123180, 123184, 123197, 123200, 123209, 123214, 123214, 123584, 123641, 124928, 125124, 125136, 125142, 125184, 125259, 125264, 125273, 126464, 126467, 126469, 126495, 126497, 126498, 126500, 126500, 126503, 126503, 126505, 126514, 126516, 126519, 126521, 126521, 126523, 126523, 126530, 126530, 126535, 126535, 126537, 126537, 126539, 126539, 126541, 126543, 126545, 126546, 126548, 126548, 126551, 126551, 126553, 126553, 126555, 126555, 126557, 126557, 126559, 126559, 126561, 126562, 126564, 126564, 126567, 126570, 126572, 126578, 126580, 126583, 126585, 126588, 126590, 126590, 126592, 126601, 126603, 126619, 126625, 126627, 126629, 126633, 126635, 126651, 131072, 173782, 173824, 177972, 177984, 178205, 178208, 183969, 183984, 191456, 194560, 195101, 917760, 917999]; +function lookupInUnicodeMap(code: number, map: readonly number[]): boolean { + // Bail out quickly if it couldn't possibly be in the map. + if (code < map[0]) { + return false; } - - const textToKeywordObj: MapLike = { - abstract: SyntaxKind.AbstractKeyword, - any: SyntaxKind.AnyKeyword, - as: SyntaxKind.AsKeyword, - asserts: SyntaxKind.AssertsKeyword, - bigint: SyntaxKind.BigIntKeyword, - boolean: SyntaxKind.BooleanKeyword, - break: SyntaxKind.BreakKeyword, - case: SyntaxKind.CaseKeyword, - catch: SyntaxKind.CatchKeyword, - class: SyntaxKind.ClassKeyword, - continue: SyntaxKind.ContinueKeyword, - const: SyntaxKind.ConstKeyword, - ["" + "constructor"]: SyntaxKind.ConstructorKeyword, - debugger: SyntaxKind.DebuggerKeyword, - declare: SyntaxKind.DeclareKeyword, - default: SyntaxKind.DefaultKeyword, - delete: SyntaxKind.DeleteKeyword, - do: SyntaxKind.DoKeyword, - else: SyntaxKind.ElseKeyword, - enum: SyntaxKind.EnumKeyword, - export: SyntaxKind.ExportKeyword, - extends: SyntaxKind.ExtendsKeyword, - false: SyntaxKind.FalseKeyword, - finally: SyntaxKind.FinallyKeyword, - for: SyntaxKind.ForKeyword, - from: SyntaxKind.FromKeyword, - function: SyntaxKind.FunctionKeyword, - get: SyntaxKind.GetKeyword, - if: SyntaxKind.IfKeyword, - implements: SyntaxKind.ImplementsKeyword, - import: SyntaxKind.ImportKeyword, - in: SyntaxKind.InKeyword, - infer: SyntaxKind.InferKeyword, - instanceof: SyntaxKind.InstanceOfKeyword, - interface: SyntaxKind.InterfaceKeyword, - is: SyntaxKind.IsKeyword, - keyof: SyntaxKind.KeyOfKeyword, - let: SyntaxKind.LetKeyword, - module: SyntaxKind.ModuleKeyword, - namespace: SyntaxKind.NamespaceKeyword, - never: SyntaxKind.NeverKeyword, - new: SyntaxKind.NewKeyword, - null: SyntaxKind.NullKeyword, - number: SyntaxKind.NumberKeyword, - object: SyntaxKind.ObjectKeyword, - package: SyntaxKind.PackageKeyword, - private: SyntaxKind.PrivateKeyword, - protected: SyntaxKind.ProtectedKeyword, - public: SyntaxKind.PublicKeyword, - readonly: SyntaxKind.ReadonlyKeyword, - require: SyntaxKind.RequireKeyword, - global: SyntaxKind.GlobalKeyword, - return: SyntaxKind.ReturnKeyword, - set: SyntaxKind.SetKeyword, - static: SyntaxKind.StaticKeyword, - string: SyntaxKind.StringKeyword, - super: SyntaxKind.SuperKeyword, - switch: SyntaxKind.SwitchKeyword, - symbol: SyntaxKind.SymbolKeyword, - this: SyntaxKind.ThisKeyword, - throw: SyntaxKind.ThrowKeyword, - true: SyntaxKind.TrueKeyword, - try: SyntaxKind.TryKeyword, - type: SyntaxKind.TypeKeyword, - typeof: SyntaxKind.TypeOfKeyword, - undefined: SyntaxKind.UndefinedKeyword, - unique: SyntaxKind.UniqueKeyword, - unknown: SyntaxKind.UnknownKeyword, - var: SyntaxKind.VarKeyword, - void: SyntaxKind.VoidKeyword, - while: SyntaxKind.WhileKeyword, - with: SyntaxKind.WithKeyword, - yield: SyntaxKind.YieldKeyword, - async: SyntaxKind.AsyncKeyword, - await: SyntaxKind.AwaitKeyword, - of: SyntaxKind.OfKeyword, - }; - - const textToKeyword = createMapFromTemplate(textToKeywordObj); - - const textToToken = createMapFromTemplate({ - ...textToKeywordObj, - "{": SyntaxKind.OpenBraceToken, - "}": SyntaxKind.CloseBraceToken, - "(": SyntaxKind.OpenParenToken, - ")": SyntaxKind.CloseParenToken, - "[": SyntaxKind.OpenBracketToken, - "]": SyntaxKind.CloseBracketToken, - ".": SyntaxKind.DotToken, - "...": SyntaxKind.DotDotDotToken, - ";": SyntaxKind.SemicolonToken, - ",": SyntaxKind.CommaToken, - "<": SyntaxKind.LessThanToken, - ">": SyntaxKind.GreaterThanToken, - "<=": SyntaxKind.LessThanEqualsToken, - ">=": SyntaxKind.GreaterThanEqualsToken, - "==": SyntaxKind.EqualsEqualsToken, - "!=": SyntaxKind.ExclamationEqualsToken, - "===": SyntaxKind.EqualsEqualsEqualsToken, - "!==": SyntaxKind.ExclamationEqualsEqualsToken, - "=>": SyntaxKind.EqualsGreaterThanToken, - "+": SyntaxKind.PlusToken, - "-": SyntaxKind.MinusToken, - "**": SyntaxKind.AsteriskAsteriskToken, - "*": SyntaxKind.AsteriskToken, - "/": SyntaxKind.SlashToken, - "%": SyntaxKind.PercentToken, - "++": SyntaxKind.PlusPlusToken, - "--": SyntaxKind.MinusMinusToken, - "<<": SyntaxKind.LessThanLessThanToken, - ">": SyntaxKind.GreaterThanGreaterThanToken, - ">>>": SyntaxKind.GreaterThanGreaterThanGreaterThanToken, - "&": SyntaxKind.AmpersandToken, - "|": SyntaxKind.BarToken, - "^": SyntaxKind.CaretToken, - "!": SyntaxKind.ExclamationToken, - "~": SyntaxKind.TildeToken, - "&&": SyntaxKind.AmpersandAmpersandToken, - "||": SyntaxKind.BarBarToken, - "?": SyntaxKind.QuestionToken, - "??": SyntaxKind.QuestionQuestionToken, - "?.": SyntaxKind.QuestionDotToken, - ":": SyntaxKind.ColonToken, - "=": SyntaxKind.EqualsToken, - "+=": SyntaxKind.PlusEqualsToken, - "-=": SyntaxKind.MinusEqualsToken, - "*=": SyntaxKind.AsteriskEqualsToken, - "**=": SyntaxKind.AsteriskAsteriskEqualsToken, - "/=": SyntaxKind.SlashEqualsToken, - "%=": SyntaxKind.PercentEqualsToken, - "<<=": SyntaxKind.LessThanLessThanEqualsToken, - ">>=": SyntaxKind.GreaterThanGreaterThanEqualsToken, - ">>>=": SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, - "&=": SyntaxKind.AmpersandEqualsToken, - "|=": SyntaxKind.BarEqualsToken, - "^=": SyntaxKind.CaretEqualsToken, - "@": SyntaxKind.AtToken, - "`": SyntaxKind.BacktickToken - }); - - /* - As per ECMAScript Language Specification 3th Edition, Section 7.6: Identifiers - IdentifierStart :: - Can contain Unicode 3.0.0 categories: - Uppercase letter (Lu), - Lowercase letter (Ll), - Titlecase letter (Lt), - Modifier letter (Lm), - Other letter (Lo), or - Letter number (Nl). - IdentifierPart :: = - Can contain IdentifierStart + Unicode 3.0.0 categories: - Non-spacing mark (Mn), - Combining spacing mark (Mc), - Decimal number (Nd), or - Connector punctuation (Pc). - - Codepoint ranges for ES3 Identifiers are extracted from the Unicode 3.0.0 specification at: - http://www.unicode.org/Public/3.0-Update/UnicodeData-3.0.0.txt - */ - const unicodeES3IdentifierStart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 543, 546, 563, 592, 685, 688, 696, 699, 705, 720, 721, 736, 740, 750, 750, 890, 890, 902, 902, 904, 906, 908, 908, 910, 929, 931, 974, 976, 983, 986, 1011, 1024, 1153, 1164, 1220, 1223, 1224, 1227, 1228, 1232, 1269, 1272, 1273, 1329, 1366, 1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1569, 1594, 1600, 1610, 1649, 1747, 1749, 1749, 1765, 1766, 1786, 1788, 1808, 1808, 1810, 1836, 1920, 1957, 2309, 2361, 2365, 2365, 2384, 2384, 2392, 2401, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2524, 2525, 2527, 2529, 2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2699, 2701, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2784, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2870, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 2997, 2999, 3001, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3168, 3169, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3294, 3294, 3296, 3297, 3333, 3340, 3342, 3344, 3346, 3368, 3370, 3385, 3424, 3425, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760, 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3805, 3840, 3840, 3904, 3911, 3913, 3946, 3976, 3979, 4096, 4129, 4131, 4135, 4137, 4138, 4176, 4181, 4256, 4293, 4304, 4342, 4352, 4441, 4447, 4514, 4520, 4601, 4608, 4614, 4616, 4678, 4680, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4742, 4744, 4744, 4746, 4749, 4752, 4782, 4784, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4814, 4816, 4822, 4824, 4846, 4848, 4878, 4880, 4880, 4882, 4885, 4888, 4894, 4896, 4934, 4936, 4954, 5024, 5108, 5121, 5740, 5743, 5750, 5761, 5786, 5792, 5866, 6016, 6067, 6176, 6263, 6272, 6312, 7680, 7835, 7840, 7929, 7936, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8319, 8319, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8497, 8499, 8505, 8544, 8579, 12293, 12295, 12321, 12329, 12337, 12341, 12344, 12346, 12353, 12436, 12445, 12446, 12449, 12538, 12540, 12542, 12549, 12588, 12593, 12686, 12704, 12727, 13312, 19893, 19968, 40869, 40960, 42124, 44032, 55203, 63744, 64045, 64256, 64262, 64275, 64279, 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65136, 65138, 65140, 65140, 65142, 65276, 65313, 65338, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; - const unicodeES3IdentifierPart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 543, 546, 563, 592, 685, 688, 696, 699, 705, 720, 721, 736, 740, 750, 750, 768, 846, 864, 866, 890, 890, 902, 902, 904, 906, 908, 908, 910, 929, 931, 974, 976, 983, 986, 1011, 1024, 1153, 1155, 1158, 1164, 1220, 1223, 1224, 1227, 1228, 1232, 1269, 1272, 1273, 1329, 1366, 1369, 1369, 1377, 1415, 1425, 1441, 1443, 1465, 1467, 1469, 1471, 1471, 1473, 1474, 1476, 1476, 1488, 1514, 1520, 1522, 1569, 1594, 1600, 1621, 1632, 1641, 1648, 1747, 1749, 1756, 1759, 1768, 1770, 1773, 1776, 1788, 1808, 1836, 1840, 1866, 1920, 1968, 2305, 2307, 2309, 2361, 2364, 2381, 2384, 2388, 2392, 2403, 2406, 2415, 2433, 2435, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2492, 2492, 2494, 2500, 2503, 2504, 2507, 2509, 2519, 2519, 2524, 2525, 2527, 2531, 2534, 2545, 2562, 2562, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2649, 2652, 2654, 2654, 2662, 2676, 2689, 2691, 2693, 2699, 2701, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765, 2768, 2768, 2784, 2784, 2790, 2799, 2817, 2819, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2870, 2873, 2876, 2883, 2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2913, 2918, 2927, 2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 2997, 2999, 3001, 3006, 3010, 3014, 3016, 3018, 3021, 3031, 3031, 3047, 3055, 3073, 3075, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3134, 3140, 3142, 3144, 3146, 3149, 3157, 3158, 3168, 3169, 3174, 3183, 3202, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3262, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3297, 3302, 3311, 3330, 3331, 3333, 3340, 3342, 3344, 3346, 3368, 3370, 3385, 3390, 3395, 3398, 3400, 3402, 3405, 3415, 3415, 3424, 3425, 3430, 3439, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551, 3570, 3571, 3585, 3642, 3648, 3662, 3664, 3673, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3769, 3771, 3773, 3776, 3780, 3782, 3782, 3784, 3789, 3792, 3801, 3804, 3805, 3840, 3840, 3864, 3865, 3872, 3881, 3893, 3893, 3895, 3895, 3897, 3897, 3902, 3911, 3913, 3946, 3953, 3972, 3974, 3979, 3984, 3991, 3993, 4028, 4038, 4038, 4096, 4129, 4131, 4135, 4137, 4138, 4140, 4146, 4150, 4153, 4160, 4169, 4176, 4185, 4256, 4293, 4304, 4342, 4352, 4441, 4447, 4514, 4520, 4601, 4608, 4614, 4616, 4678, 4680, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4742, 4744, 4744, 4746, 4749, 4752, 4782, 4784, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4814, 4816, 4822, 4824, 4846, 4848, 4878, 4880, 4880, 4882, 4885, 4888, 4894, 4896, 4934, 4936, 4954, 4969, 4977, 5024, 5108, 5121, 5740, 5743, 5750, 5761, 5786, 5792, 5866, 6016, 6099, 6112, 6121, 6160, 6169, 6176, 6263, 6272, 6313, 7680, 7835, 7840, 7929, 7936, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8255, 8256, 8319, 8319, 8400, 8412, 8417, 8417, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8497, 8499, 8505, 8544, 8579, 12293, 12295, 12321, 12335, 12337, 12341, 12344, 12346, 12353, 12436, 12441, 12442, 12445, 12446, 12449, 12542, 12549, 12588, 12593, 12686, 12704, 12727, 13312, 19893, 19968, 40869, 40960, 42124, 44032, 55203, 63744, 64045, 64256, 64262, 64275, 64279, 64285, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65056, 65059, 65075, 65076, 65101, 65103, 65136, 65138, 65140, 65140, 65142, 65276, 65296, 65305, 65313, 65338, 65343, 65343, 65345, 65370, 65381, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; - - /* - As per ECMAScript Language Specification 5th Edition, Section 7.6: ISyntaxToken Names and Identifiers - IdentifierStart :: - Can contain Unicode 6.2 categories: - Uppercase letter (Lu), - Lowercase letter (Ll), - Titlecase letter (Lt), - Modifier letter (Lm), - Other letter (Lo), or - Letter number (Nl). - IdentifierPart :: - Can contain IdentifierStart + Unicode 6.2 categories: - Non-spacing mark (Mn), - Combining spacing mark (Mc), - Decimal number (Nd), - Connector punctuation (Pc), - , or - . - - Codepoint ranges for ES5 Identifiers are extracted from the Unicode 6.2 specification at: - http://www.unicode.org/Public/6.2.0/ucd/UnicodeData.txt - */ - const unicodeES5IdentifierStart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 880, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1162, 1319, 1329, 1366, 1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1568, 1610, 1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775, 1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957, 1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2048, 2069, 2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2208, 2208, 2210, 2220, 2308, 2361, 2365, 2365, 2384, 2384, 2392, 2401, 2417, 2423, 2425, 2431, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529, 2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929, 2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3133, 3133, 3160, 3161, 3168, 3169, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261, 3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344, 3346, 3386, 3389, 3389, 3406, 3406, 3424, 3425, 3450, 3455, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760, 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3807, 3840, 3840, 3904, 3911, 3913, 3948, 3976, 3980, 4096, 4138, 4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198, 4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4992, 5007, 5024, 5108, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900, 5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000, 6016, 6067, 6103, 6103, 6108, 6108, 6176, 6263, 6272, 6312, 6314, 6314, 6320, 6389, 6400, 6428, 6480, 6509, 6512, 6516, 6528, 6571, 6593, 6599, 6656, 6678, 6688, 6740, 6823, 6823, 6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7098, 7141, 7168, 7203, 7245, 7247, 7258, 7293, 7401, 7404, 7406, 7409, 7413, 7414, 7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8305, 8305, 8319, 8319, 8336, 8348, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11502, 11506, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11823, 11823, 12293, 12295, 12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438, 12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40908, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42527, 42538, 42539, 42560, 42606, 42623, 42647, 42656, 42735, 42775, 42783, 42786, 42888, 42891, 42894, 42896, 42899, 42912, 42922, 43000, 43009, 43011, 43013, 43015, 43018, 43020, 43042, 43072, 43123, 43138, 43187, 43250, 43255, 43259, 43259, 43274, 43301, 43312, 43334, 43360, 43388, 43396, 43442, 43471, 43471, 43520, 43560, 43584, 43586, 43588, 43595, 43616, 43638, 43642, 43642, 43648, 43695, 43697, 43697, 43701, 43702, 43705, 43709, 43712, 43712, 43714, 43714, 43739, 43741, 43744, 43754, 43762, 43764, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43968, 44002, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65136, 65140, 65142, 65276, 65313, 65338, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; - const unicodeES5IdentifierPart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 768, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1155, 1159, 1162, 1319, 1329, 1366, 1369, 1369, 1377, 1415, 1425, 1469, 1471, 1471, 1473, 1474, 1476, 1477, 1479, 1479, 1488, 1514, 1520, 1522, 1552, 1562, 1568, 1641, 1646, 1747, 1749, 1756, 1759, 1768, 1770, 1788, 1791, 1791, 1808, 1866, 1869, 1969, 1984, 2037, 2042, 2042, 2048, 2093, 2112, 2139, 2208, 2208, 2210, 2220, 2276, 2302, 2304, 2403, 2406, 2415, 2417, 2423, 2425, 2431, 2433, 2435, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2492, 2500, 2503, 2504, 2507, 2510, 2519, 2519, 2524, 2525, 2527, 2531, 2534, 2545, 2561, 2563, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2641, 2641, 2649, 2652, 2654, 2654, 2662, 2677, 2689, 2691, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765, 2768, 2768, 2784, 2787, 2790, 2799, 2817, 2819, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2876, 2884, 2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2915, 2918, 2927, 2929, 2929, 2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3006, 3010, 3014, 3016, 3018, 3021, 3024, 3024, 3031, 3031, 3046, 3055, 3073, 3075, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3133, 3140, 3142, 3144, 3146, 3149, 3157, 3158, 3160, 3161, 3168, 3171, 3174, 3183, 3202, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3260, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3299, 3302, 3311, 3313, 3314, 3330, 3331, 3333, 3340, 3342, 3344, 3346, 3386, 3389, 3396, 3398, 3400, 3402, 3406, 3415, 3415, 3424, 3427, 3430, 3439, 3450, 3455, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551, 3570, 3571, 3585, 3642, 3648, 3662, 3664, 3673, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3769, 3771, 3773, 3776, 3780, 3782, 3782, 3784, 3789, 3792, 3801, 3804, 3807, 3840, 3840, 3864, 3865, 3872, 3881, 3893, 3893, 3895, 3895, 3897, 3897, 3902, 3911, 3913, 3948, 3953, 3972, 3974, 3991, 3993, 4028, 4038, 4038, 4096, 4169, 4176, 4253, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4957, 4959, 4992, 5007, 5024, 5108, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900, 5902, 5908, 5920, 5940, 5952, 5971, 5984, 5996, 5998, 6000, 6002, 6003, 6016, 6099, 6103, 6103, 6108, 6109, 6112, 6121, 6155, 6157, 6160, 6169, 6176, 6263, 6272, 6314, 6320, 6389, 6400, 6428, 6432, 6443, 6448, 6459, 6470, 6509, 6512, 6516, 6528, 6571, 6576, 6601, 6608, 6617, 6656, 6683, 6688, 6750, 6752, 6780, 6783, 6793, 6800, 6809, 6823, 6823, 6912, 6987, 6992, 7001, 7019, 7027, 7040, 7155, 7168, 7223, 7232, 7241, 7245, 7293, 7376, 7378, 7380, 7414, 7424, 7654, 7676, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8204, 8205, 8255, 8256, 8276, 8276, 8305, 8305, 8319, 8319, 8336, 8348, 8400, 8412, 8417, 8417, 8421, 8432, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11647, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11744, 11775, 11823, 11823, 12293, 12295, 12321, 12335, 12337, 12341, 12344, 12348, 12353, 12438, 12441, 12442, 12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40908, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42539, 42560, 42607, 42612, 42621, 42623, 42647, 42655, 42737, 42775, 42783, 42786, 42888, 42891, 42894, 42896, 42899, 42912, 42922, 43000, 43047, 43072, 43123, 43136, 43204, 43216, 43225, 43232, 43255, 43259, 43259, 43264, 43309, 43312, 43347, 43360, 43388, 43392, 43456, 43471, 43481, 43520, 43574, 43584, 43597, 43600, 43609, 43616, 43638, 43642, 43643, 43648, 43714, 43739, 43741, 43744, 43759, 43762, 43766, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43968, 44010, 44012, 44013, 44016, 44025, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65024, 65039, 65056, 65062, 65075, 65076, 65101, 65103, 65136, 65140, 65142, 65276, 65296, 65305, 65313, 65338, 65343, 65343, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; - - /** - * Generated by scripts/regenerate-unicode-identifier-parts.js on node v12.4.0 with unicode 12.1 - * based on http://www.unicode.org/reports/tr31/ and https://www.ecma-international.org/ecma-262/6.0/#sec-names-and-keywords - * unicodeESNextIdentifierStart corresponds to the ID_Start and Other_ID_Start property, and - * unicodeESNextIdentifierPart corresponds to ID_Continue, Other_ID_Continue, plus ID_Start and Other_ID_Start - */ - const unicodeESNextIdentifierStart = [65, 90, 97, 122, 170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 880, 884, 886, 887, 890, 893, 895, 895, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1162, 1327, 1329, 1366, 1369, 1369, 1376, 1416, 1488, 1514, 1519, 1522, 1568, 1610, 1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775, 1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957, 1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2048, 2069, 2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2144, 2154, 2208, 2228, 2230, 2237, 2308, 2361, 2365, 2365, 2384, 2384, 2392, 2401, 2417, 2432, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529, 2544, 2545, 2556, 2556, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785, 2809, 2809, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929, 2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3129, 3133, 3133, 3160, 3162, 3168, 3169, 3200, 3200, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261, 3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344, 3346, 3386, 3389, 3389, 3406, 3406, 3412, 3414, 3423, 3425, 3450, 3455, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, 3718, 3722, 3724, 3747, 3749, 3749, 3751, 3760, 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3807, 3840, 3840, 3904, 3911, 3913, 3948, 3976, 3980, 4096, 4138, 4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198, 4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4992, 5007, 5024, 5109, 5112, 5117, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5880, 5888, 5900, 5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000, 6016, 6067, 6103, 6103, 6108, 6108, 6176, 6264, 6272, 6312, 6314, 6314, 6320, 6389, 6400, 6430, 6480, 6509, 6512, 6516, 6528, 6571, 6576, 6601, 6656, 6678, 6688, 6740, 6823, 6823, 6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7098, 7141, 7168, 7203, 7245, 7247, 7258, 7293, 7296, 7304, 7312, 7354, 7357, 7359, 7401, 7404, 7406, 7411, 7413, 7414, 7418, 7418, 7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8305, 8305, 8319, 8319, 8336, 8348, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8472, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11502, 11506, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 12293, 12295, 12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438, 12443, 12447, 12449, 12538, 12540, 12543, 12549, 12591, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40943, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42527, 42538, 42539, 42560, 42606, 42623, 42653, 42656, 42735, 42775, 42783, 42786, 42888, 42891, 42943, 42946, 42950, 42999, 43009, 43011, 43013, 43015, 43018, 43020, 43042, 43072, 43123, 43138, 43187, 43250, 43255, 43259, 43259, 43261, 43262, 43274, 43301, 43312, 43334, 43360, 43388, 43396, 43442, 43471, 43471, 43488, 43492, 43494, 43503, 43514, 43518, 43520, 43560, 43584, 43586, 43588, 43595, 43616, 43638, 43642, 43642, 43646, 43695, 43697, 43697, 43701, 43702, 43705, 43709, 43712, 43712, 43714, 43714, 43739, 43741, 43744, 43754, 43762, 43764, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43824, 43866, 43868, 43879, 43888, 44002, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65136, 65140, 65142, 65276, 65313, 65338, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, 65536, 65547, 65549, 65574, 65576, 65594, 65596, 65597, 65599, 65613, 65616, 65629, 65664, 65786, 65856, 65908, 66176, 66204, 66208, 66256, 66304, 66335, 66349, 66378, 66384, 66421, 66432, 66461, 66464, 66499, 66504, 66511, 66513, 66517, 66560, 66717, 66736, 66771, 66776, 66811, 66816, 66855, 66864, 66915, 67072, 67382, 67392, 67413, 67424, 67431, 67584, 67589, 67592, 67592, 67594, 67637, 67639, 67640, 67644, 67644, 67647, 67669, 67680, 67702, 67712, 67742, 67808, 67826, 67828, 67829, 67840, 67861, 67872, 67897, 67968, 68023, 68030, 68031, 68096, 68096, 68112, 68115, 68117, 68119, 68121, 68149, 68192, 68220, 68224, 68252, 68288, 68295, 68297, 68324, 68352, 68405, 68416, 68437, 68448, 68466, 68480, 68497, 68608, 68680, 68736, 68786, 68800, 68850, 68864, 68899, 69376, 69404, 69415, 69415, 69424, 69445, 69600, 69622, 69635, 69687, 69763, 69807, 69840, 69864, 69891, 69926, 69956, 69956, 69968, 70002, 70006, 70006, 70019, 70066, 70081, 70084, 70106, 70106, 70108, 70108, 70144, 70161, 70163, 70187, 70272, 70278, 70280, 70280, 70282, 70285, 70287, 70301, 70303, 70312, 70320, 70366, 70405, 70412, 70415, 70416, 70419, 70440, 70442, 70448, 70450, 70451, 70453, 70457, 70461, 70461, 70480, 70480, 70493, 70497, 70656, 70708, 70727, 70730, 70751, 70751, 70784, 70831, 70852, 70853, 70855, 70855, 71040, 71086, 71128, 71131, 71168, 71215, 71236, 71236, 71296, 71338, 71352, 71352, 71424, 71450, 71680, 71723, 71840, 71903, 71935, 71935, 72096, 72103, 72106, 72144, 72161, 72161, 72163, 72163, 72192, 72192, 72203, 72242, 72250, 72250, 72272, 72272, 72284, 72329, 72349, 72349, 72384, 72440, 72704, 72712, 72714, 72750, 72768, 72768, 72818, 72847, 72960, 72966, 72968, 72969, 72971, 73008, 73030, 73030, 73056, 73061, 73063, 73064, 73066, 73097, 73112, 73112, 73440, 73458, 73728, 74649, 74752, 74862, 74880, 75075, 77824, 78894, 82944, 83526, 92160, 92728, 92736, 92766, 92880, 92909, 92928, 92975, 92992, 92995, 93027, 93047, 93053, 93071, 93760, 93823, 93952, 94026, 94032, 94032, 94099, 94111, 94176, 94177, 94179, 94179, 94208, 100343, 100352, 101106, 110592, 110878, 110928, 110930, 110948, 110951, 110960, 111355, 113664, 113770, 113776, 113788, 113792, 113800, 113808, 113817, 119808, 119892, 119894, 119964, 119966, 119967, 119970, 119970, 119973, 119974, 119977, 119980, 119982, 119993, 119995, 119995, 119997, 120003, 120005, 120069, 120071, 120074, 120077, 120084, 120086, 120092, 120094, 120121, 120123, 120126, 120128, 120132, 120134, 120134, 120138, 120144, 120146, 120485, 120488, 120512, 120514, 120538, 120540, 120570, 120572, 120596, 120598, 120628, 120630, 120654, 120656, 120686, 120688, 120712, 120714, 120744, 120746, 120770, 120772, 120779, 123136, 123180, 123191, 123197, 123214, 123214, 123584, 123627, 124928, 125124, 125184, 125251, 125259, 125259, 126464, 126467, 126469, 126495, 126497, 126498, 126500, 126500, 126503, 126503, 126505, 126514, 126516, 126519, 126521, 126521, 126523, 126523, 126530, 126530, 126535, 126535, 126537, 126537, 126539, 126539, 126541, 126543, 126545, 126546, 126548, 126548, 126551, 126551, 126553, 126553, 126555, 126555, 126557, 126557, 126559, 126559, 126561, 126562, 126564, 126564, 126567, 126570, 126572, 126578, 126580, 126583, 126585, 126588, 126590, 126590, 126592, 126601, 126603, 126619, 126625, 126627, 126629, 126633, 126635, 126651, 131072, 173782, 173824, 177972, 177984, 178205, 178208, 183969, 183984, 191456, 194560, 195101]; - const unicodeESNextIdentifierPart = [48, 57, 65, 90, 95, 95, 97, 122, 170, 170, 181, 181, 183, 183, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 768, 884, 886, 887, 890, 893, 895, 895, 902, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1155, 1159, 1162, 1327, 1329, 1366, 1369, 1369, 1376, 1416, 1425, 1469, 1471, 1471, 1473, 1474, 1476, 1477, 1479, 1479, 1488, 1514, 1519, 1522, 1552, 1562, 1568, 1641, 1646, 1747, 1749, 1756, 1759, 1768, 1770, 1788, 1791, 1791, 1808, 1866, 1869, 1969, 1984, 2037, 2042, 2042, 2045, 2045, 2048, 2093, 2112, 2139, 2144, 2154, 2208, 2228, 2230, 2237, 2259, 2273, 2275, 2403, 2406, 2415, 2417, 2435, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2492, 2500, 2503, 2504, 2507, 2510, 2519, 2519, 2524, 2525, 2527, 2531, 2534, 2545, 2556, 2556, 2558, 2558, 2561, 2563, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2641, 2641, 2649, 2652, 2654, 2654, 2662, 2677, 2689, 2691, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765, 2768, 2768, 2784, 2787, 2790, 2799, 2809, 2815, 2817, 2819, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2876, 2884, 2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2915, 2918, 2927, 2929, 2929, 2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3006, 3010, 3014, 3016, 3018, 3021, 3024, 3024, 3031, 3031, 3046, 3055, 3072, 3084, 3086, 3088, 3090, 3112, 3114, 3129, 3133, 3140, 3142, 3144, 3146, 3149, 3157, 3158, 3160, 3162, 3168, 3171, 3174, 3183, 3200, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3260, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3299, 3302, 3311, 3313, 3314, 3328, 3331, 3333, 3340, 3342, 3344, 3346, 3396, 3398, 3400, 3402, 3406, 3412, 3415, 3423, 3427, 3430, 3439, 3450, 3455, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551, 3558, 3567, 3570, 3571, 3585, 3642, 3648, 3662, 3664, 3673, 3713, 3714, 3716, 3716, 3718, 3722, 3724, 3747, 3749, 3749, 3751, 3773, 3776, 3780, 3782, 3782, 3784, 3789, 3792, 3801, 3804, 3807, 3840, 3840, 3864, 3865, 3872, 3881, 3893, 3893, 3895, 3895, 3897, 3897, 3902, 3911, 3913, 3948, 3953, 3972, 3974, 3991, 3993, 4028, 4038, 4038, 4096, 4169, 4176, 4253, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4957, 4959, 4969, 4977, 4992, 5007, 5024, 5109, 5112, 5117, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5880, 5888, 5900, 5902, 5908, 5920, 5940, 5952, 5971, 5984, 5996, 5998, 6000, 6002, 6003, 6016, 6099, 6103, 6103, 6108, 6109, 6112, 6121, 6155, 6157, 6160, 6169, 6176, 6264, 6272, 6314, 6320, 6389, 6400, 6430, 6432, 6443, 6448, 6459, 6470, 6509, 6512, 6516, 6528, 6571, 6576, 6601, 6608, 6618, 6656, 6683, 6688, 6750, 6752, 6780, 6783, 6793, 6800, 6809, 6823, 6823, 6832, 6845, 6912, 6987, 6992, 7001, 7019, 7027, 7040, 7155, 7168, 7223, 7232, 7241, 7245, 7293, 7296, 7304, 7312, 7354, 7357, 7359, 7376, 7378, 7380, 7418, 7424, 7673, 7675, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8255, 8256, 8276, 8276, 8305, 8305, 8319, 8319, 8336, 8348, 8400, 8412, 8417, 8417, 8421, 8432, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8472, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11647, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11744, 11775, 12293, 12295, 12321, 12335, 12337, 12341, 12344, 12348, 12353, 12438, 12441, 12447, 12449, 12538, 12540, 12543, 12549, 12591, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40943, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42539, 42560, 42607, 42612, 42621, 42623, 42737, 42775, 42783, 42786, 42888, 42891, 42943, 42946, 42950, 42999, 43047, 43072, 43123, 43136, 43205, 43216, 43225, 43232, 43255, 43259, 43259, 43261, 43309, 43312, 43347, 43360, 43388, 43392, 43456, 43471, 43481, 43488, 43518, 43520, 43574, 43584, 43597, 43600, 43609, 43616, 43638, 43642, 43714, 43739, 43741, 43744, 43759, 43762, 43766, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43824, 43866, 43868, 43879, 43888, 44010, 44012, 44013, 44016, 44025, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65024, 65039, 65056, 65071, 65075, 65076, 65101, 65103, 65136, 65140, 65142, 65276, 65296, 65305, 65313, 65338, 65343, 65343, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, 65536, 65547, 65549, 65574, 65576, 65594, 65596, 65597, 65599, 65613, 65616, 65629, 65664, 65786, 65856, 65908, 66045, 66045, 66176, 66204, 66208, 66256, 66272, 66272, 66304, 66335, 66349, 66378, 66384, 66426, 66432, 66461, 66464, 66499, 66504, 66511, 66513, 66517, 66560, 66717, 66720, 66729, 66736, 66771, 66776, 66811, 66816, 66855, 66864, 66915, 67072, 67382, 67392, 67413, 67424, 67431, 67584, 67589, 67592, 67592, 67594, 67637, 67639, 67640, 67644, 67644, 67647, 67669, 67680, 67702, 67712, 67742, 67808, 67826, 67828, 67829, 67840, 67861, 67872, 67897, 67968, 68023, 68030, 68031, 68096, 68099, 68101, 68102, 68108, 68115, 68117, 68119, 68121, 68149, 68152, 68154, 68159, 68159, 68192, 68220, 68224, 68252, 68288, 68295, 68297, 68326, 68352, 68405, 68416, 68437, 68448, 68466, 68480, 68497, 68608, 68680, 68736, 68786, 68800, 68850, 68864, 68903, 68912, 68921, 69376, 69404, 69415, 69415, 69424, 69456, 69600, 69622, 69632, 69702, 69734, 69743, 69759, 69818, 69840, 69864, 69872, 69881, 69888, 69940, 69942, 69951, 69956, 69958, 69968, 70003, 70006, 70006, 70016, 70084, 70089, 70092, 70096, 70106, 70108, 70108, 70144, 70161, 70163, 70199, 70206, 70206, 70272, 70278, 70280, 70280, 70282, 70285, 70287, 70301, 70303, 70312, 70320, 70378, 70384, 70393, 70400, 70403, 70405, 70412, 70415, 70416, 70419, 70440, 70442, 70448, 70450, 70451, 70453, 70457, 70459, 70468, 70471, 70472, 70475, 70477, 70480, 70480, 70487, 70487, 70493, 70499, 70502, 70508, 70512, 70516, 70656, 70730, 70736, 70745, 70750, 70751, 70784, 70853, 70855, 70855, 70864, 70873, 71040, 71093, 71096, 71104, 71128, 71133, 71168, 71232, 71236, 71236, 71248, 71257, 71296, 71352, 71360, 71369, 71424, 71450, 71453, 71467, 71472, 71481, 71680, 71738, 71840, 71913, 71935, 71935, 72096, 72103, 72106, 72151, 72154, 72161, 72163, 72164, 72192, 72254, 72263, 72263, 72272, 72345, 72349, 72349, 72384, 72440, 72704, 72712, 72714, 72758, 72760, 72768, 72784, 72793, 72818, 72847, 72850, 72871, 72873, 72886, 72960, 72966, 72968, 72969, 72971, 73014, 73018, 73018, 73020, 73021, 73023, 73031, 73040, 73049, 73056, 73061, 73063, 73064, 73066, 73102, 73104, 73105, 73107, 73112, 73120, 73129, 73440, 73462, 73728, 74649, 74752, 74862, 74880, 75075, 77824, 78894, 82944, 83526, 92160, 92728, 92736, 92766, 92768, 92777, 92880, 92909, 92912, 92916, 92928, 92982, 92992, 92995, 93008, 93017, 93027, 93047, 93053, 93071, 93760, 93823, 93952, 94026, 94031, 94087, 94095, 94111, 94176, 94177, 94179, 94179, 94208, 100343, 100352, 101106, 110592, 110878, 110928, 110930, 110948, 110951, 110960, 111355, 113664, 113770, 113776, 113788, 113792, 113800, 113808, 113817, 113821, 113822, 119141, 119145, 119149, 119154, 119163, 119170, 119173, 119179, 119210, 119213, 119362, 119364, 119808, 119892, 119894, 119964, 119966, 119967, 119970, 119970, 119973, 119974, 119977, 119980, 119982, 119993, 119995, 119995, 119997, 120003, 120005, 120069, 120071, 120074, 120077, 120084, 120086, 120092, 120094, 120121, 120123, 120126, 120128, 120132, 120134, 120134, 120138, 120144, 120146, 120485, 120488, 120512, 120514, 120538, 120540, 120570, 120572, 120596, 120598, 120628, 120630, 120654, 120656, 120686, 120688, 120712, 120714, 120744, 120746, 120770, 120772, 120779, 120782, 120831, 121344, 121398, 121403, 121452, 121461, 121461, 121476, 121476, 121499, 121503, 121505, 121519, 122880, 122886, 122888, 122904, 122907, 122913, 122915, 122916, 122918, 122922, 123136, 123180, 123184, 123197, 123200, 123209, 123214, 123214, 123584, 123641, 124928, 125124, 125136, 125142, 125184, 125259, 125264, 125273, 126464, 126467, 126469, 126495, 126497, 126498, 126500, 126500, 126503, 126503, 126505, 126514, 126516, 126519, 126521, 126521, 126523, 126523, 126530, 126530, 126535, 126535, 126537, 126537, 126539, 126539, 126541, 126543, 126545, 126546, 126548, 126548, 126551, 126551, 126553, 126553, 126555, 126555, 126557, 126557, 126559, 126559, 126561, 126562, 126564, 126564, 126567, 126570, 126572, 126578, 126580, 126583, 126585, 126588, 126590, 126590, 126592, 126601, 126603, 126619, 126625, 126627, 126629, 126633, 126635, 126651, 131072, 173782, 173824, 177972, 177984, 178205, 178208, 183969, 183984, 191456, 194560, 195101, 917760, 917999]; - - function lookupInUnicodeMap(code: number, map: readonly number[]): boolean { - // Bail out quickly if it couldn't possibly be in the map. - if (code < map[0]) { - return false; + // Perform binary search in one of the Unicode range maps + let lo = 0; + let hi: number = map.length; + let mid: number; + while (lo + 1 < hi) { + mid = lo + (hi - lo) / 2; + // mid has to be even to catch a range's beginning + mid -= mid % 2; + if (map[mid] <= code && code <= map[mid + 1]) { + return true; + } + if (code < map[mid]) { + hi = mid; } - - // Perform binary search in one of the Unicode range maps - let lo = 0; - let hi: number = map.length; - let mid: number; - - while (lo + 1 < hi) { - mid = lo + (hi - lo) / 2; - // mid has to be even to catch a range's beginning - mid -= mid % 2; - if (map[mid] <= code && code <= map[mid + 1]) { - return true; - } - - if (code < map[mid]) { - hi = mid; - } - else { - lo = mid + 2; - } + else { + lo = mid + 2; } - - return false; } - - /* @internal */ export function isUnicodeIdentifierStart(code: number, languageVersion: ScriptTarget | undefined) { - return languageVersion! >= ScriptTarget.ES2015 ? - lookupInUnicodeMap(code, unicodeESNextIdentifierStart) : - languageVersion! === ScriptTarget.ES5 ? lookupInUnicodeMap(code, unicodeES5IdentifierStart) : + return false; +} +/* @internal */ export function isUnicodeIdentifierStart(code: number, languageVersion: ScriptTarget | undefined) { + return (languageVersion!) >= ScriptTarget.ES2015 ? + lookupInUnicodeMap(code, unicodeESNextIdentifierStart) : + (languageVersion!) === ScriptTarget.ES5 ? lookupInUnicodeMap(code, unicodeES5IdentifierStart) : lookupInUnicodeMap(code, unicodeES3IdentifierStart); - } - - function isUnicodeIdentifierPart(code: number, languageVersion: ScriptTarget | undefined) { - return languageVersion! >= ScriptTarget.ES2015 ? - lookupInUnicodeMap(code, unicodeESNextIdentifierPart) : - languageVersion! === ScriptTarget.ES5 ? lookupInUnicodeMap(code, unicodeES5IdentifierPart) : +} +function isUnicodeIdentifierPart(code: number, languageVersion: ScriptTarget | undefined) { + return (languageVersion!) >= ScriptTarget.ES2015 ? + lookupInUnicodeMap(code, unicodeESNextIdentifierPart) : + (languageVersion!) === ScriptTarget.ES5 ? lookupInUnicodeMap(code, unicodeES5IdentifierPart) : lookupInUnicodeMap(code, unicodeES3IdentifierPart); - } - - function makeReverseMap(source: Map): string[] { - const result: string[] = []; - source.forEach((value, name) => { - result[value] = name; - }); - return result; - } - - const tokenStrings = makeReverseMap(textToToken); - export function tokenToString(t: SyntaxKind): string | undefined { - return tokenStrings[t]; - } - - /* @internal */ - export function stringToToken(s: string): SyntaxKind | undefined { - return textToToken.get(s); - } - - /* @internal */ - export function computeLineStarts(text: string): number[] { - const result: number[] = new Array(); - let pos = 0; - let lineStart = 0; - while (pos < text.length) { - const ch = text.charCodeAt(pos); - pos++; - switch (ch) { - case CharacterCodes.carriageReturn: - if (text.charCodeAt(pos) === CharacterCodes.lineFeed) { - pos++; - } - // falls through - case CharacterCodes.lineFeed: +} +function makeReverseMap(source: ts.Map): string[] { + const result: string[] = []; + source.forEach((value, name) => { + result[value] = name; + }); + return result; +} +const tokenStrings = makeReverseMap(textToToken); +export function tokenToString(t: SyntaxKind): string | undefined { + return tokenStrings[t]; +} +/* @internal */ +export function stringToToken(s: string): SyntaxKind | undefined { + return textToToken.get(s); +} +/* @internal */ +export function computeLineStarts(text: string): number[] { + const result: number[] = new Array(); + let pos = 0; + let lineStart = 0; + while (pos < text.length) { + const ch = text.charCodeAt(pos); + pos++; + switch (ch) { + case CharacterCodes.carriageReturn: + if (text.charCodeAt(pos) === CharacterCodes.lineFeed) { + pos++; + } + // falls through + case CharacterCodes.lineFeed: + result.push(lineStart); + lineStart = pos; + break; + default: + if (ch > CharacterCodes.maxAsciiCharacter && isLineBreak(ch)) { result.push(lineStart); lineStart = pos; - break; - default: - if (ch > CharacterCodes.maxAsciiCharacter && isLineBreak(ch)) { - result.push(lineStart); - lineStart = pos; - } - break; - } + } + break; } - result.push(lineStart); - return result; } - - export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number): number; - /* @internal */ - export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number, allowEdits?: true): number; // eslint-disable-line @typescript-eslint/unified-signatures - export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number, allowEdits?: true): number { - return sourceFile.getPositionOfLineAndCharacter ? - sourceFile.getPositionOfLineAndCharacter(line, character, allowEdits) : - computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text, allowEdits); - } - - /* @internal */ - export function computePositionOfLineAndCharacter(lineStarts: readonly number[], line: number, character: number, debugText?: string, allowEdits?: true): number { - if (line < 0 || line >= lineStarts.length) { - if (allowEdits) { - // Clamp line to nearest allowable value - line = line < 0 ? 0 : line >= lineStarts.length ? lineStarts.length - 1 : line; - } - else { - Debug.fail(`Bad line number. Line: ${line}, lineStarts.length: ${lineStarts.length} , line map is correct? ${debugText !== undefined ? arraysEqual(lineStarts, computeLineStarts(debugText)) : "unknown"}`); - } - } - - const res = lineStarts[line] + character; + result.push(lineStart); + return result; +} +export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number): number; +/* @internal */ +export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number, allowEdits?: true): number; // eslint-disable-line @typescript-eslint/unified-signatures +export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number, allowEdits?: true): number { + return sourceFile.getPositionOfLineAndCharacter ? + sourceFile.getPositionOfLineAndCharacter(line, character, allowEdits) : + computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text, allowEdits); +} +/* @internal */ +export function computePositionOfLineAndCharacter(lineStarts: readonly number[], line: number, character: number, debugText?: string, allowEdits?: true): number { + if (line < 0 || line >= lineStarts.length) { if (allowEdits) { - // Clamp to nearest allowable values to allow the underlying to be edited without crashing (accuracy is lost, instead) - // TODO: Somehow track edits between file as it was during the creation of sourcemap we have and the current file and - // apply them to the computed position to improve accuracy - return res > lineStarts[line + 1] ? lineStarts[line + 1] : typeof debugText === "string" && res > debugText.length ? debugText.length : res; + // Clamp line to nearest allowable value + line = line < 0 ? 0 : line >= lineStarts.length ? lineStarts.length - 1 : line; } - if (line < lineStarts.length - 1) { - Debug.assert(res < lineStarts[line + 1]); - } - else if (debugText !== undefined) { - Debug.assert(res <= debugText.length); // Allow single character overflow for trailing newline - } - return res; - } - - /* @internal */ - export function getLineStarts(sourceFile: SourceFileLike): readonly number[] { - return sourceFile.lineMap || (sourceFile.lineMap = computeLineStarts(sourceFile.text)); - } - - /* @internal */ - /** - * We assume the first line starts at position 0 and 'position' is non-negative. - */ - export function computeLineAndCharacterOfPosition(lineStarts: readonly number[], position: number): LineAndCharacter { - let lineNumber = binarySearch(lineStarts, position, identity, compareValues); - if (lineNumber < 0) { - // If the actual position was not found, - // the binary search returns the 2's-complement of the next line start - // e.g. if the line starts at [5, 10, 23, 80] and the position requested was 20 - // then the search will return -2. - // - // We want the index of the previous line start, so we subtract 1. - // Review 2's-complement if this is confusing. - lineNumber = ~lineNumber - 1; - Debug.assert(lineNumber !== -1, "position cannot precede the beginning of the file"); + else { + Debug.fail(`Bad line number. Line: ${line}, lineStarts.length: ${lineStarts.length} , line map is correct? ${debugText !== undefined ? arraysEqual(lineStarts, computeLineStarts(debugText)) : "unknown"}`); } - return { - line: lineNumber, - character: position - lineStarts[lineNumber] - }; } - - export function getLineAndCharacterOfPosition(sourceFile: SourceFileLike, position: number): LineAndCharacter { - return computeLineAndCharacterOfPosition(getLineStarts(sourceFile), position); + const res = lineStarts[line] + character; + if (allowEdits) { + // Clamp to nearest allowable values to allow the underlying to be edited without crashing (accuracy is lost, instead) + // TODO: Somehow track edits between file as it was during the creation of sourcemap we have and the current file and + // apply them to the computed position to improve accuracy + return res > lineStarts[line + 1] ? lineStarts[line + 1] : typeof debugText === "string" && res > debugText.length ? debugText.length : res; } - - export function isWhiteSpaceLike(ch: number): boolean { - return isWhiteSpaceSingleLine(ch) || isLineBreak(ch); + if (line < lineStarts.length - 1) { + Debug.assert(res < lineStarts[line + 1]); } - - /** Does not include line breaks. For that, see isWhiteSpaceLike. */ - export function isWhiteSpaceSingleLine(ch: number): boolean { - // Note: nextLine is in the Zs space, and should be considered to be a whitespace. - // It is explicitly not a line-break as it isn't in the exact set specified by EcmaScript. - return ch === CharacterCodes.space || - ch === CharacterCodes.tab || - ch === CharacterCodes.verticalTab || - ch === CharacterCodes.formFeed || - ch === CharacterCodes.nonBreakingSpace || - ch === CharacterCodes.nextLine || - ch === CharacterCodes.ogham || - ch >= CharacterCodes.enQuad && ch <= CharacterCodes.zeroWidthSpace || - ch === CharacterCodes.narrowNoBreakSpace || - ch === CharacterCodes.mathematicalSpace || - ch === CharacterCodes.ideographicSpace || - ch === CharacterCodes.byteOrderMark; + else if (debugText !== undefined) { + Debug.assert(res <= debugText.length); // Allow single character overflow for trailing newline } - - export function isLineBreak(ch: number): boolean { - // ES5 7.3: - // The ECMAScript line terminator characters are listed in Table 3. - // Table 3: Line Terminator Characters - // Code Unit Value Name Formal Name - // \u000A Line Feed - // \u000D Carriage Return - // \u2028 Line separator - // \u2029 Paragraph separator - // Only the characters in Table 3 are treated as line terminators. Other new line or line - // breaking characters are treated as white space but not as line terminators. - - return ch === CharacterCodes.lineFeed || - ch === CharacterCodes.carriageReturn || - ch === CharacterCodes.lineSeparator || - ch === CharacterCodes.paragraphSeparator; + return res; +} +/* @internal */ +export function getLineStarts(sourceFile: SourceFileLike): readonly number[] { + return sourceFile.lineMap || (sourceFile.lineMap = computeLineStarts(sourceFile.text)); +} +/* @internal */ +/** + * We assume the first line starts at position 0 and 'position' is non-negative. + */ +export function computeLineAndCharacterOfPosition(lineStarts: readonly number[], position: number): LineAndCharacter { + let lineNumber = binarySearch(lineStarts, position, identity, compareValues); + if (lineNumber < 0) { + // If the actual position was not found, + // the binary search returns the 2's-complement of the next line start + // e.g. if the line starts at [5, 10, 23, 80] and the position requested was 20 + // then the search will return -2. + // + // We want the index of the previous line start, so we subtract 1. + // Review 2's-complement if this is confusing. + lineNumber = ~lineNumber - 1; + Debug.assert(lineNumber !== -1, "position cannot precede the beginning of the file"); } - - function isDigit(ch: number): boolean { - return ch >= CharacterCodes._0 && ch <= CharacterCodes._9; + return { + line: lineNumber, + character: position - lineStarts[lineNumber] + }; +} +export function getLineAndCharacterOfPosition(sourceFile: SourceFileLike, position: number): LineAndCharacter { + return computeLineAndCharacterOfPosition(getLineStarts(sourceFile), position); +} +export function isWhiteSpaceLike(ch: number): boolean { + return isWhiteSpaceSingleLine(ch) || isLineBreak(ch); +} +/** Does not include line breaks. For that, see isWhiteSpaceLike. */ +export function isWhiteSpaceSingleLine(ch: number): boolean { + // Note: nextLine is in the Zs space, and should be considered to be a whitespace. + // It is explicitly not a line-break as it isn't in the exact set specified by EcmaScript. + return ch === CharacterCodes.space || + ch === CharacterCodes.tab || + ch === CharacterCodes.verticalTab || + ch === CharacterCodes.formFeed || + ch === CharacterCodes.nonBreakingSpace || + ch === CharacterCodes.nextLine || + ch === CharacterCodes.ogham || + ch >= CharacterCodes.enQuad && ch <= CharacterCodes.zeroWidthSpace || + ch === CharacterCodes.narrowNoBreakSpace || + ch === CharacterCodes.mathematicalSpace || + ch === CharacterCodes.ideographicSpace || + ch === CharacterCodes.byteOrderMark; +} +export function isLineBreak(ch: number): boolean { + // ES5 7.3: + // The ECMAScript line terminator characters are listed in Table 3. + // Table 3: Line Terminator Characters + // Code Unit Value Name Formal Name + // \u000A Line Feed + // \u000D Carriage Return + // \u2028 Line separator + // \u2029 Paragraph separator + // Only the characters in Table 3 are treated as line terminators. Other new line or line + // breaking characters are treated as white space but not as line terminators. + return ch === CharacterCodes.lineFeed || + ch === CharacterCodes.carriageReturn || + ch === CharacterCodes.lineSeparator || + ch === CharacterCodes.paragraphSeparator; +} +function isDigit(ch: number): boolean { + return ch >= CharacterCodes._0 && ch <= CharacterCodes._9; +} +/* @internal */ +export function isOctalDigit(ch: number): boolean { + return ch >= CharacterCodes._0 && ch <= CharacterCodes._7; +} +export function couldStartTrivia(text: string, pos: number): boolean { + // Keep in sync with skipTrivia + const ch = text.charCodeAt(pos); + switch (ch) { + case CharacterCodes.carriageReturn: + case CharacterCodes.lineFeed: + case CharacterCodes.tab: + case CharacterCodes.verticalTab: + case CharacterCodes.formFeed: + case CharacterCodes.space: + case CharacterCodes.slash: + // starts of normal trivia + // falls through + case CharacterCodes.lessThan: + case CharacterCodes.bar: + case CharacterCodes.equals: + case CharacterCodes.greaterThan: + // Starts of conflict marker trivia + return true; + case CharacterCodes.hash: + // Only if its the beginning can we have #! trivia + return pos === 0; + default: + return ch > CharacterCodes.maxAsciiCharacter; } - - /* @internal */ - export function isOctalDigit(ch: number): boolean { - return ch >= CharacterCodes._0 && ch <= CharacterCodes._7; +} +/* @internal */ +export function skipTrivia(text: string, pos: number, stopAfterLineBreak?: boolean, stopAtComments = false): number { + if (positionIsSynthesized(pos)) { + return pos; } - - export function couldStartTrivia(text: string, pos: number): boolean { - // Keep in sync with skipTrivia + // Keep in sync with couldStartTrivia + while (true) { const ch = text.charCodeAt(pos); switch (ch) { case CharacterCodes.carriageReturn: + if (text.charCodeAt(pos + 1) === CharacterCodes.lineFeed) { + pos++; + } + // falls through case CharacterCodes.lineFeed: + pos++; + if (stopAfterLineBreak) { + return pos; + } + continue; case CharacterCodes.tab: case CharacterCodes.verticalTab: case CharacterCodes.formFeed: case CharacterCodes.space: + pos++; + continue; case CharacterCodes.slash: - // starts of normal trivia - // falls through + if (stopAtComments) { + break; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { + pos += 2; + while (pos < text.length) { + if (isLineBreak(text.charCodeAt(pos))) { + break; + } + pos++; + } + continue; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { + pos += 2; + while (pos < text.length) { + if (text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) { + pos += 2; + break; + } + pos++; + } + continue; + } + break; case CharacterCodes.lessThan: case CharacterCodes.bar: case CharacterCodes.equals: case CharacterCodes.greaterThan: - // Starts of conflict marker trivia - return true; + if (isConflictMarkerTrivia(text, pos)) { + pos = scanConflictMarkerTrivia(text, pos); + continue; + } + break; case CharacterCodes.hash: - // Only if its the beginning can we have #! trivia - return pos === 0; + if (pos === 0 && isShebangTrivia(text, pos)) { + pos = scanShebangTrivia(text, pos); + continue; + } + break; default: - return ch > CharacterCodes.maxAsciiCharacter; + if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpaceLike(ch))) { + pos++; + continue; + } + break; + } + return pos; + } +} +// All conflict markers consist of the same character repeated seven times. If it is +// a <<<<<<< or >>>>>>> marker then it is also followed by a space. +const mergeConflictMarkerLength = "<<<<<<<".length; +function isConflictMarkerTrivia(text: string, pos: number) { + Debug.assert(pos >= 0); + // Conflict markers must be at the start of a line. + if (pos === 0 || isLineBreak(text.charCodeAt(pos - 1))) { + const ch = text.charCodeAt(pos); + if ((pos + mergeConflictMarkerLength) < text.length) { + for (let i = 0; i < mergeConflictMarkerLength; i++) { + if (text.charCodeAt(pos + i) !== ch) { + return false; + } + } + return ch === CharacterCodes.equals || + text.charCodeAt(pos + mergeConflictMarkerLength) === CharacterCodes.space; } } - - /* @internal */ - export function skipTrivia(text: string, pos: number, stopAfterLineBreak?: boolean, stopAtComments = false): number { - if (positionIsSynthesized(pos)) { - return pos; + return false; +} +function scanConflictMarkerTrivia(text: string, pos: number, error?: (diag: DiagnosticMessage, pos?: number, len?: number) => void) { + if (error) { + error(Diagnostics.Merge_conflict_marker_encountered, pos, mergeConflictMarkerLength); + } + const ch = text.charCodeAt(pos); + const len = text.length; + if (ch === CharacterCodes.lessThan || ch === CharacterCodes.greaterThan) { + while (pos < len && !isLineBreak(text.charCodeAt(pos))) { + pos++; } - - // Keep in sync with couldStartTrivia - while (true) { - const ch = text.charCodeAt(pos); - switch (ch) { - case CharacterCodes.carriageReturn: - if (text.charCodeAt(pos + 1) === CharacterCodes.lineFeed) { - pos++; - } - // falls through - case CharacterCodes.lineFeed: - pos++; - if (stopAfterLineBreak) { - return pos; - } - continue; - case CharacterCodes.tab: - case CharacterCodes.verticalTab: - case CharacterCodes.formFeed: - case CharacterCodes.space: + } + else { + Debug.assert(ch === CharacterCodes.bar || ch === CharacterCodes.equals); + // Consume everything from the start of a ||||||| or ======= marker to the start + // of the next ======= or >>>>>>> marker. + while (pos < len) { + const currentChar = text.charCodeAt(pos); + if ((currentChar === CharacterCodes.equals || currentChar === CharacterCodes.greaterThan) && currentChar !== ch && isConflictMarkerTrivia(text, pos)) { + break; + } + pos++; + } + } + return pos; +} +const shebangTriviaRegex = /^#!.*/; +/*@internal*/ +export function isShebangTrivia(text: string, pos: number) { + // Shebangs check must only be done at the start of the file + Debug.assert(pos === 0); + return shebangTriviaRegex.test(text); +} +/*@internal*/ +export function scanShebangTrivia(text: string, pos: number) { + const shebang = shebangTriviaRegex.exec(text)![0]; + pos = pos + shebang.length; + return pos; +} +/** + * Invokes a callback for each comment range following the provided position. + * + * Single-line comment ranges include the leading double-slash characters but not the ending + * line break. Multi-line comment ranges include the leading slash-asterisk and trailing + * asterisk-slash characters. + * + * @param reduce If true, accumulates the result of calling the callback in a fashion similar + * to reduceLeft. If false, iteration stops when the callback returns a truthy value. + * @param text The source text to scan. + * @param pos The position at which to start scanning. + * @param trailing If false, whitespace is skipped until the first line break and comments + * between that location and the next token are returned. If true, comments occurring + * between the given position and the next line break are returned. + * @param cb The callback to execute as each comment range is encountered. + * @param state A state value to pass to each iteration of the callback. + * @param initial An initial value to pass when accumulating results (when "reduce" is true). + * @returns If "reduce" is true, the accumulated value. If "reduce" is false, the first truthy + * return value of the callback. + */ +function iterateCommentRanges(reduce: boolean, text: string, pos: number, trailing: boolean, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U | undefined) => U, state: T, initial?: U): U | undefined { + let pendingPos!: number; + let pendingEnd!: number; + let pendingKind!: CommentKind; + let pendingHasTrailingNewLine!: boolean; + let hasPendingCommentRange = false; + let collecting = trailing; + let accumulator = initial; + if (pos === 0) { + collecting = true; + const shebang = getShebang(text); + if (shebang) { + pos = shebang.length; + } + } + scan: while (pos >= 0 && pos < text.length) { + const ch = text.charCodeAt(pos); + switch (ch) { + case CharacterCodes.carriageReturn: + if (text.charCodeAt(pos + 1) === CharacterCodes.lineFeed) { pos++; - continue; - case CharacterCodes.slash: - if (stopAtComments) { - break; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { - pos += 2; + } + // falls through + case CharacterCodes.lineFeed: + pos++; + if (trailing) { + break scan; + } + collecting = true; + if (hasPendingCommentRange) { + pendingHasTrailingNewLine = true; + } + continue; + case CharacterCodes.tab: + case CharacterCodes.verticalTab: + case CharacterCodes.formFeed: + case CharacterCodes.space: + pos++; + continue; + case CharacterCodes.slash: + const nextChar = text.charCodeAt(pos + 1); + let hasTrailingNewLine = false; + if (nextChar === CharacterCodes.slash || nextChar === CharacterCodes.asterisk) { + const kind = nextChar === CharacterCodes.slash ? SyntaxKind.SingleLineCommentTrivia : SyntaxKind.MultiLineCommentTrivia; + const startPos = pos; + pos += 2; + if (nextChar === CharacterCodes.slash) { while (pos < text.length) { if (isLineBreak(text.charCodeAt(pos))) { + hasTrailingNewLine = true; break; } pos++; } - continue; } - if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { - pos += 2; + else { while (pos < text.length) { if (text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) { pos += 2; @@ -550,1644 +683,1055 @@ namespace ts { } pos++; } - continue; - } - break; - - case CharacterCodes.lessThan: - case CharacterCodes.bar: - case CharacterCodes.equals: - case CharacterCodes.greaterThan: - if (isConflictMarkerTrivia(text, pos)) { - pos = scanConflictMarkerTrivia(text, pos); - continue; } - break; - - case CharacterCodes.hash: - if (pos === 0 && isShebangTrivia(text, pos)) { - pos = scanShebangTrivia(text, pos); - continue; + if (collecting) { + if (hasPendingCommentRange) { + accumulator = cb(pendingPos, pendingEnd, pendingKind, pendingHasTrailingNewLine, state, accumulator); + if (!reduce && accumulator) { + // If we are not reducing and we have a truthy result, return it. + return accumulator; + } + } + pendingPos = startPos; + pendingEnd = pos; + pendingKind = kind; + pendingHasTrailingNewLine = hasTrailingNewLine; + hasPendingCommentRange = true; } - break; - - default: - if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpaceLike(ch))) { - pos++; - continue; + continue; + } + break scan; + default: + if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpaceLike(ch))) { + if (hasPendingCommentRange && isLineBreak(ch)) { + pendingHasTrailingNewLine = true; } - break; - } - return pos; + pos++; + continue; + } + break scan; } } - - // All conflict markers consist of the same character repeated seven times. If it is - // a <<<<<<< or >>>>>>> marker then it is also followed by a space. - const mergeConflictMarkerLength = "<<<<<<<".length; - - function isConflictMarkerTrivia(text: string, pos: number) { - Debug.assert(pos >= 0); - - // Conflict markers must be at the start of a line. - if (pos === 0 || isLineBreak(text.charCodeAt(pos - 1))) { - const ch = text.charCodeAt(pos); - - if ((pos + mergeConflictMarkerLength) < text.length) { - for (let i = 0; i < mergeConflictMarkerLength; i++) { - if (text.charCodeAt(pos + i) !== ch) { - return false; - } + if (hasPendingCommentRange) { + accumulator = cb(pendingPos, pendingEnd, pendingKind, pendingHasTrailingNewLine, state, accumulator); + } + return accumulator; +} +export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean) => U): U | undefined; +export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state: T): U | undefined; +export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined { + return iterateCommentRanges(/*reduce*/ false, text, pos, /*trailing*/ false, cb, state); +} +export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean) => U): U | undefined; +export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state: T): U | undefined; +export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined { + return iterateCommentRanges(/*reduce*/ false, text, pos, /*trailing*/ true, cb, state); +} +export function reduceEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U) { + return iterateCommentRanges(/*reduce*/ true, text, pos, /*trailing*/ false, cb, state, initial); +} +export function reduceEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U) { + return iterateCommentRanges(/*reduce*/ true, text, pos, /*trailing*/ true, cb, state, initial); +} +function appendCommentRange(pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, _state: any, comments: CommentRange[]) { + if (!comments) { + comments = []; + } + comments.push({ kind, pos, end, hasTrailingNewLine }); + return comments; +} +export function getLeadingCommentRanges(text: string, pos: number): CommentRange[] | undefined { + return reduceEachLeadingCommentRange(text, pos, appendCommentRange, /*state*/ undefined, /*initial*/ undefined); +} +export function getTrailingCommentRanges(text: string, pos: number): CommentRange[] | undefined { + return reduceEachTrailingCommentRange(text, pos, appendCommentRange, /*state*/ undefined, /*initial*/ undefined); +} +/** Optionally, get the shebang */ +export function getShebang(text: string): string | undefined { + const match = shebangTriviaRegex.exec(text); + if (match) { + return match[0]; + } +} +export function isIdentifierStart(ch: number, languageVersion: ScriptTarget | undefined): boolean { + return ch >= CharacterCodes.A && ch <= CharacterCodes.Z || ch >= CharacterCodes.a && ch <= CharacterCodes.z || + ch === CharacterCodes.$ || ch === CharacterCodes._ || + ch > CharacterCodes.maxAsciiCharacter && isUnicodeIdentifierStart(ch, languageVersion); +} +export function isIdentifierPart(ch: number, languageVersion: ScriptTarget | undefined): boolean { + return ch >= CharacterCodes.A && ch <= CharacterCodes.Z || ch >= CharacterCodes.a && ch <= CharacterCodes.z || + ch >= CharacterCodes._0 && ch <= CharacterCodes._9 || ch === CharacterCodes.$ || ch === CharacterCodes._ || + ch > CharacterCodes.maxAsciiCharacter && isUnicodeIdentifierPart(ch, languageVersion); +} +/* @internal */ +export function isIdentifierText(name: string, languageVersion: ScriptTarget | undefined): boolean { + let ch = codePointAt(name, 0); + if (!isIdentifierStart(ch, languageVersion)) { + return false; + } + for (let i = charSize(ch); i < name.length; i += charSize(ch)) { + if (!isIdentifierPart(ch = codePointAt(name, i), languageVersion)) { + return false; + } + } + return true; +} +// Creates a scanner over a (possibly unspecified) range of a piece of text. +export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean, languageVariant = LanguageVariant.Standard, textInitial?: string, onError?: ErrorCallback, start?: number, length?: number): Scanner { + let text = textInitial!; + // Current position (end position of text of current token) + let pos: number; + // end of text + let end: number; + // Start position of whitespace before current token + let startPos: number; + // Start position of text of current token + let tokenPos: number; + let token: SyntaxKind; + let tokenValue!: string; + let tokenFlags: TokenFlags; + let inJSDocType = 0; + setText(text, start, length); + const scanner: Scanner = { + getStartPos: () => startPos, + getTextPos: () => pos, + getToken: () => token, + getTokenPos: () => tokenPos, + getTokenText: () => text.substring(tokenPos, pos), + getTokenValue: () => tokenValue, + hasUnicodeEscape: () => (tokenFlags & TokenFlags.UnicodeEscape) !== 0, + hasExtendedUnicodeEscape: () => (tokenFlags & TokenFlags.ExtendedUnicodeEscape) !== 0, + hasPrecedingLineBreak: () => (tokenFlags & TokenFlags.PrecedingLineBreak) !== 0, + isIdentifier: () => token === SyntaxKind.Identifier || token > SyntaxKind.LastReservedWord, + isReservedWord: () => token >= SyntaxKind.FirstReservedWord && token <= SyntaxKind.LastReservedWord, + isUnterminated: () => (tokenFlags & TokenFlags.Unterminated) !== 0, + getTokenFlags: () => tokenFlags, + reScanGreaterToken, + reScanSlashToken, + reScanTemplateToken, + scanJsxIdentifier, + scanJsxAttributeValue, + reScanJsxAttributeValue, + reScanJsxToken, + reScanLessThanToken, + reScanQuestionToken, + scanJsxToken, + scanJsDocToken, + scan, + getText, + setText, + setScriptTarget, + setLanguageVariant, + setOnError, + setTextPos, + setInJSDocType, + tryScan, + lookAhead, + scanRange, + }; + if (Debug.isDebugging) { + Object.defineProperty(scanner, "__debugShowCurrentPositionInText", { + get: () => { + const text = scanner.getText(); + return text.slice(0, scanner.getStartPos()) + "║" + text.slice(scanner.getStartPos()); + }, + }); + } + return scanner; + function error(message: DiagnosticMessage): void; + function error(message: DiagnosticMessage, errPos: number, length: number): void; + function error(message: DiagnosticMessage, errPos: number = pos, length?: number): void { + if (onError) { + const oldPos = pos; + pos = errPos; + onError(message, length || 0); + pos = oldPos; + } + } + function scanNumberFragment(): string { + let start = pos; + let allowSeparator = false; + let isPreviousTokenSeparator = false; + let result = ""; + while (true) { + const ch = text.charCodeAt(pos); + if (ch === CharacterCodes._) { + tokenFlags |= TokenFlags.ContainsSeparator; + if (allowSeparator) { + allowSeparator = false; + isPreviousTokenSeparator = true; + result += text.substring(start, pos); } - - return ch === CharacterCodes.equals || - text.charCodeAt(pos + mergeConflictMarkerLength) === CharacterCodes.space; + else if (isPreviousTokenSeparator) { + error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); + } + else { + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); + } + pos++; + start = pos; + continue; + } + if (isDigit(ch)) { + allowSeparator = true; + isPreviousTokenSeparator = false; + pos++; + continue; } + break; } - - return false; + if (text.charCodeAt(pos - 1) === CharacterCodes._) { + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); + } + return result + text.substring(start, pos); } - - function scanConflictMarkerTrivia(text: string, pos: number, error?: (diag: DiagnosticMessage, pos?: number, len?: number) => void) { - if (error) { - error(Diagnostics.Merge_conflict_marker_encountered, pos, mergeConflictMarkerLength); + function scanNumber(): { + type: SyntaxKind; + value: string; + } { + const start = pos; + const mainFragment = scanNumberFragment(); + let decimalFragment: string | undefined; + let scientificFragment: string | undefined; + if (text.charCodeAt(pos) === CharacterCodes.dot) { + pos++; + decimalFragment = scanNumberFragment(); } - - const ch = text.charCodeAt(pos); - const len = text.length; - - if (ch === CharacterCodes.lessThan || ch === CharacterCodes.greaterThan) { - while (pos < len && !isLineBreak(text.charCodeAt(pos))) { + let end = pos; + if (text.charCodeAt(pos) === CharacterCodes.E || text.charCodeAt(pos) === CharacterCodes.e) { + pos++; + tokenFlags |= TokenFlags.Scientific; + if (text.charCodeAt(pos) === CharacterCodes.plus || text.charCodeAt(pos) === CharacterCodes.minus) pos++; + const preNumericPart = pos; + const finalFragment = scanNumberFragment(); + if (!finalFragment) { + error(Diagnostics.Digit_expected); + } + else { + scientificFragment = text.substring(end, preNumericPart) + finalFragment; + end = pos; + } + } + let result: string; + if (tokenFlags & TokenFlags.ContainsSeparator) { + result = mainFragment; + if (decimalFragment) { + result += "." + decimalFragment; + } + if (scientificFragment) { + result += scientificFragment; } } else { - Debug.assert(ch === CharacterCodes.bar || ch === CharacterCodes.equals); - // Consume everything from the start of a ||||||| or ======= marker to the start - // of the next ======= or >>>>>>> marker. - while (pos < len) { - const currentChar = text.charCodeAt(pos); - if ((currentChar === CharacterCodes.equals || currentChar === CharacterCodes.greaterThan) && currentChar !== ch && isConflictMarkerTrivia(text, pos)) { - break; - } - - pos++; + result = text.substring(start, end); // No need to use all the fragments; no _ removal needed + } + if (decimalFragment !== undefined || tokenFlags & TokenFlags.Scientific) { + checkForIdentifierStartAfterNumericLiteral(start, decimalFragment === undefined && !!(tokenFlags & TokenFlags.Scientific)); + return { + type: SyntaxKind.NumericLiteral, + value: "" + +result // if value is not an integer, it can be safely coerced to a number + }; + } + else { + tokenValue = result; + const type = checkBigIntSuffix(); // if value is an integer, check whether it is a bigint + checkForIdentifierStartAfterNumericLiteral(start); + return { type, value: tokenValue }; + } + } + function checkForIdentifierStartAfterNumericLiteral(numericStart: number, isScientific?: boolean) { + if (!isIdentifierStart(codePointAt(text, pos), languageVersion)) { + return; + } + const identifierStart = pos; + const { length } = scanIdentifierParts(); + if (length === 1 && text[identifierStart] === "n") { + if (isScientific) { + error(Diagnostics.A_bigint_literal_cannot_use_exponential_notation, numericStart, identifierStart - numericStart + 1); + } + else { + error(Diagnostics.A_bigint_literal_must_be_an_integer, numericStart, identifierStart - numericStart + 1); } } - - return pos; + else { + error(Diagnostics.An_identifier_or_keyword_cannot_immediately_follow_a_numeric_literal, identifierStart, length); + pos = identifierStart; + } } - - const shebangTriviaRegex = /^#!.*/; - - /*@internal*/ - export function isShebangTrivia(text: string, pos: number) { - // Shebangs check must only be done at the start of the file - Debug.assert(pos === 0); - return shebangTriviaRegex.test(text); + function scanOctalDigits(): number { + const start = pos; + while (isOctalDigit(text.charCodeAt(pos))) { + pos++; + } + return +(text.substring(start, pos)); } - - /*@internal*/ - export function scanShebangTrivia(text: string, pos: number) { - const shebang = shebangTriviaRegex.exec(text)![0]; - pos = pos + shebang.length; - return pos; + /** + * Scans the given number of hexadecimal digits in the text, + * returning -1 if the given number is unavailable. + */ + function scanExactNumberOfHexDigits(count: number, canHaveSeparators: boolean): number { + const valueString = scanHexDigits(/*minCount*/ count, /*scanAsManyAsPossible*/ false, canHaveSeparators); + return valueString ? parseInt(valueString, 16) : -1; } - /** - * Invokes a callback for each comment range following the provided position. - * - * Single-line comment ranges include the leading double-slash characters but not the ending - * line break. Multi-line comment ranges include the leading slash-asterisk and trailing - * asterisk-slash characters. - * - * @param reduce If true, accumulates the result of calling the callback in a fashion similar - * to reduceLeft. If false, iteration stops when the callback returns a truthy value. - * @param text The source text to scan. - * @param pos The position at which to start scanning. - * @param trailing If false, whitespace is skipped until the first line break and comments - * between that location and the next token are returned. If true, comments occurring - * between the given position and the next line break are returned. - * @param cb The callback to execute as each comment range is encountered. - * @param state A state value to pass to each iteration of the callback. - * @param initial An initial value to pass when accumulating results (when "reduce" is true). - * @returns If "reduce" is true, the accumulated value. If "reduce" is false, the first truthy - * return value of the callback. + * Scans as many hexadecimal digits as are available in the text, + * returning "" if the given number of digits was unavailable. */ - function iterateCommentRanges(reduce: boolean, text: string, pos: number, trailing: boolean, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U | undefined) => U, state: T, initial?: U): U | undefined { - let pendingPos!: number; - let pendingEnd!: number; - let pendingKind!: CommentKind; - let pendingHasTrailingNewLine!: boolean; - let hasPendingCommentRange = false; - let collecting = trailing; - let accumulator = initial; - if (pos === 0) { - collecting = true; - const shebang = getShebang(text); - if (shebang) { - pos = shebang.length; + function scanMinimumNumberOfHexDigits(count: number, canHaveSeparators: boolean): string { + return scanHexDigits(/*minCount*/ count, /*scanAsManyAsPossible*/ true, canHaveSeparators); + } + function scanHexDigits(minCount: number, scanAsManyAsPossible: boolean, canHaveSeparators: boolean): string { + let valueChars: number[] = []; + let allowSeparator = false; + let isPreviousTokenSeparator = false; + while (valueChars.length < minCount || scanAsManyAsPossible) { + let ch = text.charCodeAt(pos); + if (canHaveSeparators && ch === CharacterCodes._) { + tokenFlags |= TokenFlags.ContainsSeparator; + if (allowSeparator) { + allowSeparator = false; + isPreviousTokenSeparator = true; + } + else if (isPreviousTokenSeparator) { + error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); + } + else { + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); + } + pos++; + continue; } - } - scan: while (pos >= 0 && pos < text.length) { - const ch = text.charCodeAt(pos); - switch (ch) { - case CharacterCodes.carriageReturn: - if (text.charCodeAt(pos + 1) === CharacterCodes.lineFeed) { - pos++; - } - // falls through - case CharacterCodes.lineFeed: - pos++; - if (trailing) { - break scan; - } - - collecting = true; - if (hasPendingCommentRange) { - pendingHasTrailingNewLine = true; - } - - continue; - case CharacterCodes.tab: - case CharacterCodes.verticalTab: - case CharacterCodes.formFeed: - case CharacterCodes.space: - pos++; - continue; - case CharacterCodes.slash: - const nextChar = text.charCodeAt(pos + 1); - let hasTrailingNewLine = false; - if (nextChar === CharacterCodes.slash || nextChar === CharacterCodes.asterisk) { - const kind = nextChar === CharacterCodes.slash ? SyntaxKind.SingleLineCommentTrivia : SyntaxKind.MultiLineCommentTrivia; - const startPos = pos; - pos += 2; - if (nextChar === CharacterCodes.slash) { - while (pos < text.length) { - if (isLineBreak(text.charCodeAt(pos))) { - hasTrailingNewLine = true; - break; - } - pos++; - } - } - else { - while (pos < text.length) { - if (text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) { - pos += 2; - break; - } - pos++; - } - } - - if (collecting) { - if (hasPendingCommentRange) { - accumulator = cb(pendingPos, pendingEnd, pendingKind, pendingHasTrailingNewLine, state, accumulator); - if (!reduce && accumulator) { - // If we are not reducing and we have a truthy result, return it. - return accumulator; - } - } - - pendingPos = startPos; - pendingEnd = pos; - pendingKind = kind; - pendingHasTrailingNewLine = hasTrailingNewLine; - hasPendingCommentRange = true; - } - - continue; - } - break scan; - default: - if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpaceLike(ch))) { - if (hasPendingCommentRange && isLineBreak(ch)) { - pendingHasTrailingNewLine = true; - } - pos++; - continue; - } - break scan; + allowSeparator = canHaveSeparators; + if (ch >= CharacterCodes.A && ch <= CharacterCodes.F) { + ch += CharacterCodes.a - CharacterCodes.A; // standardize hex literals to lowercase + } + else if (!((ch >= CharacterCodes._0 && ch <= CharacterCodes._9) || + (ch >= CharacterCodes.a && ch <= CharacterCodes.f))) { + break; } + valueChars.push(ch); + pos++; + isPreviousTokenSeparator = false; } - - if (hasPendingCommentRange) { - accumulator = cb(pendingPos, pendingEnd, pendingKind, pendingHasTrailingNewLine, state, accumulator); + if (valueChars.length < minCount) { + valueChars = []; } - - return accumulator; - } - - export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean) => U): U | undefined; - export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state: T): U | undefined; - export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined { - return iterateCommentRanges(/*reduce*/ false, text, pos, /*trailing*/ false, cb, state); - } - - export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean) => U): U | undefined; - export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state: T): U | undefined; - export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined { - return iterateCommentRanges(/*reduce*/ false, text, pos, /*trailing*/ true, cb, state); - } - - export function reduceEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U) { - return iterateCommentRanges(/*reduce*/ true, text, pos, /*trailing*/ false, cb, state, initial); - } - - export function reduceEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U) { - return iterateCommentRanges(/*reduce*/ true, text, pos, /*trailing*/ true, cb, state, initial); - } - - function appendCommentRange(pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, _state: any, comments: CommentRange[]) { - if (!comments) { - comments = []; + if (text.charCodeAt(pos - 1) === CharacterCodes._) { + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); } - - comments.push({ kind, pos, end, hasTrailingNewLine }); - return comments; - } - - export function getLeadingCommentRanges(text: string, pos: number): CommentRange[] | undefined { - return reduceEachLeadingCommentRange(text, pos, appendCommentRange, /*state*/ undefined, /*initial*/ undefined); + return String.fromCharCode(...valueChars); } - - export function getTrailingCommentRanges(text: string, pos: number): CommentRange[] | undefined { - return reduceEachTrailingCommentRange(text, pos, appendCommentRange, /*state*/ undefined, /*initial*/ undefined); + function scanString(jsxAttributeString = false): string { + const quote = text.charCodeAt(pos); + pos++; + let result = ""; + let start = pos; + while (true) { + if (pos >= end) { + result += text.substring(start, pos); + tokenFlags |= TokenFlags.Unterminated; + error(Diagnostics.Unterminated_string_literal); + break; + } + const ch = text.charCodeAt(pos); + if (ch === quote) { + result += text.substring(start, pos); + pos++; + break; + } + if (ch === CharacterCodes.backslash && !jsxAttributeString) { + result += text.substring(start, pos); + result += scanEscapeSequence(); + start = pos; + continue; + } + if (isLineBreak(ch) && !jsxAttributeString) { + result += text.substring(start, pos); + tokenFlags |= TokenFlags.Unterminated; + error(Diagnostics.Unterminated_string_literal); + break; + } + pos++; + } + return result; } - - /** Optionally, get the shebang */ - export function getShebang(text: string): string | undefined { - const match = shebangTriviaRegex.exec(text); - if (match) { - return match[0]; + /** + * Sets the current 'tokenValue' and returns a NoSubstitutionTemplateLiteral or + * a literal component of a TemplateExpression. + */ + function scanTemplateAndSetTokenValue(): SyntaxKind { + const startedWithBacktick = text.charCodeAt(pos) === CharacterCodes.backtick; + pos++; + let start = pos; + let contents = ""; + let resultingToken: SyntaxKind; + while (true) { + if (pos >= end) { + contents += text.substring(start, pos); + tokenFlags |= TokenFlags.Unterminated; + error(Diagnostics.Unterminated_template_literal); + resultingToken = startedWithBacktick ? SyntaxKind.NoSubstitutionTemplateLiteral : SyntaxKind.TemplateTail; + break; + } + const currChar = text.charCodeAt(pos); + // '`' + if (currChar === CharacterCodes.backtick) { + contents += text.substring(start, pos); + pos++; + resultingToken = startedWithBacktick ? SyntaxKind.NoSubstitutionTemplateLiteral : SyntaxKind.TemplateTail; + break; + } + // '${' + if (currChar === CharacterCodes.$ && pos + 1 < end && text.charCodeAt(pos + 1) === CharacterCodes.openBrace) { + contents += text.substring(start, pos); + pos += 2; + resultingToken = startedWithBacktick ? SyntaxKind.TemplateHead : SyntaxKind.TemplateMiddle; + break; + } + // Escape character + if (currChar === CharacterCodes.backslash) { + contents += text.substring(start, pos); + contents += scanEscapeSequence(); + start = pos; + continue; + } + // Speculated ECMAScript 6 Spec 11.8.6.1: + // and LineTerminatorSequences are normalized to for Template Values + if (currChar === CharacterCodes.carriageReturn) { + contents += text.substring(start, pos); + pos++; + if (pos < end && text.charCodeAt(pos) === CharacterCodes.lineFeed) { + pos++; + } + contents += "\n"; + start = pos; + continue; + } + pos++; } + Debug.assert(resultingToken !== undefined); + tokenValue = contents; + return resultingToken; } - - export function isIdentifierStart(ch: number, languageVersion: ScriptTarget | undefined): boolean { - return ch >= CharacterCodes.A && ch <= CharacterCodes.Z || ch >= CharacterCodes.a && ch <= CharacterCodes.z || - ch === CharacterCodes.$ || ch === CharacterCodes._ || - ch > CharacterCodes.maxAsciiCharacter && isUnicodeIdentifierStart(ch, languageVersion); + function scanEscapeSequence(): string { + pos++; + if (pos >= end) { + error(Diagnostics.Unexpected_end_of_text); + return ""; + } + const ch = text.charCodeAt(pos); + pos++; + switch (ch) { + case CharacterCodes._0: + return "\0"; + case CharacterCodes.b: + return "\b"; + case CharacterCodes.t: + return "\t"; + case CharacterCodes.n: + return "\n"; + case CharacterCodes.v: + return "\v"; + case CharacterCodes.f: + return "\f"; + case CharacterCodes.r: + return "\r"; + case CharacterCodes.singleQuote: + return "\'"; + case CharacterCodes.doubleQuote: + return "\""; + case CharacterCodes.u: + // '\u{DDDDDDDD}' + if (pos < end && text.charCodeAt(pos) === CharacterCodes.openBrace) { + tokenFlags |= TokenFlags.ExtendedUnicodeEscape; + pos++; + return scanExtendedUnicodeEscape(); + } + tokenFlags |= TokenFlags.UnicodeEscape; + // '\uDDDD' + return scanHexadecimalEscape(/*numDigits*/ 4); + case CharacterCodes.x: + // '\xDD' + return scanHexadecimalEscape(/*numDigits*/ 2); + // when encountering a LineContinuation (i.e. a backslash and a line terminator sequence), + // the line terminator is interpreted to be "the empty code unit sequence". + case CharacterCodes.carriageReturn: + if (pos < end && text.charCodeAt(pos) === CharacterCodes.lineFeed) { + pos++; + } + // falls through + case CharacterCodes.lineFeed: + case CharacterCodes.lineSeparator: + case CharacterCodes.paragraphSeparator: + return ""; + default: + return String.fromCharCode(ch); + } } - - export function isIdentifierPart(ch: number, languageVersion: ScriptTarget | undefined): boolean { - return ch >= CharacterCodes.A && ch <= CharacterCodes.Z || ch >= CharacterCodes.a && ch <= CharacterCodes.z || - ch >= CharacterCodes._0 && ch <= CharacterCodes._9 || ch === CharacterCodes.$ || ch === CharacterCodes._ || - ch > CharacterCodes.maxAsciiCharacter && isUnicodeIdentifierPart(ch, languageVersion); + function scanHexadecimalEscape(numDigits: number): string { + const escapedValue = scanExactNumberOfHexDigits(numDigits, /*canHaveSeparators*/ false); + if (escapedValue >= 0) { + return String.fromCharCode(escapedValue); + } + else { + error(Diagnostics.Hexadecimal_digit_expected); + return ""; + } } - - /* @internal */ - export function isIdentifierText(name: string, languageVersion: ScriptTarget | undefined): boolean { - let ch = codePointAt(name, 0); - if (!isIdentifierStart(ch, languageVersion)) { - return false; + function scanExtendedUnicodeEscape(): string { + const escapedValueString = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ false); + const escapedValue = escapedValueString ? parseInt(escapedValueString, 16) : -1; + let isInvalidExtendedEscape = false; + // Validate the value of the digit + if (escapedValue < 0) { + error(Diagnostics.Hexadecimal_digit_expected); + isInvalidExtendedEscape = true; + } + else if (escapedValue > 0x10FFFF) { + error(Diagnostics.An_extended_Unicode_escape_value_must_be_between_0x0_and_0x10FFFF_inclusive); + isInvalidExtendedEscape = true; + } + if (pos >= end) { + error(Diagnostics.Unexpected_end_of_text); + isInvalidExtendedEscape = true; + } + else if (text.charCodeAt(pos) === CharacterCodes.closeBrace) { + // Only swallow the following character up if it's a '}'. + pos++; } - - for (let i = charSize(ch); i < name.length; i += charSize(ch)) { - if (!isIdentifierPart(ch = codePointAt(name, i), languageVersion)) { - return false; - } + else { + error(Diagnostics.Unterminated_Unicode_escape_sequence); + isInvalidExtendedEscape = true; } - - return true; + if (isInvalidExtendedEscape) { + return ""; + } + return utf16EncodeAsString(escapedValue); } - - // Creates a scanner over a (possibly unspecified) range of a piece of text. - export function createScanner(languageVersion: ScriptTarget, - skipTrivia: boolean, - languageVariant = LanguageVariant.Standard, - textInitial?: string, - onError?: ErrorCallback, - start?: number, - length?: number): Scanner { - - let text = textInitial!; - - // Current position (end position of text of current token) - let pos: number; - - - // end of text - let end: number; - - // Start position of whitespace before current token - let startPos: number; - - // Start position of text of current token - let tokenPos: number; - - let token: SyntaxKind; - let tokenValue!: string; - let tokenFlags: TokenFlags; - - let inJSDocType = 0; - - setText(text, start, length); - - const scanner: Scanner = { - getStartPos: () => startPos, - getTextPos: () => pos, - getToken: () => token, - getTokenPos: () => tokenPos, - getTokenText: () => text.substring(tokenPos, pos), - getTokenValue: () => tokenValue, - hasUnicodeEscape: () => (tokenFlags & TokenFlags.UnicodeEscape) !== 0, - hasExtendedUnicodeEscape: () => (tokenFlags & TokenFlags.ExtendedUnicodeEscape) !== 0, - hasPrecedingLineBreak: () => (tokenFlags & TokenFlags.PrecedingLineBreak) !== 0, - isIdentifier: () => token === SyntaxKind.Identifier || token > SyntaxKind.LastReservedWord, - isReservedWord: () => token >= SyntaxKind.FirstReservedWord && token <= SyntaxKind.LastReservedWord, - isUnterminated: () => (tokenFlags & TokenFlags.Unterminated) !== 0, - getTokenFlags: () => tokenFlags, - reScanGreaterToken, - reScanSlashToken, - reScanTemplateToken, - scanJsxIdentifier, - scanJsxAttributeValue, - reScanJsxAttributeValue, - reScanJsxToken, - reScanLessThanToken, - reScanQuestionToken, - scanJsxToken, - scanJsDocToken, - scan, - getText, - setText, - setScriptTarget, - setLanguageVariant, - setOnError, - setTextPos, - setInJSDocType, - tryScan, - lookAhead, - scanRange, - }; - - if (Debug.isDebugging) { - Object.defineProperty(scanner, "__debugShowCurrentPositionInText", { - get: () => { - const text = scanner.getText(); - return text.slice(0, scanner.getStartPos()) + "║" + text.slice(scanner.getStartPos()); - }, - }); + // Derived from the 10.1.1 UTF16Encoding of the ES6 Spec. + function utf16EncodeAsString(codePoint: number): string { + Debug.assert(0x0 <= codePoint && codePoint <= 0x10FFFF); + if (codePoint <= 65535) { + return String.fromCharCode(codePoint); + } + const codeUnit1 = Math.floor((codePoint - 65536) / 1024) + 0xD800; + const codeUnit2 = ((codePoint - 65536) % 1024) + 0xDC00; + return String.fromCharCode(codeUnit1, codeUnit2); + } + // Current character is known to be a backslash. Check for Unicode escape of the form '\uXXXX' + // and return code point value if valid Unicode escape is found. Otherwise return -1. + function peekUnicodeEscape(): number { + if (pos + 5 < end && text.charCodeAt(pos + 1) === CharacterCodes.u) { + const start = pos; + pos += 2; + const value = scanExactNumberOfHexDigits(4, /*canHaveSeparators*/ false); + pos = start; + return value; } - - return scanner; - - function error(message: DiagnosticMessage): void; - function error(message: DiagnosticMessage, errPos: number, length: number): void; - function error(message: DiagnosticMessage, errPos: number = pos, length?: number): void { - if (onError) { - const oldPos = pos; - pos = errPos; - onError(message, length || 0); - pos = oldPos; - } + return -1; + } + function peekExtendedUnicodeEscape(): number { + if (languageVersion >= ScriptTarget.ES2015 && codePointAt(text, pos + 1) === CharacterCodes.u && codePointAt(text, pos + 2) === CharacterCodes.openBrace) { + const start = pos; + pos += 3; + const escapedValueString = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ false); + const escapedValue = escapedValueString ? parseInt(escapedValueString, 16) : -1; + pos = start; + return escapedValue; } - - function scanNumberFragment(): string { - let start = pos; - let allowSeparator = false; - let isPreviousTokenSeparator = false; - let result = ""; - while (true) { - const ch = text.charCodeAt(pos); - if (ch === CharacterCodes._) { - tokenFlags |= TokenFlags.ContainsSeparator; - if (allowSeparator) { - allowSeparator = false; - isPreviousTokenSeparator = true; - result += text.substring(start, pos); - } - else if (isPreviousTokenSeparator) { - error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); - } - else { - error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); - } - pos++; + return -1; + } + function scanIdentifierParts(): string { + let result = ""; + let start = pos; + while (pos < end) { + let ch = codePointAt(text, pos); + if (isIdentifierPart(ch, languageVersion)) { + pos += charSize(ch); + } + else if (ch === CharacterCodes.backslash) { + ch = peekExtendedUnicodeEscape(); + if (ch >= 0 && isIdentifierPart(ch, languageVersion)) { + pos += 3; + tokenFlags |= TokenFlags.ExtendedUnicodeEscape; + result += scanExtendedUnicodeEscape(); start = pos; continue; } - if (isDigit(ch)) { - allowSeparator = true; - isPreviousTokenSeparator = false; - pos++; - continue; + ch = peekUnicodeEscape(); + if (!(ch >= 0 && isIdentifierPart(ch, languageVersion))) { + break; } - break; + tokenFlags |= TokenFlags.UnicodeEscape; + result += text.substring(start, pos); + result += utf16EncodeAsString(ch); + // Valid Unicode escape is always six characters + pos += 6; + start = pos; } - if (text.charCodeAt(pos - 1) === CharacterCodes._) { - error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); + else { + break; } - return result + text.substring(start, pos); } - - function scanNumber(): {type: SyntaxKind, value: string} { - const start = pos; - const mainFragment = scanNumberFragment(); - let decimalFragment: string | undefined; - let scientificFragment: string | undefined; - if (text.charCodeAt(pos) === CharacterCodes.dot) { - pos++; - decimalFragment = scanNumberFragment(); + result += text.substring(start, pos); + return result; + } + function getIdentifierToken(): SyntaxKind.Identifier | KeywordSyntaxKind { + // Reserved words are between 2 and 11 characters long and start with a lowercase letter + const len = tokenValue.length; + if (len >= 2 && len <= 11) { + const ch = tokenValue.charCodeAt(0); + if (ch >= CharacterCodes.a && ch <= CharacterCodes.z) { + const keyword = textToKeyword.get(tokenValue); + if (keyword !== undefined) { + return token = keyword; + } } - let end = pos; - if (text.charCodeAt(pos) === CharacterCodes.E || text.charCodeAt(pos) === CharacterCodes.e) { - pos++; - tokenFlags |= TokenFlags.Scientific; - if (text.charCodeAt(pos) === CharacterCodes.plus || text.charCodeAt(pos) === CharacterCodes.minus) pos++; - const preNumericPart = pos; - const finalFragment = scanNumberFragment(); - if (!finalFragment) { - error(Diagnostics.Digit_expected); + } + return token = SyntaxKind.Identifier; + } + function scanBinaryOrOctalDigits(base: 2 | 8): string { + let value = ""; + // For counting number of digits; Valid binaryIntegerLiteral must have at least one binary digit following B or b. + // Similarly valid octalIntegerLiteral must have at least one octal digit following o or O. + let separatorAllowed = false; + let isPreviousTokenSeparator = false; + while (true) { + const ch = text.charCodeAt(pos); + // Numeric separators are allowed anywhere within a numeric literal, except not at the beginning, or following another separator + if (ch === CharacterCodes._) { + tokenFlags |= TokenFlags.ContainsSeparator; + if (separatorAllowed) { + separatorAllowed = false; + isPreviousTokenSeparator = true; + } + else if (isPreviousTokenSeparator) { + error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); } else { - scientificFragment = text.substring(end, preNumericPart) + finalFragment; - end = pos; + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); } + pos++; + continue; } - let result: string; - if (tokenFlags & TokenFlags.ContainsSeparator) { - result = mainFragment; - if (decimalFragment) { - result += "." + decimalFragment; - } - if (scientificFragment) { - result += scientificFragment; - } + separatorAllowed = true; + if (!isDigit(ch) || ch - CharacterCodes._0 >= base) { + break; } - else { - result = text.substring(start, end); // No need to use all the fragments; no _ removal needed - } - - if (decimalFragment !== undefined || tokenFlags & TokenFlags.Scientific) { - checkForIdentifierStartAfterNumericLiteral(start, decimalFragment === undefined && !!(tokenFlags & TokenFlags.Scientific)); - return { - type: SyntaxKind.NumericLiteral, - value: "" + +result // if value is not an integer, it can be safely coerced to a number - }; - } - else { - tokenValue = result; - const type = checkBigIntSuffix(); // if value is an integer, check whether it is a bigint - checkForIdentifierStartAfterNumericLiteral(start); - return { type, value: tokenValue }; + value += text[pos]; + pos++; + isPreviousTokenSeparator = false; + } + if (text.charCodeAt(pos - 1) === CharacterCodes._) { + // Literal ends with underscore - not allowed + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); + } + return value; + } + function checkBigIntSuffix(): SyntaxKind { + if (text.charCodeAt(pos) === CharacterCodes.n) { + tokenValue += "n"; + // Use base 10 instead of base 2 or base 8 for shorter literals + if (tokenFlags & TokenFlags.BinaryOrOctalSpecifier) { + tokenValue = parsePseudoBigInt(tokenValue) + "n"; } + pos++; + return SyntaxKind.BigIntLiteral; + } + else { // not a bigint, so can convert to number in simplified form + // Number() may not support 0b or 0o, so use parseInt() instead + const numericValue = tokenFlags & TokenFlags.BinarySpecifier + ? parseInt(tokenValue.slice(2), 2) // skip "0b" + : tokenFlags & TokenFlags.OctalSpecifier + ? parseInt(tokenValue.slice(2), 8) // skip "0o" + : +tokenValue; + tokenValue = "" + numericValue; + return SyntaxKind.NumericLiteral; } - - function checkForIdentifierStartAfterNumericLiteral(numericStart: number, isScientific?: boolean) { - if (!isIdentifierStart(codePointAt(text, pos), languageVersion)) { - return; + } + function scan(): SyntaxKind { + startPos = pos; + tokenFlags = TokenFlags.None; + let asteriskSeen = false; + while (true) { + tokenPos = pos; + if (pos >= end) { + return token = SyntaxKind.EndOfFileToken; } - - const identifierStart = pos; - const { length } = scanIdentifierParts(); - - if (length === 1 && text[identifierStart] === "n") { - if (isScientific) { - error(Diagnostics.A_bigint_literal_cannot_use_exponential_notation, numericStart, identifierStart - numericStart + 1); + let ch = codePointAt(text, pos); + // Special handling for shebang + if (ch === CharacterCodes.hash && pos === 0 && isShebangTrivia(text, pos)) { + pos = scanShebangTrivia(text, pos); + if (skipTrivia) { + continue; } else { - error(Diagnostics.A_bigint_literal_must_be_an_integer, numericStart, identifierStart - numericStart + 1); + return token = SyntaxKind.ShebangTrivia; } } - else { - error(Diagnostics.An_identifier_or_keyword_cannot_immediately_follow_a_numeric_literal, identifierStart, length); - pos = identifierStart; - } - } - - function scanOctalDigits(): number { - const start = pos; - while (isOctalDigit(text.charCodeAt(pos))) { - pos++; - } - return +(text.substring(start, pos)); - } - - /** - * Scans the given number of hexadecimal digits in the text, - * returning -1 if the given number is unavailable. - */ - function scanExactNumberOfHexDigits(count: number, canHaveSeparators: boolean): number { - const valueString = scanHexDigits(/*minCount*/ count, /*scanAsManyAsPossible*/ false, canHaveSeparators); - return valueString ? parseInt(valueString, 16) : -1; - } - - /** - * Scans as many hexadecimal digits as are available in the text, - * returning "" if the given number of digits was unavailable. - */ - function scanMinimumNumberOfHexDigits(count: number, canHaveSeparators: boolean): string { - return scanHexDigits(/*minCount*/ count, /*scanAsManyAsPossible*/ true, canHaveSeparators); - } - - function scanHexDigits(minCount: number, scanAsManyAsPossible: boolean, canHaveSeparators: boolean): string { - let valueChars: number[] = []; - let allowSeparator = false; - let isPreviousTokenSeparator = false; - while (valueChars.length < minCount || scanAsManyAsPossible) { - let ch = text.charCodeAt(pos); - if (canHaveSeparators && ch === CharacterCodes._) { - tokenFlags |= TokenFlags.ContainsSeparator; - if (allowSeparator) { - allowSeparator = false; - isPreviousTokenSeparator = true; + switch (ch) { + case CharacterCodes.lineFeed: + case CharacterCodes.carriageReturn: + tokenFlags |= TokenFlags.PrecedingLineBreak; + if (skipTrivia) { + pos++; + continue; } - else if (isPreviousTokenSeparator) { - error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); + else { + if (ch === CharacterCodes.carriageReturn && pos + 1 < end && text.charCodeAt(pos + 1) === CharacterCodes.lineFeed) { + // consume both CR and LF + pos += 2; + } + else { + pos++; + } + return token = SyntaxKind.NewLineTrivia; + } + case CharacterCodes.tab: + case CharacterCodes.verticalTab: + case CharacterCodes.formFeed: + case CharacterCodes.space: + case CharacterCodes.nonBreakingSpace: + case CharacterCodes.ogham: + case CharacterCodes.enQuad: + case CharacterCodes.emQuad: + case CharacterCodes.enSpace: + case CharacterCodes.emSpace: + case CharacterCodes.threePerEmSpace: + case CharacterCodes.fourPerEmSpace: + case CharacterCodes.sixPerEmSpace: + case CharacterCodes.figureSpace: + case CharacterCodes.punctuationSpace: + case CharacterCodes.thinSpace: + case CharacterCodes.hairSpace: + case CharacterCodes.zeroWidthSpace: + case CharacterCodes.narrowNoBreakSpace: + case CharacterCodes.mathematicalSpace: + case CharacterCodes.ideographicSpace: + case CharacterCodes.byteOrderMark: + if (skipTrivia) { + pos++; + continue; } else { - error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); + while (pos < end && isWhiteSpaceSingleLine(text.charCodeAt(pos))) { + pos++; + } + return token = SyntaxKind.WhitespaceTrivia; + } + case CharacterCodes.exclamation: + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { + return pos += 3, token = SyntaxKind.ExclamationEqualsEqualsToken; + } + return pos += 2, token = SyntaxKind.ExclamationEqualsToken; } pos++; - continue; - } - allowSeparator = canHaveSeparators; - if (ch >= CharacterCodes.A && ch <= CharacterCodes.F) { - ch += CharacterCodes.a - CharacterCodes.A; // standardize hex literals to lowercase - } - else if (!((ch >= CharacterCodes._0 && ch <= CharacterCodes._9) || - (ch >= CharacterCodes.a && ch <= CharacterCodes.f) - )) { - break; - } - valueChars.push(ch); - pos++; - isPreviousTokenSeparator = false; - } - if (valueChars.length < minCount) { - valueChars = []; - } - if (text.charCodeAt(pos - 1) === CharacterCodes._) { - error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); - } - return String.fromCharCode(...valueChars); - } - - function scanString(jsxAttributeString = false): string { - const quote = text.charCodeAt(pos); - pos++; - let result = ""; - let start = pos; - while (true) { - if (pos >= end) { - result += text.substring(start, pos); - tokenFlags |= TokenFlags.Unterminated; - error(Diagnostics.Unterminated_string_literal); - break; - } - const ch = text.charCodeAt(pos); - if (ch === quote) { - result += text.substring(start, pos); + return token = SyntaxKind.ExclamationToken; + case CharacterCodes.doubleQuote: + case CharacterCodes.singleQuote: + tokenValue = scanString(); + return token = SyntaxKind.StringLiteral; + case CharacterCodes.backtick: + return token = scanTemplateAndSetTokenValue(); + case CharacterCodes.percent: + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.PercentEqualsToken; + } pos++; - break; - } - if (ch === CharacterCodes.backslash && !jsxAttributeString) { - result += text.substring(start, pos); - result += scanEscapeSequence(); - start = pos; - continue; - } - if (isLineBreak(ch) && !jsxAttributeString) { - result += text.substring(start, pos); - tokenFlags |= TokenFlags.Unterminated; - error(Diagnostics.Unterminated_string_literal); - break; - } - pos++; - } - return result; - } - - /** - * Sets the current 'tokenValue' and returns a NoSubstitutionTemplateLiteral or - * a literal component of a TemplateExpression. - */ - function scanTemplateAndSetTokenValue(): SyntaxKind { - const startedWithBacktick = text.charCodeAt(pos) === CharacterCodes.backtick; - - pos++; - let start = pos; - let contents = ""; - let resultingToken: SyntaxKind; - - while (true) { - if (pos >= end) { - contents += text.substring(start, pos); - tokenFlags |= TokenFlags.Unterminated; - error(Diagnostics.Unterminated_template_literal); - resultingToken = startedWithBacktick ? SyntaxKind.NoSubstitutionTemplateLiteral : SyntaxKind.TemplateTail; - break; - } - - const currChar = text.charCodeAt(pos); - - // '`' - if (currChar === CharacterCodes.backtick) { - contents += text.substring(start, pos); + return token = SyntaxKind.PercentToken; + case CharacterCodes.ampersand: + if (text.charCodeAt(pos + 1) === CharacterCodes.ampersand) { + return pos += 2, token = SyntaxKind.AmpersandAmpersandToken; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.AmpersandEqualsToken; + } pos++; - resultingToken = startedWithBacktick ? SyntaxKind.NoSubstitutionTemplateLiteral : SyntaxKind.TemplateTail; - break; - } - - // '${' - if (currChar === CharacterCodes.$ && pos + 1 < end && text.charCodeAt(pos + 1) === CharacterCodes.openBrace) { - contents += text.substring(start, pos); - pos += 2; - resultingToken = startedWithBacktick ? SyntaxKind.TemplateHead : SyntaxKind.TemplateMiddle; - break; - } - - // Escape character - if (currChar === CharacterCodes.backslash) { - contents += text.substring(start, pos); - contents += scanEscapeSequence(); - start = pos; - continue; - } - - // Speculated ECMAScript 6 Spec 11.8.6.1: - // and LineTerminatorSequences are normalized to for Template Values - if (currChar === CharacterCodes.carriageReturn) { - contents += text.substring(start, pos); + return token = SyntaxKind.AmpersandToken; + case CharacterCodes.openParen: pos++; - - if (pos < end && text.charCodeAt(pos) === CharacterCodes.lineFeed) { - pos++; - } - - contents += "\n"; - start = pos; - continue; - } - - pos++; - } - - Debug.assert(resultingToken !== undefined); - - tokenValue = contents; - return resultingToken; - } - - function scanEscapeSequence(): string { - pos++; - if (pos >= end) { - error(Diagnostics.Unexpected_end_of_text); - return ""; - } - const ch = text.charCodeAt(pos); - pos++; - switch (ch) { - case CharacterCodes._0: - return "\0"; - case CharacterCodes.b: - return "\b"; - case CharacterCodes.t: - return "\t"; - case CharacterCodes.n: - return "\n"; - case CharacterCodes.v: - return "\v"; - case CharacterCodes.f: - return "\f"; - case CharacterCodes.r: - return "\r"; - case CharacterCodes.singleQuote: - return "\'"; - case CharacterCodes.doubleQuote: - return "\""; - case CharacterCodes.u: - // '\u{DDDDDDDD}' - if (pos < end && text.charCodeAt(pos) === CharacterCodes.openBrace) { - tokenFlags |= TokenFlags.ExtendedUnicodeEscape; - pos++; - return scanExtendedUnicodeEscape(); + return token = SyntaxKind.OpenParenToken; + case CharacterCodes.closeParen: + pos++; + return token = SyntaxKind.CloseParenToken; + case CharacterCodes.asterisk: + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.AsteriskEqualsToken; } - - tokenFlags |= TokenFlags.UnicodeEscape; - // '\uDDDD' - return scanHexadecimalEscape(/*numDigits*/ 4); - - case CharacterCodes.x: - // '\xDD' - return scanHexadecimalEscape(/*numDigits*/ 2); - - // when encountering a LineContinuation (i.e. a backslash and a line terminator sequence), - // the line terminator is interpreted to be "the empty code unit sequence". - case CharacterCodes.carriageReturn: - if (pos < end && text.charCodeAt(pos) === CharacterCodes.lineFeed) { - pos++; + if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { + if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { + return pos += 3, token = SyntaxKind.AsteriskAsteriskEqualsToken; + } + return pos += 2, token = SyntaxKind.AsteriskAsteriskToken; } - // falls through - case CharacterCodes.lineFeed: - case CharacterCodes.lineSeparator: - case CharacterCodes.paragraphSeparator: - return ""; - default: - return String.fromCharCode(ch); - } - } - - function scanHexadecimalEscape(numDigits: number): string { - const escapedValue = scanExactNumberOfHexDigits(numDigits, /*canHaveSeparators*/ false); - - if (escapedValue >= 0) { - return String.fromCharCode(escapedValue); - } - else { - error(Diagnostics.Hexadecimal_digit_expected); - return ""; - } - } - - function scanExtendedUnicodeEscape(): string { - const escapedValueString = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ false); - const escapedValue = escapedValueString ? parseInt(escapedValueString, 16) : -1; - let isInvalidExtendedEscape = false; - - // Validate the value of the digit - if (escapedValue < 0) { - error(Diagnostics.Hexadecimal_digit_expected); - isInvalidExtendedEscape = true; - } - else if (escapedValue > 0x10FFFF) { - error(Diagnostics.An_extended_Unicode_escape_value_must_be_between_0x0_and_0x10FFFF_inclusive); - isInvalidExtendedEscape = true; - } - - if (pos >= end) { - error(Diagnostics.Unexpected_end_of_text); - isInvalidExtendedEscape = true; - } - else if (text.charCodeAt(pos) === CharacterCodes.closeBrace) { - // Only swallow the following character up if it's a '}'. - pos++; - } - else { - error(Diagnostics.Unterminated_Unicode_escape_sequence); - isInvalidExtendedEscape = true; - } - - if (isInvalidExtendedEscape) { - return ""; - } - - return utf16EncodeAsString(escapedValue); - } - - // Derived from the 10.1.1 UTF16Encoding of the ES6 Spec. - function utf16EncodeAsString(codePoint: number): string { - Debug.assert(0x0 <= codePoint && codePoint <= 0x10FFFF); - - if (codePoint <= 65535) { - return String.fromCharCode(codePoint); - } - - const codeUnit1 = Math.floor((codePoint - 65536) / 1024) + 0xD800; - const codeUnit2 = ((codePoint - 65536) % 1024) + 0xDC00; - - return String.fromCharCode(codeUnit1, codeUnit2); - } - - // Current character is known to be a backslash. Check for Unicode escape of the form '\uXXXX' - // and return code point value if valid Unicode escape is found. Otherwise return -1. - function peekUnicodeEscape(): number { - if (pos + 5 < end && text.charCodeAt(pos + 1) === CharacterCodes.u) { - const start = pos; - pos += 2; - const value = scanExactNumberOfHexDigits(4, /*canHaveSeparators*/ false); - pos = start; - return value; - } - return -1; - } - - - function peekExtendedUnicodeEscape(): number { - if (languageVersion >= ScriptTarget.ES2015 && codePointAt(text, pos + 1) === CharacterCodes.u && codePointAt(text, pos + 2) === CharacterCodes.openBrace) { - const start = pos; - pos += 3; - const escapedValueString = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ false); - const escapedValue = escapedValueString ? parseInt(escapedValueString, 16) : -1; - pos = start; - return escapedValue; - } - return -1; - } - - function scanIdentifierParts(): string { - let result = ""; - let start = pos; - while (pos < end) { - let ch = codePointAt(text, pos); - if (isIdentifierPart(ch, languageVersion)) { - pos += charSize(ch); - } - else if (ch === CharacterCodes.backslash) { - ch = peekExtendedUnicodeEscape(); - if (ch >= 0 && isIdentifierPart(ch, languageVersion)) { - pos += 3; - tokenFlags |= TokenFlags.ExtendedUnicodeEscape; - result += scanExtendedUnicodeEscape(); - start = pos; + pos++; + if (inJSDocType && !asteriskSeen && (tokenFlags & TokenFlags.PrecedingLineBreak)) { + // decoration at the start of a JSDoc comment line + asteriskSeen = true; continue; } - ch = peekUnicodeEscape(); - if (!(ch >= 0 && isIdentifierPart(ch, languageVersion))) { - break; - } - tokenFlags |= TokenFlags.UnicodeEscape; - result += text.substring(start, pos); - result += utf16EncodeAsString(ch); - // Valid Unicode escape is always six characters - pos += 6; - start = pos; - } - else { - break; - } - } - result += text.substring(start, pos); - return result; - } - - function getIdentifierToken(): SyntaxKind.Identifier | KeywordSyntaxKind { - // Reserved words are between 2 and 11 characters long and start with a lowercase letter - const len = tokenValue.length; - if (len >= 2 && len <= 11) { - const ch = tokenValue.charCodeAt(0); - if (ch >= CharacterCodes.a && ch <= CharacterCodes.z) { - const keyword = textToKeyword.get(tokenValue); - if (keyword !== undefined) { - return token = keyword; + return token = SyntaxKind.AsteriskToken; + case CharacterCodes.plus: + if (text.charCodeAt(pos + 1) === CharacterCodes.plus) { + return pos += 2, token = SyntaxKind.PlusPlusToken; } - } - } - return token = SyntaxKind.Identifier; - } - - function scanBinaryOrOctalDigits(base: 2 | 8): string { - let value = ""; - // For counting number of digits; Valid binaryIntegerLiteral must have at least one binary digit following B or b. - // Similarly valid octalIntegerLiteral must have at least one octal digit following o or O. - let separatorAllowed = false; - let isPreviousTokenSeparator = false; - while (true) { - const ch = text.charCodeAt(pos); - // Numeric separators are allowed anywhere within a numeric literal, except not at the beginning, or following another separator - if (ch === CharacterCodes._) { - tokenFlags |= TokenFlags.ContainsSeparator; - if (separatorAllowed) { - separatorAllowed = false; - isPreviousTokenSeparator = true; + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.PlusEqualsToken; } - else if (isPreviousTokenSeparator) { - error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); + pos++; + return token = SyntaxKind.PlusToken; + case CharacterCodes.comma: + pos++; + return token = SyntaxKind.CommaToken; + case CharacterCodes.minus: + if (text.charCodeAt(pos + 1) === CharacterCodes.minus) { + return pos += 2, token = SyntaxKind.MinusMinusToken; } - else { - error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.MinusEqualsToken; } pos++; - continue; - } - separatorAllowed = true; - if (!isDigit(ch) || ch - CharacterCodes._0 >= base) { - break; - } - value += text[pos]; - pos++; - isPreviousTokenSeparator = false; - } - if (text.charCodeAt(pos - 1) === CharacterCodes._) { - // Literal ends with underscore - not allowed - error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); - } - return value; - } - - function checkBigIntSuffix(): SyntaxKind { - if (text.charCodeAt(pos) === CharacterCodes.n) { - tokenValue += "n"; - // Use base 10 instead of base 2 or base 8 for shorter literals - if (tokenFlags & TokenFlags.BinaryOrOctalSpecifier) { - tokenValue = parsePseudoBigInt(tokenValue) + "n"; - } - pos++; - return SyntaxKind.BigIntLiteral; - } - else { // not a bigint, so can convert to number in simplified form - // Number() may not support 0b or 0o, so use parseInt() instead - const numericValue = tokenFlags & TokenFlags.BinarySpecifier - ? parseInt(tokenValue.slice(2), 2) // skip "0b" - : tokenFlags & TokenFlags.OctalSpecifier - ? parseInt(tokenValue.slice(2), 8) // skip "0o" - : +tokenValue; - tokenValue = "" + numericValue; - return SyntaxKind.NumericLiteral; - } - } - - function scan(): SyntaxKind { - startPos = pos; - tokenFlags = TokenFlags.None; - let asteriskSeen = false; - while (true) { - tokenPos = pos; - if (pos >= end) { - return token = SyntaxKind.EndOfFileToken; - } - let ch = codePointAt(text, pos); - - // Special handling for shebang - if (ch === CharacterCodes.hash && pos === 0 && isShebangTrivia(text, pos)) { - pos = scanShebangTrivia(text, pos); - if (skipTrivia) { - continue; + return token = SyntaxKind.MinusToken; + case CharacterCodes.dot: + if (isDigit(text.charCodeAt(pos + 1))) { + tokenValue = scanNumber().value; + return token = SyntaxKind.NumericLiteral; } - else { - return token = SyntaxKind.ShebangTrivia; + if (text.charCodeAt(pos + 1) === CharacterCodes.dot && text.charCodeAt(pos + 2) === CharacterCodes.dot) { + return pos += 3, token = SyntaxKind.DotDotDotToken; } - } - - switch (ch) { - case CharacterCodes.lineFeed: - case CharacterCodes.carriageReturn: - tokenFlags |= TokenFlags.PrecedingLineBreak; - if (skipTrivia) { - pos++; - continue; - } - else { - if (ch === CharacterCodes.carriageReturn && pos + 1 < end && text.charCodeAt(pos + 1) === CharacterCodes.lineFeed) { - // consume both CR and LF - pos += 2; - } - else { - pos++; + pos++; + return token = SyntaxKind.DotToken; + case CharacterCodes.slash: + // Single-line comment + if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { + pos += 2; + while (pos < end) { + if (isLineBreak(text.charCodeAt(pos))) { + break; } - return token = SyntaxKind.NewLineTrivia; + pos++; } - case CharacterCodes.tab: - case CharacterCodes.verticalTab: - case CharacterCodes.formFeed: - case CharacterCodes.space: - case CharacterCodes.nonBreakingSpace: - case CharacterCodes.ogham: - case CharacterCodes.enQuad: - case CharacterCodes.emQuad: - case CharacterCodes.enSpace: - case CharacterCodes.emSpace: - case CharacterCodes.threePerEmSpace: - case CharacterCodes.fourPerEmSpace: - case CharacterCodes.sixPerEmSpace: - case CharacterCodes.figureSpace: - case CharacterCodes.punctuationSpace: - case CharacterCodes.thinSpace: - case CharacterCodes.hairSpace: - case CharacterCodes.zeroWidthSpace: - case CharacterCodes.narrowNoBreakSpace: - case CharacterCodes.mathematicalSpace: - case CharacterCodes.ideographicSpace: - case CharacterCodes.byteOrderMark: if (skipTrivia) { - pos++; continue; } else { - while (pos < end && isWhiteSpaceSingleLine(text.charCodeAt(pos))) { - pos++; - } - return token = SyntaxKind.WhitespaceTrivia; - } - case CharacterCodes.exclamation: - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.ExclamationEqualsEqualsToken; - } - return pos += 2, token = SyntaxKind.ExclamationEqualsToken; - } - pos++; - return token = SyntaxKind.ExclamationToken; - case CharacterCodes.doubleQuote: - case CharacterCodes.singleQuote: - tokenValue = scanString(); - return token = SyntaxKind.StringLiteral; - case CharacterCodes.backtick: - return token = scanTemplateAndSetTokenValue(); - case CharacterCodes.percent: - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.PercentEqualsToken; - } - pos++; - return token = SyntaxKind.PercentToken; - case CharacterCodes.ampersand: - if (text.charCodeAt(pos + 1) === CharacterCodes.ampersand) { - return pos += 2, token = SyntaxKind.AmpersandAmpersandToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.AmpersandEqualsToken; - } - pos++; - return token = SyntaxKind.AmpersandToken; - case CharacterCodes.openParen: - pos++; - return token = SyntaxKind.OpenParenToken; - case CharacterCodes.closeParen: - pos++; - return token = SyntaxKind.CloseParenToken; - case CharacterCodes.asterisk: - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.AsteriskEqualsToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { - if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.AsteriskAsteriskEqualsToken; - } - return pos += 2, token = SyntaxKind.AsteriskAsteriskToken; - } - pos++; - if (inJSDocType && !asteriskSeen && (tokenFlags & TokenFlags.PrecedingLineBreak)) { - // decoration at the start of a JSDoc comment line - asteriskSeen = true; - continue; - } - return token = SyntaxKind.AsteriskToken; - case CharacterCodes.plus: - if (text.charCodeAt(pos + 1) === CharacterCodes.plus) { - return pos += 2, token = SyntaxKind.PlusPlusToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.PlusEqualsToken; - } - pos++; - return token = SyntaxKind.PlusToken; - case CharacterCodes.comma: - pos++; - return token = SyntaxKind.CommaToken; - case CharacterCodes.minus: - if (text.charCodeAt(pos + 1) === CharacterCodes.minus) { - return pos += 2, token = SyntaxKind.MinusMinusToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.MinusEqualsToken; - } - pos++; - return token = SyntaxKind.MinusToken; - case CharacterCodes.dot: - if (isDigit(text.charCodeAt(pos + 1))) { - tokenValue = scanNumber().value; - return token = SyntaxKind.NumericLiteral; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.dot && text.charCodeAt(pos + 2) === CharacterCodes.dot) { - return pos += 3, token = SyntaxKind.DotDotDotToken; + return token = SyntaxKind.SingleLineCommentTrivia; } - pos++; - return token = SyntaxKind.DotToken; - case CharacterCodes.slash: - // Single-line comment - if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { - pos += 2; - - while (pos < end) { - if (isLineBreak(text.charCodeAt(pos))) { - break; - } - pos++; - - } - - if (skipTrivia) { - continue; - } - else { - return token = SyntaxKind.SingleLineCommentTrivia; - } - } - // Multi-line comment - if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { - pos += 2; - if (text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) !== CharacterCodes.slash) { - tokenFlags |= TokenFlags.PrecedingJSDocComment; - } - - let commentClosed = false; - while (pos < end) { - const ch = text.charCodeAt(pos); - - if (ch === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) { - pos += 2; - commentClosed = true; - break; - } - - if (isLineBreak(ch)) { - tokenFlags |= TokenFlags.PrecedingLineBreak; - } - pos++; - } - - if (!commentClosed) { - error(Diagnostics.Asterisk_Slash_expected); - } - - if (skipTrivia) { - continue; - } - else { - if (!commentClosed) { - tokenFlags |= TokenFlags.Unterminated; - } - return token = SyntaxKind.MultiLineCommentTrivia; - } - } - - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.SlashEqualsToken; - } - - pos++; - return token = SyntaxKind.SlashToken; - - case CharacterCodes._0: - if (pos + 2 < end && (text.charCodeAt(pos + 1) === CharacterCodes.X || text.charCodeAt(pos + 1) === CharacterCodes.x)) { - pos += 2; - tokenValue = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ true); - if (!tokenValue) { - error(Diagnostics.Hexadecimal_digit_expected); - tokenValue = "0"; - } - tokenValue = "0x" + tokenValue; - tokenFlags |= TokenFlags.HexSpecifier; - return token = checkBigIntSuffix(); - } - else if (pos + 2 < end && (text.charCodeAt(pos + 1) === CharacterCodes.B || text.charCodeAt(pos + 1) === CharacterCodes.b)) { - pos += 2; - tokenValue = scanBinaryOrOctalDigits(/* base */ 2); - if (!tokenValue) { - error(Diagnostics.Binary_digit_expected); - tokenValue = "0"; - } - tokenValue = "0b" + tokenValue; - tokenFlags |= TokenFlags.BinarySpecifier; - return token = checkBigIntSuffix(); - } - else if (pos + 2 < end && (text.charCodeAt(pos + 1) === CharacterCodes.O || text.charCodeAt(pos + 1) === CharacterCodes.o)) { - pos += 2; - tokenValue = scanBinaryOrOctalDigits(/* base */ 8); - if (!tokenValue) { - error(Diagnostics.Octal_digit_expected); - tokenValue = "0"; - } - tokenValue = "0o" + tokenValue; - tokenFlags |= TokenFlags.OctalSpecifier; - return token = checkBigIntSuffix(); - } - // Try to parse as an octal - if (pos + 1 < end && isOctalDigit(text.charCodeAt(pos + 1))) { - tokenValue = "" + scanOctalDigits(); - tokenFlags |= TokenFlags.Octal; - return token = SyntaxKind.NumericLiteral; - } - // This fall-through is a deviation from the EcmaScript grammar. The grammar says that a leading zero - // can only be followed by an octal digit, a dot, or the end of the number literal. However, we are being - // permissive and allowing decimal digits of the form 08* and 09* (which many browsers also do). - // falls through - case CharacterCodes._1: - case CharacterCodes._2: - case CharacterCodes._3: - case CharacterCodes._4: - case CharacterCodes._5: - case CharacterCodes._6: - case CharacterCodes._7: - case CharacterCodes._8: - case CharacterCodes._9: - ({ type: token, value: tokenValue } = scanNumber()); - return token; - case CharacterCodes.colon: - pos++; - return token = SyntaxKind.ColonToken; - case CharacterCodes.semicolon: - pos++; - return token = SyntaxKind.SemicolonToken; - case CharacterCodes.lessThan: - if (isConflictMarkerTrivia(text, pos)) { - pos = scanConflictMarkerTrivia(text, pos, error); - if (skipTrivia) { - continue; - } - else { - return token = SyntaxKind.ConflictMarkerTrivia; - } - } - - if (text.charCodeAt(pos + 1) === CharacterCodes.lessThan) { - if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.LessThanLessThanEqualsToken; - } - return pos += 2, token = SyntaxKind.LessThanLessThanToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.LessThanEqualsToken; - } - if (languageVariant === LanguageVariant.JSX && - text.charCodeAt(pos + 1) === CharacterCodes.slash && - text.charCodeAt(pos + 2) !== CharacterCodes.asterisk) { - return pos += 2, token = SyntaxKind.LessThanSlashToken; - } - pos++; - return token = SyntaxKind.LessThanToken; - case CharacterCodes.equals: - if (isConflictMarkerTrivia(text, pos)) { - pos = scanConflictMarkerTrivia(text, pos, error); - if (skipTrivia) { - continue; - } - else { - return token = SyntaxKind.ConflictMarkerTrivia; - } - } - - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.EqualsEqualsEqualsToken; - } - return pos += 2, token = SyntaxKind.EqualsEqualsToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.greaterThan) { - return pos += 2, token = SyntaxKind.EqualsGreaterThanToken; + } + // Multi-line comment + if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { + pos += 2; + if (text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) !== CharacterCodes.slash) { + tokenFlags |= TokenFlags.PrecedingJSDocComment; } - pos++; - return token = SyntaxKind.EqualsToken; - case CharacterCodes.greaterThan: - if (isConflictMarkerTrivia(text, pos)) { - pos = scanConflictMarkerTrivia(text, pos, error); - if (skipTrivia) { - continue; + let commentClosed = false; + while (pos < end) { + const ch = text.charCodeAt(pos); + if (ch === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) { + pos += 2; + commentClosed = true; + break; } - else { - return token = SyntaxKind.ConflictMarkerTrivia; + if (isLineBreak(ch)) { + tokenFlags |= TokenFlags.PrecedingLineBreak; } - } - - pos++; - return token = SyntaxKind.GreaterThanToken; - case CharacterCodes.question: - pos++; - if (text.charCodeAt(pos) === CharacterCodes.dot && !isDigit(text.charCodeAt(pos + 1))) { pos++; - return token = SyntaxKind.QuestionDotToken; } - if (text.charCodeAt(pos) === CharacterCodes.question) { - pos++; - return token = SyntaxKind.QuestionQuestionToken; + if (!commentClosed) { + error(Diagnostics.Asterisk_Slash_expected); } - return token = SyntaxKind.QuestionToken; - case CharacterCodes.openBracket: - pos++; - return token = SyntaxKind.OpenBracketToken; - case CharacterCodes.closeBracket: - pos++; - return token = SyntaxKind.CloseBracketToken; - case CharacterCodes.caret: - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.CaretEqualsToken; + if (skipTrivia) { + continue; } - pos++; - return token = SyntaxKind.CaretToken; - case CharacterCodes.openBrace: - pos++; - return token = SyntaxKind.OpenBraceToken; - case CharacterCodes.bar: - if (isConflictMarkerTrivia(text, pos)) { - pos = scanConflictMarkerTrivia(text, pos, error); - if (skipTrivia) { - continue; - } - else { - return token = SyntaxKind.ConflictMarkerTrivia; + else { + if (!commentClosed) { + tokenFlags |= TokenFlags.Unterminated; } + return token = SyntaxKind.MultiLineCommentTrivia; } - - if (text.charCodeAt(pos + 1) === CharacterCodes.bar) { - return pos += 2, token = SyntaxKind.BarBarToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.BarEqualsToken; - } - pos++; - return token = SyntaxKind.BarToken; - case CharacterCodes.closeBrace: - pos++; - return token = SyntaxKind.CloseBraceToken; - case CharacterCodes.tilde: - pos++; - return token = SyntaxKind.TildeToken; - case CharacterCodes.at: - pos++; - return token = SyntaxKind.AtToken; - case CharacterCodes.backslash: - const extendedCookedChar = peekExtendedUnicodeEscape(); - if (extendedCookedChar >= 0 && isIdentifierStart(extendedCookedChar, languageVersion)) { - pos += 3; - tokenFlags |= TokenFlags.ExtendedUnicodeEscape; - tokenValue = scanExtendedUnicodeEscape() + scanIdentifierParts(); - return token = getIdentifierToken(); + } + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.SlashEqualsToken; + } + pos++; + return token = SyntaxKind.SlashToken; + case CharacterCodes._0: + if (pos + 2 < end && (text.charCodeAt(pos + 1) === CharacterCodes.X || text.charCodeAt(pos + 1) === CharacterCodes.x)) { + pos += 2; + tokenValue = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ true); + if (!tokenValue) { + error(Diagnostics.Hexadecimal_digit_expected); + tokenValue = "0"; } - - const cookedChar = peekUnicodeEscape(); - if (cookedChar >= 0 && isIdentifierStart(cookedChar, languageVersion)) { - pos += 6; - tokenFlags |= TokenFlags.UnicodeEscape; - tokenValue = String.fromCharCode(cookedChar) + scanIdentifierParts(); - return token = getIdentifierToken(); + tokenValue = "0x" + tokenValue; + tokenFlags |= TokenFlags.HexSpecifier; + return token = checkBigIntSuffix(); + } + else if (pos + 2 < end && (text.charCodeAt(pos + 1) === CharacterCodes.B || text.charCodeAt(pos + 1) === CharacterCodes.b)) { + pos += 2; + tokenValue = scanBinaryOrOctalDigits(/* base */ 2); + if (!tokenValue) { + error(Diagnostics.Binary_digit_expected); + tokenValue = "0"; } - - error(Diagnostics.Invalid_character); - pos++; - return token = SyntaxKind.Unknown; - default: - if (isIdentifierStart(ch, languageVersion)) { - pos += charSize(ch); - while (pos < end && isIdentifierPart(ch = codePointAt(text, pos), languageVersion)) pos += charSize(ch); - tokenValue = text.substring(tokenPos, pos); - if (ch === CharacterCodes.backslash) { - tokenValue += scanIdentifierParts(); - } - return token = getIdentifierToken(); + tokenValue = "0b" + tokenValue; + tokenFlags |= TokenFlags.BinarySpecifier; + return token = checkBigIntSuffix(); + } + else if (pos + 2 < end && (text.charCodeAt(pos + 1) === CharacterCodes.O || text.charCodeAt(pos + 1) === CharacterCodes.o)) { + pos += 2; + tokenValue = scanBinaryOrOctalDigits(/* base */ 8); + if (!tokenValue) { + error(Diagnostics.Octal_digit_expected); + tokenValue = "0"; } - else if (isWhiteSpaceSingleLine(ch)) { - pos += charSize(ch); + tokenValue = "0o" + tokenValue; + tokenFlags |= TokenFlags.OctalSpecifier; + return token = checkBigIntSuffix(); + } + // Try to parse as an octal + if (pos + 1 < end && isOctalDigit(text.charCodeAt(pos + 1))) { + tokenValue = "" + scanOctalDigits(); + tokenFlags |= TokenFlags.Octal; + return token = SyntaxKind.NumericLiteral; + } + // This fall-through is a deviation from the EcmaScript grammar. The grammar says that a leading zero + // can only be followed by an octal digit, a dot, or the end of the number literal. However, we are being + // permissive and allowing decimal digits of the form 08* and 09* (which many browsers also do). + // falls through + case CharacterCodes._1: + case CharacterCodes._2: + case CharacterCodes._3: + case CharacterCodes._4: + case CharacterCodes._5: + case CharacterCodes._6: + case CharacterCodes._7: + case CharacterCodes._8: + case CharacterCodes._9: + ({ type: token, value: tokenValue } = scanNumber()); + return token; + case CharacterCodes.colon: + pos++; + return token = SyntaxKind.ColonToken; + case CharacterCodes.semicolon: + pos++; + return token = SyntaxKind.SemicolonToken; + case CharacterCodes.lessThan: + if (isConflictMarkerTrivia(text, pos)) { + pos = scanConflictMarkerTrivia(text, pos, error); + if (skipTrivia) { continue; } - else if (isLineBreak(ch)) { - tokenFlags |= TokenFlags.PrecedingLineBreak; - pos += charSize(ch); - continue; + else { + return token = SyntaxKind.ConflictMarkerTrivia; } - error(Diagnostics.Invalid_character); - pos += charSize(ch); - return token = SyntaxKind.Unknown; - } - } - } - - function reScanGreaterToken(): SyntaxKind { - if (token === SyntaxKind.GreaterThanToken) { - if (text.charCodeAt(pos) === CharacterCodes.greaterThan) { - if (text.charCodeAt(pos + 1) === CharacterCodes.greaterThan) { + } + if (text.charCodeAt(pos + 1) === CharacterCodes.lessThan) { if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken; + return pos += 3, token = SyntaxKind.LessThanLessThanEqualsToken; } - return pos += 2, token = SyntaxKind.GreaterThanGreaterThanGreaterThanToken; + return pos += 2, token = SyntaxKind.LessThanLessThanToken; } if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.GreaterThanGreaterThanEqualsToken; + return pos += 2, token = SyntaxKind.LessThanEqualsToken; } - pos++; - return token = SyntaxKind.GreaterThanGreaterThanToken; - } - if (text.charCodeAt(pos) === CharacterCodes.equals) { - pos++; - return token = SyntaxKind.GreaterThanEqualsToken; - } - } - return token; - } - - function reScanSlashToken(): SyntaxKind { - if (token === SyntaxKind.SlashToken || token === SyntaxKind.SlashEqualsToken) { - let p = tokenPos + 1; - let inEscape = false; - let inCharacterClass = false; - while (true) { - // If we reach the end of a file, or hit a newline, then this is an unterminated - // regex. Report error and return what we have so far. - if (p >= end) { - tokenFlags |= TokenFlags.Unterminated; - error(Diagnostics.Unterminated_regular_expression_literal); - break; - } - - const ch = text.charCodeAt(p); - if (isLineBreak(ch)) { - tokenFlags |= TokenFlags.Unterminated; - error(Diagnostics.Unterminated_regular_expression_literal); - break; - } - - if (inEscape) { - // Parsing an escape character; - // reset the flag and just advance to the next char. - inEscape = false; - } - else if (ch === CharacterCodes.slash && !inCharacterClass) { - // A slash within a character class is permissible, - // but in general it signals the end of the regexp literal. - p++; - break; - } - else if (ch === CharacterCodes.openBracket) { - inCharacterClass = true; - } - else if (ch === CharacterCodes.backslash) { - inEscape = true; + if (languageVariant === LanguageVariant.JSX && + text.charCodeAt(pos + 1) === CharacterCodes.slash && + text.charCodeAt(pos + 2) !== CharacterCodes.asterisk) { + return pos += 2, token = SyntaxKind.LessThanSlashToken; } - else if (ch === CharacterCodes.closeBracket) { - inCharacterClass = false; - } - p++; - } - - while (p < end && isIdentifierPart(text.charCodeAt(p), languageVersion)) { - p++; - } - pos = p; - tokenValue = text.substring(tokenPos, pos); - token = SyntaxKind.RegularExpressionLiteral; - } - return token; - } - - /** - * Unconditionally back up and scan a template expression portion. - */ - function reScanTemplateToken(): SyntaxKind { - Debug.assert(token === SyntaxKind.CloseBraceToken, "'reScanTemplateToken' should only be called on a '}'"); - pos = tokenPos; - return token = scanTemplateAndSetTokenValue(); - } - - function reScanJsxToken(): JsxTokenSyntaxKind { - pos = tokenPos = startPos; - return token = scanJsxToken(); - } - - function reScanLessThanToken(): SyntaxKind { - if (token === SyntaxKind.LessThanLessThanToken) { - pos = tokenPos + 1; - return token = SyntaxKind.LessThanToken; - } - return token; - } - - function reScanQuestionToken(): SyntaxKind { - Debug.assert(token === SyntaxKind.QuestionQuestionToken, "'reScanQuestionToken' should only be called on a '??'"); - pos = tokenPos + 1; - return token = SyntaxKind.QuestionToken; - } - - function scanJsxToken(): JsxTokenSyntaxKind { - startPos = tokenPos = pos; - - if (pos >= end) { - return token = SyntaxKind.EndOfFileToken; - } - - let char = text.charCodeAt(pos); - if (char === CharacterCodes.lessThan) { - if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { - pos += 2; - return token = SyntaxKind.LessThanSlashToken; - } - pos++; - return token = SyntaxKind.LessThanToken; - } - - if (char === CharacterCodes.openBrace) { - pos++; - return token = SyntaxKind.OpenBraceToken; - } - - // First non-whitespace character on this line. - let firstNonWhitespace = 0; - // These initial values are special because the first line is: - // firstNonWhitespace = 0 to indicate that we want leading whitespace, - - while (pos < end) { - char = text.charCodeAt(pos); - if (char === CharacterCodes.openBrace) { - break; - } - if (char === CharacterCodes.lessThan) { + pos++; + return token = SyntaxKind.LessThanToken; + case CharacterCodes.equals: + if (isConflictMarkerTrivia(text, pos)) { + pos = scanConflictMarkerTrivia(text, pos, error); + if (skipTrivia) { + continue; + } + else { + return token = SyntaxKind.ConflictMarkerTrivia; + } + } + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { + return pos += 3, token = SyntaxKind.EqualsEqualsEqualsToken; + } + return pos += 2, token = SyntaxKind.EqualsEqualsToken; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.greaterThan) { + return pos += 2, token = SyntaxKind.EqualsGreaterThanToken; + } + pos++; + return token = SyntaxKind.EqualsToken; + case CharacterCodes.greaterThan: if (isConflictMarkerTrivia(text, pos)) { pos = scanConflictMarkerTrivia(text, pos, error); - return token = SyntaxKind.ConflictMarkerTrivia; + if (skipTrivia) { + continue; + } + else { + return token = SyntaxKind.ConflictMarkerTrivia; + } } - break; - } - - // FirstNonWhitespace is 0, then we only see whitespaces so far. If we see a linebreak, we want to ignore that whitespaces. - // i.e (- : whitespace) - //
---- - //
becomes
- // - //
----
becomes
----
- if (isLineBreak(char) && firstNonWhitespace === 0) { - firstNonWhitespace = -1; - } - else if (!isWhiteSpaceLike(char)) { - firstNonWhitespace = pos; - } - pos++; - } - - tokenValue = text.substring(startPos, pos); - return firstNonWhitespace === -1 ? SyntaxKind.JsxTextAllWhiteSpaces : SyntaxKind.JsxText; - } - - // Scans a JSX identifier; these differ from normal identifiers in that - // they allow dashes - function scanJsxIdentifier(): SyntaxKind { - if (tokenIsIdentifierOrKeyword(token)) { - // An identifier or keyword has already been parsed - check for a `-` and then append it and everything after it to the token - // Do note that this means that `scanJsxIdentifier` effectively _mutates_ the visible token without advancing to a new token - // Any caller should be expecting this behavior and should only read the pos or token value after calling it. - while (pos < end) { - const ch = text.charCodeAt(pos); - if (ch === CharacterCodes.minus) { - tokenValue += "-"; + pos++; + return token = SyntaxKind.GreaterThanToken; + case CharacterCodes.question: + pos++; + if (text.charCodeAt(pos) === CharacterCodes.dot && !isDigit(text.charCodeAt(pos + 1))) { pos++; - continue; - } - const oldPos = pos; - tokenValue += scanIdentifierParts(); // reuse `scanIdentifierParts` so unicode escapes are handled - if (pos === oldPos) { - break; + return token = SyntaxKind.QuestionDotToken; } - } - } - return token; - } - - function scanJsxAttributeValue(): SyntaxKind { - startPos = pos; - - switch (text.charCodeAt(pos)) { - case CharacterCodes.doubleQuote: - case CharacterCodes.singleQuote: - tokenValue = scanString(/*jsxAttributeString*/ true); - return token = SyntaxKind.StringLiteral; - default: - // If this scans anything other than `{`, it's a parse error. - return scan(); - } - } - - function reScanJsxAttributeValue(): SyntaxKind { - pos = tokenPos = startPos; - return scanJsxAttributeValue(); - } - - function scanJsDocToken(): JSDocSyntaxKind { - startPos = tokenPos = pos; - tokenFlags = TokenFlags.None; - if (pos >= end) { - return token = SyntaxKind.EndOfFileToken; - } - - const ch = codePointAt(text, pos); - pos += charSize(ch); - switch (ch) { - case CharacterCodes.tab: - case CharacterCodes.verticalTab: - case CharacterCodes.formFeed: - case CharacterCodes.space: - while (pos < end && isWhiteSpaceSingleLine(text.charCodeAt(pos))) { + if (text.charCodeAt(pos) === CharacterCodes.question) { pos++; + return token = SyntaxKind.QuestionQuestionToken; } - return token = SyntaxKind.WhitespaceTrivia; - case CharacterCodes.at: - return token = SyntaxKind.AtToken; - case CharacterCodes.lineFeed: - case CharacterCodes.carriageReturn: - tokenFlags |= TokenFlags.PrecedingLineBreak; - return token = SyntaxKind.NewLineTrivia; - case CharacterCodes.asterisk: - return token = SyntaxKind.AsteriskToken; - case CharacterCodes.openBrace: - return token = SyntaxKind.OpenBraceToken; - case CharacterCodes.closeBrace: - return token = SyntaxKind.CloseBraceToken; + return token = SyntaxKind.QuestionToken; case CharacterCodes.openBracket: + pos++; return token = SyntaxKind.OpenBracketToken; case CharacterCodes.closeBracket: + pos++; return token = SyntaxKind.CloseBracketToken; - case CharacterCodes.lessThan: - return token = SyntaxKind.LessThanToken; - case CharacterCodes.greaterThan: - return token = SyntaxKind.GreaterThanToken; - case CharacterCodes.equals: - return token = SyntaxKind.EqualsToken; - case CharacterCodes.comma: - return token = SyntaxKind.CommaToken; - case CharacterCodes.dot: - return token = SyntaxKind.DotToken; - case CharacterCodes.backtick: - return token = SyntaxKind.BacktickToken; + case CharacterCodes.caret: + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.CaretEqualsToken; + } + pos++; + return token = SyntaxKind.CaretToken; + case CharacterCodes.openBrace: + pos++; + return token = SyntaxKind.OpenBraceToken; + case CharacterCodes.bar: + if (isConflictMarkerTrivia(text, pos)) { + pos = scanConflictMarkerTrivia(text, pos, error); + if (skipTrivia) { + continue; + } + else { + return token = SyntaxKind.ConflictMarkerTrivia; + } + } + if (text.charCodeAt(pos + 1) === CharacterCodes.bar) { + return pos += 2, token = SyntaxKind.BarBarToken; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.BarEqualsToken; + } + pos++; + return token = SyntaxKind.BarToken; + case CharacterCodes.closeBrace: + pos++; + return token = SyntaxKind.CloseBraceToken; + case CharacterCodes.tilde: + pos++; + return token = SyntaxKind.TildeToken; + case CharacterCodes.at: + pos++; + return token = SyntaxKind.AtToken; case CharacterCodes.backslash: - pos--; const extendedCookedChar = peekExtendedUnicodeEscape(); if (extendedCookedChar >= 0 && isIdentifierStart(extendedCookedChar, languageVersion)) { pos += 3; @@ -2195,7 +1739,6 @@ namespace ts { tokenValue = scanExtendedUnicodeEscape() + scanIdentifierParts(); return token = getIdentifierToken(); } - const cookedChar = peekUnicodeEscape(); if (cookedChar >= 0 && isIdentifierStart(cookedChar, languageVersion)) { pos += 6; @@ -2203,140 +1746,398 @@ namespace ts { tokenValue = String.fromCharCode(cookedChar) + scanIdentifierParts(); return token = getIdentifierToken(); } + error(Diagnostics.Invalid_character); pos++; return token = SyntaxKind.Unknown; + default: + if (isIdentifierStart(ch, languageVersion)) { + pos += charSize(ch); + while (pos < end && isIdentifierPart(ch = codePointAt(text, pos), languageVersion)) + pos += charSize(ch); + tokenValue = text.substring(tokenPos, pos); + if (ch === CharacterCodes.backslash) { + tokenValue += scanIdentifierParts(); + } + return token = getIdentifierToken(); + } + else if (isWhiteSpaceSingleLine(ch)) { + pos += charSize(ch); + continue; + } + else if (isLineBreak(ch)) { + tokenFlags |= TokenFlags.PrecedingLineBreak; + pos += charSize(ch); + continue; + } + error(Diagnostics.Invalid_character); + pos += charSize(ch); + return token = SyntaxKind.Unknown; } - - if (isIdentifierStart(ch, languageVersion)) { - let char = ch; - while (pos < end && isIdentifierPart(char = codePointAt(text, pos), languageVersion) || text.charCodeAt(pos) === CharacterCodes.minus) pos += charSize(char); - tokenValue = text.substring(tokenPos, pos); - if (char === CharacterCodes.backslash) { - tokenValue += scanIdentifierParts(); + } + } + function reScanGreaterToken(): SyntaxKind { + if (token === SyntaxKind.GreaterThanToken) { + if (text.charCodeAt(pos) === CharacterCodes.greaterThan) { + if (text.charCodeAt(pos + 1) === CharacterCodes.greaterThan) { + if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { + return pos += 3, token = SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken; + } + return pos += 2, token = SyntaxKind.GreaterThanGreaterThanGreaterThanToken; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.GreaterThanGreaterThanEqualsToken; } - return token = getIdentifierToken(); + pos++; + return token = SyntaxKind.GreaterThanGreaterThanToken; } - else { - return token = SyntaxKind.Unknown; + if (text.charCodeAt(pos) === CharacterCodes.equals) { + pos++; + return token = SyntaxKind.GreaterThanEqualsToken; } } - - function speculationHelper(callback: () => T, isLookahead: boolean): T { - const savePos = pos; - const saveStartPos = startPos; - const saveTokenPos = tokenPos; - const saveToken = token; - const saveTokenValue = tokenValue; - const saveTokenFlags = tokenFlags; - const result = callback(); - - // If our callback returned something 'falsy' or we're just looking ahead, - // then unconditionally restore us to where we were. - if (!result || isLookahead) { - pos = savePos; - startPos = saveStartPos; - tokenPos = saveTokenPos; - token = saveToken; - tokenValue = saveTokenValue; - tokenFlags = saveTokenFlags; + return token; + } + function reScanSlashToken(): SyntaxKind { + if (token === SyntaxKind.SlashToken || token === SyntaxKind.SlashEqualsToken) { + let p = tokenPos + 1; + let inEscape = false; + let inCharacterClass = false; + while (true) { + // If we reach the end of a file, or hit a newline, then this is an unterminated + // regex. Report error and return what we have so far. + if (p >= end) { + tokenFlags |= TokenFlags.Unterminated; + error(Diagnostics.Unterminated_regular_expression_literal); + break; + } + const ch = text.charCodeAt(p); + if (isLineBreak(ch)) { + tokenFlags |= TokenFlags.Unterminated; + error(Diagnostics.Unterminated_regular_expression_literal); + break; + } + if (inEscape) { + // Parsing an escape character; + // reset the flag and just advance to the next char. + inEscape = false; + } + else if (ch === CharacterCodes.slash && !inCharacterClass) { + // A slash within a character class is permissible, + // but in general it signals the end of the regexp literal. + p++; + break; + } + else if (ch === CharacterCodes.openBracket) { + inCharacterClass = true; + } + else if (ch === CharacterCodes.backslash) { + inEscape = true; + } + else if (ch === CharacterCodes.closeBracket) { + inCharacterClass = false; + } + p++; } - return result; - } - - function scanRange(start: number, length: number, callback: () => T): T { - const saveEnd = end; - const savePos = pos; - const saveStartPos = startPos; - const saveTokenPos = tokenPos; - const saveToken = token; - const saveTokenValue = tokenValue; - const saveTokenFlags = tokenFlags; - - setText(text, start, length); - const result = callback(); - - end = saveEnd; - pos = savePos; - startPos = saveStartPos; - tokenPos = saveTokenPos; - token = saveToken; - tokenValue = saveTokenValue; - tokenFlags = saveTokenFlags; - - return result; - } - - function lookAhead(callback: () => T): T { - return speculationHelper(callback, /*isLookahead*/ true); + while (p < end && isIdentifierPart(text.charCodeAt(p), languageVersion)) { + p++; + } + pos = p; + tokenValue = text.substring(tokenPos, pos); + token = SyntaxKind.RegularExpressionLiteral; } - - function tryScan(callback: () => T): T { - return speculationHelper(callback, /*isLookahead*/ false); + return token; + } + /** + * Unconditionally back up and scan a template expression portion. + */ + function reScanTemplateToken(): SyntaxKind { + Debug.assert(token === SyntaxKind.CloseBraceToken, "'reScanTemplateToken' should only be called on a '}'"); + pos = tokenPos; + return token = scanTemplateAndSetTokenValue(); + } + function reScanJsxToken(): JsxTokenSyntaxKind { + pos = tokenPos = startPos; + return token = scanJsxToken(); + } + function reScanLessThanToken(): SyntaxKind { + if (token === SyntaxKind.LessThanLessThanToken) { + pos = tokenPos + 1; + return token = SyntaxKind.LessThanToken; } - - function getText(): string { - return text; + return token; + } + function reScanQuestionToken(): SyntaxKind { + Debug.assert(token === SyntaxKind.QuestionQuestionToken, "'reScanQuestionToken' should only be called on a '??'"); + pos = tokenPos + 1; + return token = SyntaxKind.QuestionToken; + } + function scanJsxToken(): JsxTokenSyntaxKind { + startPos = tokenPos = pos; + if (pos >= end) { + return token = SyntaxKind.EndOfFileToken; + } + let char = text.charCodeAt(pos); + if (char === CharacterCodes.lessThan) { + if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { + pos += 2; + return token = SyntaxKind.LessThanSlashToken; + } + pos++; + return token = SyntaxKind.LessThanToken; } - - function setText(newText: string | undefined, start: number | undefined, length: number | undefined) { - text = newText || ""; - end = length === undefined ? text.length : start! + length; - setTextPos(start || 0); + if (char === CharacterCodes.openBrace) { + pos++; + return token = SyntaxKind.OpenBraceToken; + } + // First non-whitespace character on this line. + let firstNonWhitespace = 0; + // These initial values are special because the first line is: + // firstNonWhitespace = 0 to indicate that we want leading whitespace, + while (pos < end) { + char = text.charCodeAt(pos); + if (char === CharacterCodes.openBrace) { + break; + } + if (char === CharacterCodes.lessThan) { + if (isConflictMarkerTrivia(text, pos)) { + pos = scanConflictMarkerTrivia(text, pos, error); + return token = SyntaxKind.ConflictMarkerTrivia; + } + break; + } + // FirstNonWhitespace is 0, then we only see whitespaces so far. If we see a linebreak, we want to ignore that whitespaces. + // i.e (- : whitespace) + //
---- + //
becomes
+ // + //
----
becomes
----
+ if (isLineBreak(char) && firstNonWhitespace === 0) { + firstNonWhitespace = -1; + } + else if (!isWhiteSpaceLike(char)) { + firstNonWhitespace = pos; + } + pos++; } - - function setOnError(errorCallback: ErrorCallback | undefined) { - onError = errorCallback; + tokenValue = text.substring(startPos, pos); + return firstNonWhitespace === -1 ? SyntaxKind.JsxTextAllWhiteSpaces : SyntaxKind.JsxText; + } + // Scans a JSX identifier; these differ from normal identifiers in that + // they allow dashes + function scanJsxIdentifier(): SyntaxKind { + if (tokenIsIdentifierOrKeyword(token)) { + // An identifier or keyword has already been parsed - check for a `-` and then append it and everything after it to the token + // Do note that this means that `scanJsxIdentifier` effectively _mutates_ the visible token without advancing to a new token + // Any caller should be expecting this behavior and should only read the pos or token value after calling it. + while (pos < end) { + const ch = text.charCodeAt(pos); + if (ch === CharacterCodes.minus) { + tokenValue += "-"; + pos++; + continue; + } + const oldPos = pos; + tokenValue += scanIdentifierParts(); // reuse `scanIdentifierParts` so unicode escapes are handled + if (pos === oldPos) { + break; + } + } } - - function setScriptTarget(scriptTarget: ScriptTarget) { - languageVersion = scriptTarget; + return token; + } + function scanJsxAttributeValue(): SyntaxKind { + startPos = pos; + switch (text.charCodeAt(pos)) { + case CharacterCodes.doubleQuote: + case CharacterCodes.singleQuote: + tokenValue = scanString(/*jsxAttributeString*/ true); + return token = SyntaxKind.StringLiteral; + default: + // If this scans anything other than `{`, it's a parse error. + return scan(); } - - function setLanguageVariant(variant: LanguageVariant) { - languageVariant = variant; + } + function reScanJsxAttributeValue(): SyntaxKind { + pos = tokenPos = startPos; + return scanJsxAttributeValue(); + } + function scanJsDocToken(): JSDocSyntaxKind { + startPos = tokenPos = pos; + tokenFlags = TokenFlags.None; + if (pos >= end) { + return token = SyntaxKind.EndOfFileToken; + } + const ch = codePointAt(text, pos); + pos += charSize(ch); + switch (ch) { + case CharacterCodes.tab: + case CharacterCodes.verticalTab: + case CharacterCodes.formFeed: + case CharacterCodes.space: + while (pos < end && isWhiteSpaceSingleLine(text.charCodeAt(pos))) { + pos++; + } + return token = SyntaxKind.WhitespaceTrivia; + case CharacterCodes.at: + return token = SyntaxKind.AtToken; + case CharacterCodes.lineFeed: + case CharacterCodes.carriageReturn: + tokenFlags |= TokenFlags.PrecedingLineBreak; + return token = SyntaxKind.NewLineTrivia; + case CharacterCodes.asterisk: + return token = SyntaxKind.AsteriskToken; + case CharacterCodes.openBrace: + return token = SyntaxKind.OpenBraceToken; + case CharacterCodes.closeBrace: + return token = SyntaxKind.CloseBraceToken; + case CharacterCodes.openBracket: + return token = SyntaxKind.OpenBracketToken; + case CharacterCodes.closeBracket: + return token = SyntaxKind.CloseBracketToken; + case CharacterCodes.lessThan: + return token = SyntaxKind.LessThanToken; + case CharacterCodes.greaterThan: + return token = SyntaxKind.GreaterThanToken; + case CharacterCodes.equals: + return token = SyntaxKind.EqualsToken; + case CharacterCodes.comma: + return token = SyntaxKind.CommaToken; + case CharacterCodes.dot: + return token = SyntaxKind.DotToken; + case CharacterCodes.backtick: + return token = SyntaxKind.BacktickToken; + case CharacterCodes.backslash: + pos--; + const extendedCookedChar = peekExtendedUnicodeEscape(); + if (extendedCookedChar >= 0 && isIdentifierStart(extendedCookedChar, languageVersion)) { + pos += 3; + tokenFlags |= TokenFlags.ExtendedUnicodeEscape; + tokenValue = scanExtendedUnicodeEscape() + scanIdentifierParts(); + return token = getIdentifierToken(); + } + const cookedChar = peekUnicodeEscape(); + if (cookedChar >= 0 && isIdentifierStart(cookedChar, languageVersion)) { + pos += 6; + tokenFlags |= TokenFlags.UnicodeEscape; + tokenValue = String.fromCharCode(cookedChar) + scanIdentifierParts(); + return token = getIdentifierToken(); + } + pos++; + return token = SyntaxKind.Unknown; } - - function setTextPos(textPos: number) { - Debug.assert(textPos >= 0); - pos = textPos; - startPos = textPos; - tokenPos = textPos; - token = SyntaxKind.Unknown; - tokenValue = undefined!; - tokenFlags = TokenFlags.None; + if (isIdentifierStart(ch, languageVersion)) { + let char = ch; + while (pos < end && isIdentifierPart(char = codePointAt(text, pos), languageVersion) || text.charCodeAt(pos) === CharacterCodes.minus) + pos += charSize(char); + tokenValue = text.substring(tokenPos, pos); + if (char === CharacterCodes.backslash) { + tokenValue += scanIdentifierParts(); + } + return token = getIdentifierToken(); } - - function setInJSDocType(inType: boolean) { - inJSDocType += inType ? 1 : -1; + else { + return token = SyntaxKind.Unknown; } } - - /* @internal */ - const codePointAt: (s: string, i: number) => number = (String.prototype as any).codePointAt ? (s, i) => (s as any).codePointAt(i) : function codePointAt(str, i): number { - // from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt - const size = str.length; - // Account for out-of-bounds indices: - if (i < 0 || i >= size) { - return undefined!; // String.codePointAt returns `undefined` for OOB indexes - } - // Get the first code unit - const first = str.charCodeAt(i); - // check if it’s the start of a surrogate pair - if (first >= 0xD800 && first <= 0xDBFF && size > i + 1) { // high surrogate and there is a next code unit - const second = str.charCodeAt(i + 1); - if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate - // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae - return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; - } + function speculationHelper(callback: () => T, isLookahead: boolean): T { + const savePos = pos; + const saveStartPos = startPos; + const saveTokenPos = tokenPos; + const saveToken = token; + const saveTokenValue = tokenValue; + const saveTokenFlags = tokenFlags; + const result = callback(); + // If our callback returned something 'falsy' or we're just looking ahead, + // then unconditionally restore us to where we were. + if (!result || isLookahead) { + pos = savePos; + startPos = saveStartPos; + tokenPos = saveTokenPos; + token = saveToken; + tokenValue = saveTokenValue; + tokenFlags = saveTokenFlags; } - return first; - }; - - /* @internal */ - function charSize(ch: number) { - if (ch >= 0x10000) { - return 2; + return result; + } + function scanRange(start: number, length: number, callback: () => T): T { + const saveEnd = end; + const savePos = pos; + const saveStartPos = startPos; + const saveTokenPos = tokenPos; + const saveToken = token; + const saveTokenValue = tokenValue; + const saveTokenFlags = tokenFlags; + setText(text, start, length); + const result = callback(); + end = saveEnd; + pos = savePos; + startPos = saveStartPos; + tokenPos = saveTokenPos; + token = saveToken; + tokenValue = saveTokenValue; + tokenFlags = saveTokenFlags; + return result; + } + function lookAhead(callback: () => T): T { + return speculationHelper(callback, /*isLookahead*/ true); + } + function tryScan(callback: () => T): T { + return speculationHelper(callback, /*isLookahead*/ false); + } + function getText(): string { + return text; + } + function setText(newText: string | undefined, start: number | undefined, length: number | undefined) { + text = newText || ""; + end = length === undefined ? text.length : start! + length; + setTextPos(start || 0); + } + function setOnError(errorCallback: ErrorCallback | undefined) { + onError = errorCallback; + } + function setScriptTarget(scriptTarget: ScriptTarget) { + languageVersion = scriptTarget; + } + function setLanguageVariant(variant: LanguageVariant) { + languageVariant = variant; + } + function setTextPos(textPos: number) { + Debug.assert(textPos >= 0); + pos = textPos; + startPos = textPos; + tokenPos = textPos; + token = SyntaxKind.Unknown; + tokenValue = undefined!; + tokenFlags = TokenFlags.None; + } + function setInJSDocType(inType: boolean) { + inJSDocType += inType ? 1 : -1; + } +} +/* @internal */ +const codePointAt: (s: string, i: number) => number = (String.prototype as any).codePointAt ? (s, i) => (s as any).codePointAt(i) : function codePointAt(str, i): number { + // from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt + const size = str.length; + // Account for out-of-bounds indices: + if (i < 0 || i >= size) { + return undefined!; // String.codePointAt returns `undefined` for OOB indexes + } + // Get the first code unit + const first = str.charCodeAt(i); + // check if it’s the start of a surrogate pair + if (first >= 0xD800 && first <= 0xDBFF && size > i + 1) { // high surrogate and there is a next code unit + const second = str.charCodeAt(i + 1); + if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; } - return 1; } + return first; +}; +/* @internal */ +function charSize(ch: number) { + if (ch >= 0x10000) { + return 2; + } + return 1; } diff --git a/src/compiler/semver.ts b/src/compiler/semver.ts index b4f9ca03e3305..ab499675a7c43 100644 --- a/src/compiler/semver.ts +++ b/src/compiler/semver.ts @@ -1,391 +1,383 @@ +import { Debug, emptyArray, Comparison, compareValues, some, compareStringsCaseSensitive, map } from "./ts"; /* @internal */ -namespace ts { - // https://semver.org/#spec-item-2 - // > A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative - // > integers, and MUST NOT contain leading zeroes. X is the major version, Y is the minor - // > version, and Z is the patch version. Each element MUST increase numerically. - // - // NOTE: We differ here in that we allow X and X.Y, with missing parts having the default - // value of `0`. - const versionRegExp = /^(0|[1-9]\d*)(?:\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*)(?:\-([a-z0-9-.]+))?(?:\+([a-z0-9-.]+))?)?)?$/i; - - // https://semver.org/#spec-item-9 - // > A pre-release version MAY be denoted by appending a hyphen and a series of dot separated - // > identifiers immediately following the patch version. Identifiers MUST comprise only ASCII - // > alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. Numeric identifiers - // > MUST NOT include leading zeroes. - const prereleaseRegExp = /^(?:0|[1-9]\d*|[a-z-][a-z0-9-]*)(?:\.(?:0|[1-9]\d*|[a-z-][a-z0-9-]*))*$/i; - - // https://semver.org/#spec-item-10 - // > Build metadata MAY be denoted by appending a plus sign and a series of dot separated - // > identifiers immediately following the patch or pre-release version. Identifiers MUST - // > comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. - const buildRegExp = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*$/i; - - // https://semver.org/#spec-item-9 - // > Numeric identifiers MUST NOT include leading zeroes. - const numericIdentifierRegExp = /^(0|[1-9]\d*)$/; - - /** - * Describes a precise semantic version number, https://semver.org - */ - export class Version { - static readonly zero = new Version(0, 0, 0); - - readonly major: number; - readonly minor: number; - readonly patch: number; - readonly prerelease: readonly string[]; - readonly build: readonly string[]; - - constructor(text: string); - constructor(major: number, minor?: number, patch?: number, prerelease?: string, build?: string); - constructor(major: number | string, minor = 0, patch = 0, prerelease = "", build = "") { - if (typeof major === "string") { - const result = Debug.assertDefined(tryParseComponents(major), "Invalid version"); - ({ major, minor, patch, prerelease, build } = result); - } - - Debug.assert(major >= 0, "Invalid argument: major"); - Debug.assert(minor >= 0, "Invalid argument: minor"); - Debug.assert(patch >= 0, "Invalid argument: patch"); - Debug.assert(!prerelease || prereleaseRegExp.test(prerelease), "Invalid argument: prerelease"); - Debug.assert(!build || buildRegExp.test(build), "Invalid argument: build"); - this.major = major; - this.minor = minor; - this.patch = patch; - this.prerelease = prerelease ? prerelease.split(".") : emptyArray; - this.build = build ? build.split(".") : emptyArray; - } - - static tryParse(text: string) { - const result = tryParseComponents(text); - if (!result) return undefined; - - const { major, minor, patch, prerelease, build } = result; - return new Version(major, minor, patch, prerelease, build); - } - - compareTo(other: Version | undefined) { - // https://semver.org/#spec-item-11 - // > Precedence is determined by the first difference when comparing each of these - // > identifiers from left to right as follows: Major, minor, and patch versions are - // > always compared numerically. - // - // https://semver.org/#spec-item-11 - // > Precedence for two pre-release versions with the same major, minor, and patch version - // > MUST be determined by comparing each dot separated identifier from left to right until - // > a difference is found [...] - // - // https://semver.org/#spec-item-11 - // > Build metadata does not figure into precedence - if (this === other) return Comparison.EqualTo; - if (other === undefined) return Comparison.GreaterThan; - return compareValues(this.major, other.major) - || compareValues(this.minor, other.minor) - || compareValues(this.patch, other.patch) - || comparePrerelaseIdentifiers(this.prerelease, other.prerelease); - } - - increment(field: "major" | "minor" | "patch") { - switch (field) { - case "major": return new Version(this.major + 1, 0, 0); - case "minor": return new Version(this.major, this.minor + 1, 0); - case "patch": return new Version(this.major, this.minor, this.patch + 1); - default: return Debug.assertNever(field); - } - } - - toString() { - let result = `${this.major}.${this.minor}.${this.patch}`; - if (some(this.prerelease)) result += `-${this.prerelease.join(".")}`; - if (some(this.build)) result += `+${this.build.join(".")}`; - return result; +// https://semver.org/#spec-item-2 +// > A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative +// > integers, and MUST NOT contain leading zeroes. X is the major version, Y is the minor +// > version, and Z is the patch version. Each element MUST increase numerically. +// +// NOTE: We differ here in that we allow X and X.Y, with missing parts having the default +// value of `0`. +const versionRegExp = /^(0|[1-9]\d*)(?:\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*)(?:\-([a-z0-9-.]+))?(?:\+([a-z0-9-.]+))?)?)?$/i; +// https://semver.org/#spec-item-9 +// > A pre-release version MAY be denoted by appending a hyphen and a series of dot separated +// > identifiers immediately following the patch version. Identifiers MUST comprise only ASCII +// > alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. Numeric identifiers +// > MUST NOT include leading zeroes. +/* @internal */ +const prereleaseRegExp = /^(?:0|[1-9]\d*|[a-z-][a-z0-9-]*)(?:\.(?:0|[1-9]\d*|[a-z-][a-z0-9-]*))*$/i; +// https://semver.org/#spec-item-10 +// > Build metadata MAY be denoted by appending a plus sign and a series of dot separated +// > identifiers immediately following the patch or pre-release version. Identifiers MUST +// > comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. +/* @internal */ +const buildRegExp = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*$/i; +// https://semver.org/#spec-item-9 +// > Numeric identifiers MUST NOT include leading zeroes. +/* @internal */ +const numericIdentifierRegExp = /^(0|[1-9]\d*)$/; +/** + * Describes a precise semantic version number, https://semver.org + */ +/* @internal */ +export class Version { + static readonly zero = new Version(0, 0, 0); + readonly major: number; + readonly minor: number; + readonly patch: number; + readonly prerelease: readonly string[]; + readonly build: readonly string[]; + constructor(text: string); + constructor(major: number, minor?: number, patch?: number, prerelease?: string, build?: string); + constructor(major: number | string, minor = 0, patch = 0, prerelease = "", build = "") { + if (typeof major === "string") { + const result = Debug.assertDefined(tryParseComponents(major), "Invalid version"); + ({ major, minor, patch, prerelease, build } = result); } + Debug.assert(major >= 0, "Invalid argument: major"); + Debug.assert(minor >= 0, "Invalid argument: minor"); + Debug.assert(patch >= 0, "Invalid argument: patch"); + Debug.assert(!prerelease || prereleaseRegExp.test(prerelease), "Invalid argument: prerelease"); + Debug.assert(!build || buildRegExp.test(build), "Invalid argument: build"); + this.major = major; + this.minor = minor; + this.patch = patch; + this.prerelease = prerelease ? prerelease.split(".") : emptyArray; + this.build = build ? build.split(".") : emptyArray; } - - function tryParseComponents(text: string) { - const match = versionRegExp.exec(text); - if (!match) return undefined; - - const [, major, minor = "0", patch = "0", prerelease = "", build = ""] = match; - if (prerelease && !prereleaseRegExp.test(prerelease)) return undefined; - if (build && !buildRegExp.test(build)) return undefined; - return { - major: parseInt(major, 10), - minor: parseInt(minor, 10), - patch: parseInt(patch, 10), - prerelease, - build - }; + static tryParse(text: string) { + const result = tryParseComponents(text); + if (!result) + return undefined; + const { major, minor, patch, prerelease, build } = result; + return new Version(major, minor, patch, prerelease, build); } - - function comparePrerelaseIdentifiers(left: readonly string[], right: readonly string[]) { + compareTo(other: Version | undefined) { // https://semver.org/#spec-item-11 - // > When major, minor, and patch are equal, a pre-release version has lower precedence - // > than a normal version. - if (left === right) return Comparison.EqualTo; - if (left.length === 0) return right.length === 0 ? Comparison.EqualTo : Comparison.GreaterThan; - if (right.length === 0) return Comparison.LessThan; - + // > Precedence is determined by the first difference when comparing each of these + // > identifiers from left to right as follows: Major, minor, and patch versions are + // > always compared numerically. + // // https://semver.org/#spec-item-11 // > Precedence for two pre-release versions with the same major, minor, and patch version // > MUST be determined by comparing each dot separated identifier from left to right until // > a difference is found [...] - const length = Math.min(left.length, right.length); - for (let i = 0; i < length; i++) { - const leftIdentifier = left[i]; - const rightIdentifier = right[i]; - if (leftIdentifier === rightIdentifier) continue; - - const leftIsNumeric = numericIdentifierRegExp.test(leftIdentifier); - const rightIsNumeric = numericIdentifierRegExp.test(rightIdentifier); - if (leftIsNumeric || rightIsNumeric) { - // https://semver.org/#spec-item-11 - // > Numeric identifiers always have lower precedence than non-numeric identifiers. - if (leftIsNumeric !== rightIsNumeric) return leftIsNumeric ? Comparison.LessThan : Comparison.GreaterThan; - - // https://semver.org/#spec-item-11 - // > identifiers consisting of only digits are compared numerically - const result = compareValues(+leftIdentifier, +rightIdentifier); - if (result) return result; - } - else { - // https://semver.org/#spec-item-11 - // > identifiers with letters or hyphens are compared lexically in ASCII sort order. - const result = compareStringsCaseSensitive(leftIdentifier, rightIdentifier); - if (result) return result; - } - } - + // // https://semver.org/#spec-item-11 - // > A larger set of pre-release fields has a higher precedence than a smaller set, if all - // > of the preceding identifiers are equal. - return compareValues(left.length, right.length); + // > Build metadata does not figure into precedence + if (this === other) + return Comparison.EqualTo; + if (other === undefined) + return Comparison.GreaterThan; + return compareValues(this.major, other.major) + || compareValues(this.minor, other.minor) + || compareValues(this.patch, other.patch) + || comparePrerelaseIdentifiers(this.prerelease, other.prerelease); } - - /** - * Describes a semantic version range, per https://github.com/npm/node-semver#ranges - */ - export class VersionRange { - private _alternatives: readonly (readonly Comparator[])[]; - - constructor(spec: string) { - this._alternatives = spec ? Debug.assertDefined(parseRange(spec), "Invalid range spec.") : emptyArray; - } - - static tryParse(text: string) { - const sets = parseRange(text); - if (sets) { - const range = new VersionRange(""); - range._alternatives = sets; - return range; - } - return undefined; + increment(field: "major" | "minor" | "patch") { + switch (field) { + case "major": return new Version(this.major + 1, 0, 0); + case "minor": return new Version(this.major, this.minor + 1, 0); + case "patch": return new Version(this.major, this.minor, this.patch + 1); + default: return Debug.assertNever(field); } - - test(version: Version | string) { - if (typeof version === "string") version = new Version(version); - return testDisjunction(version, this._alternatives); + } + toString() { + let result = `${this.major}.${this.minor}.${this.patch}`; + if (some(this.prerelease)) + result += `-${this.prerelease.join(".")}`; + if (some(this.build)) + result += `+${this.build.join(".")}`; + return result; + } +} +/* @internal */ +function tryParseComponents(text: string) { + const match = versionRegExp.exec(text); + if (!match) + return undefined; + const [, major, minor = "0", patch = "0", prerelease = "", build = ""] = match; + if (prerelease && !prereleaseRegExp.test(prerelease)) + return undefined; + if (build && !buildRegExp.test(build)) + return undefined; + return { + major: parseInt(major, 10), + minor: parseInt(minor, 10), + patch: parseInt(patch, 10), + prerelease, + build + }; +} +/* @internal */ +function comparePrerelaseIdentifiers(left: readonly string[], right: readonly string[]) { + // https://semver.org/#spec-item-11 + // > When major, minor, and patch are equal, a pre-release version has lower precedence + // > than a normal version. + if (left === right) + return Comparison.EqualTo; + if (left.length === 0) + return right.length === 0 ? Comparison.EqualTo : Comparison.GreaterThan; + if (right.length === 0) + return Comparison.LessThan; + // https://semver.org/#spec-item-11 + // > Precedence for two pre-release versions with the same major, minor, and patch version + // > MUST be determined by comparing each dot separated identifier from left to right until + // > a difference is found [...] + const length = Math.min(left.length, right.length); + for (let i = 0; i < length; i++) { + const leftIdentifier = left[i]; + const rightIdentifier = right[i]; + if (leftIdentifier === rightIdentifier) + continue; + const leftIsNumeric = numericIdentifierRegExp.test(leftIdentifier); + const rightIsNumeric = numericIdentifierRegExp.test(rightIdentifier); + if (leftIsNumeric || rightIsNumeric) { + // https://semver.org/#spec-item-11 + // > Numeric identifiers always have lower precedence than non-numeric identifiers. + if (leftIsNumeric !== rightIsNumeric) + return leftIsNumeric ? Comparison.LessThan : Comparison.GreaterThan; + // https://semver.org/#spec-item-11 + // > identifiers consisting of only digits are compared numerically + const result = compareValues(+leftIdentifier, +rightIdentifier); + if (result) + return result; } - - toString() { - return formatDisjunction(this._alternatives); + else { + // https://semver.org/#spec-item-11 + // > identifiers with letters or hyphens are compared lexically in ASCII sort order. + const result = compareStringsCaseSensitive(leftIdentifier, rightIdentifier); + if (result) + return result; } } - - interface Comparator { - readonly operator: "<" | "<=" | ">" | ">=" | "="; - readonly operand: Version; + // https://semver.org/#spec-item-11 + // > A larger set of pre-release fields has a higher precedence than a smaller set, if all + // > of the preceding identifiers are equal. + return compareValues(left.length, right.length); +} +/** + * Describes a semantic version range, per https://github.com/npm/node-semver#ranges + */ +/* @internal */ +export class VersionRange { + private _alternatives: readonly (readonly Comparator[])[]; + constructor(spec: string) { + this._alternatives = spec ? Debug.assertDefined(parseRange(spec), "Invalid range spec.") : emptyArray; } - - // https://github.com/npm/node-semver#range-grammar - // - // range-set ::= range ( logical-or range ) * - // range ::= hyphen | simple ( ' ' simple ) * | '' - // logical-or ::= ( ' ' ) * '||' ( ' ' ) * - const logicalOrRegExp = /\s*\|\|\s*/g; - const whitespaceRegExp = /\s+/g; - - // https://github.com/npm/node-semver#range-grammar - // - // partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )? - // xr ::= 'x' | 'X' | '*' | nr - // nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) * - // qualifier ::= ( '-' pre )? ( '+' build )? - // pre ::= parts - // build ::= parts - // parts ::= part ( '.' part ) * - // part ::= nr | [-0-9A-Za-z]+ - const partialRegExp = /^([xX*0]|[1-9]\d*)(?:\.([xX*0]|[1-9]\d*)(?:\.([xX*0]|[1-9]\d*)(?:-([a-z0-9-.]+))?(?:\+([a-z0-9-.]+))?)?)?$/i; - - // https://github.com/npm/node-semver#range-grammar - // - // hyphen ::= partial ' - ' partial - const hyphenRegExp = /^\s*([a-z0-9-+.*]+)\s+-\s+([a-z0-9-+.*]+)\s*$/i; - - // https://github.com/npm/node-semver#range-grammar - // - // simple ::= primitive | partial | tilde | caret - // primitive ::= ( '<' | '>' | '>=' | '<=' | '=' ) partial - // tilde ::= '~' partial - // caret ::= '^' partial - const rangeRegExp = /^\s*(~|\^|<|<=|>|>=|=)?\s*([a-z0-9-+.*]+)$/i; - - function parseRange(text: string) { - const alternatives: Comparator[][] = []; - for (const range of text.trim().split(logicalOrRegExp)) { - if (!range) continue; - const comparators: Comparator[] = []; - const match = hyphenRegExp.exec(range); - if (match) { - if (!parseHyphen(match[1], match[2], comparators)) return undefined; - } - else { - for (const simple of range.split(whitespaceRegExp)) { - const match = rangeRegExp.exec(simple); - if (!match || !parseComparator(match[1], match[2], comparators)) return undefined; - } - } - alternatives.push(comparators); + static tryParse(text: string) { + const sets = parseRange(text); + if (sets) { + const range = new VersionRange(""); + range._alternatives = sets; + return range; } - return alternatives; + return undefined; } - - function parsePartial(text: string) { - const match = partialRegExp.exec(text); - if (!match) return undefined; - - const [, major, minor = "*", patch = "*", prerelease, build] = match; - const version = new Version( - isWildcard(major) ? 0 : parseInt(major, 10), - isWildcard(major) || isWildcard(minor) ? 0 : parseInt(minor, 10), - isWildcard(major) || isWildcard(minor) || isWildcard(patch) ? 0 : parseInt(patch, 10), - prerelease, - build); - - return { version, major, minor, patch }; + test(version: Version | string) { + if (typeof version === "string") + version = new Version(version); + return testDisjunction(version, this._alternatives); } - - function parseHyphen(left: string, right: string, comparators: Comparator[]) { - const leftResult = parsePartial(left); - if (!leftResult) return false; - - const rightResult = parsePartial(right); - if (!rightResult) return false; - - if (!isWildcard(leftResult.major)) { - comparators.push(createComparator(">=", leftResult.version)); - } - - if (!isWildcard(rightResult.major)) { - comparators.push( - isWildcard(rightResult.minor) ? createComparator("<", rightResult.version.increment("major")) : - isWildcard(rightResult.patch) ? createComparator("<", rightResult.version.increment("minor")) : - createComparator("<=", rightResult.version)); - } - - return true; + toString() { + return formatDisjunction(this._alternatives); } - - function parseComparator(operator: string, text: string, comparators: Comparator[]) { - const result = parsePartial(text); - if (!result) return false; - - const { version, major, minor, patch } = result; - if (!isWildcard(major)) { - switch (operator) { - case "~": - comparators.push(createComparator(">=", version)); - comparators.push(createComparator("<", version.increment( - isWildcard(minor) ? "major" : - "minor"))); - break; - case "^": - comparators.push(createComparator(">=", version)); - comparators.push(createComparator("<", version.increment( - version.major > 0 || isWildcard(minor) ? "major" : - version.minor > 0 || isWildcard(patch) ? "minor" : - "patch"))); - break; - case "<": - case ">=": - comparators.push(createComparator(operator, version)); - break; - case "<=": - case ">": - comparators.push( - isWildcard(minor) ? createComparator(operator === "<=" ? "<" : ">=", version.increment("major")) : - isWildcard(patch) ? createComparator(operator === "<=" ? "<" : ">=", version.increment("minor")) : - createComparator(operator, version)); - break; - case "=": - case undefined: - if (isWildcard(minor) || isWildcard(patch)) { - comparators.push(createComparator(">=", version)); - comparators.push(createComparator("<", version.increment(isWildcard(minor) ? "major" : "minor"))); - } - else { - comparators.push(createComparator("=", version)); - } - break; - default: - // unrecognized - return false; - } +} +/* @internal */ +interface Comparator { + readonly operator: "<" | "<=" | ">" | ">=" | "="; + readonly operand: Version; +} +// https://github.com/npm/node-semver#range-grammar +// +// range-set ::= range ( logical-or range ) * +// range ::= hyphen | simple ( ' ' simple ) * | '' +// logical-or ::= ( ' ' ) * '||' ( ' ' ) * +/* @internal */ +const logicalOrRegExp = /\s*\|\|\s*/g; +/* @internal */ +const whitespaceRegExp = /\s+/g; +// https://github.com/npm/node-semver#range-grammar +// +// partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )? +// xr ::= 'x' | 'X' | '*' | nr +// nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) * +// qualifier ::= ( '-' pre )? ( '+' build )? +// pre ::= parts +// build ::= parts +// parts ::= part ( '.' part ) * +// part ::= nr | [-0-9A-Za-z]+ +/* @internal */ +const partialRegExp = /^([xX*0]|[1-9]\d*)(?:\.([xX*0]|[1-9]\d*)(?:\.([xX*0]|[1-9]\d*)(?:-([a-z0-9-.]+))?(?:\+([a-z0-9-.]+))?)?)?$/i; +// https://github.com/npm/node-semver#range-grammar +// +// hyphen ::= partial ' - ' partial +/* @internal */ +const hyphenRegExp = /^\s*([a-z0-9-+.*]+)\s+-\s+([a-z0-9-+.*]+)\s*$/i; +// https://github.com/npm/node-semver#range-grammar +// +// simple ::= primitive | partial | tilde | caret +// primitive ::= ( '<' | '>' | '>=' | '<=' | '=' ) partial +// tilde ::= '~' partial +// caret ::= '^' partial +/* @internal */ +const rangeRegExp = /^\s*(~|\^|<|<=|>|>=|=)?\s*([a-z0-9-+.*]+)$/i; +/* @internal */ +function parseRange(text: string) { + const alternatives: Comparator[][] = []; + for (const range of text.trim().split(logicalOrRegExp)) { + if (!range) + continue; + const comparators: Comparator[] = []; + const match = hyphenRegExp.exec(range); + if (match) { + if (!parseHyphen(match[1], match[2], comparators)) + return undefined; } - else if (operator === "<" || operator === ">") { - comparators.push(createComparator("<", Version.zero)); + else { + for (const simple of range.split(whitespaceRegExp)) { + const match = rangeRegExp.exec(simple); + if (!match || !parseComparator(match[1], match[2], comparators)) + return undefined; + } } - - return true; - } - - function isWildcard(part: string) { - return part === "*" || part === "x" || part === "X"; - } - - function createComparator(operator: Comparator["operator"], operand: Version) { - return { operator, operand }; + alternatives.push(comparators); } - - function testDisjunction(version: Version, alternatives: readonly (readonly Comparator[])[]) { - // an empty disjunction is treated as "*" (all versions) - if (alternatives.length === 0) return true; - for (const alternative of alternatives) { - if (testAlternative(version, alternative)) return true; - } + return alternatives; +} +/* @internal */ +function parsePartial(text: string) { + const match = partialRegExp.exec(text); + if (!match) + return undefined; + const [, major, minor = "*", patch = "*", prerelease, build] = match; + const version = new Version(isWildcard(major) ? 0 : parseInt(major, 10), isWildcard(major) || isWildcard(minor) ? 0 : parseInt(minor, 10), isWildcard(major) || isWildcard(minor) || isWildcard(patch) ? 0 : parseInt(patch, 10), prerelease, build); + return { version, major, minor, patch }; +} +/* @internal */ +function parseHyphen(left: string, right: string, comparators: Comparator[]) { + const leftResult = parsePartial(left); + if (!leftResult) + return false; + const rightResult = parsePartial(right); + if (!rightResult) return false; + if (!isWildcard(leftResult.major)) { + comparators.push(createComparator(">=", leftResult.version)); } - - function testAlternative(version: Version, comparators: readonly Comparator[]) { - for (const comparator of comparators) { - if (!testComparator(version, comparator.operator, comparator.operand)) return false; - } - return true; + if (!isWildcard(rightResult.major)) { + comparators.push(isWildcard(rightResult.minor) ? createComparator("<", rightResult.version.increment("major")) : + isWildcard(rightResult.patch) ? createComparator("<", rightResult.version.increment("minor")) : + createComparator("<=", rightResult.version)); } - - function testComparator(version: Version, operator: Comparator["operator"], operand: Version) { - const cmp = version.compareTo(operand); + return true; +} +/* @internal */ +function parseComparator(operator: string, text: string, comparators: Comparator[]) { + const result = parsePartial(text); + if (!result) + return false; + const { version, major, minor, patch } = result; + if (!isWildcard(major)) { switch (operator) { - case "<": return cmp < 0; - case "<=": return cmp <= 0; - case ">": return cmp > 0; - case ">=": return cmp >= 0; - case "=": return cmp === 0; - default: return Debug.assertNever(operator); + case "~": + comparators.push(createComparator(">=", version)); + comparators.push(createComparator("<", version.increment(isWildcard(minor) ? "major" : + "minor"))); + break; + case "^": + comparators.push(createComparator(">=", version)); + comparators.push(createComparator("<", version.increment(version.major > 0 || isWildcard(minor) ? "major" : + version.minor > 0 || isWildcard(patch) ? "minor" : + "patch"))); + break; + case "<": + case ">=": + comparators.push(createComparator(operator, version)); + break; + case "<=": + case ">": + comparators.push(isWildcard(minor) ? createComparator(operator === "<=" ? "<" : ">=", version.increment("major")) : + isWildcard(patch) ? createComparator(operator === "<=" ? "<" : ">=", version.increment("minor")) : + createComparator(operator, version)); + break; + case "=": + case undefined: + if (isWildcard(minor) || isWildcard(patch)) { + comparators.push(createComparator(">=", version)); + comparators.push(createComparator("<", version.increment(isWildcard(minor) ? "major" : "minor"))); + } + else { + comparators.push(createComparator("=", version)); + } + break; + default: + // unrecognized + return false; } } - - function formatDisjunction(alternatives: readonly (readonly Comparator[])[]) { - return map(alternatives, formatAlternative).join(" || ") || "*"; + else if (operator === "<" || operator === ">") { + comparators.push(createComparator("<", Version.zero)); + } + return true; +} +/* @internal */ +function isWildcard(part: string) { + return part === "*" || part === "x" || part === "X"; +} +/* @internal */ +function createComparator(operator: Comparator["operator"], operand: Version) { + return { operator, operand }; +} +/* @internal */ +function testDisjunction(version: Version, alternatives: readonly (readonly Comparator[])[]) { + // an empty disjunction is treated as "*" (all versions) + if (alternatives.length === 0) + return true; + for (const alternative of alternatives) { + if (testAlternative(version, alternative)) + return true; } - - function formatAlternative(comparators: readonly Comparator[]) { - return map(comparators, formatComparator).join(" "); + return false; +} +/* @internal */ +function testAlternative(version: Version, comparators: readonly Comparator[]) { + for (const comparator of comparators) { + if (!testComparator(version, comparator.operator, comparator.operand)) + return false; } - - function formatComparator(comparator: Comparator) { - return `${comparator.operator}${comparator.operand}`; + return true; +} +/* @internal */ +function testComparator(version: Version, operator: Comparator["operator"], operand: Version) { + const cmp = version.compareTo(operand); + switch (operator) { + case "<": return cmp < 0; + case "<=": return cmp <= 0; + case ">": return cmp > 0; + case ">=": return cmp >= 0; + case "=": return cmp === 0; + default: return Debug.assertNever(operator); } -} \ No newline at end of file +} +/* @internal */ +function formatDisjunction(alternatives: readonly (readonly Comparator[])[]) { + return map(alternatives, formatAlternative).join(" || ") || "*"; +} +/* @internal */ +function formatAlternative(comparators: readonly Comparator[]) { + return map(comparators, formatComparator).join(" "); +} +/* @internal */ +function formatComparator(comparator: Comparator) { + return `${comparator.operator}${comparator.operand}`; +} diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 5fae21601a388..d7d1382663aca 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -1,743 +1,695 @@ +import { EmitHost, SourceMapGenerator, createMap, getRelativePathToDirectoryOrUrl, Debug, RawSourceMap, LineAndCharacter, combinePaths, getDirectoryPath, isArray, every, isString, CharacterCodes, compareValues, DocumentPositionMapperHost, DocumentPositionMapper, getNormalizedAbsolutePath, createMapFromEntries, SortedReadonlyArray, getPositionOfLineAndCharacter, arrayFrom, emptyArray, sortAndDeduplicate, DocumentPosition, some, binarySearchKey, identity } from "./ts"; +import { createTimer, nullTimer } from "./ts.performance"; +import * as ts from "./ts"; /* @internal */ -namespace ts { - export interface SourceMapGeneratorOptions { - extendedDiagnostics?: boolean; - } - - export function createSourceMapGenerator(host: EmitHost, file: string, sourceRoot: string, sourcesDirectoryPath: string, generatorOptions: SourceMapGeneratorOptions): SourceMapGenerator { - const { enter, exit } = generatorOptions.extendedDiagnostics - ? performance.createTimer("Source Map", "beforeSourcemap", "afterSourcemap") - : performance.nullTimer; - - // Current source map file and its index in the sources list - const rawSources: string[] = []; - const sources: string[] = []; - const sourceToSourceIndexMap = createMap(); - let sourcesContent: (string | null)[] | undefined; - - const names: string[] = []; - let nameToNameIndexMap: Map | undefined; - let mappings = ""; - - // Last recorded and encoded mappings - let lastGeneratedLine = 0; - let lastGeneratedCharacter = 0; - let lastSourceIndex = 0; - let lastSourceLine = 0; - let lastSourceCharacter = 0; - let lastNameIndex = 0; - let hasLast = false; - - let pendingGeneratedLine = 0; - let pendingGeneratedCharacter = 0; - let pendingSourceIndex = 0; - let pendingSourceLine = 0; - let pendingSourceCharacter = 0; - let pendingNameIndex = 0; - let hasPending = false; - let hasPendingSource = false; - let hasPendingName = false; - - return { - getSources: () => rawSources, - addSource, - setSourceContent, - addName, - addMapping, - appendSourceMap, - toJSON, - toString: () => JSON.stringify(toJSON()) - }; - - function addSource(fileName: string) { - enter(); - const source = getRelativePathToDirectoryOrUrl(sourcesDirectoryPath, - fileName, - host.getCurrentDirectory(), - host.getCanonicalFileName, - /*isAbsolutePathAnUrl*/ true); - - let sourceIndex = sourceToSourceIndexMap.get(source); - if (sourceIndex === undefined) { - sourceIndex = sources.length; - sources.push(source); - rawSources.push(fileName); - sourceToSourceIndexMap.set(source, sourceIndex); - } - exit(); - return sourceIndex; +export interface SourceMapGeneratorOptions { + extendedDiagnostics?: boolean; +} +/* @internal */ +export function createSourceMapGenerator(host: EmitHost, file: string, sourceRoot: string, sourcesDirectoryPath: string, generatorOptions: SourceMapGeneratorOptions): SourceMapGenerator { + const { enter, exit } = generatorOptions.extendedDiagnostics + ? createTimer("Source Map", "beforeSourcemap", "afterSourcemap") + : nullTimer; + // Current source map file and its index in the sources list + const rawSources: string[] = []; + const sources: string[] = []; + const sourceToSourceIndexMap = createMap(); + let sourcesContent: (string | null)[] | undefined; + const names: string[] = []; + let nameToNameIndexMap: ts.Map | undefined; + let mappings = ""; + // Last recorded and encoded mappings + let lastGeneratedLine = 0; + let lastGeneratedCharacter = 0; + let lastSourceIndex = 0; + let lastSourceLine = 0; + let lastSourceCharacter = 0; + let lastNameIndex = 0; + let hasLast = false; + let pendingGeneratedLine = 0; + let pendingGeneratedCharacter = 0; + let pendingSourceIndex = 0; + let pendingSourceLine = 0; + let pendingSourceCharacter = 0; + let pendingNameIndex = 0; + let hasPending = false; + let hasPendingSource = false; + let hasPendingName = false; + return { + getSources: () => rawSources, + addSource, + setSourceContent, + addName, + addMapping, + appendSourceMap, + toJSON, + toString: () => JSON.stringify(toJSON()) + }; + function addSource(fileName: string) { + enter(); + const source = getRelativePathToDirectoryOrUrl(sourcesDirectoryPath, fileName, host.getCurrentDirectory(), host.getCanonicalFileName, + /*isAbsolutePathAnUrl*/ true); + let sourceIndex = sourceToSourceIndexMap.get(source); + if (sourceIndex === undefined) { + sourceIndex = sources.length; + sources.push(source); + rawSources.push(fileName); + sourceToSourceIndexMap.set(source, sourceIndex); } - - /* eslint-disable boolean-trivia, no-null/no-null */ - function setSourceContent(sourceIndex: number, content: string | null) { - enter(); - if (content !== null) { - if (!sourcesContent) sourcesContent = []; - while (sourcesContent.length < sourceIndex) { - sourcesContent.push(null); - } - sourcesContent[sourceIndex] = content; + exit(); + return sourceIndex; + } + /* eslint-disable boolean-trivia, no-null/no-null */ + function setSourceContent(sourceIndex: number, content: string | null) { + enter(); + if (content !== null) { + if (!sourcesContent) + sourcesContent = []; + while (sourcesContent.length < sourceIndex) { + sourcesContent.push(null); } - exit(); + sourcesContent[sourceIndex] = content; } - /* eslint-enable boolean-trivia, no-null/no-null */ - - function addName(name: string) { - enter(); - if (!nameToNameIndexMap) nameToNameIndexMap = createMap(); - let nameIndex = nameToNameIndexMap.get(name); - if (nameIndex === undefined) { - nameIndex = names.length; - names.push(name); - nameToNameIndexMap.set(name, nameIndex); - } - exit(); - return nameIndex; + exit(); + } + /* eslint-enable boolean-trivia, no-null/no-null */ + function addName(name: string) { + enter(); + if (!nameToNameIndexMap) + nameToNameIndexMap = createMap(); + let nameIndex = nameToNameIndexMap.get(name); + if (nameIndex === undefined) { + nameIndex = names.length; + names.push(name); + nameToNameIndexMap.set(name, nameIndex); } - - function isNewGeneratedPosition(generatedLine: number, generatedCharacter: number) { - return !hasPending - || pendingGeneratedLine !== generatedLine - || pendingGeneratedCharacter !== generatedCharacter; + exit(); + return nameIndex; + } + function isNewGeneratedPosition(generatedLine: number, generatedCharacter: number) { + return !hasPending + || pendingGeneratedLine !== generatedLine + || pendingGeneratedCharacter !== generatedCharacter; + } + function isBacktrackingSourcePosition(sourceIndex: number | undefined, sourceLine: number | undefined, sourceCharacter: number | undefined) { + return sourceIndex !== undefined + && sourceLine !== undefined + && sourceCharacter !== undefined + && pendingSourceIndex === sourceIndex + && (pendingSourceLine > sourceLine + || pendingSourceLine === sourceLine && pendingSourceCharacter > sourceCharacter); + } + function addMapping(generatedLine: number, generatedCharacter: number, sourceIndex?: number, sourceLine?: number, sourceCharacter?: number, nameIndex?: number) { + Debug.assert(generatedLine >= pendingGeneratedLine, "generatedLine cannot backtrack"); + Debug.assert(generatedCharacter >= 0, "generatedCharacter cannot be negative"); + Debug.assert(sourceIndex === undefined || sourceIndex >= 0, "sourceIndex cannot be negative"); + Debug.assert(sourceLine === undefined || sourceLine >= 0, "sourceLine cannot be negative"); + Debug.assert(sourceCharacter === undefined || sourceCharacter >= 0, "sourceCharacter cannot be negative"); + enter(); + // If this location wasn't recorded or the location in source is going backwards, record the mapping + if (isNewGeneratedPosition(generatedLine, generatedCharacter) || + isBacktrackingSourcePosition(sourceIndex, sourceLine, sourceCharacter)) { + commitPendingMapping(); + pendingGeneratedLine = generatedLine; + pendingGeneratedCharacter = generatedCharacter; + hasPendingSource = false; + hasPendingName = false; + hasPending = true; } - - function isBacktrackingSourcePosition(sourceIndex: number | undefined, sourceLine: number | undefined, sourceCharacter: number | undefined) { - return sourceIndex !== undefined - && sourceLine !== undefined - && sourceCharacter !== undefined - && pendingSourceIndex === sourceIndex - && (pendingSourceLine > sourceLine - || pendingSourceLine === sourceLine && pendingSourceCharacter > sourceCharacter); + if (sourceIndex !== undefined && sourceLine !== undefined && sourceCharacter !== undefined) { + pendingSourceIndex = sourceIndex; + pendingSourceLine = sourceLine; + pendingSourceCharacter = sourceCharacter; + hasPendingSource = true; + if (nameIndex !== undefined) { + pendingNameIndex = nameIndex; + hasPendingName = true; + } } - - function addMapping(generatedLine: number, generatedCharacter: number, sourceIndex?: number, sourceLine?: number, sourceCharacter?: number, nameIndex?: number) { - Debug.assert(generatedLine >= pendingGeneratedLine, "generatedLine cannot backtrack"); - Debug.assert(generatedCharacter >= 0, "generatedCharacter cannot be negative"); - Debug.assert(sourceIndex === undefined || sourceIndex >= 0, "sourceIndex cannot be negative"); - Debug.assert(sourceLine === undefined || sourceLine >= 0, "sourceLine cannot be negative"); - Debug.assert(sourceCharacter === undefined || sourceCharacter >= 0, "sourceCharacter cannot be negative"); - enter(); - // If this location wasn't recorded or the location in source is going backwards, record the mapping - if (isNewGeneratedPosition(generatedLine, generatedCharacter) || - isBacktrackingSourcePosition(sourceIndex, sourceLine, sourceCharacter)) { - commitPendingMapping(); - pendingGeneratedLine = generatedLine; - pendingGeneratedCharacter = generatedCharacter; - hasPendingSource = false; - hasPendingName = false; - hasPending = true; + exit(); + } + function appendSourceMap(generatedLine: number, generatedCharacter: number, map: RawSourceMap, sourceMapPath: string, start?: LineAndCharacter, end?: LineAndCharacter) { + Debug.assert(generatedLine >= pendingGeneratedLine, "generatedLine cannot backtrack"); + Debug.assert(generatedCharacter >= 0, "generatedCharacter cannot be negative"); + enter(); + // First, decode the old component sourcemap + const sourceIndexToNewSourceIndexMap: number[] = []; + let nameIndexToNewNameIndexMap: number[] | undefined; + const mappingIterator = decodeMappings(map.mappings); + for (let iterResult = mappingIterator.next(); !iterResult.done; iterResult = mappingIterator.next()) { + const raw = iterResult.value; + if (end && (raw.generatedLine > end.line || + (raw.generatedLine === end.line && raw.generatedCharacter > end.character))) { + break; } - - if (sourceIndex !== undefined && sourceLine !== undefined && sourceCharacter !== undefined) { - pendingSourceIndex = sourceIndex; - pendingSourceLine = sourceLine; - pendingSourceCharacter = sourceCharacter; - hasPendingSource = true; - if (nameIndex !== undefined) { - pendingNameIndex = nameIndex; - hasPendingName = true; - } + if (start && (raw.generatedLine < start.line || + (start.line === raw.generatedLine && raw.generatedCharacter < start.character))) { + continue; } - exit(); - } - - function appendSourceMap(generatedLine: number, generatedCharacter: number, map: RawSourceMap, sourceMapPath: string, start?: LineAndCharacter, end?: LineAndCharacter) { - Debug.assert(generatedLine >= pendingGeneratedLine, "generatedLine cannot backtrack"); - Debug.assert(generatedCharacter >= 0, "generatedCharacter cannot be negative"); - enter(); - // First, decode the old component sourcemap - const sourceIndexToNewSourceIndexMap: number[] = []; - let nameIndexToNewNameIndexMap: number[] | undefined; - const mappingIterator = decodeMappings(map.mappings); - for (let iterResult = mappingIterator.next(); !iterResult.done; iterResult = mappingIterator.next()) { - const raw = iterResult.value; - if (end && ( - raw.generatedLine > end.line || - (raw.generatedLine === end.line && raw.generatedCharacter > end.character))) { - break; - } - - if (start && ( - raw.generatedLine < start.line || - (start.line === raw.generatedLine && raw.generatedCharacter < start.character))) { - continue; - } - // Then reencode all the updated mappings into the overall map - let newSourceIndex: number | undefined; - let newSourceLine: number | undefined; - let newSourceCharacter: number | undefined; - let newNameIndex: number | undefined; - if (raw.sourceIndex !== undefined) { - newSourceIndex = sourceIndexToNewSourceIndexMap[raw.sourceIndex]; - if (newSourceIndex === undefined) { - // Apply offsets to each position and fixup source entries - const rawPath = map.sources[raw.sourceIndex]; - const relativePath = map.sourceRoot ? combinePaths(map.sourceRoot, rawPath) : rawPath; - const combinedPath = combinePaths(getDirectoryPath(sourceMapPath), relativePath); - sourceIndexToNewSourceIndexMap[raw.sourceIndex] = newSourceIndex = addSource(combinedPath); - if (map.sourcesContent && typeof map.sourcesContent[raw.sourceIndex] === "string") { - setSourceContent(newSourceIndex, map.sourcesContent[raw.sourceIndex]); - } + // Then reencode all the updated mappings into the overall map + let newSourceIndex: number | undefined; + let newSourceLine: number | undefined; + let newSourceCharacter: number | undefined; + let newNameIndex: number | undefined; + if (raw.sourceIndex !== undefined) { + newSourceIndex = sourceIndexToNewSourceIndexMap[raw.sourceIndex]; + if (newSourceIndex === undefined) { + // Apply offsets to each position and fixup source entries + const rawPath = map.sources[raw.sourceIndex]; + const relativePath = map.sourceRoot ? combinePaths(map.sourceRoot, rawPath) : rawPath; + const combinedPath = combinePaths(getDirectoryPath(sourceMapPath), relativePath); + sourceIndexToNewSourceIndexMap[raw.sourceIndex] = newSourceIndex = addSource(combinedPath); + if (map.sourcesContent && typeof map.sourcesContent[raw.sourceIndex] === "string") { + setSourceContent(newSourceIndex, map.sourcesContent[raw.sourceIndex]); } - - newSourceLine = raw.sourceLine; - newSourceCharacter = raw.sourceCharacter; - if (map.names && raw.nameIndex !== undefined) { - if (!nameIndexToNewNameIndexMap) nameIndexToNewNameIndexMap = []; - newNameIndex = nameIndexToNewNameIndexMap[raw.nameIndex]; - if (newNameIndex === undefined) { - nameIndexToNewNameIndexMap[raw.nameIndex] = newNameIndex = addName(map.names[raw.nameIndex]); - } + } + newSourceLine = raw.sourceLine; + newSourceCharacter = raw.sourceCharacter; + if (map.names && raw.nameIndex !== undefined) { + if (!nameIndexToNewNameIndexMap) + nameIndexToNewNameIndexMap = []; + newNameIndex = nameIndexToNewNameIndexMap[raw.nameIndex]; + if (newNameIndex === undefined) { + nameIndexToNewNameIndexMap[raw.nameIndex] = newNameIndex = addName(map.names[raw.nameIndex]); } } - - const rawGeneratedLine = raw.generatedLine - (start ? start.line : 0); - const newGeneratedLine = rawGeneratedLine + generatedLine; - const rawGeneratedCharacter = start && start.line === raw.generatedLine ? raw.generatedCharacter - start.character : raw.generatedCharacter; - const newGeneratedCharacter = rawGeneratedLine === 0 ? rawGeneratedCharacter + generatedCharacter : rawGeneratedCharacter; - addMapping(newGeneratedLine, newGeneratedCharacter, newSourceIndex, newSourceLine, newSourceCharacter, newNameIndex); } - exit(); + const rawGeneratedLine = raw.generatedLine - (start ? start.line : 0); + const newGeneratedLine = rawGeneratedLine + generatedLine; + const rawGeneratedCharacter = start && start.line === raw.generatedLine ? raw.generatedCharacter - start.character : raw.generatedCharacter; + const newGeneratedCharacter = rawGeneratedLine === 0 ? rawGeneratedCharacter + generatedCharacter : rawGeneratedCharacter; + addMapping(newGeneratedLine, newGeneratedCharacter, newSourceIndex, newSourceLine, newSourceCharacter, newNameIndex); } - - function shouldCommitMapping() { - return !hasLast - || lastGeneratedLine !== pendingGeneratedLine - || lastGeneratedCharacter !== pendingGeneratedCharacter - || lastSourceIndex !== pendingSourceIndex - || lastSourceLine !== pendingSourceLine - || lastSourceCharacter !== pendingSourceCharacter - || lastNameIndex !== pendingNameIndex; + exit(); + } + function shouldCommitMapping() { + return !hasLast + || lastGeneratedLine !== pendingGeneratedLine + || lastGeneratedCharacter !== pendingGeneratedCharacter + || lastSourceIndex !== pendingSourceIndex + || lastSourceLine !== pendingSourceLine + || lastSourceCharacter !== pendingSourceCharacter + || lastNameIndex !== pendingNameIndex; + } + function commitPendingMapping() { + if (!hasPending || !shouldCommitMapping()) { + return; } - - function commitPendingMapping() { - if (!hasPending || !shouldCommitMapping()) { - return; - } - - enter(); - - // Line/Comma delimiters - if (lastGeneratedLine < pendingGeneratedLine) { - // Emit line delimiters - do { - mappings += ";"; - lastGeneratedLine++; - lastGeneratedCharacter = 0; - } - while (lastGeneratedLine < pendingGeneratedLine); - } - else { - Debug.assertEqual(lastGeneratedLine, pendingGeneratedLine, "generatedLine cannot backtrack"); - // Emit comma to separate the entry - if (hasLast) { - mappings += ","; - } - } - - // 1. Relative generated character - mappings += base64VLQFormatEncode(pendingGeneratedCharacter - lastGeneratedCharacter); - lastGeneratedCharacter = pendingGeneratedCharacter; - - if (hasPendingSource) { - // 2. Relative sourceIndex - mappings += base64VLQFormatEncode(pendingSourceIndex - lastSourceIndex); - lastSourceIndex = pendingSourceIndex; - - // 3. Relative source line - mappings += base64VLQFormatEncode(pendingSourceLine - lastSourceLine); - lastSourceLine = pendingSourceLine; - - // 4. Relative source character - mappings += base64VLQFormatEncode(pendingSourceCharacter - lastSourceCharacter); - lastSourceCharacter = pendingSourceCharacter; - - if (hasPendingName) { - // 5. Relative nameIndex - mappings += base64VLQFormatEncode(pendingNameIndex - lastNameIndex); - lastNameIndex = pendingNameIndex; - } + enter(); + // Line/Comma delimiters + if (lastGeneratedLine < pendingGeneratedLine) { + // Emit line delimiters + do { + mappings += ";"; + lastGeneratedLine++; + lastGeneratedCharacter = 0; + } while (lastGeneratedLine < pendingGeneratedLine); + } + else { + Debug.assertEqual(lastGeneratedLine, pendingGeneratedLine, "generatedLine cannot backtrack"); + // Emit comma to separate the entry + if (hasLast) { + mappings += ","; } - - hasLast = true; - exit(); } - - function toJSON(): RawSourceMap { - commitPendingMapping(); - return { - version: 3, - file, - sourceRoot, - sources, - names, - mappings, - sourcesContent, - }; + // 1. Relative generated character + mappings += base64VLQFormatEncode(pendingGeneratedCharacter - lastGeneratedCharacter); + lastGeneratedCharacter = pendingGeneratedCharacter; + if (hasPendingSource) { + // 2. Relative sourceIndex + mappings += base64VLQFormatEncode(pendingSourceIndex - lastSourceIndex); + lastSourceIndex = pendingSourceIndex; + // 3. Relative source line + mappings += base64VLQFormatEncode(pendingSourceLine - lastSourceLine); + lastSourceLine = pendingSourceLine; + // 4. Relative source character + mappings += base64VLQFormatEncode(pendingSourceCharacter - lastSourceCharacter); + lastSourceCharacter = pendingSourceCharacter; + if (hasPendingName) { + // 5. Relative nameIndex + mappings += base64VLQFormatEncode(pendingNameIndex - lastNameIndex); + lastNameIndex = pendingNameIndex; + } } + hasLast = true; + exit(); } - - // Sometimes tools can see the following line as a source mapping url comment, so we mangle it a bit (the [M]) - const sourceMapCommentRegExp = /^\/\/[@#] source[M]appingURL=(.+)\s*$/; - const whitespaceOrMapCommentRegExp = /^\s*(\/\/[@#] .*)?$/; - - export interface LineInfo { - getLineCount(): number; - getLineText(line: number): string; - } - - export function getLineInfo(text: string, lineStarts: readonly number[]): LineInfo { + function toJSON(): RawSourceMap { + commitPendingMapping(); return { - getLineCount: () => lineStarts.length, - getLineText: line => text.substring(lineStarts[line], lineStarts[line + 1]) + version: 3, + file, + sourceRoot, + sources, + names, + mappings, + sourcesContent, }; } - - /** - * Tries to find the sourceMappingURL comment at the end of a file. - */ - export function tryGetSourceMappingURL(lineInfo: LineInfo) { - for (let index = lineInfo.getLineCount() - 1; index >= 0; index--) { - const line = lineInfo.getLineText(index); - const comment = sourceMapCommentRegExp.exec(line); - if (comment) { - return comment[1]; - } - // If we see a non-whitespace/map comment-like line, break, to avoid scanning up the entire file - else if (!line.match(whitespaceOrMapCommentRegExp)) { - break; - } - } - } - - /* eslint-disable no-null/no-null */ - function isStringOrNull(x: any) { - return typeof x === "string" || x === null; - } - - export function isRawSourceMap(x: any): x is RawSourceMap { - return x !== null - && typeof x === "object" - && x.version === 3 - && typeof x.file === "string" - && typeof x.mappings === "string" - && isArray(x.sources) && every(x.sources, isString) - && (x.sourceRoot === undefined || x.sourceRoot === null || typeof x.sourceRoot === "string") - && (x.sourcesContent === undefined || x.sourcesContent === null || isArray(x.sourcesContent) && every(x.sourcesContent, isStringOrNull)) - && (x.names === undefined || x.names === null || isArray(x.names) && every(x.names, isString)); - } - /* eslint-enable no-null/no-null */ - - export function tryParseRawSourceMap(text: string) { - try { - const parsed = JSON.parse(text); - if (isRawSourceMap(parsed)) { - return parsed; - } +} +// Sometimes tools can see the following line as a source mapping url comment, so we mangle it a bit (the [M]) +/* @internal */ +const sourceMapCommentRegExp = /^\/\/[@#] source[M]appingURL=(.+)\s*$/; +/* @internal */ +const whitespaceOrMapCommentRegExp = /^\s*(\/\/[@#] .*)?$/; +/* @internal */ +export interface LineInfo { + getLineCount(): number; + getLineText(line: number): string; +} +/* @internal */ +export function getLineInfo(text: string, lineStarts: readonly number[]): LineInfo { + return { + getLineCount: () => lineStarts.length, + getLineText: line => text.substring(lineStarts[line], lineStarts[line + 1]) + }; +} +/** + * Tries to find the sourceMappingURL comment at the end of a file. + */ +/* @internal */ +export function tryGetSourceMappingURL(lineInfo: LineInfo) { + for (let index = lineInfo.getLineCount() - 1; index >= 0; index--) { + const line = lineInfo.getLineText(index); + const comment = sourceMapCommentRegExp.exec(line); + if (comment) { + return comment[1]; } - catch { - // empty + // If we see a non-whitespace/map comment-like line, break, to avoid scanning up the entire file + else if (!line.match(whitespaceOrMapCommentRegExp)) { + break; } - - return undefined; - } - - export interface MappingsDecoder extends Iterator { - readonly pos: number; - readonly error: string | undefined; - readonly state: Required; } - - export interface Mapping { - generatedLine: number; - generatedCharacter: number; - sourceIndex?: number; - sourceLine?: number; - sourceCharacter?: number; - nameIndex?: number; +} +/* eslint-disable no-null/no-null */ +/* @internal */ +function isStringOrNull(x: any) { + return typeof x === "string" || x === null; +} +/* @internal */ +export function isRawSourceMap(x: any): x is RawSourceMap { + return x !== null + && typeof x === "object" + && x.version === 3 + && typeof x.file === "string" + && typeof x.mappings === "string" + && isArray(x.sources) && every(x.sources, isString) + && (x.sourceRoot === undefined || x.sourceRoot === null || typeof x.sourceRoot === "string") + && (x.sourcesContent === undefined || x.sourcesContent === null || isArray(x.sourcesContent) && every(x.sourcesContent, isStringOrNull)) + && (x.names === undefined || x.names === null || isArray(x.names) && every(x.names, isString)); +} +/* eslint-enable no-null/no-null */ +/* @internal */ +export function tryParseRawSourceMap(text: string) { + try { + const parsed = JSON.parse(text); + if (isRawSourceMap(parsed)) { + return parsed; + } } - - export interface SourceMapping extends Mapping { - sourceIndex: number; - sourceLine: number; - sourceCharacter: number; + catch { + // empty } - - export function decodeMappings(mappings: string): MappingsDecoder { - let done = false; - let pos = 0; - let generatedLine = 0; - let generatedCharacter = 0; - let sourceIndex = 0; - let sourceLine = 0; - let sourceCharacter = 0; - let nameIndex = 0; - let error: string | undefined; - - return { - get pos() { return pos; }, - get error() { return error; }, - get state() { return captureMapping(/*hasSource*/ true, /*hasName*/ true); }, - next() { - while (!done && pos < mappings.length) { - const ch = mappings.charCodeAt(pos); - if (ch === CharacterCodes.semicolon) { - // new line - generatedLine++; - generatedCharacter = 0; - pos++; - continue; - } - - if (ch === CharacterCodes.comma) { - // Next entry is on same line - no action needed - pos++; - continue; - } - - let hasSource = false; - let hasName = false; - - generatedCharacter += base64VLQFormatDecode(); - if (hasReportedError()) return stopIterating(); - if (generatedCharacter < 0) return setErrorAndStopIterating("Invalid generatedCharacter found"); - + return undefined; +} +/* @internal */ +export interface MappingsDecoder extends ts.Iterator { + readonly pos: number; + readonly error: string | undefined; + readonly state: Required; +} +/* @internal */ +export interface Mapping { + generatedLine: number; + generatedCharacter: number; + sourceIndex?: number; + sourceLine?: number; + sourceCharacter?: number; + nameIndex?: number; +} +/* @internal */ +export interface SourceMapping extends Mapping { + sourceIndex: number; + sourceLine: number; + sourceCharacter: number; +} +/* @internal */ +export function decodeMappings(mappings: string): MappingsDecoder { + let done = false; + let pos = 0; + let generatedLine = 0; + let generatedCharacter = 0; + let sourceIndex = 0; + let sourceLine = 0; + let sourceCharacter = 0; + let nameIndex = 0; + let error: string | undefined; + return { + get pos() { return pos; }, + get error() { return error; }, + get state() { return captureMapping(/*hasSource*/ true, /*hasName*/ true); }, + next() { + while (!done && pos < mappings.length) { + const ch = mappings.charCodeAt(pos); + if (ch === CharacterCodes.semicolon) { + // new line + generatedLine++; + generatedCharacter = 0; + pos++; + continue; + } + if (ch === CharacterCodes.comma) { + // Next entry is on same line - no action needed + pos++; + continue; + } + let hasSource = false; + let hasName = false; + generatedCharacter += base64VLQFormatDecode(); + if (hasReportedError()) + return stopIterating(); + if (generatedCharacter < 0) + return setErrorAndStopIterating("Invalid generatedCharacter found"); + if (!isSourceMappingSegmentEnd()) { + hasSource = true; + sourceIndex += base64VLQFormatDecode(); + if (hasReportedError()) + return stopIterating(); + if (sourceIndex < 0) + return setErrorAndStopIterating("Invalid sourceIndex found"); + if (isSourceMappingSegmentEnd()) + return setErrorAndStopIterating("Unsupported Format: No entries after sourceIndex"); + sourceLine += base64VLQFormatDecode(); + if (hasReportedError()) + return stopIterating(); + if (sourceLine < 0) + return setErrorAndStopIterating("Invalid sourceLine found"); + if (isSourceMappingSegmentEnd()) + return setErrorAndStopIterating("Unsupported Format: No entries after sourceLine"); + sourceCharacter += base64VLQFormatDecode(); + if (hasReportedError()) + return stopIterating(); + if (sourceCharacter < 0) + return setErrorAndStopIterating("Invalid sourceCharacter found"); if (!isSourceMappingSegmentEnd()) { - hasSource = true; - - sourceIndex += base64VLQFormatDecode(); - if (hasReportedError()) return stopIterating(); - if (sourceIndex < 0) return setErrorAndStopIterating("Invalid sourceIndex found"); - if (isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Format: No entries after sourceIndex"); - - sourceLine += base64VLQFormatDecode(); - if (hasReportedError()) return stopIterating(); - if (sourceLine < 0) return setErrorAndStopIterating("Invalid sourceLine found"); - if (isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Format: No entries after sourceLine"); - - sourceCharacter += base64VLQFormatDecode(); - if (hasReportedError()) return stopIterating(); - if (sourceCharacter < 0) return setErrorAndStopIterating("Invalid sourceCharacter found"); - - if (!isSourceMappingSegmentEnd()) { - hasName = true; - nameIndex += base64VLQFormatDecode(); - if (hasReportedError()) return stopIterating(); - if (nameIndex < 0) return setErrorAndStopIterating("Invalid nameIndex found"); - - if (!isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Error Format: Entries after nameIndex"); - } + hasName = true; + nameIndex += base64VLQFormatDecode(); + if (hasReportedError()) + return stopIterating(); + if (nameIndex < 0) + return setErrorAndStopIterating("Invalid nameIndex found"); + if (!isSourceMappingSegmentEnd()) + return setErrorAndStopIterating("Unsupported Error Format: Entries after nameIndex"); } - - return { value: captureMapping(hasSource, hasName), done }; } - - return stopIterating(); - } - }; - - function captureMapping(hasSource: true, hasName: true): Required; - function captureMapping(hasSource: boolean, hasName: boolean): Mapping; - function captureMapping(hasSource: boolean, hasName: boolean): Mapping { - return { - generatedLine, - generatedCharacter, - sourceIndex: hasSource ? sourceIndex : undefined, - sourceLine: hasSource ? sourceLine : undefined, - sourceCharacter: hasSource ? sourceCharacter : undefined, - nameIndex: hasName ? nameIndex : undefined - }; - } - - function stopIterating(): { value: never, done: true } { - done = true; - return { value: undefined!, done: true }; - } - - function setError(message: string) { - if (error === undefined) { - error = message; + return { value: captureMapping(hasSource, hasName), done }; } - } - - function setErrorAndStopIterating(message: string) { - setError(message); return stopIterating(); } - - function hasReportedError() { - return error !== undefined; + }; + function captureMapping(hasSource: true, hasName: true): Required; + function captureMapping(hasSource: boolean, hasName: boolean): Mapping; + function captureMapping(hasSource: boolean, hasName: boolean): Mapping { + return { + generatedLine, + generatedCharacter, + sourceIndex: hasSource ? sourceIndex : undefined, + sourceLine: hasSource ? sourceLine : undefined, + sourceCharacter: hasSource ? sourceCharacter : undefined, + nameIndex: hasName ? nameIndex : undefined + }; + } + function stopIterating(): { + value: never; + done: true; + } { + done = true; + return { value: undefined!, done: true }; + } + function setError(message: string) { + if (error === undefined) { + error = message; } - - function isSourceMappingSegmentEnd() { - return (pos === mappings.length || - mappings.charCodeAt(pos) === CharacterCodes.comma || - mappings.charCodeAt(pos) === CharacterCodes.semicolon); + } + function setErrorAndStopIterating(message: string) { + setError(message); + return stopIterating(); + } + function hasReportedError() { + return error !== undefined; + } + function isSourceMappingSegmentEnd() { + return (pos === mappings.length || + mappings.charCodeAt(pos) === CharacterCodes.comma || + mappings.charCodeAt(pos) === CharacterCodes.semicolon); + } + function base64VLQFormatDecode(): number { + let moreDigits = true; + let shiftCount = 0; + let value = 0; + for (; moreDigits; pos++) { + if (pos >= mappings.length) + return setError("Error in decoding base64VLQFormatDecode, past the mapping string"), -1; + // 6 digit number + const currentByte = base64FormatDecode(mappings.charCodeAt(pos)); + if (currentByte === -1) + return setError("Invalid character in VLQ"), -1; + // If msb is set, we still have more bits to continue + moreDigits = (currentByte & 32) !== 0; + // least significant 5 bits are the next msbs in the final value. + value = value | ((currentByte & 31) << shiftCount); + shiftCount += 5; } - - function base64VLQFormatDecode(): number { - let moreDigits = true; - let shiftCount = 0; - let value = 0; - - for (; moreDigits; pos++) { - if (pos >= mappings.length) return setError("Error in decoding base64VLQFormatDecode, past the mapping string"), -1; - - // 6 digit number - const currentByte = base64FormatDecode(mappings.charCodeAt(pos)); - if (currentByte === -1) return setError("Invalid character in VLQ"), -1; - - // If msb is set, we still have more bits to continue - moreDigits = (currentByte & 32) !== 0; - - // least significant 5 bits are the next msbs in the final value. - value = value | ((currentByte & 31) << shiftCount); - shiftCount += 5; - } - - // Least significant bit if 1 represents negative and rest of the msb is actual absolute value - if ((value & 1) === 0) { - // + number - value = value >> 1; - } - else { - // - number - value = value >> 1; - value = -value; - } - - return value; + // Least significant bit if 1 represents negative and rest of the msb is actual absolute value + if ((value & 1) === 0) { + // + number + value = value >> 1; + } + else { + // - number + value = value >> 1; + value = -value; } + return value; } - - export function sameMapping(left: T, right: T) { - return left === right - || left.generatedLine === right.generatedLine +} +/* @internal */ +export function sameMapping(left: T, right: T) { + return left === right + || left.generatedLine === right.generatedLine && left.generatedCharacter === right.generatedCharacter && left.sourceIndex === right.sourceIndex && left.sourceLine === right.sourceLine && left.sourceCharacter === right.sourceCharacter && left.nameIndex === right.nameIndex; - } - - export function isSourceMapping(mapping: Mapping): mapping is SourceMapping { - return mapping.sourceIndex !== undefined - && mapping.sourceLine !== undefined - && mapping.sourceCharacter !== undefined; - } - - function base64FormatEncode(value: number) { - return value >= 0 && value < 26 ? CharacterCodes.A + value : - value >= 26 && value < 52 ? CharacterCodes.a + value - 26 : +} +/* @internal */ +export function isSourceMapping(mapping: Mapping): mapping is SourceMapping { + return mapping.sourceIndex !== undefined + && mapping.sourceLine !== undefined + && mapping.sourceCharacter !== undefined; +} +/* @internal */ +function base64FormatEncode(value: number) { + return value >= 0 && value < 26 ? CharacterCodes.A + value : + value >= 26 && value < 52 ? CharacterCodes.a + value - 26 : value >= 52 && value < 62 ? CharacterCodes._0 + value - 52 : - value === 62 ? CharacterCodes.plus : - value === 63 ? CharacterCodes.slash : - Debug.fail(`${value}: not a base64 value`); - } - - function base64FormatDecode(ch: number) { - return ch >= CharacterCodes.A && ch <= CharacterCodes.Z ? ch - CharacterCodes.A : - ch >= CharacterCodes.a && ch <= CharacterCodes.z ? ch - CharacterCodes.a + 26 : + value === 62 ? CharacterCodes.plus : + value === 63 ? CharacterCodes.slash : + Debug.fail(`${value}: not a base64 value`); +} +/* @internal */ +function base64FormatDecode(ch: number) { + return ch >= CharacterCodes.A && ch <= CharacterCodes.Z ? ch - CharacterCodes.A : + ch >= CharacterCodes.a && ch <= CharacterCodes.z ? ch - CharacterCodes.a + 26 : ch >= CharacterCodes._0 && ch <= CharacterCodes._9 ? ch - CharacterCodes._0 + 52 : - ch === CharacterCodes.plus ? 62 : - ch === CharacterCodes.slash ? 63 : - -1; + ch === CharacterCodes.plus ? 62 : + ch === CharacterCodes.slash ? 63 : + -1; +} +/* @internal */ +function base64VLQFormatEncode(inValue: number) { + // Add a new least significant bit that has the sign of the value. + // if negative number the least significant bit that gets added to the number has value 1 + // else least significant bit value that gets added is 0 + // eg. -1 changes to binary : 01 [1] => 3 + // +1 changes to binary : 01 [0] => 2 + if (inValue < 0) { + inValue = ((-inValue) << 1) + 1; } - - function base64VLQFormatEncode(inValue: number) { - // Add a new least significant bit that has the sign of the value. - // if negative number the least significant bit that gets added to the number has value 1 - // else least significant bit value that gets added is 0 - // eg. -1 changes to binary : 01 [1] => 3 - // +1 changes to binary : 01 [0] => 2 - if (inValue < 0) { - inValue = ((-inValue) << 1) + 1; + else { + inValue = inValue << 1; + } + // Encode 5 bits at a time starting from least significant bits + let encodedStr = ""; + do { + let currentDigit = inValue & 31; // 11111 + inValue = inValue >> 5; + if (inValue > 0) { + // There are still more digits to decode, set the msb (6th bit) + currentDigit = currentDigit | 32; } - else { - inValue = inValue << 1; + encodedStr = encodedStr + String.fromCharCode(base64FormatEncode(currentDigit)); + } while (inValue > 0); + return encodedStr; +} +/* @internal */ +interface MappedPosition { + generatedPosition: number; + source: string | undefined; + sourceIndex: number | undefined; + sourcePosition: number | undefined; + nameIndex: number | undefined; +} +/* @internal */ +interface SourceMappedPosition extends MappedPosition { + source: string; + sourceIndex: number; + sourcePosition: number; +} +/* @internal */ +function isSourceMappedPosition(value: MappedPosition): value is SourceMappedPosition { + return value.sourceIndex !== undefined + && value.sourcePosition !== undefined; +} +/* @internal */ +function sameMappedPosition(left: MappedPosition, right: MappedPosition) { + return left.generatedPosition === right.generatedPosition + && left.sourceIndex === right.sourceIndex + && left.sourcePosition === right.sourcePosition; +} +/* @internal */ +function compareSourcePositions(left: SourceMappedPosition, right: SourceMappedPosition) { + // Compares sourcePosition without comparing sourceIndex + // since the mappings are grouped by sourceIndex + Debug.assert(left.sourceIndex === right.sourceIndex); + return compareValues(left.sourcePosition, right.sourcePosition); +} +/* @internal */ +function compareGeneratedPositions(left: MappedPosition, right: MappedPosition) { + return compareValues(left.generatedPosition, right.generatedPosition); +} +/* @internal */ +function getSourcePositionOfMapping(value: SourceMappedPosition) { + return value.sourcePosition; +} +/* @internal */ +function getGeneratedPositionOfMapping(value: MappedPosition) { + return value.generatedPosition; +} +/* @internal */ +export function createDocumentPositionMapper(host: DocumentPositionMapperHost, map: RawSourceMap, mapPath: string): DocumentPositionMapper { + const mapDirectory = getDirectoryPath(mapPath); + const sourceRoot = map.sourceRoot ? getNormalizedAbsolutePath(map.sourceRoot, mapDirectory) : mapDirectory; + const generatedAbsoluteFilePath = getNormalizedAbsolutePath(map.file, mapDirectory); + const generatedFile = host.getSourceFileLike(generatedAbsoluteFilePath); + const sourceFileAbsolutePaths = map.sources.map(source => getNormalizedAbsolutePath(source, sourceRoot)); + const sourceToSourceIndexMap = createMapFromEntries(sourceFileAbsolutePaths.map((source, i) => [host.getCanonicalFileName(source), i] as [string, number])); + let decodedMappings: readonly MappedPosition[] | undefined; + let generatedMappings: SortedReadonlyArray | undefined; + let sourceMappings: readonly SortedReadonlyArray[] | undefined; + return { + getSourcePosition, + getGeneratedPosition + }; + function processMapping(mapping: Mapping): MappedPosition { + const generatedPosition = generatedFile !== undefined + ? getPositionOfLineAndCharacter(generatedFile, mapping.generatedLine, mapping.generatedCharacter, /*allowEdits*/ true) + : -1; + let source: string | undefined; + let sourcePosition: number | undefined; + if (isSourceMapping(mapping)) { + const sourceFile = host.getSourceFileLike(sourceFileAbsolutePaths[mapping.sourceIndex]); + source = map.sources[mapping.sourceIndex]; + sourcePosition = sourceFile !== undefined + ? getPositionOfLineAndCharacter(sourceFile, mapping.sourceLine, mapping.sourceCharacter, /*allowEdits*/ true) + : -1; } - - // Encode 5 bits at a time starting from least significant bits - let encodedStr = ""; - do { - let currentDigit = inValue & 31; // 11111 - inValue = inValue >> 5; - if (inValue > 0) { - // There are still more digits to decode, set the msb (6th bit) - currentDigit = currentDigit | 32; - } - encodedStr = encodedStr + String.fromCharCode(base64FormatEncode(currentDigit)); - } while (inValue > 0); - - return encodedStr; - } - - interface MappedPosition { - generatedPosition: number; - source: string | undefined; - sourceIndex: number | undefined; - sourcePosition: number | undefined; - nameIndex: number | undefined; - } - - interface SourceMappedPosition extends MappedPosition { - source: string; - sourceIndex: number; - sourcePosition: number; - } - - function isSourceMappedPosition(value: MappedPosition): value is SourceMappedPosition { - return value.sourceIndex !== undefined - && value.sourcePosition !== undefined; - } - - function sameMappedPosition(left: MappedPosition, right: MappedPosition) { - return left.generatedPosition === right.generatedPosition - && left.sourceIndex === right.sourceIndex - && left.sourcePosition === right.sourcePosition; - } - - function compareSourcePositions(left: SourceMappedPosition, right: SourceMappedPosition) { - // Compares sourcePosition without comparing sourceIndex - // since the mappings are grouped by sourceIndex - Debug.assert(left.sourceIndex === right.sourceIndex); - return compareValues(left.sourcePosition, right.sourcePosition); - } - - function compareGeneratedPositions(left: MappedPosition, right: MappedPosition) { - return compareValues(left.generatedPosition, right.generatedPosition); - } - - function getSourcePositionOfMapping(value: SourceMappedPosition) { - return value.sourcePosition; - } - - function getGeneratedPositionOfMapping(value: MappedPosition) { - return value.generatedPosition; - } - - export function createDocumentPositionMapper(host: DocumentPositionMapperHost, map: RawSourceMap, mapPath: string): DocumentPositionMapper { - const mapDirectory = getDirectoryPath(mapPath); - const sourceRoot = map.sourceRoot ? getNormalizedAbsolutePath(map.sourceRoot, mapDirectory) : mapDirectory; - const generatedAbsoluteFilePath = getNormalizedAbsolutePath(map.file, mapDirectory); - const generatedFile = host.getSourceFileLike(generatedAbsoluteFilePath); - const sourceFileAbsolutePaths = map.sources.map(source => getNormalizedAbsolutePath(source, sourceRoot)); - const sourceToSourceIndexMap = createMapFromEntries(sourceFileAbsolutePaths.map((source, i) => [host.getCanonicalFileName(source), i] as [string, number])); - let decodedMappings: readonly MappedPosition[] | undefined; - let generatedMappings: SortedReadonlyArray | undefined; - let sourceMappings: readonly SortedReadonlyArray[] | undefined; - return { - getSourcePosition, - getGeneratedPosition + generatedPosition, + source, + sourceIndex: mapping.sourceIndex, + sourcePosition, + nameIndex: mapping.nameIndex }; - - function processMapping(mapping: Mapping): MappedPosition { - const generatedPosition = generatedFile !== undefined - ? getPositionOfLineAndCharacter(generatedFile, mapping.generatedLine, mapping.generatedCharacter, /*allowEdits*/ true) - : -1; - let source: string | undefined; - let sourcePosition: number | undefined; - if (isSourceMapping(mapping)) { - const sourceFile = host.getSourceFileLike(sourceFileAbsolutePaths[mapping.sourceIndex]); - source = map.sources[mapping.sourceIndex]; - sourcePosition = sourceFile !== undefined - ? getPositionOfLineAndCharacter(sourceFile, mapping.sourceLine, mapping.sourceCharacter, /*allowEdits*/ true) - : -1; - } - return { - generatedPosition, - source, - sourceIndex: mapping.sourceIndex, - sourcePosition, - nameIndex: mapping.nameIndex - }; - } - - function getDecodedMappings() { - if (decodedMappings === undefined) { - const decoder = decodeMappings(map.mappings); - const mappings = arrayFrom(decoder, processMapping); - if (decoder.error !== undefined) { - if (host.log) { - host.log(`Encountered error while decoding sourcemap: ${decoder.error}`); - } - decodedMappings = emptyArray; - } - else { - decodedMappings = mappings; + } + function getDecodedMappings() { + if (decodedMappings === undefined) { + const decoder = decodeMappings(map.mappings); + const mappings = arrayFrom(decoder, processMapping); + if (decoder.error !== undefined) { + if (host.log) { + host.log(`Encountered error while decoding sourcemap: ${decoder.error}`); } + decodedMappings = emptyArray; } - return decodedMappings; - } - - function getSourceMappings(sourceIndex: number) { - if (sourceMappings === undefined) { - const lists: SourceMappedPosition[][] = []; - for (const mapping of getDecodedMappings()) { - if (!isSourceMappedPosition(mapping)) continue; - let list = lists[mapping.sourceIndex]; - if (!list) lists[mapping.sourceIndex] = list = []; - list.push(mapping); - } - sourceMappings = lists.map(list => sortAndDeduplicate(list, compareSourcePositions, sameMappedPosition)); + else { + decodedMappings = mappings; } - return sourceMappings[sourceIndex]; } - - function getGeneratedMappings() { - if (generatedMappings === undefined) { - const list: MappedPosition[] = []; - for (const mapping of getDecodedMappings()) { - list.push(mapping); - } - generatedMappings = sortAndDeduplicate(list, compareGeneratedPositions, sameMappedPosition); + return decodedMappings; + } + function getSourceMappings(sourceIndex: number) { + if (sourceMappings === undefined) { + const lists: SourceMappedPosition[][] = []; + for (const mapping of getDecodedMappings()) { + if (!isSourceMappedPosition(mapping)) + continue; + let list = lists[mapping.sourceIndex]; + if (!list) + lists[mapping.sourceIndex] = list = []; + list.push(mapping); } - return generatedMappings; + sourceMappings = lists.map(list => sortAndDeduplicate(list, compareSourcePositions, sameMappedPosition)); } - - function getGeneratedPosition(loc: DocumentPosition): DocumentPosition { - const sourceIndex = sourceToSourceIndexMap.get(host.getCanonicalFileName(loc.fileName)); - if (sourceIndex === undefined) return loc; - - const sourceMappings = getSourceMappings(sourceIndex); - if (!some(sourceMappings)) return loc; - - let targetIndex = binarySearchKey(sourceMappings, loc.pos, getSourcePositionOfMapping, compareValues); - if (targetIndex < 0) { - // if no exact match, closest is 2's complement of result - targetIndex = ~targetIndex; - } - - const mapping = sourceMappings[targetIndex]; - if (mapping === undefined || mapping.sourceIndex !== sourceIndex) { - return loc; + return sourceMappings[sourceIndex]; + } + function getGeneratedMappings() { + if (generatedMappings === undefined) { + const list: MappedPosition[] = []; + for (const mapping of getDecodedMappings()) { + list.push(mapping); } - - return { fileName: generatedAbsoluteFilePath, pos: mapping.generatedPosition }; // Closest pos + generatedMappings = sortAndDeduplicate(list, compareGeneratedPositions, sameMappedPosition); } - - function getSourcePosition(loc: DocumentPosition): DocumentPosition { - const generatedMappings = getGeneratedMappings(); - if (!some(generatedMappings)) return loc; - - let targetIndex = binarySearchKey(generatedMappings, loc.pos, getGeneratedPositionOfMapping, compareValues); - if (targetIndex < 0) { - // if no exact match, closest is 2's complement of result - targetIndex = ~targetIndex; - } - - const mapping = generatedMappings[targetIndex]; - if (mapping === undefined || !isSourceMappedPosition(mapping)) { - return loc; - } - - return { fileName: sourceFileAbsolutePaths[mapping.sourceIndex], pos: mapping.sourcePosition }; // Closest pos + return generatedMappings; + } + function getGeneratedPosition(loc: DocumentPosition): DocumentPosition { + const sourceIndex = sourceToSourceIndexMap.get(host.getCanonicalFileName(loc.fileName)); + if (sourceIndex === undefined) + return loc; + const sourceMappings = getSourceMappings(sourceIndex); + if (!some(sourceMappings)) + return loc; + let targetIndex = binarySearchKey(sourceMappings, loc.pos, getSourcePositionOfMapping, compareValues); + if (targetIndex < 0) { + // if no exact match, closest is 2's complement of result + targetIndex = ~targetIndex; + } + const mapping = sourceMappings[targetIndex]; + if (mapping === undefined || mapping.sourceIndex !== sourceIndex) { + return loc; } + return { fileName: generatedAbsoluteFilePath, pos: mapping.generatedPosition }; // Closest pos + } + function getSourcePosition(loc: DocumentPosition): DocumentPosition { + const generatedMappings = getGeneratedMappings(); + if (!some(generatedMappings)) + return loc; + let targetIndex = binarySearchKey(generatedMappings, loc.pos, getGeneratedPositionOfMapping, compareValues); + if (targetIndex < 0) { + // if no exact match, closest is 2's complement of result + targetIndex = ~targetIndex; + } + const mapping = generatedMappings[targetIndex]; + if (mapping === undefined || !isSourceMappedPosition(mapping)) { + return loc; + } + return { fileName: sourceFileAbsolutePaths[mapping.sourceIndex], pos: mapping.sourcePosition }; // Closest pos } - - export const identitySourceMapConsumer: DocumentPositionMapper = { - getSourcePosition: identity, - getGeneratedPosition: identity - }; } +/* @internal */ +export const identitySourceMapConsumer: DocumentPositionMapper = { + getSourcePosition: identity, + getGeneratedPosition: identity +}; diff --git a/src/compiler/symbolWalker.ts b/src/compiler/symbolWalker.ts index b03ef31db6e11..ed9d63b6a2120 100644 --- a/src/compiler/symbolWalker.ts +++ b/src/compiler/symbolWalker.ts @@ -1,193 +1,163 @@ +import { Signature, Type, TypePredicate, ObjectType, ResolvedType, Symbol, Node, IndexKind, TypeParameter, EntityNameOrEntityNameExpression, Identifier, TypeReference, SymbolWalker, getOwnValues, clear, TypeFlags, ObjectFlags, MappedType, InterfaceType, UnionOrIntersectionType, IndexType, IndexedAccessType, forEach, getSymbolId, SyntaxKind, TypeQueryNode } from "./ts"; /** @internal */ -namespace ts { - export function createGetSymbolWalker( - getRestTypeOfSignature: (sig: Signature) => Type, - getTypePredicateOfSignature: (sig: Signature) => TypePredicate | undefined, - getReturnTypeOfSignature: (sig: Signature) => Type, - getBaseTypes: (type: Type) => Type[], - resolveStructuredTypeMembers: (type: ObjectType) => ResolvedType, - getTypeOfSymbol: (sym: Symbol) => Type, - getResolvedSymbol: (node: Node) => Symbol, - getIndexTypeOfStructuredType: (type: Type, kind: IndexKind) => Type | undefined, - getConstraintOfTypeParameter: (typeParameter: TypeParameter) => Type | undefined, - getFirstIdentifier: (node: EntityNameOrEntityNameExpression) => Identifier, - getTypeArguments: (type: TypeReference) => readonly Type[]) { - - return getSymbolWalker; - - function getSymbolWalker(accept: (symbol: Symbol) => boolean = () => true): SymbolWalker { - const visitedTypes: Type[] = []; // Sparse array from id to type - const visitedSymbols: Symbol[] = []; // Sparse array from id to symbol - - return { - walkType: type => { - try { - visitType(type); - return { visitedTypes: getOwnValues(visitedTypes), visitedSymbols: getOwnValues(visitedSymbols) }; - } - finally { - clear(visitedTypes); - clear(visitedSymbols); - } - }, - walkSymbol: symbol => { - try { - visitSymbol(symbol); - return { visitedTypes: getOwnValues(visitedTypes), visitedSymbols: getOwnValues(visitedSymbols) }; - } - finally { - clear(visitedTypes); - clear(visitedSymbols); - } - }, - }; - - function visitType(type: Type | undefined): void { - if (!type) { - return; - } - - if (visitedTypes[type.id]) { - return; - } - visitedTypes[type.id] = type; - - // Reuse visitSymbol to visit the type's symbol, - // but be sure to bail on recuring into the type if accept declines the symbol. - const shouldBail = visitSymbol(type.symbol); - if (shouldBail) return; - - // Visit the type's related types, if any - if (type.flags & TypeFlags.Object) { - const objectType = type as ObjectType; - const objectFlags = objectType.objectFlags; - if (objectFlags & ObjectFlags.Reference) { - visitTypeReference(type as TypeReference); - } - if (objectFlags & ObjectFlags.Mapped) { - visitMappedType(type as MappedType); - } - if (objectFlags & (ObjectFlags.Class | ObjectFlags.Interface)) { - visitInterfaceType(type as InterfaceType); - } - if (objectFlags & (ObjectFlags.Tuple | ObjectFlags.Anonymous)) { - visitObjectType(objectType); - } - } - if (type.flags & TypeFlags.TypeParameter) { - visitTypeParameter(type as TypeParameter); - } - if (type.flags & TypeFlags.UnionOrIntersection) { - visitUnionOrIntersectionType(type as UnionOrIntersectionType); - } - if (type.flags & TypeFlags.Index) { - visitIndexType(type as IndexType); - } - if (type.flags & TypeFlags.IndexedAccess) { - visitIndexedAccessType(type as IndexedAccessType); - } +export function createGetSymbolWalker(getRestTypeOfSignature: (sig: Signature) => Type, getTypePredicateOfSignature: (sig: Signature) => TypePredicate | undefined, getReturnTypeOfSignature: (sig: Signature) => Type, getBaseTypes: (type: Type) => Type[], resolveStructuredTypeMembers: (type: ObjectType) => ResolvedType, getTypeOfSymbol: (sym: Symbol) => Type, getResolvedSymbol: (node: Node) => Symbol, getIndexTypeOfStructuredType: (type: Type, kind: IndexKind) => Type | undefined, getConstraintOfTypeParameter: (typeParameter: TypeParameter) => Type | undefined, getFirstIdentifier: (node: EntityNameOrEntityNameExpression) => Identifier, getTypeArguments: (type: TypeReference) => readonly Type[]) { + return getSymbolWalker; + function getSymbolWalker(accept: (symbol: Symbol) => boolean = () => true): SymbolWalker { + const visitedTypes: Type[] = []; // Sparse array from id to type + const visitedSymbols: Symbol[] = []; // Sparse array from id to symbol + return { + walkType: type => { + try { + visitType(type); + return { visitedTypes: getOwnValues(visitedTypes), visitedSymbols: getOwnValues(visitedSymbols) }; + } + finally { + clear(visitedTypes); + clear(visitedSymbols); + } + }, + walkSymbol: symbol => { + try { + visitSymbol(symbol); + return { visitedTypes: getOwnValues(visitedTypes), visitedSymbols: getOwnValues(visitedSymbols) }; + } + finally { + clear(visitedTypes); + clear(visitedSymbols); + } + }, + }; + function visitType(type: Type | undefined): void { + if (!type) { + return; } - - function visitTypeReference(type: TypeReference): void { - visitType(type.target); - forEach(getTypeArguments(type), visitType); - } - - function visitTypeParameter(type: TypeParameter): void { - visitType(getConstraintOfTypeParameter(type)); - } - - function visitUnionOrIntersectionType(type: UnionOrIntersectionType): void { - forEach(type.types, visitType); - } - - function visitIndexType(type: IndexType): void { - visitType(type.type); - } - - function visitIndexedAccessType(type: IndexedAccessType): void { - visitType(type.objectType); - visitType(type.indexType); - visitType(type.constraint); - } - - function visitMappedType(type: MappedType): void { - visitType(type.typeParameter); - visitType(type.constraintType); - visitType(type.templateType); - visitType(type.modifiersType); - } - - function visitSignature(signature: Signature): void { - const typePredicate = getTypePredicateOfSignature(signature); - if (typePredicate) { - visitType(typePredicate.type); - } - forEach(signature.typeParameters, visitType); - - for (const parameter of signature.parameters) { - visitSymbol(parameter); - } - visitType(getRestTypeOfSignature(signature)); - visitType(getReturnTypeOfSignature(signature)); - } - - function visitInterfaceType(interfaceT: InterfaceType): void { - visitObjectType(interfaceT); - forEach(interfaceT.typeParameters, visitType); - forEach(getBaseTypes(interfaceT), visitType); - visitType(interfaceT.thisType); - } - - function visitObjectType(type: ObjectType): void { - const stringIndexType = getIndexTypeOfStructuredType(type, IndexKind.String); - visitType(stringIndexType); - const numberIndexType = getIndexTypeOfStructuredType(type, IndexKind.Number); - visitType(numberIndexType); - - // The two checks above *should* have already resolved the type (if needed), so this should be cached - const resolved = resolveStructuredTypeMembers(type); - for (const signature of resolved.callSignatures) { - visitSignature(signature); - } - for (const signature of resolved.constructSignatures) { - visitSignature(signature); - } - for (const p of resolved.properties) { - visitSymbol(p); - } + if (visitedTypes[type.id]) { + return; } - - function visitSymbol(symbol: Symbol | undefined): boolean { - if (!symbol) { - return false; - } - const symbolId = getSymbolId(symbol); - if (visitedSymbols[symbolId]) { - return false; - } - visitedSymbols[symbolId] = symbol; - if (!accept(symbol)) { - return true; - } - const t = getTypeOfSymbol(symbol); - visitType(t); // Should handle members on classes and such - if (symbol.exports) { - symbol.exports.forEach(visitSymbol); + visitedTypes[type.id] = type; + // Reuse visitSymbol to visit the type's symbol, + // but be sure to bail on recuring into the type if accept declines the symbol. + const shouldBail = visitSymbol(type.symbol); + if (shouldBail) + return; + // Visit the type's related types, if any + if (type.flags & TypeFlags.Object) { + const objectType = (type as ObjectType); + const objectFlags = objectType.objectFlags; + if (objectFlags & ObjectFlags.Reference) { + visitTypeReference((type as TypeReference)); + } + if (objectFlags & ObjectFlags.Mapped) { + visitMappedType((type as MappedType)); + } + if (objectFlags & (ObjectFlags.Class | ObjectFlags.Interface)) { + visitInterfaceType((type as InterfaceType)); + } + if (objectFlags & (ObjectFlags.Tuple | ObjectFlags.Anonymous)) { + visitObjectType(objectType); } - forEach(symbol.declarations, d => { - // Type queries are too far resolved when we just visit the symbol's type - // (their type resolved directly to the member deeply referenced) - // So to get the intervening symbols, we need to check if there's a type - // query node on any of the symbol's declarations and get symbols there - if ((d as any).type && (d as any).type.kind === SyntaxKind.TypeQuery) { - const query = (d as any).type as TypeQueryNode; - const entity = getResolvedSymbol(getFirstIdentifier(query.exprName)); - visitSymbol(entity); - } - }); + } + if (type.flags & TypeFlags.TypeParameter) { + visitTypeParameter((type as TypeParameter)); + } + if (type.flags & TypeFlags.UnionOrIntersection) { + visitUnionOrIntersectionType((type as UnionOrIntersectionType)); + } + if (type.flags & TypeFlags.Index) { + visitIndexType((type as IndexType)); + } + if (type.flags & TypeFlags.IndexedAccess) { + visitIndexedAccessType((type as IndexedAccessType)); + } + } + function visitTypeReference(type: TypeReference): void { + visitType(type.target); + forEach(getTypeArguments(type), visitType); + } + function visitTypeParameter(type: TypeParameter): void { + visitType(getConstraintOfTypeParameter(type)); + } + function visitUnionOrIntersectionType(type: UnionOrIntersectionType): void { + forEach(type.types, visitType); + } + function visitIndexType(type: IndexType): void { + visitType(type.type); + } + function visitIndexedAccessType(type: IndexedAccessType): void { + visitType(type.objectType); + visitType(type.indexType); + visitType(type.constraint); + } + function visitMappedType(type: MappedType): void { + visitType(type.typeParameter); + visitType(type.constraintType); + visitType(type.templateType); + visitType(type.modifiersType); + } + function visitSignature(signature: Signature): void { + const typePredicate = getTypePredicateOfSignature(signature); + if (typePredicate) { + visitType(typePredicate.type); + } + forEach(signature.typeParameters, visitType); + for (const parameter of signature.parameters) { + visitSymbol(parameter); + } + visitType(getRestTypeOfSignature(signature)); + visitType(getReturnTypeOfSignature(signature)); + } + function visitInterfaceType(interfaceT: InterfaceType): void { + visitObjectType(interfaceT); + forEach(interfaceT.typeParameters, visitType); + forEach(getBaseTypes(interfaceT), visitType); + visitType(interfaceT.thisType); + } + function visitObjectType(type: ObjectType): void { + const stringIndexType = getIndexTypeOfStructuredType(type, IndexKind.String); + visitType(stringIndexType); + const numberIndexType = getIndexTypeOfStructuredType(type, IndexKind.Number); + visitType(numberIndexType); + // The two checks above *should* have already resolved the type (if needed), so this should be cached + const resolved = resolveStructuredTypeMembers(type); + for (const signature of resolved.callSignatures) { + visitSignature(signature); + } + for (const signature of resolved.constructSignatures) { + visitSignature(signature); + } + for (const p of resolved.properties) { + visitSymbol(p); + } + } + function visitSymbol(symbol: Symbol | undefined): boolean { + if (!symbol) { return false; } + const symbolId = getSymbolId(symbol); + if (visitedSymbols[symbolId]) { + return false; + } + visitedSymbols[symbolId] = symbol; + if (!accept(symbol)) { + return true; + } + const t = getTypeOfSymbol(symbol); + visitType(t); // Should handle members on classes and such + if (symbol.exports) { + symbol.exports.forEach(visitSymbol); + } + forEach(symbol.declarations, d => { + // Type queries are too far resolved when we just visit the symbol's type + // (their type resolved directly to the member deeply referenced) + // So to get the intervening symbols, we need to check if there's a type + // query node on any of the symbol's declarations and get symbols there + if ((d as any).type && (d as any).type.kind === SyntaxKind.TypeQuery) { + const query = ((d as any).type as TypeQueryNode); + const entity = getResolvedSymbol(getFirstIdentifier(query.exprName)); + visitSymbol(entity); + } + }); + return false; } } -} \ No newline at end of file +} diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 514e4333af1e6..9ce79c1da18dc 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -1,1791 +1,1561 @@ +import { WatchOptions, unorderedRemoveItem, Debug, createMultiMap, createMap, createGetCanonicalFileName, getDirectoryPath, isString, getNormalizedAbsolutePath, forEach, closeFileWatcherOf, noop, getStringComparer, Path, emptyArray, closeFileWatcher, startsWith, directorySeparator, timestamp, enumerateInsertsAndDeletes, mapDefined, normalizePath, Comparison, some, stringContains, combinePaths, WatchFileKind, getFallbackOptions, PollingWatchKind, WatchDirectoryKind, writeFileEnsuringDirectories, RequireResult, resolveJSModule, normalizeSlashes, getRootLength, containsPath, getRelativePathToDirectoryOrUrl, perfLogger, FileSystemEntries, emptyFileSystemEntries, matchFiles, filter, getFileMatcherPatterns, AssertionLevel } from "./ts"; +import * as ts from "./ts"; declare function setTimeout(handler: (...args: any[]) => void, timeout: number): any; declare function clearTimeout(handle: any): void; - -namespace ts { - /** - * djb2 hashing algorithm - * http://www.cse.yorku.ca/~oz/hash.html - */ - /* @internal */ - export function generateDjb2Hash(data: string): string { - let acc = 5381; - for (let i = 0; i < data.length; i++) { - acc = ((acc << 5) + acc) + data.charCodeAt(i); - } - return acc.toString(); +/** + * djb2 hashing algorithm + * http://www.cse.yorku.ca/~oz/hash.html + */ +/* @internal */ +export function generateDjb2Hash(data: string): string { + let acc = 5381; + for (let i = 0; i < data.length; i++) { + acc = ((acc << 5) + acc) + data.charCodeAt(i); } - - /** - * Set a high stack trace limit to provide more information in case of an error. - * Called for command-line and server use cases. - * Not called if TypeScript is used as a library. - */ - /* @internal */ - export function setStackTraceLimit() { - if ((Error as any).stackTraceLimit < 100) { // Also tests that we won't set the property if it doesn't exist. - (Error as any).stackTraceLimit = 100; + return acc.toString(); +} +/** + * Set a high stack trace limit to provide more information in case of an error. + * Called for command-line and server use cases. + * Not called if TypeScript is used as a library. + */ +/* @internal */ +export function setStackTraceLimit() { + if ((Error as any).stackTraceLimit < 100) { // Also tests that we won't set the property if it doesn't exist. + (Error as any).stackTraceLimit = 100; + } +} +export enum FileWatcherEventKind { + Created, + Changed, + Deleted +} +export type FileWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind) => void; +export type DirectoryWatcherCallback = (fileName: string) => void; +/*@internal*/ +export interface WatchedFile { + readonly fileName: string; + readonly callback: FileWatcherCallback; + mtime: Date; +} +/* @internal */ +export enum PollingInterval { + High = 2000, + Medium = 500, + Low = 250 +} +/* @internal */ +export type HostWatchFile = (fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined) => FileWatcher; +/* @internal */ +export type HostWatchDirectory = (fileName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined) => FileWatcher; +/* @internal */ +export const missingFileModifiedTime = new Date(0); // Any subsequent modification will occur after this time +interface Levels { + Low: number; + Medium: number; + High: number; +} +function createPollingIntervalBasedLevels(levels: Levels) { + return { + [PollingInterval.Low]: levels.Low, + [PollingInterval.Medium]: levels.Medium, + [PollingInterval.High]: levels.High + }; +} +const defaultChunkLevels: Levels = { Low: 32, Medium: 64, High: 256 }; +let pollingChunkSize = createPollingIntervalBasedLevels(defaultChunkLevels); +/* @internal */ +export let unchangedPollThresholds = createPollingIntervalBasedLevels(defaultChunkLevels); +/* @internal */ +export function setCustomPollingValues(system: System) { + if (!system.getEnvironmentVariable) { + return; + } + const pollingIntervalChanged = setCustomLevels("TSC_WATCH_POLLINGINTERVAL", PollingInterval); + pollingChunkSize = getCustomPollingBasedLevels("TSC_WATCH_POLLINGCHUNKSIZE", defaultChunkLevels) || pollingChunkSize; + unchangedPollThresholds = getCustomPollingBasedLevels("TSC_WATCH_UNCHANGEDPOLLTHRESHOLDS", defaultChunkLevels) || unchangedPollThresholds; + function getLevel(envVar: string, level: keyof Levels) { + return system.getEnvironmentVariable(`${envVar}_${level.toUpperCase()}`); + } + function getCustomLevels(baseVariable: string) { + let customLevels: Partial | undefined; + setCustomLevel("Low"); + setCustomLevel("Medium"); + setCustomLevel("High"); + return customLevels; + function setCustomLevel(level: keyof Levels) { + const customLevel = getLevel(baseVariable, level); + if (customLevel) { + (customLevels || (customLevels = {}))[level] = Number(customLevel); + } } } - - export enum FileWatcherEventKind { - Created, - Changed, - Deleted + function setCustomLevels(baseVariable: string, levels: Levels) { + const customLevels = getCustomLevels(baseVariable); + if (customLevels) { + setLevel("Low"); + setLevel("Medium"); + setLevel("High"); + return true; + } + return false; + function setLevel(level: keyof Levels) { + levels[level] = customLevels![level] || levels[level]; + } } - - export type FileWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind) => void; - export type DirectoryWatcherCallback = (fileName: string) => void; - /*@internal*/ - export interface WatchedFile { - readonly fileName: string; - readonly callback: FileWatcherCallback; - mtime: Date; + function getCustomPollingBasedLevels(baseVariable: string, defaultLevels: Levels) { + const customLevels = getCustomLevels(baseVariable); + return (pollingIntervalChanged || customLevels) && + createPollingIntervalBasedLevels(customLevels ? { ...defaultLevels, ...customLevels } : defaultLevels); } - - /* @internal */ - export enum PollingInterval { - High = 2000, - Medium = 500, - Low = 250 +} +/* @internal */ +export function createDynamicPriorityPollingWatchFile(host: { + getModifiedTime: NonNullable; + setTimeout: NonNullable; +}): HostWatchFile { + interface WatchedFile extends ts.WatchedFile { + isClosed?: boolean; + unchangedPolls: number; } - - /* @internal */ - export type HostWatchFile = (fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined) => FileWatcher; - /* @internal */ - export type HostWatchDirectory = (fileName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined) => FileWatcher; - - /* @internal */ - export const missingFileModifiedTime = new Date(0); // Any subsequent modification will occur after this time - - interface Levels { - Low: number; - Medium: number; - High: number; + interface PollingIntervalQueue extends Array { + pollingInterval: PollingInterval; + pollIndex: number; + pollScheduled: boolean; } - - function createPollingIntervalBasedLevels(levels: Levels) { + const watchedFiles: WatchedFile[] = []; + const changedFilesInLastPoll: WatchedFile[] = []; + const lowPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Low); + const mediumPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Medium); + const highPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.High); + return watchFile; + function watchFile(fileName: string, callback: FileWatcherCallback, defaultPollingInterval: PollingInterval): FileWatcher { + const file: WatchedFile = { + fileName, + callback, + unchangedPolls: 0, + mtime: getModifiedTime(fileName) + }; + watchedFiles.push(file); + addToPollingIntervalQueue(file, defaultPollingInterval); return { - [PollingInterval.Low]: levels.Low, - [PollingInterval.Medium]: levels.Medium, - [PollingInterval.High]: levels.High + close: () => { + file.isClosed = true; + // Remove from watchedFiles + unorderedRemoveItem(watchedFiles, file); + // Do not update polling interval queue since that will happen as part of polling + } }; } - - const defaultChunkLevels: Levels = { Low: 32, Medium: 64, High: 256 }; - let pollingChunkSize = createPollingIntervalBasedLevels(defaultChunkLevels); - /* @internal */ - export let unchangedPollThresholds = createPollingIntervalBasedLevels(defaultChunkLevels); - - /* @internal */ - export function setCustomPollingValues(system: System) { - if (!system.getEnvironmentVariable) { - return; - } - const pollingIntervalChanged = setCustomLevels("TSC_WATCH_POLLINGINTERVAL", PollingInterval); - pollingChunkSize = getCustomPollingBasedLevels("TSC_WATCH_POLLINGCHUNKSIZE", defaultChunkLevels) || pollingChunkSize; - unchangedPollThresholds = getCustomPollingBasedLevels("TSC_WATCH_UNCHANGEDPOLLTHRESHOLDS", defaultChunkLevels) || unchangedPollThresholds; - - function getLevel(envVar: string, level: keyof Levels) { - return system.getEnvironmentVariable(`${envVar}_${level.toUpperCase()}`); - } - - function getCustomLevels(baseVariable: string) { - let customLevels: Partial | undefined; - setCustomLevel("Low"); - setCustomLevel("Medium"); - setCustomLevel("High"); - return customLevels; - - function setCustomLevel(level: keyof Levels) { - const customLevel = getLevel(baseVariable, level); - if (customLevel) { - (customLevels || (customLevels = {}))[level] = Number(customLevel); - } - } - } - - function setCustomLevels(baseVariable: string, levels: Levels) { - const customLevels = getCustomLevels(baseVariable); - if (customLevels) { - setLevel("Low"); - setLevel("Medium"); - setLevel("High"); - return true; - } - return false; - - function setLevel(level: keyof Levels) { - levels[level] = customLevels![level] || levels[level]; - } - } - - function getCustomPollingBasedLevels(baseVariable: string, defaultLevels: Levels) { - const customLevels = getCustomLevels(baseVariable); - return (pollingIntervalChanged || customLevels) && - createPollingIntervalBasedLevels(customLevels ? { ...defaultLevels, ...customLevels } : defaultLevels); - } + function createPollingIntervalQueue(pollingInterval: PollingInterval): PollingIntervalQueue { + const queue = [] as WatchedFile[] as PollingIntervalQueue; + queue.pollingInterval = pollingInterval; + queue.pollIndex = 0; + queue.pollScheduled = false; + return queue; } - - /* @internal */ - export function createDynamicPriorityPollingWatchFile(host: { - getModifiedTime: NonNullable; - setTimeout: NonNullable; - }): HostWatchFile { - interface WatchedFile extends ts.WatchedFile { - isClosed?: boolean; - unchangedPolls: number; + function pollPollingIntervalQueue(queue: PollingIntervalQueue) { + queue.pollIndex = pollQueue(queue, queue.pollingInterval, queue.pollIndex, pollingChunkSize[queue.pollingInterval]); + // Set the next polling index and timeout + if (queue.length) { + scheduleNextPoll(queue.pollingInterval); } - - interface PollingIntervalQueue extends Array { - pollingInterval: PollingInterval; - pollIndex: number; - pollScheduled: boolean; - } - - const watchedFiles: WatchedFile[] = []; - const changedFilesInLastPoll: WatchedFile[] = []; - const lowPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Low); - const mediumPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Medium); - const highPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.High); - return watchFile; - - function watchFile(fileName: string, callback: FileWatcherCallback, defaultPollingInterval: PollingInterval): FileWatcher { - const file: WatchedFile = { - fileName, - callback, - unchangedPolls: 0, - mtime: getModifiedTime(fileName) - }; - watchedFiles.push(file); - - addToPollingIntervalQueue(file, defaultPollingInterval); - return { - close: () => { - file.isClosed = true; - // Remove from watchedFiles - unorderedRemoveItem(watchedFiles, file); - // Do not update polling interval queue since that will happen as part of polling - } - }; - } - - function createPollingIntervalQueue(pollingInterval: PollingInterval): PollingIntervalQueue { - const queue = [] as WatchedFile[] as PollingIntervalQueue; - queue.pollingInterval = pollingInterval; - queue.pollIndex = 0; + else { + Debug.assert(queue.pollIndex === 0); queue.pollScheduled = false; - return queue; } - - function pollPollingIntervalQueue(queue: PollingIntervalQueue) { - queue.pollIndex = pollQueue(queue, queue.pollingInterval, queue.pollIndex, pollingChunkSize[queue.pollingInterval]); - // Set the next polling index and timeout - if (queue.length) { - scheduleNextPoll(queue.pollingInterval); + } + function pollLowPollingIntervalQueue(queue: PollingIntervalQueue) { + // Always poll complete list of changedFilesInLastPoll + pollQueue(changedFilesInLastPoll, PollingInterval.Low, /*pollIndex*/ 0, changedFilesInLastPoll.length); + // Finally do the actual polling of the queue + pollPollingIntervalQueue(queue); + // Schedule poll if there are files in changedFilesInLastPoll but no files in the actual queue + // as pollPollingIntervalQueue wont schedule for next poll + if (!queue.pollScheduled && changedFilesInLastPoll.length) { + scheduleNextPoll(PollingInterval.Low); + } + } + function pollQueue(queue: (WatchedFile | undefined)[], pollingInterval: PollingInterval, pollIndex: number, chunkSize: number) { + // Max visit would be all elements of the queue + let needsVisit = queue.length; + let definedValueCopyToIndex = pollIndex; + for (let polled = 0; polled < chunkSize && needsVisit > 0; nextPollIndex(), needsVisit--) { + const watchedFile = queue[pollIndex]; + if (!watchedFile) { + continue; } - else { - Debug.assert(queue.pollIndex === 0); - queue.pollScheduled = false; + else if (watchedFile.isClosed) { + queue[pollIndex] = undefined; + continue; } - } - - function pollLowPollingIntervalQueue(queue: PollingIntervalQueue) { - // Always poll complete list of changedFilesInLastPoll - pollQueue(changedFilesInLastPoll, PollingInterval.Low, /*pollIndex*/ 0, changedFilesInLastPoll.length); - - // Finally do the actual polling of the queue - pollPollingIntervalQueue(queue); - // Schedule poll if there are files in changedFilesInLastPoll but no files in the actual queue - // as pollPollingIntervalQueue wont schedule for next poll - if (!queue.pollScheduled && changedFilesInLastPoll.length) { - scheduleNextPoll(PollingInterval.Low); + polled++; + const fileChanged = onWatchedFileStat(watchedFile, getModifiedTime(watchedFile.fileName)); + if (watchedFile.isClosed) { + // Closed watcher as part of callback + queue[pollIndex] = undefined; } - } - - function pollQueue(queue: (WatchedFile | undefined)[], pollingInterval: PollingInterval, pollIndex: number, chunkSize: number) { - // Max visit would be all elements of the queue - let needsVisit = queue.length; - let definedValueCopyToIndex = pollIndex; - for (let polled = 0; polled < chunkSize && needsVisit > 0; nextPollIndex(), needsVisit--) { - const watchedFile = queue[pollIndex]; - if (!watchedFile) { - continue; - } - else if (watchedFile.isClosed) { - queue[pollIndex] = undefined; - continue; - } - - polled++; - const fileChanged = onWatchedFileStat(watchedFile, getModifiedTime(watchedFile.fileName)); - if (watchedFile.isClosed) { - // Closed watcher as part of callback - queue[pollIndex] = undefined; - } - else if (fileChanged) { - watchedFile.unchangedPolls = 0; - // Changed files go to changedFilesInLastPoll queue - if (queue !== changedFilesInLastPoll) { - queue[pollIndex] = undefined; - addChangedFileToLowPollingIntervalQueue(watchedFile); - } - } - else if (watchedFile.unchangedPolls !== unchangedPollThresholds[pollingInterval]) { - watchedFile.unchangedPolls++; - } - else if (queue === changedFilesInLastPoll) { - // Restart unchangedPollCount for unchanged file and move to low polling interval queue - watchedFile.unchangedPolls = 1; - queue[pollIndex] = undefined; - addToPollingIntervalQueue(watchedFile, PollingInterval.Low); - } - else if (pollingInterval !== PollingInterval.High) { - watchedFile.unchangedPolls++; + else if (fileChanged) { + watchedFile.unchangedPolls = 0; + // Changed files go to changedFilesInLastPoll queue + if (queue !== changedFilesInLastPoll) { queue[pollIndex] = undefined; - addToPollingIntervalQueue(watchedFile, pollingInterval === PollingInterval.Low ? PollingInterval.Medium : PollingInterval.High); - } - - if (queue[pollIndex]) { - // Copy this file to the non hole location - if (definedValueCopyToIndex < pollIndex) { - queue[definedValueCopyToIndex] = watchedFile; - queue[pollIndex] = undefined; - } - definedValueCopyToIndex++; + addChangedFileToLowPollingIntervalQueue(watchedFile); } } - - // Return next poll index - return pollIndex; - - function nextPollIndex() { - pollIndex++; - if (pollIndex === queue.length) { - if (definedValueCopyToIndex < pollIndex) { - // There are holes from nextDefinedValueIndex to end of queue, change queue size - queue.length = definedValueCopyToIndex; - } - pollIndex = 0; - definedValueCopyToIndex = 0; - } + else if (watchedFile.unchangedPolls !== unchangedPollThresholds[pollingInterval]) { + watchedFile.unchangedPolls++; } - } - - function pollingIntervalQueue(pollingInterval: PollingInterval) { - switch (pollingInterval) { - case PollingInterval.Low: - return lowPollingIntervalQueue; - case PollingInterval.Medium: - return mediumPollingIntervalQueue; - case PollingInterval.High: - return highPollingIntervalQueue; + else if (queue === changedFilesInLastPoll) { + // Restart unchangedPollCount for unchanged file and move to low polling interval queue + watchedFile.unchangedPolls = 1; + queue[pollIndex] = undefined; + addToPollingIntervalQueue(watchedFile, PollingInterval.Low); } - } - - function addToPollingIntervalQueue(file: WatchedFile, pollingInterval: PollingInterval) { - pollingIntervalQueue(pollingInterval).push(file); - scheduleNextPollIfNotAlreadyScheduled(pollingInterval); - } - - function addChangedFileToLowPollingIntervalQueue(file: WatchedFile) { - changedFilesInLastPoll.push(file); - scheduleNextPollIfNotAlreadyScheduled(PollingInterval.Low); - } - - function scheduleNextPollIfNotAlreadyScheduled(pollingInterval: PollingInterval) { - if (!pollingIntervalQueue(pollingInterval).pollScheduled) { - scheduleNextPoll(pollingInterval); + else if (pollingInterval !== PollingInterval.High) { + watchedFile.unchangedPolls++; + queue[pollIndex] = undefined; + addToPollingIntervalQueue(watchedFile, pollingInterval === PollingInterval.Low ? PollingInterval.Medium : PollingInterval.High); + } + if (queue[pollIndex]) { + // Copy this file to the non hole location + if (definedValueCopyToIndex < pollIndex) { + queue[definedValueCopyToIndex] = watchedFile; + queue[pollIndex] = undefined; + } + definedValueCopyToIndex++; } } - - function scheduleNextPoll(pollingInterval: PollingInterval) { - pollingIntervalQueue(pollingInterval).pollScheduled = host.setTimeout(pollingInterval === PollingInterval.Low ? pollLowPollingIntervalQueue : pollPollingIntervalQueue, pollingInterval, pollingIntervalQueue(pollingInterval)); - } - - function getModifiedTime(fileName: string) { - return host.getModifiedTime(fileName) || missingFileModifiedTime; - } - } - - function createUseFsEventsOnParentDirectoryWatchFile(fsWatch: FsWatch, useCaseSensitiveFileNames: boolean): HostWatchFile { - // One file can have multiple watchers - const fileWatcherCallbacks = createMultiMap(); - const dirWatchers = createMap(); - const toCanonicalName = createGetCanonicalFileName(useCaseSensitiveFileNames); - return nonPollingWatchFile; - - function nonPollingWatchFile(fileName: string, callback: FileWatcherCallback, _pollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined): FileWatcher { - const filePath = toCanonicalName(fileName); - fileWatcherCallbacks.add(filePath, callback); - const dirPath = getDirectoryPath(filePath) || "."; - const watcher = dirWatchers.get(dirPath) || - createDirectoryWatcher(getDirectoryPath(fileName) || ".", dirPath, fallbackOptions); - watcher.referenceCount++; - return { - close: () => { - if (watcher.referenceCount === 1) { - watcher.close(); - dirWatchers.delete(dirPath); - } - else { - watcher.referenceCount--; - } - fileWatcherCallbacks.remove(filePath, callback); + // Return next poll index + return pollIndex; + function nextPollIndex() { + pollIndex++; + if (pollIndex === queue.length) { + if (definedValueCopyToIndex < pollIndex) { + // There are holes from nextDefinedValueIndex to end of queue, change queue size + queue.length = definedValueCopyToIndex; } - }; + pollIndex = 0; + definedValueCopyToIndex = 0; + } } - - function createDirectoryWatcher(dirName: string, dirPath: string, fallbackOptions: WatchOptions | undefined) { - const watcher = fsWatch( - dirName, - FileSystemEntryKind.Directory, - (_eventName: string, relativeFileName) => { - // When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined" - if (!isString(relativeFileName)) { return; } - const fileName = getNormalizedAbsolutePath(relativeFileName, dirName); - // Some applications save a working file via rename operations - const callbacks = fileName && fileWatcherCallbacks.get(toCanonicalName(fileName)); - if (callbacks) { - for (const fileCallback of callbacks) { - fileCallback(fileName, FileWatcherEventKind.Changed); - } - } - }, - /*recursive*/ false, - PollingInterval.Medium, - fallbackOptions - ) as DirectoryWatcher; - watcher.referenceCount = 0; - dirWatchers.set(dirPath, watcher); - return watcher; + } + function pollingIntervalQueue(pollingInterval: PollingInterval) { + switch (pollingInterval) { + case PollingInterval.Low: + return lowPollingIntervalQueue; + case PollingInterval.Medium: + return mediumPollingIntervalQueue; + case PollingInterval.High: + return highPollingIntervalQueue; } } - - /* @internal */ - export function createSingleFileWatcherPerName( - watchFile: HostWatchFile, - useCaseSensitiveFileNames: boolean - ): HostWatchFile { - interface SingleFileWatcher { - watcher: FileWatcher; - refCount: number; + function addToPollingIntervalQueue(file: WatchedFile, pollingInterval: PollingInterval) { + pollingIntervalQueue(pollingInterval).push(file); + scheduleNextPollIfNotAlreadyScheduled(pollingInterval); + } + function addChangedFileToLowPollingIntervalQueue(file: WatchedFile) { + changedFilesInLastPoll.push(file); + scheduleNextPollIfNotAlreadyScheduled(PollingInterval.Low); + } + function scheduleNextPollIfNotAlreadyScheduled(pollingInterval: PollingInterval) { + if (!pollingIntervalQueue(pollingInterval).pollScheduled) { + scheduleNextPoll(pollingInterval); } - const cache = createMap(); - const callbacksCache = createMultiMap(); - const toCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); - - return (fileName, callback, pollingInterval, options) => { - const path = toCanonicalFileName(fileName); - const existing = cache.get(path); - if (existing) { - existing.refCount++; + } + function scheduleNextPoll(pollingInterval: PollingInterval) { + pollingIntervalQueue(pollingInterval).pollScheduled = host.setTimeout(pollingInterval === PollingInterval.Low ? pollLowPollingIntervalQueue : pollPollingIntervalQueue, pollingInterval, pollingIntervalQueue(pollingInterval)); + } + function getModifiedTime(fileName: string) { + return host.getModifiedTime(fileName) || missingFileModifiedTime; + } +} +function createUseFsEventsOnParentDirectoryWatchFile(fsWatch: FsWatch, useCaseSensitiveFileNames: boolean): HostWatchFile { + // One file can have multiple watchers + const fileWatcherCallbacks = createMultiMap(); + const dirWatchers = createMap(); + const toCanonicalName = createGetCanonicalFileName(useCaseSensitiveFileNames); + return nonPollingWatchFile; + function nonPollingWatchFile(fileName: string, callback: FileWatcherCallback, _pollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined): FileWatcher { + const filePath = toCanonicalName(fileName); + fileWatcherCallbacks.add(filePath, callback); + const dirPath = getDirectoryPath(filePath) || "."; + const watcher = dirWatchers.get(dirPath) || + createDirectoryWatcher(getDirectoryPath(fileName) || ".", dirPath, fallbackOptions); + watcher.referenceCount++; + return { + close: () => { + if (watcher.referenceCount === 1) { + watcher.close(); + dirWatchers.delete(dirPath); + } + else { + watcher.referenceCount--; + } + fileWatcherCallbacks.remove(filePath, callback); } - else { - cache.set(path, { - watcher: watchFile( - fileName, - (fileName, eventKind) => forEach( - callbacksCache.get(path), - cb => cb(fileName, eventKind) - ), - pollingInterval, - options - ), - refCount: 1 - }); + }; + } + function createDirectoryWatcher(dirName: string, dirPath: string, fallbackOptions: WatchOptions | undefined) { + const watcher = (fsWatch(dirName, FileSystemEntryKind.Directory, (_eventName: string, relativeFileName) => { + // When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined" + if (!isString(relativeFileName)) { + return; } - callbacksCache.add(path, callback); - - return { - close: () => { - const watcher = Debug.assertDefined(cache.get(path)); - callbacksCache.remove(path, callback); - watcher.refCount--; - if (watcher.refCount) return; - cache.delete(path); - closeFileWatcherOf(watcher); + const fileName = getNormalizedAbsolutePath(relativeFileName, dirName); + // Some applications save a working file via rename operations + const callbacks = fileName && fileWatcherCallbacks.get(toCanonicalName(fileName)); + if (callbacks) { + for (const fileCallback of callbacks) { + fileCallback(fileName, FileWatcherEventKind.Changed); } - }; - }; + } + }, + /*recursive*/ false, PollingInterval.Medium, fallbackOptions) as DirectoryWatcher); + watcher.referenceCount = 0; + dirWatchers.set(dirPath, watcher); + return watcher; } - - /** - * Returns true if file status changed - */ - /*@internal*/ - export function onWatchedFileStat(watchedFile: WatchedFile, modifiedTime: Date): boolean { - const oldTime = watchedFile.mtime.getTime(); - const newTime = modifiedTime.getTime(); - if (oldTime !== newTime) { - watchedFile.mtime = modifiedTime; - watchedFile.callback(watchedFile.fileName, getFileWatcherEventKind(oldTime, newTime)); - return true; - } - - return false; +} +/* @internal */ +export function createSingleFileWatcherPerName(watchFile: HostWatchFile, useCaseSensitiveFileNames: boolean): HostWatchFile { + interface SingleFileWatcher { + watcher: FileWatcher; + refCount: number; } - - /*@internal*/ - export function getFileWatcherEventKind(oldTime: number, newTime: number) { - return oldTime === 0 - ? FileWatcherEventKind.Created - : newTime === 0 - ? FileWatcherEventKind.Deleted - : FileWatcherEventKind.Changed; + const cache = createMap(); + const callbacksCache = createMultiMap(); + const toCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); + return (fileName, callback, pollingInterval, options) => { + const path = toCanonicalFileName(fileName); + const existing = cache.get(path); + if (existing) { + existing.refCount++; + } + else { + cache.set(path, { + watcher: watchFile(fileName, (fileName, eventKind) => forEach(callbacksCache.get(path), cb => cb(fileName, eventKind)), pollingInterval, options), + refCount: 1 + }); + } + callbacksCache.add(path, callback); + return { + close: () => { + const watcher = Debug.assertDefined(cache.get(path)); + callbacksCache.remove(path, callback); + watcher.refCount--; + if (watcher.refCount) + return; + cache.delete(path); + closeFileWatcherOf(watcher); + } + }; + }; +} +/** + * Returns true if file status changed + */ +/*@internal*/ +export function onWatchedFileStat(watchedFile: WatchedFile, modifiedTime: Date): boolean { + const oldTime = watchedFile.mtime.getTime(); + const newTime = modifiedTime.getTime(); + if (oldTime !== newTime) { + watchedFile.mtime = modifiedTime; + watchedFile.callback(watchedFile.fileName, getFileWatcherEventKind(oldTime, newTime)); + return true; } - - /*@internal*/ - export const ignoredPaths = ["/node_modules/.", "/.git", "/.#"]; - - /*@internal*/ - export let sysLog: (s: string) => void = noop; // eslint-disable-line prefer-const - - /*@internal*/ - export function setSysLog(logger: typeof sysLog) { - sysLog = logger; + return false; +} +/*@internal*/ +export function getFileWatcherEventKind(oldTime: number, newTime: number) { + return oldTime === 0 + ? FileWatcherEventKind.Created + : newTime === 0 + ? FileWatcherEventKind.Deleted + : FileWatcherEventKind.Changed; +} +/*@internal*/ +export const ignoredPaths = ["/node_modules/.", "/.git", "/.#"]; +/*@internal*/ +export let sysLog: (s: string) => void = noop; // eslint-disable-line prefer-const +/*@internal*/ +export function setSysLog(logger: typeof sysLog) { + sysLog = logger; +} +/*@internal*/ +export interface RecursiveDirectoryWatcherHost { + watchDirectory: HostWatchDirectory; + useCaseSensitiveFileNames: boolean; + getAccessibleSortedChildDirectories(path: string): readonly string[]; + directoryExists(dir: string): boolean; + realpath(s: string): string; + setTimeout: NonNullable; + clearTimeout: NonNullable; +} +/** + * Watch the directory recursively using host provided method to watch child directories + * that means if this is recursive watcher, watch the children directories as well + * (eg on OS that dont support recursive watch using fs.watch use fs.watchFile) + */ +/*@internal*/ +export function createDirectoryWatcherSupportingRecursive(host: RecursiveDirectoryWatcherHost): HostWatchDirectory { + interface ChildDirectoryWatcher extends FileWatcher { + dirName: string; } - - /*@internal*/ - export interface RecursiveDirectoryWatcherHost { - watchDirectory: HostWatchDirectory; - useCaseSensitiveFileNames: boolean; - getAccessibleSortedChildDirectories(path: string): readonly string[]; - directoryExists(dir: string): boolean; - realpath(s: string): string; - setTimeout: NonNullable; - clearTimeout: NonNullable; + type ChildWatches = readonly ChildDirectoryWatcher[]; + interface HostDirectoryWatcher { + watcher: FileWatcher; + childWatches: ChildWatches; + refCount: number; } - + const cache = createMap(); + const callbackCache = createMultiMap<{ + dirName: string; + callback: DirectoryWatcherCallback; + }>(); + const cacheToUpdateChildWatches = createMap<{ + dirName: string; + options: WatchOptions | undefined; + }>(); + let timerToUpdateChildWatches: any; + const filePathComparer = getStringComparer(!host.useCaseSensitiveFileNames); + const toCanonicalFilePath = createGetCanonicalFileName(host.useCaseSensitiveFileNames); + return (dirName, callback, recursive, options) => recursive ? + createDirectoryWatcher(dirName, options, callback) : + host.watchDirectory(dirName, callback, recursive, options); /** - * Watch the directory recursively using host provided method to watch child directories - * that means if this is recursive watcher, watch the children directories as well - * (eg on OS that dont support recursive watch using fs.watch use fs.watchFile) + * Create the directory watcher for the dirPath. */ - /*@internal*/ - export function createDirectoryWatcherSupportingRecursive(host: RecursiveDirectoryWatcherHost): HostWatchDirectory { - interface ChildDirectoryWatcher extends FileWatcher { - dirName: string; - } - type ChildWatches = readonly ChildDirectoryWatcher[]; - interface HostDirectoryWatcher { - watcher: FileWatcher; - childWatches: ChildWatches; - refCount: number; + function createDirectoryWatcher(dirName: string, options: WatchOptions | undefined, callback?: DirectoryWatcherCallback): ChildDirectoryWatcher { + const dirPath = (toCanonicalFilePath(dirName) as Path); + let directoryWatcher = cache.get(dirPath); + if (directoryWatcher) { + directoryWatcher.refCount++; } - - const cache = createMap(); - const callbackCache = createMultiMap<{ dirName: string; callback: DirectoryWatcherCallback; }>(); - const cacheToUpdateChildWatches = createMap<{ dirName: string; options: WatchOptions | undefined; }>(); - let timerToUpdateChildWatches: any; - - const filePathComparer = getStringComparer(!host.useCaseSensitiveFileNames); - const toCanonicalFilePath = createGetCanonicalFileName(host.useCaseSensitiveFileNames); - - return (dirName, callback, recursive, options) => recursive ? - createDirectoryWatcher(dirName, options, callback) : - host.watchDirectory(dirName, callback, recursive, options); - - /** - * Create the directory watcher for the dirPath. - */ - function createDirectoryWatcher(dirName: string, options: WatchOptions | undefined, callback?: DirectoryWatcherCallback): ChildDirectoryWatcher { - const dirPath = toCanonicalFilePath(dirName) as Path; - let directoryWatcher = cache.get(dirPath); - if (directoryWatcher) { - directoryWatcher.refCount++; - } - else { - directoryWatcher = { - watcher: host.watchDirectory(dirName, fileName => { - if (isIgnoredPath(fileName)) return; - - if (options?.synchronousWatchDirectory) { - // Call the actual callback - invokeCallbacks(dirPath, fileName); - - // Iterate through existing children and update the watches if needed - updateChildWatches(dirName, dirPath, options); - } - else { - nonSyncUpdateChildWatches(dirName, dirPath, fileName, options); - } - }, /*recursive*/ false, options), - refCount: 1, - childWatches: emptyArray - }; - cache.set(dirPath, directoryWatcher); - updateChildWatches(dirName, dirPath, options); - } - - const callbackToAdd = callback && { dirName, callback }; - if (callbackToAdd) { - callbackCache.add(dirPath, callbackToAdd); - } - - return { - dirName, - close: () => { - const directoryWatcher = Debug.assertDefined(cache.get(dirPath)); - if (callbackToAdd) callbackCache.remove(dirPath, callbackToAdd); - directoryWatcher.refCount--; - - if (directoryWatcher.refCount) return; - - cache.delete(dirPath); - closeFileWatcherOf(directoryWatcher); - directoryWatcher.childWatches.forEach(closeFileWatcher); - } - }; - } - - function invokeCallbacks(dirPath: Path, fileNameOrInvokeMap: string | Map) { - let fileName: string | undefined; - let invokeMap: Map | undefined; - if (isString(fileNameOrInvokeMap)) { - fileName = fileNameOrInvokeMap; - } - else { - invokeMap = fileNameOrInvokeMap; - } - // Call the actual callback - callbackCache.forEach((callbacks, rootDirName) => { - if (invokeMap && invokeMap.has(rootDirName)) return; - if (rootDirName === dirPath || (startsWith(dirPath, rootDirName) && dirPath[rootDirName.length] === directorySeparator)) { - if (invokeMap) { - invokeMap.set(rootDirName, true); + else { + directoryWatcher = { + watcher: host.watchDirectory(dirName, fileName => { + if (isIgnoredPath(fileName)) + return; + if (options?.synchronousWatchDirectory) { + // Call the actual callback + invokeCallbacks(dirPath, fileName); + // Iterate through existing children and update the watches if needed + updateChildWatches(dirName, dirPath, options); } else { - callbacks.forEach(({ callback }) => callback(fileName!)); + nonSyncUpdateChildWatches(dirName, dirPath, fileName, options); } - } - }); + }, /*recursive*/ false, options), + refCount: 1, + childWatches: emptyArray + }; + cache.set(dirPath, directoryWatcher); + updateChildWatches(dirName, dirPath, options); } - - function nonSyncUpdateChildWatches(dirName: string, dirPath: Path, fileName: string, options: WatchOptions | undefined) { - // Iterate through existing children and update the watches if needed - const parentWatcher = cache.get(dirPath); - if (parentWatcher && host.directoryExists(dirName)) { - // Schedule the update and postpone invoke for callbacks - scheduleUpdateChildWatches(dirName, dirPath, options); - return; - } - - // Call the actual callbacks and remove child watches - invokeCallbacks(dirPath, fileName); - removeChildWatches(parentWatcher); + const callbackToAdd = callback && { dirName, callback }; + if (callbackToAdd) { + callbackCache.add(dirPath, callbackToAdd); } - - function scheduleUpdateChildWatches(dirName: string, dirPath: Path, options: WatchOptions | undefined) { - if (!cacheToUpdateChildWatches.has(dirPath)) { - cacheToUpdateChildWatches.set(dirPath, { dirName, options }); + return { + dirName, + close: () => { + const directoryWatcher = Debug.assertDefined(cache.get(dirPath)); + if (callbackToAdd) + callbackCache.remove(dirPath, callbackToAdd); + directoryWatcher.refCount--; + if (directoryWatcher.refCount) + return; + cache.delete(dirPath); + closeFileWatcherOf(directoryWatcher); + directoryWatcher.childWatches.forEach(closeFileWatcher); } - if (timerToUpdateChildWatches) { - host.clearTimeout(timerToUpdateChildWatches); - timerToUpdateChildWatches = undefined; + }; + } + function invokeCallbacks(dirPath: Path, fileNameOrInvokeMap: string | ts.Map) { + let fileName: string | undefined; + let invokeMap: ts.Map | undefined; + if (isString(fileNameOrInvokeMap)) { + fileName = fileNameOrInvokeMap; + } + else { + invokeMap = fileNameOrInvokeMap; + } + // Call the actual callback + callbackCache.forEach((callbacks, rootDirName) => { + if (invokeMap && invokeMap.has(rootDirName)) + return; + if (rootDirName === dirPath || (startsWith(dirPath, rootDirName) && dirPath[rootDirName.length] === directorySeparator)) { + if (invokeMap) { + invokeMap.set(rootDirName, true); + } + else { + callbacks.forEach(({ callback }) => callback(fileName!)); + } } - timerToUpdateChildWatches = host.setTimeout(onTimerToUpdateChildWatches, 1000); + }); + } + function nonSyncUpdateChildWatches(dirName: string, dirPath: Path, fileName: string, options: WatchOptions | undefined) { + // Iterate through existing children and update the watches if needed + const parentWatcher = cache.get(dirPath); + if (parentWatcher && host.directoryExists(dirName)) { + // Schedule the update and postpone invoke for callbacks + scheduleUpdateChildWatches(dirName, dirPath, options); + return; } - - function onTimerToUpdateChildWatches() { + // Call the actual callbacks and remove child watches + invokeCallbacks(dirPath, fileName); + removeChildWatches(parentWatcher); + } + function scheduleUpdateChildWatches(dirName: string, dirPath: Path, options: WatchOptions | undefined) { + if (!cacheToUpdateChildWatches.has(dirPath)) { + cacheToUpdateChildWatches.set(dirPath, { dirName, options }); + } + if (timerToUpdateChildWatches) { + host.clearTimeout(timerToUpdateChildWatches); timerToUpdateChildWatches = undefined; - sysLog(`sysLog:: onTimerToUpdateChildWatches:: ${cacheToUpdateChildWatches.size}`); - const start = timestamp(); - const invokeMap = createMap(); - - while (!timerToUpdateChildWatches && cacheToUpdateChildWatches.size) { - const { value: [dirPath, { dirName, options }], done } = cacheToUpdateChildWatches.entries().next(); - Debug.assert(!done); - cacheToUpdateChildWatches.delete(dirPath); - // Because the child refresh is fresh, we would need to invalidate whole root directory being watched - // to ensure that all the changes are reflected at this time - invokeCallbacks(dirPath as Path, invokeMap); - updateChildWatches(dirName, dirPath as Path, options); - } - - sysLog(`sysLog:: invokingWatchers:: ${timestamp() - start}ms:: ${cacheToUpdateChildWatches.size}`); - callbackCache.forEach((callbacks, rootDirName) => { - if (invokeMap.has(rootDirName)) { - callbacks.forEach(({ callback, dirName }) => callback(dirName)); - } - }); - - const elapsed = timestamp() - start; - sysLog(`sysLog:: Elapsed ${elapsed}ms:: onTimerToUpdateChildWatches:: ${cacheToUpdateChildWatches.size} ${timerToUpdateChildWatches}`); } - - function removeChildWatches(parentWatcher: HostDirectoryWatcher | undefined) { - if (!parentWatcher) return; - const existingChildWatches = parentWatcher.childWatches; - parentWatcher.childWatches = emptyArray; - for (const childWatcher of existingChildWatches) { - childWatcher.close(); - removeChildWatches(cache.get(toCanonicalFilePath(childWatcher.dirName))); - } + timerToUpdateChildWatches = host.setTimeout(onTimerToUpdateChildWatches, 1000); + } + function onTimerToUpdateChildWatches() { + timerToUpdateChildWatches = undefined; + sysLog(`sysLog:: onTimerToUpdateChildWatches:: ${cacheToUpdateChildWatches.size}`); + const start = timestamp(); + const invokeMap = createMap(); + while (!timerToUpdateChildWatches && cacheToUpdateChildWatches.size) { + const { value: [dirPath, { dirName, options }], done } = cacheToUpdateChildWatches.entries().next(); + Debug.assert(!done); + cacheToUpdateChildWatches.delete(dirPath); + // Because the child refresh is fresh, we would need to invalidate whole root directory being watched + // to ensure that all the changes are reflected at this time + invokeCallbacks((dirPath as Path), invokeMap); + updateChildWatches(dirName, (dirPath as Path), options); } - - function updateChildWatches(dirName: string, dirPath: Path, options: WatchOptions | undefined) { - // Iterate through existing children and update the watches if needed - const parentWatcher = cache.get(dirPath); - if (parentWatcher) { - parentWatcher.childWatches = watchChildDirectories(dirName, parentWatcher.childWatches, options); + sysLog(`sysLog:: invokingWatchers:: ${timestamp() - start}ms:: ${cacheToUpdateChildWatches.size}`); + callbackCache.forEach((callbacks, rootDirName) => { + if (invokeMap.has(rootDirName)) { + callbacks.forEach(({ callback, dirName }) => callback(dirName)); } + }); + const elapsed = timestamp() - start; + sysLog(`sysLog:: Elapsed ${elapsed}ms:: onTimerToUpdateChildWatches:: ${cacheToUpdateChildWatches.size} ${timerToUpdateChildWatches}`); + } + function removeChildWatches(parentWatcher: HostDirectoryWatcher | undefined) { + if (!parentWatcher) + return; + const existingChildWatches = parentWatcher.childWatches; + parentWatcher.childWatches = emptyArray; + for (const childWatcher of existingChildWatches) { + childWatcher.close(); + removeChildWatches(cache.get(toCanonicalFilePath(childWatcher.dirName))); + } + } + function updateChildWatches(dirName: string, dirPath: Path, options: WatchOptions | undefined) { + // Iterate through existing children and update the watches if needed + const parentWatcher = cache.get(dirPath); + if (parentWatcher) { + parentWatcher.childWatches = watchChildDirectories(dirName, parentWatcher.childWatches, options); } - + } + /** + * Watch the directories in the parentDir + */ + function watchChildDirectories(parentDir: string, existingChildWatches: ChildWatches, options: WatchOptions | undefined): ChildWatches { + let newChildWatches: ChildDirectoryWatcher[] | undefined; + enumerateInsertsAndDeletes(host.directoryExists(parentDir) ? mapDefined(host.getAccessibleSortedChildDirectories(parentDir), child => { + const childFullName = getNormalizedAbsolutePath(child, parentDir); + // Filter our the symbolic link directories since those arent included in recursive watch + // which is same behaviour when recursive: true is passed to fs.watch + return !isIgnoredPath(childFullName) && filePathComparer(childFullName, normalizePath(host.realpath(childFullName))) === Comparison.EqualTo ? childFullName : undefined; + }) : emptyArray, existingChildWatches, (child, childWatcher) => filePathComparer(child, childWatcher.dirName), createAndAddChildDirectoryWatcher, closeFileWatcher, addChildDirectoryWatcher); + return newChildWatches || emptyArray; /** - * Watch the directories in the parentDir + * Create new childDirectoryWatcher and add it to the new ChildDirectoryWatcher list */ - function watchChildDirectories(parentDir: string, existingChildWatches: ChildWatches, options: WatchOptions | undefined): ChildWatches { - let newChildWatches: ChildDirectoryWatcher[] | undefined; - enumerateInsertsAndDeletes( - host.directoryExists(parentDir) ? mapDefined(host.getAccessibleSortedChildDirectories(parentDir), child => { - const childFullName = getNormalizedAbsolutePath(child, parentDir); - // Filter our the symbolic link directories since those arent included in recursive watch - // which is same behaviour when recursive: true is passed to fs.watch - return !isIgnoredPath(childFullName) && filePathComparer(childFullName, normalizePath(host.realpath(childFullName))) === Comparison.EqualTo ? childFullName : undefined; - }) : emptyArray, - existingChildWatches, - (child, childWatcher) => filePathComparer(child, childWatcher.dirName), - createAndAddChildDirectoryWatcher, - closeFileWatcher, - addChildDirectoryWatcher - ); - - return newChildWatches || emptyArray; - - /** - * Create new childDirectoryWatcher and add it to the new ChildDirectoryWatcher list - */ - function createAndAddChildDirectoryWatcher(childName: string) { - const result = createDirectoryWatcher(childName, options); - addChildDirectoryWatcher(result); - } - - /** - * Add child directory watcher to the new ChildDirectoryWatcher list - */ - function addChildDirectoryWatcher(childWatcher: ChildDirectoryWatcher) { - (newChildWatches || (newChildWatches = [])).push(childWatcher); - } - } - - function isIgnoredPath(path: string) { - return some(ignoredPaths, searchPath => isInPath(path, searchPath)); + function createAndAddChildDirectoryWatcher(childName: string) { + const result = createDirectoryWatcher(childName, options); + addChildDirectoryWatcher(result); } - - function isInPath(path: string, searchPath: string) { - if (stringContains(path, searchPath)) return true; - if (host.useCaseSensitiveFileNames) return false; - return stringContains(toCanonicalFilePath(path), searchPath); + /** + * Add child directory watcher to the new ChildDirectoryWatcher list + */ + function addChildDirectoryWatcher(childWatcher: ChildDirectoryWatcher) { + (newChildWatches || (newChildWatches = [])).push(childWatcher); } } - - /*@internal*/ - export type FsWatchCallback = (eventName: "rename" | "change", relativeFileName: string | undefined) => void; - /*@internal*/ - export type FsWatch = (fileOrDirectory: string, entryKind: FileSystemEntryKind, callback: FsWatchCallback, recursive: boolean, fallbackPollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined) => FileWatcher; - - /*@internal*/ - export const enum FileSystemEntryKind { - File, - Directory, + function isIgnoredPath(path: string) { + return some(ignoredPaths, searchPath => isInPath(path, searchPath)); } - - /*@internal*/ - export function createFileWatcherCallback(callback: FsWatchCallback): FileWatcherCallback { - return (_fileName, eventKind) => callback(eventKind === FileWatcherEventKind.Changed ? "change" : "rename", ""); + function isInPath(path: string, searchPath: string) { + if (stringContains(path, searchPath)) + return true; + if (host.useCaseSensitiveFileNames) + return false; + return stringContains(toCanonicalFilePath(path), searchPath); } - - function createFsWatchCallbackForFileWatcherCallback( - fileName: string, - callback: FileWatcherCallback, - fileExists: System["fileExists"] - ): FsWatchCallback { - return eventName => { - if (eventName === "rename") { - callback(fileName, fileExists(fileName) ? FileWatcherEventKind.Created : FileWatcherEventKind.Deleted); - } - else { - // Change - callback(fileName, FileWatcherEventKind.Changed); - } - }; +} +/*@internal*/ +export type FsWatchCallback = (eventName: "rename" | "change", relativeFileName: string | undefined) => void; +/*@internal*/ +export type FsWatch = (fileOrDirectory: string, entryKind: FileSystemEntryKind, callback: FsWatchCallback, recursive: boolean, fallbackPollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined) => FileWatcher; +/*@internal*/ +export const enum FileSystemEntryKind { + File, + Directory +} +/*@internal*/ +export function createFileWatcherCallback(callback: FsWatchCallback): FileWatcherCallback { + return (_fileName, eventKind) => callback(eventKind === FileWatcherEventKind.Changed ? "change" : "rename", ""); +} +function createFsWatchCallbackForFileWatcherCallback(fileName: string, callback: FileWatcherCallback, fileExists: System["fileExists"]): FsWatchCallback { + return eventName => { + if (eventName === "rename") { + callback(fileName, fileExists(fileName) ? FileWatcherEventKind.Created : FileWatcherEventKind.Deleted); + } + else { + // Change + callback(fileName, FileWatcherEventKind.Changed); + } + }; +} +function createFsWatchCallbackForDirectoryWatcherCallback(directoryName: string, callback: DirectoryWatcherCallback): FsWatchCallback { + return (eventName, relativeFileName) => { + // In watchDirectory we only care about adding and removing files (when event name is + // "rename"); changes made within files are handled by corresponding fileWatchers (when + // event name is "change") + if (eventName === "rename") { + // When deleting a file, the passed baseFileName is null + callback(!relativeFileName ? directoryName : normalizePath(combinePaths(directoryName, relativeFileName))); + } + }; +} +/*@internal*/ +export interface CreateSystemWatchFunctions { + // Polling watch file + pollingWatchFile: HostWatchFile; + // For dynamic polling watch file + getModifiedTime: NonNullable; + setTimeout: NonNullable; + clearTimeout: NonNullable; + // For fs events : + fsWatch: FsWatch; + fileExists: System["fileExists"]; + useCaseSensitiveFileNames: boolean; + fsSupportsRecursiveFsWatch: boolean; + directoryExists: System["directoryExists"]; + getAccessibleSortedChildDirectories(path: string): readonly string[]; + realpath(s: string): string; + // For backward compatibility environment variables + tscWatchFile: string | undefined; + useNonPollingWatchers?: boolean; + tscWatchDirectory: string | undefined; +} +/*@internal*/ +export function createSystemWatchFunctions({ pollingWatchFile, getModifiedTime, setTimeout, clearTimeout, fsWatch, fileExists, useCaseSensitiveFileNames, fsSupportsRecursiveFsWatch, directoryExists, getAccessibleSortedChildDirectories, realpath, tscWatchFile, useNonPollingWatchers, tscWatchDirectory, }: CreateSystemWatchFunctions): { + watchFile: HostWatchFile; + watchDirectory: HostWatchDirectory; +} { + let dynamicPollingWatchFile: HostWatchFile | undefined; + let nonPollingWatchFile: HostWatchFile | undefined; + let hostRecursiveDirectoryWatcher: HostWatchDirectory | undefined; + return { + watchFile, + watchDirectory + }; + function watchFile(fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined): FileWatcher { + options = updateOptionsForWatchFile(options, useNonPollingWatchers); + const watchFileKind = Debug.assertDefined(options.watchFile); + switch (watchFileKind) { + case WatchFileKind.FixedPollingInterval: + return pollingWatchFile(fileName, callback, PollingInterval.Low, /*options*/ undefined); + case WatchFileKind.PriorityPollingInterval: + return pollingWatchFile(fileName, callback, pollingInterval, /*options*/ undefined); + case WatchFileKind.DynamicPriorityPolling: + return ensureDynamicPollingWatchFile()(fileName, callback, pollingInterval, /*options*/ undefined); + case WatchFileKind.UseFsEvents: + return fsWatch(fileName, FileSystemEntryKind.File, createFsWatchCallbackForFileWatcherCallback(fileName, callback, fileExists), + /*recursive*/ false, pollingInterval, getFallbackOptions(options)); + case WatchFileKind.UseFsEventsOnParentDirectory: + if (!nonPollingWatchFile) { + nonPollingWatchFile = createUseFsEventsOnParentDirectoryWatchFile(fsWatch, useCaseSensitiveFileNames); + } + return nonPollingWatchFile(fileName, callback, pollingInterval, getFallbackOptions(options)); + default: + Debug.assertNever(watchFileKind); + } } - - function createFsWatchCallbackForDirectoryWatcherCallback(directoryName: string, callback: DirectoryWatcherCallback): FsWatchCallback { - return (eventName, relativeFileName) => { - // In watchDirectory we only care about adding and removing files (when event name is - // "rename"); changes made within files are handled by corresponding fileWatchers (when - // event name is "change") - if (eventName === "rename") { - // When deleting a file, the passed baseFileName is null - callback(!relativeFileName ? directoryName : normalizePath(combinePaths(directoryName, relativeFileName))); - } - }; + function ensureDynamicPollingWatchFile() { + return dynamicPollingWatchFile || + (dynamicPollingWatchFile = createDynamicPriorityPollingWatchFile({ getModifiedTime, setTimeout })); } - - /*@internal*/ - export interface CreateSystemWatchFunctions { - // Polling watch file - pollingWatchFile: HostWatchFile; - // For dynamic polling watch file - getModifiedTime: NonNullable; - setTimeout: NonNullable; - clearTimeout: NonNullable; - // For fs events : - fsWatch: FsWatch; - fileExists: System["fileExists"]; - useCaseSensitiveFileNames: boolean; - fsSupportsRecursiveFsWatch: boolean; - directoryExists: System["directoryExists"]; - getAccessibleSortedChildDirectories(path: string): readonly string[]; - realpath(s: string): string; - // For backward compatibility environment variables - tscWatchFile: string | undefined; - useNonPollingWatchers?: boolean; - tscWatchDirectory: string | undefined; + function updateOptionsForWatchFile(options: WatchOptions | undefined, useNonPollingWatchers?: boolean): WatchOptions { + if (options && options.watchFile !== undefined) + return options; + switch (tscWatchFile) { + case "PriorityPollingInterval": + // Use polling interval based on priority when create watch using host.watchFile + return { watchFile: WatchFileKind.PriorityPollingInterval }; + case "DynamicPriorityPolling": + // Use polling interval but change the interval depending on file changes and their default polling interval + return { watchFile: WatchFileKind.DynamicPriorityPolling }; + case "UseFsEvents": + // Use notifications from FS to watch with falling back to fs.watchFile + return generateWatchFileOptions(WatchFileKind.UseFsEvents, PollingWatchKind.PriorityInterval, options); + case "UseFsEventsWithFallbackDynamicPolling": + // Use notifications from FS to watch with falling back to dynamic watch file + return generateWatchFileOptions(WatchFileKind.UseFsEvents, PollingWatchKind.DynamicPriority, options); + case "UseFsEventsOnParentDirectory": + useNonPollingWatchers = true; + // fall through + default: + return useNonPollingWatchers ? + // Use notifications from FS to watch with falling back to fs.watchFile + generateWatchFileOptions(WatchFileKind.UseFsEventsOnParentDirectory, PollingWatchKind.PriorityInterval, options) : + // Default to do not use fixed polling interval + { watchFile: WatchFileKind.FixedPollingInterval }; + } } - - /*@internal*/ - export function createSystemWatchFunctions({ - pollingWatchFile, - getModifiedTime, - setTimeout, - clearTimeout, - fsWatch, - fileExists, - useCaseSensitiveFileNames, - fsSupportsRecursiveFsWatch, - directoryExists, - getAccessibleSortedChildDirectories, - realpath, - tscWatchFile, - useNonPollingWatchers, - tscWatchDirectory, - }: CreateSystemWatchFunctions): { watchFile: HostWatchFile; watchDirectory: HostWatchDirectory; } { - let dynamicPollingWatchFile: HostWatchFile | undefined; - let nonPollingWatchFile: HostWatchFile | undefined; - let hostRecursiveDirectoryWatcher: HostWatchDirectory | undefined; + function generateWatchFileOptions(watchFile: WatchFileKind, fallbackPolling: PollingWatchKind, options: WatchOptions | undefined): WatchOptions { + const defaultFallbackPolling = options?.fallbackPolling; return { watchFile, - watchDirectory + fallbackPolling: defaultFallbackPolling === undefined ? + fallbackPolling : + defaultFallbackPolling }; - - function watchFile(fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined): FileWatcher { - options = updateOptionsForWatchFile(options, useNonPollingWatchers); - const watchFileKind = Debug.assertDefined(options.watchFile); - switch (watchFileKind) { - case WatchFileKind.FixedPollingInterval: - return pollingWatchFile(fileName, callback, PollingInterval.Low, /*options*/ undefined); - case WatchFileKind.PriorityPollingInterval: - return pollingWatchFile(fileName, callback, pollingInterval, /*options*/ undefined); - case WatchFileKind.DynamicPriorityPolling: - return ensureDynamicPollingWatchFile()(fileName, callback, pollingInterval, /*options*/ undefined); - case WatchFileKind.UseFsEvents: - return fsWatch( - fileName, - FileSystemEntryKind.File, - createFsWatchCallbackForFileWatcherCallback(fileName, callback, fileExists), - /*recursive*/ false, - pollingInterval, - getFallbackOptions(options) - ); - case WatchFileKind.UseFsEventsOnParentDirectory: - if (!nonPollingWatchFile) { - nonPollingWatchFile = createUseFsEventsOnParentDirectoryWatchFile(fsWatch, useCaseSensitiveFileNames); - } - return nonPollingWatchFile(fileName, callback, pollingInterval, getFallbackOptions(options)); - default: - Debug.assertNever(watchFileKind); - } - } - - function ensureDynamicPollingWatchFile() { - return dynamicPollingWatchFile || - (dynamicPollingWatchFile = createDynamicPriorityPollingWatchFile({ getModifiedTime, setTimeout })); - } - - function updateOptionsForWatchFile(options: WatchOptions | undefined, useNonPollingWatchers?: boolean): WatchOptions { - if (options && options.watchFile !== undefined) return options; - switch (tscWatchFile) { - case "PriorityPollingInterval": - // Use polling interval based on priority when create watch using host.watchFile - return { watchFile: WatchFileKind.PriorityPollingInterval }; - case "DynamicPriorityPolling": - // Use polling interval but change the interval depending on file changes and their default polling interval - return { watchFile: WatchFileKind.DynamicPriorityPolling }; - case "UseFsEvents": - // Use notifications from FS to watch with falling back to fs.watchFile - return generateWatchFileOptions(WatchFileKind.UseFsEvents, PollingWatchKind.PriorityInterval, options); - case "UseFsEventsWithFallbackDynamicPolling": - // Use notifications from FS to watch with falling back to dynamic watch file - return generateWatchFileOptions(WatchFileKind.UseFsEvents, PollingWatchKind.DynamicPriority, options); - case "UseFsEventsOnParentDirectory": - useNonPollingWatchers = true; - // fall through - default: - return useNonPollingWatchers ? - // Use notifications from FS to watch with falling back to fs.watchFile - generateWatchFileOptions(WatchFileKind.UseFsEventsOnParentDirectory, PollingWatchKind.PriorityInterval, options) : - // Default to do not use fixed polling interval - { watchFile: WatchFileKind.FixedPollingInterval }; - } - } - - function generateWatchFileOptions( - watchFile: WatchFileKind, - fallbackPolling: PollingWatchKind, - options: WatchOptions | undefined - ): WatchOptions { - const defaultFallbackPolling = options?.fallbackPolling; - return { - watchFile, - fallbackPolling: defaultFallbackPolling === undefined ? - fallbackPolling : - defaultFallbackPolling - }; + } + function watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined): FileWatcher { + if (fsSupportsRecursiveFsWatch) { + return fsWatch(directoryName, FileSystemEntryKind.Directory, createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback), recursive, PollingInterval.Medium, getFallbackOptions(options)); } - - function watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined): FileWatcher { - if (fsSupportsRecursiveFsWatch) { - return fsWatch( - directoryName, - FileSystemEntryKind.Directory, - createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback), - recursive, - PollingInterval.Medium, - getFallbackOptions(options) - ); - } - - if (!hostRecursiveDirectoryWatcher) { - hostRecursiveDirectoryWatcher = createDirectoryWatcherSupportingRecursive({ - useCaseSensitiveFileNames, - directoryExists, - getAccessibleSortedChildDirectories, - watchDirectory: nonRecursiveWatchDirectory, - realpath, - setTimeout, - clearTimeout - }); - } - return hostRecursiveDirectoryWatcher(directoryName, callback, recursive, options); + if (!hostRecursiveDirectoryWatcher) { + hostRecursiveDirectoryWatcher = createDirectoryWatcherSupportingRecursive({ + useCaseSensitiveFileNames, + directoryExists, + getAccessibleSortedChildDirectories, + watchDirectory: nonRecursiveWatchDirectory, + realpath, + setTimeout, + clearTimeout + }); } - - function nonRecursiveWatchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined): FileWatcher { - Debug.assert(!recursive); - options = updateOptionsForWatchDirectory(options); - const watchDirectoryKind = Debug.assertDefined(options.watchDirectory); - switch (watchDirectoryKind) { - case WatchDirectoryKind.FixedPollingInterval: - return pollingWatchFile( - directoryName, - () => callback(directoryName), - PollingInterval.Medium, - /*options*/ undefined - ); - case WatchDirectoryKind.DynamicPriorityPolling: - return ensureDynamicPollingWatchFile()( - directoryName, - () => callback(directoryName), - PollingInterval.Medium, - /*options*/ undefined - ); - case WatchDirectoryKind.UseFsEvents: - return fsWatch( - directoryName, - FileSystemEntryKind.Directory, - createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback), - recursive, - PollingInterval.Medium, - getFallbackOptions(options) - ); - default: - Debug.assertNever(watchDirectoryKind); - } + return hostRecursiveDirectoryWatcher(directoryName, callback, recursive, options); + } + function nonRecursiveWatchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined): FileWatcher { + Debug.assert(!recursive); + options = updateOptionsForWatchDirectory(options); + const watchDirectoryKind = Debug.assertDefined(options.watchDirectory); + switch (watchDirectoryKind) { + case WatchDirectoryKind.FixedPollingInterval: + return pollingWatchFile(directoryName, () => callback(directoryName), PollingInterval.Medium, + /*options*/ undefined); + case WatchDirectoryKind.DynamicPriorityPolling: + return ensureDynamicPollingWatchFile()(directoryName, () => callback(directoryName), PollingInterval.Medium, + /*options*/ undefined); + case WatchDirectoryKind.UseFsEvents: + return fsWatch(directoryName, FileSystemEntryKind.Directory, createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback), recursive, PollingInterval.Medium, getFallbackOptions(options)); + default: + Debug.assertNever(watchDirectoryKind); } - - function updateOptionsForWatchDirectory(options: WatchOptions | undefined): WatchOptions { - if (options && options.watchDirectory !== undefined) return options; - switch (tscWatchDirectory) { - case "RecursiveDirectoryUsingFsWatchFile": - // Use polling interval based on priority when create watch using host.watchFile - return { watchDirectory: WatchDirectoryKind.FixedPollingInterval }; - case "RecursiveDirectoryUsingDynamicPriorityPolling": - // Use polling interval but change the interval depending on file changes and their default polling interval - return { watchDirectory: WatchDirectoryKind.DynamicPriorityPolling }; - default: - const defaultFallbackPolling = options?.fallbackPolling; - return { - watchDirectory: WatchDirectoryKind.UseFsEvents, - fallbackPolling: defaultFallbackPolling !== undefined ? - defaultFallbackPolling : - undefined - }; - } + } + function updateOptionsForWatchDirectory(options: WatchOptions | undefined): WatchOptions { + if (options && options.watchDirectory !== undefined) + return options; + switch (tscWatchDirectory) { + case "RecursiveDirectoryUsingFsWatchFile": + // Use polling interval based on priority when create watch using host.watchFile + return { watchDirectory: WatchDirectoryKind.FixedPollingInterval }; + case "RecursiveDirectoryUsingDynamicPriorityPolling": + // Use polling interval but change the interval depending on file changes and their default polling interval + return { watchDirectory: WatchDirectoryKind.DynamicPriorityPolling }; + default: + const defaultFallbackPolling = options?.fallbackPolling; + return { + watchDirectory: WatchDirectoryKind.UseFsEvents, + fallbackPolling: defaultFallbackPolling !== undefined ? + defaultFallbackPolling : + undefined + }; } } - +} +/** + * patch writefile to create folder before writing the file + */ +/*@internal*/ +export function patchWriteFileEnsuringDirectory(sys: System) { + // patch writefile to create folder before writing the file + const originalWriteFile = sys.writeFile; + sys.writeFile = (path, data, writeBom) => writeFileEnsuringDirectories(path, data, !!writeBom, (path, data, writeByteOrderMark) => originalWriteFile.call(sys, path, data, writeByteOrderMark), path => sys.createDirectory(path), path => sys.directoryExists(path)); +} +/*@internal*/ +export type BufferEncoding = "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex"; +/*@internal*/ +interface NodeBuffer extends Uint8Array { + constructor: any; + write(str: string, encoding?: BufferEncoding): number; + write(str: string, offset: number, encoding?: BufferEncoding): number; + write(str: string, offset: number, length: number, encoding?: BufferEncoding): number; + toString(encoding?: string, start?: number, end?: number): string; + toJSON(): { + type: "Buffer"; + data: number[]; + }; + equals(otherBuffer: Uint8Array): boolean; + compare(otherBuffer: Uint8Array, targetStart?: number, targetEnd?: number, sourceStart?: number, sourceEnd?: number): number; + copy(targetBuffer: Uint8Array, targetStart?: number, sourceStart?: number, sourceEnd?: number): number; + slice(begin?: number, end?: number): Buffer; + subarray(begin?: number, end?: number): Buffer; + writeUIntLE(value: number, offset: number, byteLength: number): number; + writeUIntBE(value: number, offset: number, byteLength: number): number; + writeIntLE(value: number, offset: number, byteLength: number): number; + writeIntBE(value: number, offset: number, byteLength: number): number; + readUIntLE(offset: number, byteLength: number): number; + readUIntBE(offset: number, byteLength: number): number; + readIntLE(offset: number, byteLength: number): number; + readIntBE(offset: number, byteLength: number): number; + readUInt8(offset: number): number; + readUInt16LE(offset: number): number; + readUInt16BE(offset: number): number; + readUInt32LE(offset: number): number; + readUInt32BE(offset: number): number; + readInt8(offset: number): number; + readInt16LE(offset: number): number; + readInt16BE(offset: number): number; + readInt32LE(offset: number): number; + readInt32BE(offset: number): number; + readFloatLE(offset: number): number; + readFloatBE(offset: number): number; + readDoubleLE(offset: number): number; + readDoubleBE(offset: number): number; + reverse(): this; + swap16(): Buffer; + swap32(): Buffer; + swap64(): Buffer; + writeUInt8(value: number, offset: number): number; + writeUInt16LE(value: number, offset: number): number; + writeUInt16BE(value: number, offset: number): number; + writeUInt32LE(value: number, offset: number): number; + writeUInt32BE(value: number, offset: number): number; + writeInt8(value: number, offset: number): number; + writeInt16LE(value: number, offset: number): number; + writeInt16BE(value: number, offset: number): number; + writeInt32LE(value: number, offset: number): number; + writeInt32BE(value: number, offset: number): number; + writeFloatLE(value: number, offset: number): number; + writeFloatBE(value: number, offset: number): number; + writeDoubleLE(value: number, offset: number): number; + writeDoubleBE(value: number, offset: number): number; + readBigUInt64BE(offset?: number): bigint; + readBigUInt64LE(offset?: number): bigint; + readBigInt64BE(offset?: number): bigint; + readBigInt64LE(offset?: number): bigint; + writeBigInt64BE(value: bigint, offset?: number): number; + writeBigInt64LE(value: bigint, offset?: number): number; + writeBigUInt64BE(value: bigint, offset?: number): number; + writeBigUInt64LE(value: bigint, offset?: number): number; + fill(value: string | Uint8Array | number, offset?: number, end?: number, encoding?: BufferEncoding): this; + indexOf(value: string | number | Uint8Array, byteOffset?: number, encoding?: BufferEncoding): number; + lastIndexOf(value: string | number | Uint8Array, byteOffset?: number, encoding?: BufferEncoding): number; + entries(): IterableIterator<[number, number]>; + includes(value: string | number | Buffer, byteOffset?: number, encoding?: BufferEncoding): boolean; + keys(): IterableIterator; + values(): IterableIterator; +} +/*@internal*/ +interface Buffer extends NodeBuffer { +} +// TODO: GH#18217 Methods on System are often used as if they are certainly defined +export interface System { + args: string[]; + newLine: string; + useCaseSensitiveFileNames: boolean; + write(s: string): void; + writeOutputIsTTY?(): boolean; + readFile(path: string, encoding?: string): string | undefined; + getFileSize?(path: string): number; + writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; /** - * patch writefile to create folder before writing the file + * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that + * use native OS file watching */ - /*@internal*/ - export function patchWriteFileEnsuringDirectory(sys: System) { - // patch writefile to create folder before writing the file - const originalWriteFile = sys.writeFile; - sys.writeFile = (path, data, writeBom) => - writeFileEnsuringDirectories( - path, - data, - !!writeBom, - (path, data, writeByteOrderMark) => originalWriteFile.call(sys, path, data, writeByteOrderMark), - path => sys.createDirectory(path), - path => sys.directoryExists(path)); - } - - /*@internal*/ - export type BufferEncoding = "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex"; - - /*@internal*/ - interface NodeBuffer extends Uint8Array { - constructor: any; - write(str: string, encoding?: BufferEncoding): number; - write(str: string, offset: number, encoding?: BufferEncoding): number; - write(str: string, offset: number, length: number, encoding?: BufferEncoding): number; - toString(encoding?: string, start?: number, end?: number): string; - toJSON(): { type: "Buffer"; data: number[] }; - equals(otherBuffer: Uint8Array): boolean; - compare( - otherBuffer: Uint8Array, - targetStart?: number, - targetEnd?: number, - sourceStart?: number, - sourceEnd?: number - ): number; - copy(targetBuffer: Uint8Array, targetStart?: number, sourceStart?: number, sourceEnd?: number): number; - slice(begin?: number, end?: number): Buffer; - subarray(begin?: number, end?: number): Buffer; - writeUIntLE(value: number, offset: number, byteLength: number): number; - writeUIntBE(value: number, offset: number, byteLength: number): number; - writeIntLE(value: number, offset: number, byteLength: number): number; - writeIntBE(value: number, offset: number, byteLength: number): number; - readUIntLE(offset: number, byteLength: number): number; - readUIntBE(offset: number, byteLength: number): number; - readIntLE(offset: number, byteLength: number): number; - readIntBE(offset: number, byteLength: number): number; - readUInt8(offset: number): number; - readUInt16LE(offset: number): number; - readUInt16BE(offset: number): number; - readUInt32LE(offset: number): number; - readUInt32BE(offset: number): number; - readInt8(offset: number): number; - readInt16LE(offset: number): number; - readInt16BE(offset: number): number; - readInt32LE(offset: number): number; - readInt32BE(offset: number): number; - readFloatLE(offset: number): number; - readFloatBE(offset: number): number; - readDoubleLE(offset: number): number; - readDoubleBE(offset: number): number; - reverse(): this; - swap16(): Buffer; - swap32(): Buffer; - swap64(): Buffer; - writeUInt8(value: number, offset: number): number; - writeUInt16LE(value: number, offset: number): number; - writeUInt16BE(value: number, offset: number): number; - writeUInt32LE(value: number, offset: number): number; - writeUInt32BE(value: number, offset: number): number; - writeInt8(value: number, offset: number): number; - writeInt16LE(value: number, offset: number): number; - writeInt16BE(value: number, offset: number): number; - writeInt32LE(value: number, offset: number): number; - writeInt32BE(value: number, offset: number): number; - writeFloatLE(value: number, offset: number): number; - writeFloatBE(value: number, offset: number): number; - writeDoubleLE(value: number, offset: number): number; - writeDoubleBE(value: number, offset: number): number; - readBigUInt64BE(offset?: number): bigint; - readBigUInt64LE(offset?: number): bigint; - readBigInt64BE(offset?: number): bigint; - readBigInt64LE(offset?: number): bigint; - writeBigInt64BE(value: bigint, offset?: number): number; - writeBigInt64LE(value: bigint, offset?: number): number; - writeBigUInt64BE(value: bigint, offset?: number): number; - writeBigUInt64LE(value: bigint, offset?: number): number; - fill(value: string | Uint8Array | number, offset?: number, end?: number, encoding?: BufferEncoding): this; - indexOf(value: string | number | Uint8Array, byteOffset?: number, encoding?: BufferEncoding): number; - lastIndexOf(value: string | number | Uint8Array, byteOffset?: number, encoding?: BufferEncoding): number; - entries(): IterableIterator<[number, number]>; - includes(value: string | number | Buffer, byteOffset?: number, encoding?: BufferEncoding): boolean; - keys(): IterableIterator; - values(): IterableIterator; - } - - /*@internal*/ - interface Buffer extends NodeBuffer { } - - // TODO: GH#18217 Methods on System are often used as if they are certainly defined - export interface System { - args: string[]; - newLine: string; - useCaseSensitiveFileNames: boolean; - write(s: string): void; - writeOutputIsTTY?(): boolean; - readFile(path: string, encoding?: string): string | undefined; - getFileSize?(path: string): number; - writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; - - /** - * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that - * use native OS file watching - */ - watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher; - watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher; - resolvePath(path: string): string; - fileExists(path: string): boolean; - directoryExists(path: string): boolean; - createDirectory(path: string): void; - getExecutingFilePath(): string; - getCurrentDirectory(): string; - getDirectories(path: string): string[]; - readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; - getModifiedTime?(path: string): Date | undefined; - setModifiedTime?(path: string, time: Date): void; - deleteFile?(path: string): void; - /** - * A good implementation is node.js' `crypto.createHash`. (https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm) - */ - createHash?(data: string): string; - /** This must be cryptographically secure. Only implement this method using `crypto.createHash("sha256")`. */ - createSHA256Hash?(data: string): string; - getMemoryUsage?(): number; - exit(exitCode?: number): void; - /*@internal*/ enableCPUProfiler?(path: string, continuation: () => void): boolean; - /*@internal*/ disableCPUProfiler?(continuation: () => void): boolean; - realpath?(path: string): string; - /*@internal*/ getEnvironmentVariable(name: string): string; - /*@internal*/ tryEnableSourceMapsForHost?(): void; - /*@internal*/ debugMode?: boolean; - setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; - clearTimeout?(timeoutId: any): void; - clearScreen?(): void; - /*@internal*/ setBlocking?(): void; - base64decode?(input: string): string; - base64encode?(input: string): string; - /*@internal*/ bufferFrom?(input: string, encoding?: string): Buffer; - // For testing - /*@internal*/ now?(): Date; - /*@internal*/ require?(baseDir: string, moduleName: string): RequireResult; + watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher; + watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher; + resolvePath(path: string): string; + fileExists(path: string): boolean; + directoryExists(path: string): boolean; + createDirectory(path: string): void; + getExecutingFilePath(): string; + getCurrentDirectory(): string; + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; + getModifiedTime?(path: string): Date | undefined; + setModifiedTime?(path: string, time: Date): void; + deleteFile?(path: string): void; + /** + * A good implementation is node.js' `crypto.createHash`. (https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm) + */ + createHash?(data: string): string; + /** This must be cryptographically secure. Only implement this method using `crypto.createHash("sha256")`. */ + createSHA256Hash?(data: string): string; + getMemoryUsage?(): number; + exit(exitCode?: number): void; + /*@internal*/ enableCPUProfiler?(path: string, continuation: () => void): boolean; + /*@internal*/ disableCPUProfiler?(continuation: () => void): boolean; + realpath?(path: string): string; + /*@internal*/ getEnvironmentVariable(name: string): string; + /*@internal*/ tryEnableSourceMapsForHost?(): void; + /*@internal*/ debugMode?: boolean; + setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; + clearTimeout?(timeoutId: any): void; + clearScreen?(): void; + /*@internal*/ setBlocking?(): void; + base64decode?(input: string): string; + base64encode?(input: string): string; + /*@internal*/ bufferFrom?(input: string, encoding?: string): Buffer; + // For testing + /*@internal*/ now?(): Date; + /*@internal*/ require?(baseDir: string, moduleName: string): RequireResult; +} +export interface FileWatcher { + close(): void; +} +interface DirectoryWatcher extends FileWatcher { + referenceCount: number; +} +declare const require: any; +declare const process: any; +declare const global: any; +declare const __filename: string; +declare const __dirname: string; +export function getNodeMajorVersion(): number | undefined { + if (typeof process === "undefined") { + return undefined; } - - export interface FileWatcher { - close(): void; + const version: string = process.version; + if (!version) { + return undefined; } - - interface DirectoryWatcher extends FileWatcher { - referenceCount: number; + const dot = version.indexOf("."); + if (dot === -1) { + return undefined; } - - declare const require: any; - declare const process: any; - declare const global: any; - declare const __filename: string; - declare const __dirname: string; - - export function getNodeMajorVersion(): number | undefined { - if (typeof process === "undefined") { - return undefined; - } - const version: string = process.version; - if (!version) { - return undefined; + return parseInt(version.substring(1, dot)); +} +declare const ChakraHost: { + args: string[]; + currentDirectory: string; + executingFile: string; + newLine?: string; + useCaseSensitiveFileNames?: boolean; + echo(s: string): void; + quit(exitCode?: number): void; + fileExists(path: string): boolean; + deleteFile(path: string): boolean; + getModifiedTime(path: string): Date; + setModifiedTime(path: string, time: Date): void; + directoryExists(path: string): boolean; + createDirectory(path: string): void; + resolvePath(path: string): string; + readFile(path: string): string | undefined; + writeFile(path: string, contents: string): void; + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: readonly string[], basePaths?: readonly string[], excludeEx?: string, includeFileEx?: string, includeDirEx?: string): string[]; + watchFile?(path: string, callback: FileWatcherCallback): FileWatcher; + watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; + realpath(path: string): string; + getEnvironmentVariable?(name: string): string; +}; +// TODO: GH#18217 this is used as if it's certainly defined in many places. +// eslint-disable-next-line prefer-const +export let sys: System = (() => { + // NodeJS detects "\uFEFF" at the start of the string and *replaces* it with the actual + // byte order mark from the specified encoding. Using any other byte order mark does + // not actually work. + const byteOrderMarkIndicator = "\uFEFF"; + function getNodeSystem(): System { + const nativePattern = /^native |^\([^)]+\)$|^(internal[\\/]|[a-zA-Z0-9_\s]+(\.js)?$)/; + const _fs: typeof import("fs") = require("fs"); + const _path: typeof import("path") = require("path"); + const _os = require("os"); + // crypto can be absent on reduced node installations + let _crypto: typeof import("crypto") | undefined; + try { + _crypto = require("crypto"); } - const dot = version.indexOf("."); - if (dot === -1) { - return undefined; + catch { + _crypto = undefined; } - return parseInt(version.substring(1, dot)); - } - - declare const ChakraHost: { - args: string[]; - currentDirectory: string; - executingFile: string; - newLine?: string; - useCaseSensitiveFileNames?: boolean; - echo(s: string): void; - quit(exitCode?: number): void; - fileExists(path: string): boolean; - deleteFile(path: string): boolean; - getModifiedTime(path: string): Date; - setModifiedTime(path: string, time: Date): void; - directoryExists(path: string): boolean; - createDirectory(path: string): void; - resolvePath(path: string): string; - readFile(path: string): string | undefined; - writeFile(path: string, contents: string): void; - getDirectories(path: string): string[]; - readDirectory(path: string, extensions?: readonly string[], basePaths?: readonly string[], excludeEx?: string, includeFileEx?: string, includeDirEx?: string): string[]; - watchFile?(path: string, callback: FileWatcherCallback): FileWatcher; - watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; - realpath(path: string): string; - getEnvironmentVariable?(name: string): string; - }; - - // TODO: GH#18217 this is used as if it's certainly defined in many places. - // eslint-disable-next-line prefer-const - export let sys: System = (() => { - // NodeJS detects "\uFEFF" at the start of the string and *replaces* it with the actual - // byte order mark from the specified encoding. Using any other byte order mark does - // not actually work. - const byteOrderMarkIndicator = "\uFEFF"; - - function getNodeSystem(): System { - const nativePattern = /^native |^\([^)]+\)$|^(internal[\\/]|[a-zA-Z0-9_\s]+(\.js)?$)/; - const _fs: typeof import("fs") = require("fs"); - const _path: typeof import("path") = require("path"); - const _os = require("os"); - // crypto can be absent on reduced node installations - let _crypto: typeof import("crypto") | undefined; - try { - _crypto = require("crypto"); - } - catch { - _crypto = undefined; - } - let activeSession: import("inspector").Session | "stopping" | undefined; - let profilePath = "./profile.cpuprofile"; - - const Buffer: { - new (input: string, encoding?: string): any; - from?(input: string, encoding?: string): any; - } = require("buffer").Buffer; - - const nodeVersion = getNodeMajorVersion(); - const isNode4OrLater = nodeVersion! >= 4; - const isLinuxOrMacOs = process.platform === "linux" || process.platform === "darwin"; - - const platform: string = _os.platform(); - const useCaseSensitiveFileNames = isFileSystemCaseSensitive(); - const fsSupportsRecursiveFsWatch = isNode4OrLater && (process.platform === "win32" || process.platform === "darwin"); - const { watchFile, watchDirectory } = createSystemWatchFunctions({ - pollingWatchFile: createSingleFileWatcherPerName(fsWatchFileWorker, useCaseSensitiveFileNames), - getModifiedTime, - setTimeout, - clearTimeout, - fsWatch, - useCaseSensitiveFileNames, - fileExists, - // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows - // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) - fsSupportsRecursiveFsWatch, - directoryExists, - getAccessibleSortedChildDirectories: path => getAccessibleFileSystemEntries(path).directories, - realpath, - tscWatchFile: process.env.TSC_WATCHFILE, - useNonPollingWatchers: process.env.TSC_NONPOLLING_WATCHER, - tscWatchDirectory: process.env.TSC_WATCHDIRECTORY, - }); - const nodeSystem: System = { - args: process.argv.slice(2), - newLine: _os.EOL, - useCaseSensitiveFileNames, - write(s: string): void { - process.stdout.write(s); - }, - writeOutputIsTTY() { - return process.stdout.isTTY; - }, - readFile, - writeFile, - watchFile, - watchDirectory, - resolvePath: path => _path.resolve(path), - fileExists, - directoryExists, - createDirectory(directoryName: string) { - if (!nodeSystem.directoryExists(directoryName)) { - // Wrapped in a try-catch to prevent crashing if we are in a race - // with another copy of ourselves to create the same directory - try { - _fs.mkdirSync(directoryName); - } - catch (e) { - if (e.code !== "EEXIST") { - // Failed for some other reason (access denied?); still throw - throw e; - } - } - } - }, - getExecutingFilePath() { - return __filename; - }, - getCurrentDirectory() { - return process.cwd(); - }, - getDirectories, - getEnvironmentVariable(name: string) { - return process.env[name] || ""; - }, - readDirectory, - getModifiedTime, - setModifiedTime, - deleteFile, - createHash: _crypto ? createSHA256Hash : generateDjb2Hash, - createSHA256Hash: _crypto ? createSHA256Hash : undefined, - getMemoryUsage() { - if (global.gc) { - global.gc(); - } - return process.memoryUsage().heapUsed; - }, - getFileSize(path) { - try { - const stat = _fs.statSync(path); - if (stat.isFile()) { - return stat.size; - } - } - catch { /*ignore*/ } - return 0; - }, - exit(exitCode?: number): void { - disableCPUProfiler(() => process.exit(exitCode)); - }, - enableCPUProfiler, - disableCPUProfiler, - realpath, - debugMode: some(process.execArgv, arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)), - tryEnableSourceMapsForHost() { + let activeSession: import("inspector").Session | "stopping" | undefined; + let profilePath = "./profile.cpuprofile"; + const Buffer: { + new (input: string, encoding?: string): any; + from?(input: string, encoding?: string): any; + } = require("buffer").Buffer; + const nodeVersion = getNodeMajorVersion(); + const isNode4OrLater = nodeVersion! >= 4; + const isLinuxOrMacOs = process.platform === "linux" || process.platform === "darwin"; + const platform: string = _os.platform(); + const useCaseSensitiveFileNames = isFileSystemCaseSensitive(); + const fsSupportsRecursiveFsWatch = isNode4OrLater && (process.platform === "win32" || process.platform === "darwin"); + const { watchFile, watchDirectory } = createSystemWatchFunctions({ + pollingWatchFile: createSingleFileWatcherPerName(fsWatchFileWorker, useCaseSensitiveFileNames), + getModifiedTime, + setTimeout, + clearTimeout, + fsWatch, + useCaseSensitiveFileNames, + fileExists, + // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows + // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) + fsSupportsRecursiveFsWatch, + directoryExists, + getAccessibleSortedChildDirectories: path => getAccessibleFileSystemEntries(path).directories, + realpath, + tscWatchFile: process.env.TSC_WATCHFILE, + useNonPollingWatchers: process.env.TSC_NONPOLLING_WATCHER, + tscWatchDirectory: process.env.TSC_WATCHDIRECTORY, + }); + const nodeSystem: System = { + args: process.argv.slice(2), + newLine: _os.EOL, + useCaseSensitiveFileNames, + write(s: string): void { + process.stdout.write(s); + }, + writeOutputIsTTY() { + return process.stdout.isTTY; + }, + readFile, + writeFile, + watchFile, + watchDirectory, + resolvePath: path => _path.resolve(path), + fileExists, + directoryExists, + createDirectory(directoryName: string) { + if (!nodeSystem.directoryExists(directoryName)) { + // Wrapped in a try-catch to prevent crashing if we are in a race + // with another copy of ourselves to create the same directory try { - require("source-map-support").install(); - } - catch { - // Could not enable source maps. + _fs.mkdirSync(directoryName); } - }, - setTimeout, - clearTimeout, - clearScreen: () => { - process.stdout.write("\x1Bc"); - }, - setBlocking: () => { - if (process.stdout && process.stdout._handle && process.stdout._handle.setBlocking) { - process.stdout._handle.setBlocking(true); - } - }, - bufferFrom, - base64decode: input => bufferFrom(input, "base64").toString("utf8"), - base64encode: input => bufferFrom(input).toString("base64"), - require: (baseDir, moduleName) => { - try { - const modulePath = resolveJSModule(moduleName, baseDir, nodeSystem); - return { module: require(modulePath), modulePath, error: undefined }; + catch (e) { + if (e.code !== "EEXIST") { + // Failed for some other reason (access denied?); still throw + throw e; + } } - catch (error) { - return { module: undefined, modulePath: undefined, error }; + } + }, + getExecutingFilePath() { + return __filename; + }, + getCurrentDirectory() { + return process.cwd(); + }, + getDirectories, + getEnvironmentVariable(name: string) { + return process.env[name] || ""; + }, + readDirectory, + getModifiedTime, + setModifiedTime, + deleteFile, + createHash: _crypto ? createSHA256Hash : generateDjb2Hash, + createSHA256Hash: _crypto ? createSHA256Hash : undefined, + getMemoryUsage() { + if (global.gc) { + global.gc(); + } + return process.memoryUsage().heapUsed; + }, + getFileSize(path) { + try { + const stat = _fs.statSync(path); + if (stat.isFile()) { + return stat.size; } } - }; - return nodeSystem; - - /** - * Uses the builtin inspector APIs to capture a CPU profile - * See https://nodejs.org/api/inspector.html#inspector_example_usage for details - */ - function enableCPUProfiler(path: string, cb: () => void) { - if (activeSession) { - cb(); - return false; + catch { /*ignore*/ } + return 0; + }, + exit(exitCode?: number): void { + disableCPUProfiler(() => process.exit(exitCode)); + }, + enableCPUProfiler, + disableCPUProfiler, + realpath, + debugMode: some((process.execArgv), arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)), + tryEnableSourceMapsForHost() { + try { + require("source-map-support").install(); } - const inspector: typeof import("inspector") = require("inspector"); - if (!inspector || !inspector.Session) { - cb(); - return false; + catch { + // Could not enable source maps. } - const session = new inspector.Session(); - session.connect(); - - session.post("Profiler.enable", () => { - session.post("Profiler.start", () => { - activeSession = session; - profilePath = path; - cb(); - }); - }); - return true; - } - - /** - * Strips non-TS paths from the profile, so users with private projects shouldn't - * need to worry about leaking paths by submitting a cpu profile to us - */ - function cleanupPaths(profile: import("inspector").Profiler.Profile) { - let externalFileCounter = 0; - const remappedPaths = createMap(); - const normalizedDir = normalizeSlashes(__dirname); - // Windows rooted dir names need an extra `/` prepended to be valid file:/// urls - const fileUrlRoot = `file://${getRootLength(normalizedDir) === 1 ? "" : "/"}${normalizedDir}`; - for (const node of profile.nodes) { - if (node.callFrame.url) { - const url = normalizeSlashes(node.callFrame.url); - if (containsPath(fileUrlRoot, url, useCaseSensitiveFileNames)) { - node.callFrame.url = getRelativePathToDirectoryOrUrl(fileUrlRoot, url, fileUrlRoot, createGetCanonicalFileName(useCaseSensitiveFileNames), /*isAbsolutePathAnUrl*/ true); - } - else if (!nativePattern.test(url)) { - node.callFrame.url = (remappedPaths.has(url) ? remappedPaths : remappedPaths.set(url, `external${externalFileCounter}.js`)).get(url)!; - externalFileCounter++; - } - } + }, + setTimeout, + clearTimeout, + clearScreen: () => { + process.stdout.write("\x1Bc"); + }, + setBlocking: () => { + if (process.stdout && process.stdout._handle && process.stdout._handle.setBlocking) { + process.stdout._handle.setBlocking(true); } - return profile; - } - - function disableCPUProfiler(cb: () => void) { - if (activeSession && activeSession !== "stopping") { - const s = activeSession; - activeSession.post("Profiler.stop", (err, { profile }) => { - if (!err) { - try { - if (_fs.statSync(profilePath).isDirectory()) { - profilePath = _path.join(profilePath, `${(new Date()).toISOString().replace(/:/g, "-")}+P${process.pid}.cpuprofile`); - } - } - catch { - // do nothing and ignore fallible fs operation - } - try { - _fs.mkdirSync(_path.dirname(profilePath), { recursive: true }); - } - catch { - // do nothing and ignore fallible fs operation - } - _fs.writeFileSync(profilePath, JSON.stringify(cleanupPaths(profile))); - } - activeSession = undefined; - s.disconnect(); - cb(); - }); - activeSession = "stopping"; - return true; + }, + bufferFrom, + base64decode: input => bufferFrom(input, "base64").toString("utf8"), + base64encode: input => bufferFrom(input).toString("base64"), + require: (baseDir, moduleName) => { + try { + const modulePath = resolveJSModule(moduleName, baseDir, nodeSystem); + return { module: require(modulePath), modulePath, error: undefined }; } - else { - cb(); - return false; + catch (error) { + return { module: undefined, modulePath: undefined, error }; } } - - function bufferFrom(input: string, encoding?: string): Buffer { - // See https://github.com/Microsoft/TypeScript/issues/25652 - return Buffer.from && (Buffer.from as Function) !== Int8Array.from - ? Buffer.from(input, encoding) - : new Buffer(input, encoding); + }; + return nodeSystem; + /** + * Uses the builtin inspector APIs to capture a CPU profile + * See https://nodejs.org/api/inspector.html#inspector_example_usage for details + */ + function enableCPUProfiler(path: string, cb: () => void) { + if (activeSession) { + cb(); + return false; } - - function isFileSystemCaseSensitive(): boolean { - // win32\win64 are case insensitive platforms - if (platform === "win32" || platform === "win64") { - return false; - } - // If this file exists under a different case, we must be case-insensitve. - return !fileExists(swapCase(__filename)); + const inspector: typeof import("inspector") = require("inspector"); + if (!inspector || !inspector.Session) { + cb(); + return false; } - - /** Convert all lowercase chars to uppercase, and vice-versa */ - function swapCase(s: string): string { - return s.replace(/\w/g, (ch) => { - const up = ch.toUpperCase(); - return ch === up ? ch.toLowerCase() : up; + const session = new inspector.Session(); + session.connect(); + session.post("Profiler.enable", () => { + session.post("Profiler.start", () => { + activeSession = session; + profilePath = path; + cb(); }); - } - - function fsWatchFileWorker(fileName: string, callback: FileWatcherCallback, pollingInterval: number): FileWatcher { - _fs.watchFile(fileName, { persistent: true, interval: pollingInterval }, fileChanged); - let eventKind: FileWatcherEventKind; - return { - close: () => _fs.unwatchFile(fileName, fileChanged) - }; - - function fileChanged(curr: any, prev: any) { - // previous event kind check is to ensure we recongnize the file as previously also missing when it is restored or renamed twice (that is it disappears and reappears) - // In such case, prevTime returned is same as prev time of event when file was deleted as per node documentation - const isPreviouslyDeleted = +prev.mtime === 0 || eventKind === FileWatcherEventKind.Deleted; - if (+curr.mtime === 0) { - if (isPreviouslyDeleted) { - // Already deleted file, no need to callback again - return; - } - eventKind = FileWatcherEventKind.Deleted; - } - else if (isPreviouslyDeleted) { - eventKind = FileWatcherEventKind.Created; - } - // If there is no change in modified time, ignore the event - else if (+curr.mtime === +prev.mtime) { - return; + }); + return true; + } + /** + * Strips non-TS paths from the profile, so users with private projects shouldn't + * need to worry about leaking paths by submitting a cpu profile to us + */ + function cleanupPaths(profile: import("inspector").Profiler.Profile) { + let externalFileCounter = 0; + const remappedPaths = createMap(); + const normalizedDir = normalizeSlashes(__dirname); + // Windows rooted dir names need an extra `/` prepended to be valid file:/// urls + const fileUrlRoot = `file://${getRootLength(normalizedDir) === 1 ? "" : "/"}${normalizedDir}`; + for (const node of profile.nodes) { + if (node.callFrame.url) { + const url = normalizeSlashes(node.callFrame.url); + if (containsPath(fileUrlRoot, url, useCaseSensitiveFileNames)) { + node.callFrame.url = getRelativePathToDirectoryOrUrl(fileUrlRoot, url, fileUrlRoot, createGetCanonicalFileName(useCaseSensitiveFileNames), /*isAbsolutePathAnUrl*/ true); } - else { - // File changed - eventKind = FileWatcherEventKind.Changed; + else if (!nativePattern.test(url)) { + node.callFrame.url = (remappedPaths.has(url) ? remappedPaths : remappedPaths.set(url, `external${externalFileCounter}.js`)).get(url)!; + externalFileCounter++; } - callback(fileName, eventKind); } } - - function fsWatch( - fileOrDirectory: string, - entryKind: FileSystemEntryKind, - callback: FsWatchCallback, - recursive: boolean, - fallbackPollingInterval: PollingInterval, - fallbackOptions: WatchOptions | undefined - ): FileWatcher { - let options: any; - let lastDirectoryPartWithDirectorySeparator: string | undefined; - let lastDirectoryPart: string | undefined; - if (isLinuxOrMacOs) { - lastDirectoryPartWithDirectorySeparator = fileOrDirectory.substr(fileOrDirectory.lastIndexOf(directorySeparator)); - lastDirectoryPart = lastDirectoryPartWithDirectorySeparator.slice(directorySeparator.length); - } - /** Watcher for the file system entry depending on whether it is missing or present */ - let watcher = !fileSystemEntryExists(fileOrDirectory, entryKind) ? - watchMissingFileSystemEntry() : - watchPresentFileSystemEntry(); - return { - close: () => { - // Close the watcher (either existing file system entry watcher or missing file system entry watcher) - watcher.close(); - watcher = undefined!; - } - }; - - /** - * Invoke the callback with rename and update the watcher if not closed - * @param createWatcher - */ - function invokeCallbackAndUpdateWatcher(createWatcher: () => FileWatcher) { - sysLog(`sysLog:: ${fileOrDirectory}:: Changing watcher to ${createWatcher === watchPresentFileSystemEntry ? "Present" : "Missing"}FileSystemEntryWatcher`); - // Call the callback for current directory - callback("rename", ""); - - // If watcher is not closed, update it - if (watcher) { - watcher.close(); - watcher = createWatcher(); - } - } - - /** - * Watch the file or directory that is currently present - * and when the watched file or directory is deleted, switch to missing file system entry watcher - */ - function watchPresentFileSystemEntry(): FileWatcher { - // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows - // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) - if (options === undefined) { - if (fsSupportsRecursiveFsWatch) { - options = { persistent: true, recursive: !!recursive }; + return profile; + } + function disableCPUProfiler(cb: () => void) { + if (activeSession && activeSession !== "stopping") { + const s = activeSession; + activeSession.post("Profiler.stop", (err, { profile }) => { + if (!err) { + try { + if (_fs.statSync(profilePath).isDirectory()) { + profilePath = _path.join(profilePath, `${(new Date()).toISOString().replace(/:/g, "-")}+P${process.pid}.cpuprofile`); + } } - else { - options = { persistent: true }; + catch { + // do nothing and ignore fallible fs operation } + try { + _fs.mkdirSync(_path.dirname(profilePath), { recursive: true }); + } + catch { + // do nothing and ignore fallible fs operation + } + _fs.writeFileSync(profilePath, JSON.stringify(cleanupPaths(profile))); } - try { - const presentWatcher = _fs.watch( - fileOrDirectory, - options, - isLinuxOrMacOs ? - callbackChangingToMissingFileSystemEntry : - callback - ); - // Watch the missing file or directory or error - presentWatcher.on("error", () => invokeCallbackAndUpdateWatcher(watchMissingFileSystemEntry)); - return presentWatcher; - } - catch (e) { - // Catch the exception and use polling instead - // Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point - // so instead of throwing error, use fs.watchFile - return watchPresentFileSystemEntryWithFsWatchFile(); - } - } - - function callbackChangingToMissingFileSystemEntry(event: "rename" | "change", relativeName: string | undefined) { - // because relativeName is not guaranteed to be correct we need to check on each rename with few combinations - // Eg on ubuntu while watching app/node_modules the relativeName is "node_modules" which is neither relative nor full path - return event === "rename" && - (!relativeName || - relativeName === lastDirectoryPart || - relativeName.lastIndexOf(lastDirectoryPartWithDirectorySeparator!) === relativeName.length - lastDirectoryPartWithDirectorySeparator!.length) && - !fileSystemEntryExists(fileOrDirectory, entryKind) ? - invokeCallbackAndUpdateWatcher(watchMissingFileSystemEntry) : - callback(event, relativeName); - } - - /** - * Watch the file or directory using fs.watchFile since fs.watch threw exception - * Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point - */ - function watchPresentFileSystemEntryWithFsWatchFile(): FileWatcher { - sysLog(`sysLog:: ${fileOrDirectory}:: Changing to fsWatchFile`); - return watchFile( - fileOrDirectory, - createFileWatcherCallback(callback), - fallbackPollingInterval, - fallbackOptions - ); - } - - /** - * Watch the file or directory that is missing - * and switch to existing file or directory when the missing filesystem entry is created - */ - function watchMissingFileSystemEntry(): FileWatcher { - return watchFile( - fileOrDirectory, - (_fileName, eventKind) => { - if (eventKind === FileWatcherEventKind.Created && fileSystemEntryExists(fileOrDirectory, entryKind)) { - // Call the callback for current file or directory - // For now it could be callback for the inner directory creation, - // but just return current directory, better than current no-op - invokeCallbackAndUpdateWatcher(watchPresentFileSystemEntry); - } - }, - fallbackPollingInterval, - fallbackOptions - ); - } + activeSession = undefined; + s.disconnect(); + cb(); + }); + activeSession = "stopping"; + return true; } - - function readFileWorker(fileName: string, _encoding?: string): string | undefined { - if (!fileExists(fileName)) { - return undefined; - } - const buffer = _fs.readFileSync(fileName); - let len = buffer.length; - if (len >= 2 && buffer[0] === 0xFE && buffer[1] === 0xFF) { - // Big endian UTF-16 byte order mark detected. Since big endian is not supported by node.js, - // flip all byte pairs and treat as little endian. - len &= ~1; // Round down to a multiple of 2 - for (let i = 0; i < len; i += 2) { - const temp = buffer[i]; - buffer[i] = buffer[i + 1]; - buffer[i + 1] = temp; + else { + cb(); + return false; + } + } + function bufferFrom(input: string, encoding?: string): Buffer { + // See https://github.com/Microsoft/TypeScript/issues/25652 + return Buffer.from && (Buffer.from as Function) !== Int8Array.from + ? Buffer.from(input, encoding) + : new Buffer(input, encoding); + } + function isFileSystemCaseSensitive(): boolean { + // win32\win64 are case insensitive platforms + if (platform === "win32" || platform === "win64") { + return false; + } + // If this file exists under a different case, we must be case-insensitve. + return !fileExists(swapCase(__filename)); + } + /** Convert all lowercase chars to uppercase, and vice-versa */ + function swapCase(s: string): string { + return s.replace(/\w/g, (ch) => { + const up = ch.toUpperCase(); + return ch === up ? ch.toLowerCase() : up; + }); + } + function fsWatchFileWorker(fileName: string, callback: FileWatcherCallback, pollingInterval: number): FileWatcher { + _fs.watchFile(fileName, { persistent: true, interval: pollingInterval }, fileChanged); + let eventKind: FileWatcherEventKind; + return { + close: () => _fs.unwatchFile(fileName, fileChanged) + }; + function fileChanged(curr: any, prev: any) { + // previous event kind check is to ensure we recongnize the file as previously also missing when it is restored or renamed twice (that is it disappears and reappears) + // In such case, prevTime returned is same as prev time of event when file was deleted as per node documentation + const isPreviouslyDeleted = +prev.mtime === 0 || eventKind === FileWatcherEventKind.Deleted; + if (+curr.mtime === 0) { + if (isPreviouslyDeleted) { + // Already deleted file, no need to callback again + return; } - return buffer.toString("utf16le", 2); + eventKind = FileWatcherEventKind.Deleted; } - if (len >= 2 && buffer[0] === 0xFF && buffer[1] === 0xFE) { - // Little endian UTF-16 byte order mark detected - return buffer.toString("utf16le", 2); + else if (isPreviouslyDeleted) { + eventKind = FileWatcherEventKind.Created; } - if (len >= 3 && buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) { - // UTF-8 byte order mark detected - return buffer.toString("utf8", 3); + // If there is no change in modified time, ignore the event + else if (+curr.mtime === +prev.mtime) { + return; + } + else { + // File changed + eventKind = FileWatcherEventKind.Changed; } - // Default is UTF-8 with no byte order mark - return buffer.toString("utf8"); + callback(fileName, eventKind); } - - function readFile(fileName: string, _encoding?: string): string | undefined { - perfLogger.logStartReadFile(fileName); - const file = readFileWorker(fileName, _encoding); - perfLogger.logStopReadFile(); - return file; + } + function fsWatch(fileOrDirectory: string, entryKind: FileSystemEntryKind, callback: FsWatchCallback, recursive: boolean, fallbackPollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined): FileWatcher { + let options: any; + let lastDirectoryPartWithDirectorySeparator: string | undefined; + let lastDirectoryPart: string | undefined; + if (isLinuxOrMacOs) { + lastDirectoryPartWithDirectorySeparator = fileOrDirectory.substr(fileOrDirectory.lastIndexOf(directorySeparator)); + lastDirectoryPart = lastDirectoryPartWithDirectorySeparator.slice(directorySeparator.length); } - - function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { - perfLogger.logEvent("WriteFile: " + fileName); - // If a BOM is required, emit one - if (writeByteOrderMark) { - data = byteOrderMarkIndicator + data; + /** Watcher for the file system entry depending on whether it is missing or present */ + let watcher = !fileSystemEntryExists(fileOrDirectory, entryKind) ? + watchMissingFileSystemEntry() : + watchPresentFileSystemEntry(); + return { + close: () => { + // Close the watcher (either existing file system entry watcher or missing file system entry watcher) + watcher.close(); + watcher = undefined!; } - - let fd: number | undefined; - - try { - fd = _fs.openSync(fileName, "w"); - _fs.writeSync(fd, data, /*position*/ undefined, "utf8"); + }; + /** + * Invoke the callback with rename and update the watcher if not closed + * @param createWatcher + */ + function invokeCallbackAndUpdateWatcher(createWatcher: () => FileWatcher) { + sysLog(`sysLog:: ${fileOrDirectory}:: Changing watcher to ${createWatcher === watchPresentFileSystemEntry ? "Present" : "Missing"}FileSystemEntryWatcher`); + // Call the callback for current directory + callback("rename", ""); + // If watcher is not closed, update it + if (watcher) { + watcher.close(); + watcher = createWatcher(); } - finally { - if (fd !== undefined) { - _fs.closeSync(fd); + } + /** + * Watch the file or directory that is currently present + * and when the watched file or directory is deleted, switch to missing file system entry watcher + */ + function watchPresentFileSystemEntry(): FileWatcher { + // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows + // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) + if (options === undefined) { + if (fsSupportsRecursiveFsWatch) { + options = { persistent: true, recursive: !!recursive }; + } + else { + options = { persistent: true }; } } - } - - function getAccessibleFileSystemEntries(path: string): FileSystemEntries { - perfLogger.logEvent("ReadDir: " + (path || ".")); try { - const entries = _fs.readdirSync(path || ".").sort(); - const files: string[] = []; - const directories: string[] = []; - for (const entry of entries) { - // This is necessary because on some file system node fails to exclude - // "." and "..". See https://github.com/nodejs/node/issues/4002 - if (entry === "." || entry === "..") { - continue; - } - const name = combinePaths(path, entry); - - let stat: any; - try { - stat = _fs.statSync(name); - } - catch (e) { - continue; - } - - if (stat.isFile()) { - files.push(entry); - } - else if (stat.isDirectory()) { - directories.push(entry); - } - } - return { files, directories }; + const presentWatcher = _fs.watch(fileOrDirectory, options, isLinuxOrMacOs ? + callbackChangingToMissingFileSystemEntry : + callback); + // Watch the missing file or directory or error + presentWatcher.on("error", () => invokeCallbackAndUpdateWatcher(watchMissingFileSystemEntry)); + return presentWatcher; } catch (e) { - return emptyFileSystemEntries; + // Catch the exception and use polling instead + // Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point + // so instead of throwing error, use fs.watchFile + return watchPresentFileSystemEntryWithFsWatchFile(); } } - - function readDirectory(path: string, extensions?: readonly string[], excludes?: readonly string[], includes?: readonly string[], depth?: number): string[] { - return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), depth, getAccessibleFileSystemEntries, realpath); + function callbackChangingToMissingFileSystemEntry(event: "rename" | "change", relativeName: string | undefined) { + // because relativeName is not guaranteed to be correct we need to check on each rename with few combinations + // Eg on ubuntu while watching app/node_modules the relativeName is "node_modules" which is neither relative nor full path + return event === "rename" && + (!relativeName || + relativeName === lastDirectoryPart || + relativeName.lastIndexOf(lastDirectoryPartWithDirectorySeparator!) === relativeName.length - lastDirectoryPartWithDirectorySeparator!.length) && + !fileSystemEntryExists(fileOrDirectory, entryKind) ? + invokeCallbackAndUpdateWatcher(watchMissingFileSystemEntry) : + callback(event, relativeName); } - - function fileSystemEntryExists(path: string, entryKind: FileSystemEntryKind): boolean { - try { - const stat = _fs.statSync(path); - switch (entryKind) { - case FileSystemEntryKind.File: return stat.isFile(); - case FileSystemEntryKind.Directory: return stat.isDirectory(); - default: return false; + /** + * Watch the file or directory using fs.watchFile since fs.watch threw exception + * Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point + */ + function watchPresentFileSystemEntryWithFsWatchFile(): FileWatcher { + sysLog(`sysLog:: ${fileOrDirectory}:: Changing to fsWatchFile`); + return watchFile(fileOrDirectory, createFileWatcherCallback(callback), fallbackPollingInterval, fallbackOptions); + } + /** + * Watch the file or directory that is missing + * and switch to existing file or directory when the missing filesystem entry is created + */ + function watchMissingFileSystemEntry(): FileWatcher { + return watchFile(fileOrDirectory, (_fileName, eventKind) => { + if (eventKind === FileWatcherEventKind.Created && fileSystemEntryExists(fileOrDirectory, entryKind)) { + // Call the callback for current file or directory + // For now it could be callback for the inner directory creation, + // but just return current directory, better than current no-op + invokeCallbackAndUpdateWatcher(watchPresentFileSystemEntry); } + }, fallbackPollingInterval, fallbackOptions); + } + } + function readFileWorker(fileName: string, _encoding?: string): string | undefined { + if (!fileExists(fileName)) { + return undefined; + } + const buffer = _fs.readFileSync(fileName); + let len = buffer.length; + if (len >= 2 && buffer[0] === 0xFE && buffer[1] === 0xFF) { + // Big endian UTF-16 byte order mark detected. Since big endian is not supported by node.js, + // flip all byte pairs and treat as little endian. + len &= ~1; // Round down to a multiple of 2 + for (let i = 0; i < len; i += 2) { + const temp = buffer[i]; + buffer[i] = buffer[i + 1]; + buffer[i + 1] = temp; } - catch (e) { - return false; - } + return buffer.toString("utf16le", 2); } - - function fileExists(path: string): boolean { - return fileSystemEntryExists(path, FileSystemEntryKind.File); + if (len >= 2 && buffer[0] === 0xFF && buffer[1] === 0xFE) { + // Little endian UTF-16 byte order mark detected + return buffer.toString("utf16le", 2); } - - function directoryExists(path: string): boolean { - return fileSystemEntryExists(path, FileSystemEntryKind.Directory); + if (len >= 3 && buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) { + // UTF-8 byte order mark detected + return buffer.toString("utf8", 3); } - - function getDirectories(path: string): string[] { - perfLogger.logEvent("ReadDir: " + path); - return filter(_fs.readdirSync(path), dir => fileSystemEntryExists(combinePaths(path, dir), FileSystemEntryKind.Directory)); + // Default is UTF-8 with no byte order mark + return buffer.toString("utf8"); + } + function readFile(fileName: string, _encoding?: string): string | undefined { + perfLogger.logStartReadFile(fileName); + const file = readFileWorker(fileName, _encoding); + perfLogger.logStopReadFile(); + return file; + } + function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { + perfLogger.logEvent("WriteFile: " + fileName); + // If a BOM is required, emit one + if (writeByteOrderMark) { + data = byteOrderMarkIndicator + data; } - - function realpath(path: string): string { - try { - return _fs.realpathSync(path); - } - catch { - return path; - } + let fd: number | undefined; + try { + fd = _fs.openSync(fileName, "w"); + _fs.writeSync(fd, data, /*position*/ undefined, "utf8"); } - - function getModifiedTime(path: string) { - try { - return _fs.statSync(path).mtime; - } - catch (e) { - return undefined; + finally { + if (fd !== undefined) { + _fs.closeSync(fd); } } - - function setModifiedTime(path: string, time: Date) { - try { - _fs.utimesSync(path, time, time); - } - catch (e) { - return; + } + function getAccessibleFileSystemEntries(path: string): FileSystemEntries { + perfLogger.logEvent("ReadDir: " + (path || ".")); + try { + const entries = _fs.readdirSync(path || ".").sort(); + const files: string[] = []; + const directories: string[] = []; + for (const entry of entries) { + // This is necessary because on some file system node fails to exclude + // "." and "..". See https://github.com/nodejs/node/issues/4002 + if (entry === "." || entry === "..") { + continue; + } + const name = combinePaths(path, entry); + let stat: any; + try { + stat = _fs.statSync(name); + } + catch (e) { + continue; + } + if (stat.isFile()) { + files.push(entry); + } + else if (stat.isDirectory()) { + directories.push(entry); + } } + return { files, directories }; } - - function deleteFile(path: string) { - try { - return _fs.unlinkSync(path); - } - catch (e) { - return; + catch (e) { + return emptyFileSystemEntries; + } + } + function readDirectory(path: string, extensions?: readonly string[], excludes?: readonly string[], includes?: readonly string[], depth?: number): string[] { + return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), depth, getAccessibleFileSystemEntries, realpath); + } + function fileSystemEntryExists(path: string, entryKind: FileSystemEntryKind): boolean { + try { + const stat = _fs.statSync(path); + switch (entryKind) { + case FileSystemEntryKind.File: return stat.isFile(); + case FileSystemEntryKind.Directory: return stat.isDirectory(); + default: return false; } } - - function createSHA256Hash(data: string): string { - const hash = _crypto!.createHash("sha256"); - hash.update(data); - return hash.digest("hex"); + catch (e) { + return false; } } - - function getChakraSystem(): System { - const realpath = ChakraHost.realpath && ((path: string) => ChakraHost.realpath(path)); - return { - newLine: ChakraHost.newLine || "\r\n", - args: ChakraHost.args, - useCaseSensitiveFileNames: !!ChakraHost.useCaseSensitiveFileNames, - write: ChakraHost.echo, - readFile(path: string, _encoding?: string) { - // encoding is automatically handled by the implementation in ChakraHost - return ChakraHost.readFile(path); - }, - writeFile(path: string, data: string, writeByteOrderMark?: boolean) { - // If a BOM is required, emit one - if (writeByteOrderMark) { - data = byteOrderMarkIndicator + data; - } - - ChakraHost.writeFile(path, data); - }, - resolvePath: ChakraHost.resolvePath, - fileExists: ChakraHost.fileExists, - deleteFile: ChakraHost.deleteFile, - getModifiedTime: ChakraHost.getModifiedTime, - setModifiedTime: ChakraHost.setModifiedTime, - directoryExists: ChakraHost.directoryExists, - createDirectory: ChakraHost.createDirectory, - getExecutingFilePath: () => ChakraHost.executingFile, - getCurrentDirectory: () => ChakraHost.currentDirectory, - getDirectories: ChakraHost.getDirectories, - getEnvironmentVariable: ChakraHost.getEnvironmentVariable || (() => ""), - readDirectory(path, extensions, excludes, includes, _depth) { - const pattern = getFileMatcherPatterns(path, excludes, includes, !!ChakraHost.useCaseSensitiveFileNames, ChakraHost.currentDirectory); - return ChakraHost.readDirectory(path, extensions, pattern.basePaths, pattern.excludePattern, pattern.includeFilePattern, pattern.includeDirectoryPattern); - }, - exit: ChakraHost.quit, - realpath - }; + function fileExists(path: string): boolean { + return fileSystemEntryExists(path, FileSystemEntryKind.File); + } + function directoryExists(path: string): boolean { + return fileSystemEntryExists(path, FileSystemEntryKind.Directory); } - - let sys: System | undefined; - if (typeof ChakraHost !== "undefined") { - sys = getChakraSystem(); + function getDirectories(path: string): string[] { + perfLogger.logEvent("ReadDir: " + path); + return filter(_fs.readdirSync(path), dir => fileSystemEntryExists(combinePaths(path, dir), FileSystemEntryKind.Directory)); } - else if (typeof process !== "undefined" && process.nextTick && !process.browser && typeof require !== "undefined") { - // process and process.nextTick checks if current environment is node-like - // process.browser check excludes webpack and browserify - sys = getNodeSystem(); + function realpath(path: string): string { + try { + return _fs.realpathSync(path); + } + catch { + return path; + } } - if (sys) { - // patch writefile to create folder before writing the file - patchWriteFileEnsuringDirectory(sys); + function getModifiedTime(path: string) { + try { + return _fs.statSync(path).mtime; + } + catch (e) { + return undefined; + } } - return sys!; - })(); - - if (sys && sys.getEnvironmentVariable) { - setCustomPollingValues(sys); - Debug.currentAssertionLevel = /^development$/i.test(sys.getEnvironmentVariable("NODE_ENV")) - ? AssertionLevel.Normal - : AssertionLevel.None; + function setModifiedTime(path: string, time: Date) { + try { + _fs.utimesSync(path, time, time); + } + catch (e) { + return; + } + } + function deleteFile(path: string) { + try { + return _fs.unlinkSync(path); + } + catch (e) { + return; + } + } + function createSHA256Hash(data: string): string { + const hash = _crypto!.createHash("sha256"); + hash.update(data); + return hash.digest("hex"); + } + } + function getChakraSystem(): System { + const realpath = ChakraHost.realpath && ((path: string) => ChakraHost.realpath(path)); + return { + newLine: ChakraHost.newLine || "\r\n", + args: ChakraHost.args, + useCaseSensitiveFileNames: !!ChakraHost.useCaseSensitiveFileNames, + write: ChakraHost.echo, + readFile(path: string, _encoding?: string) { + // encoding is automatically handled by the implementation in ChakraHost + return ChakraHost.readFile(path); + }, + writeFile(path: string, data: string, writeByteOrderMark?: boolean) { + // If a BOM is required, emit one + if (writeByteOrderMark) { + data = byteOrderMarkIndicator + data; + } + ChakraHost.writeFile(path, data); + }, + resolvePath: ChakraHost.resolvePath, + fileExists: ChakraHost.fileExists, + deleteFile: ChakraHost.deleteFile, + getModifiedTime: ChakraHost.getModifiedTime, + setModifiedTime: ChakraHost.setModifiedTime, + directoryExists: ChakraHost.directoryExists, + createDirectory: ChakraHost.createDirectory, + getExecutingFilePath: () => ChakraHost.executingFile, + getCurrentDirectory: () => ChakraHost.currentDirectory, + getDirectories: ChakraHost.getDirectories, + getEnvironmentVariable: ChakraHost.getEnvironmentVariable || (() => ""), + readDirectory(path, extensions, excludes, includes, _depth) { + const pattern = getFileMatcherPatterns(path, excludes, includes, !!ChakraHost.useCaseSensitiveFileNames, ChakraHost.currentDirectory); + return ChakraHost.readDirectory(path, extensions, pattern.basePaths, pattern.excludePattern, pattern.includeFilePattern, pattern.includeDirectoryPattern); + }, + exit: ChakraHost.quit, + realpath + }; + } + let sys: System | undefined; + if (typeof ChakraHost !== "undefined") { + sys = getChakraSystem(); } - if (sys && sys.debugMode) { - Debug.isDebugging = true; + else if (typeof process !== "undefined" && process.nextTick && !process.browser && typeof require !== "undefined") { + // process and process.nextTick checks if current environment is node-like + // process.browser check excludes webpack and browserify + sys = getNodeSystem(); } + if (sys) { + // patch writefile to create folder before writing the file + patchWriteFileEnsuringDirectory(sys); + } + return sys!; +})(); +if (sys && sys.getEnvironmentVariable) { + setCustomPollingValues(sys); + Debug.currentAssertionLevel = /^development$/i.test(sys.getEnvironmentVariable("NODE_ENV")) + ? AssertionLevel.Normal + : AssertionLevel.None; +} +if (sys && sys.debugMode) { + Debug.isDebugging = true; } diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index bd8f34ee128c6..19dfee7693f92 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -1,439 +1,392 @@ +import { ModuleKind, TransformerFactory, SourceFile, Bundle, transformES2015Module, transformSystemModule, transformModule, EmitTransformers, emptyArray, CompilerOptions, CustomTransformers, getEmitScriptTarget, getEmitModuleKind, addRange, map, transformTypeScript, transformClassFields, JsxEmit, transformJsx, ScriptTarget, transformESNext, transformES2019, transformES2018, transformES2017, transformES2016, transformES2015, transformGenerators, transformES5, transformDeclarations, CustomTransformer, Transformer, isBundle, CustomTransformerFactory, chainBundle, identity, EmitHint, Node, EmitResolver, EmitHost, TransformationResult, SyntaxKind, VariableDeclaration, FunctionDeclaration, EmitHelper, TransformationContext, DiagnosticWithLocation, Debug, disposeEmitNodes, getSourceFileOfNode, getParseTreeNode, isSourceFile, getEmitFlags, EmitFlags, Identifier, setEmitFlags, createVariableDeclaration, Statement, createVariableStatement, createVariableDeclarationList, append } from "./ts"; +import { mark, measure } from "./ts.performance"; /* @internal */ -namespace ts { - function getModuleTransformer(moduleKind: ModuleKind): TransformerFactory { - switch (moduleKind) { - case ModuleKind.ESNext: - case ModuleKind.ES2015: - return transformES2015Module; - case ModuleKind.System: - return transformSystemModule; - default: - return transformModule; - } +function getModuleTransformer(moduleKind: ModuleKind): TransformerFactory { + switch (moduleKind) { + case ModuleKind.ESNext: + case ModuleKind.ES2015: + return transformES2015Module; + case ModuleKind.System: + return transformSystemModule; + default: + return transformModule; } - - const enum TransformationState { - Uninitialized, - Initialized, - Completed, - Disposed +} +/* @internal */ +const enum TransformationState { + Uninitialized, + Initialized, + Completed, + Disposed +} +/* @internal */ +const enum SyntaxKindFeatureFlags { + Substitution = 1 << 0, + EmitNotifications = 1 << 1 +} +/* @internal */ +export const noTransformers: EmitTransformers = { scriptTransformers: emptyArray, declarationTransformers: emptyArray }; +/* @internal */ +export function getTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnlyDtsFiles?: boolean): EmitTransformers { + return { + scriptTransformers: getScriptTransformers(compilerOptions, customTransformers, emitOnlyDtsFiles), + declarationTransformers: getDeclarationTransformers(customTransformers), + }; +} +/* @internal */ +function getScriptTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnlyDtsFiles?: boolean) { + if (emitOnlyDtsFiles) + return emptyArray; + const jsx = compilerOptions.jsx; + const languageVersion = getEmitScriptTarget(compilerOptions); + const moduleKind = getEmitModuleKind(compilerOptions); + const transformers: TransformerFactory[] = []; + addRange(transformers, customTransformers && map(customTransformers.before, wrapScriptTransformerFactory)); + transformers.push(transformTypeScript); + transformers.push(transformClassFields); + if (jsx === JsxEmit.React) { + transformers.push(transformJsx); } - - const enum SyntaxKindFeatureFlags { - Substitution = 1 << 0, - EmitNotifications = 1 << 1, + if (languageVersion < ScriptTarget.ESNext) { + transformers.push(transformESNext); } - - export const noTransformers: EmitTransformers = { scriptTransformers: emptyArray, declarationTransformers: emptyArray }; - - export function getTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnlyDtsFiles?: boolean): EmitTransformers { - return { - scriptTransformers: getScriptTransformers(compilerOptions, customTransformers, emitOnlyDtsFiles), - declarationTransformers: getDeclarationTransformers(customTransformers), - }; + if (languageVersion < ScriptTarget.ES2019) { + transformers.push(transformES2019); } - - function getScriptTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnlyDtsFiles?: boolean) { - if (emitOnlyDtsFiles) return emptyArray; - - const jsx = compilerOptions.jsx; - const languageVersion = getEmitScriptTarget(compilerOptions); - const moduleKind = getEmitModuleKind(compilerOptions); - const transformers: TransformerFactory[] = []; - - addRange(transformers, customTransformers && map(customTransformers.before, wrapScriptTransformerFactory)); - - transformers.push(transformTypeScript); - transformers.push(transformClassFields); - - if (jsx === JsxEmit.React) { - transformers.push(transformJsx); - } - - if (languageVersion < ScriptTarget.ESNext) { - transformers.push(transformESNext); - } - - if (languageVersion < ScriptTarget.ES2019) { - transformers.push(transformES2019); - } - - if (languageVersion < ScriptTarget.ES2018) { - transformers.push(transformES2018); - } - - if (languageVersion < ScriptTarget.ES2017) { - transformers.push(transformES2017); - } - - if (languageVersion < ScriptTarget.ES2016) { - transformers.push(transformES2016); - } - - if (languageVersion < ScriptTarget.ES2015) { - transformers.push(transformES2015); - transformers.push(transformGenerators); - } - - transformers.push(getModuleTransformer(moduleKind)); - - // The ES5 transformer is last so that it can substitute expressions like `exports.default` - // for ES3. - if (languageVersion < ScriptTarget.ES5) { - transformers.push(transformES5); + if (languageVersion < ScriptTarget.ES2018) { + transformers.push(transformES2018); + } + if (languageVersion < ScriptTarget.ES2017) { + transformers.push(transformES2017); + } + if (languageVersion < ScriptTarget.ES2016) { + transformers.push(transformES2016); + } + if (languageVersion < ScriptTarget.ES2015) { + transformers.push(transformES2015); + transformers.push(transformGenerators); + } + transformers.push(getModuleTransformer(moduleKind)); + // The ES5 transformer is last so that it can substitute expressions like `exports.default` + // for ES3. + if (languageVersion < ScriptTarget.ES5) { + transformers.push(transformES5); + } + addRange(transformers, customTransformers && map(customTransformers.after, wrapScriptTransformerFactory)); + return transformers; +} +/* @internal */ +function getDeclarationTransformers(customTransformers?: CustomTransformers) { + const transformers: TransformerFactory[] = []; + transformers.push(transformDeclarations); + addRange(transformers, customTransformers && map(customTransformers.afterDeclarations, wrapDeclarationTransformerFactory)); + return transformers; +} +/** + * Wrap a custom script or declaration transformer object in a `Transformer` callback with fallback support for transforming bundles. + */ +/* @internal */ +function wrapCustomTransformer(transformer: CustomTransformer): Transformer { + return node => isBundle(node) ? transformer.transformBundle(node) : transformer.transformSourceFile(node); +} +/** + * Wrap a transformer factory that may return a custom script or declaration transformer object. + */ +/* @internal */ +function wrapCustomTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory, handleDefault: (node: Transformer) => Transformer): TransformerFactory { + return context => { + const customTransformer = transformer(context); + return typeof customTransformer === "function" + ? handleDefault(customTransformer) + : wrapCustomTransformer(customTransformer); + }; +} +/* @internal */ +function wrapScriptTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory): TransformerFactory { + return wrapCustomTransformerFactory(transformer, chainBundle); +} +/* @internal */ +function wrapDeclarationTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory): TransformerFactory { + return wrapCustomTransformerFactory(transformer, identity); +} +/* @internal */ +export function noEmitSubstitution(_hint: EmitHint, node: Node) { + return node; +} +/* @internal */ +export function noEmitNotification(hint: EmitHint, node: Node, callback: (hint: EmitHint, node: Node) => void) { + callback(hint, node); +} +/** + * Transforms an array of SourceFiles by passing them through each transformer. + * + * @param resolver The emit resolver provided by the checker. + * @param host The emit host object used to interact with the file system. + * @param options Compiler options to surface in the `TransformationContext`. + * @param nodes An array of nodes to transform. + * @param transforms An array of `TransformerFactory` callbacks. + * @param allowDtsFiles A value indicating whether to allow the transformation of .d.ts files. + */ +/* @internal */ +export function transformNodes(resolver: EmitResolver | undefined, host: EmitHost | undefined, options: CompilerOptions, nodes: readonly T[], transformers: readonly TransformerFactory[], allowDtsFiles: boolean): TransformationResult { + const enabledSyntaxKindFeatures = new Array(SyntaxKind.Count); + let lexicalEnvironmentVariableDeclarations: VariableDeclaration[]; + let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[]; + let lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; + let lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; + let lexicalEnvironmentStackOffset = 0; + let lexicalEnvironmentSuspended = false; + let emitHelpers: EmitHelper[] | undefined; + let onSubstituteNode: TransformationContext["onSubstituteNode"] = noEmitSubstitution; + let onEmitNode: TransformationContext["onEmitNode"] = noEmitNotification; + let state = TransformationState.Uninitialized; + const diagnostics: DiagnosticWithLocation[] = []; + // The transformation context is provided to each transformer as part of transformer + // initialization. + const context: TransformationContext = { + getCompilerOptions: () => options, + getEmitResolver: () => resolver!, + getEmitHost: () => host!, + startLexicalEnvironment, + suspendLexicalEnvironment, + resumeLexicalEnvironment, + endLexicalEnvironment, + hoistVariableDeclaration, + hoistFunctionDeclaration, + requestEmitHelper, + readEmitHelpers, + enableSubstitution, + enableEmitNotification, + isSubstitutionEnabled, + isEmitNotificationEnabled, + get onSubstituteNode() { return onSubstituteNode; }, + set onSubstituteNode(value) { + Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); + Debug.assert(value !== undefined, "Value must not be 'undefined'"); + onSubstituteNode = value; + }, + get onEmitNode() { return onEmitNode; }, + set onEmitNode(value) { + Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); + Debug.assert(value !== undefined, "Value must not be 'undefined'"); + onEmitNode = value; + }, + addDiagnostic(diag) { + diagnostics.push(diag); } - - addRange(transformers, customTransformers && map(customTransformers.after, wrapScriptTransformerFactory)); - return transformers; + }; + // Ensure the parse tree is clean before applying transformations + for (const node of nodes) { + disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node))); } - - function getDeclarationTransformers(customTransformers?: CustomTransformers) { - const transformers: TransformerFactory[] = []; - transformers.push(transformDeclarations); - addRange(transformers, customTransformers && map(customTransformers.afterDeclarations, wrapDeclarationTransformerFactory)); - return transformers; + mark("beforeTransform"); + // Chain together and initialize each transformer. + const transformersWithContext = transformers.map(t => t(context)); + const transformation = (node: T): T => { + for (const transform of transformersWithContext) { + node = transform(node); + } + return node; + }; + // prevent modification of transformation hooks. + state = TransformationState.Initialized; + // Transform each node. + const transformed = map(nodes, allowDtsFiles ? transformation : transformRoot); + // prevent modification of the lexical environment. + state = TransformationState.Completed; + mark("afterTransform"); + measure("transformTime", "beforeTransform", "afterTransform"); + return { + transformed, + substituteNode, + emitNodeWithNotification, + dispose, + diagnostics + }; + function transformRoot(node: T) { + return node && (!isSourceFile(node) || !node.isDeclarationFile) ? transformation(node) : node; } - /** - * Wrap a custom script or declaration transformer object in a `Transformer` callback with fallback support for transforming bundles. + * Enables expression substitutions in the pretty printer for the provided SyntaxKind. */ - function wrapCustomTransformer(transformer: CustomTransformer): Transformer { - return node => isBundle(node) ? transformer.transformBundle(node) : transformer.transformSourceFile(node); + function enableSubstitution(kind: SyntaxKind) { + Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); + enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.Substitution; } - /** - * Wrap a transformer factory that may return a custom script or declaration transformer object. + * Determines whether expression substitutions are enabled for the provided node. */ - function wrapCustomTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory, handleDefault: (node: Transformer) => Transformer): TransformerFactory { - return context => { - const customTransformer = transformer(context); - return typeof customTransformer === "function" - ? handleDefault(customTransformer) - : wrapCustomTransformer(customTransformer); - }; - } - - function wrapScriptTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory): TransformerFactory { - return wrapCustomTransformerFactory(transformer, chainBundle); + function isSubstitutionEnabled(node: Node) { + return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.Substitution) !== 0 + && (getEmitFlags(node) & EmitFlags.NoSubstitution) === 0; } - - function wrapDeclarationTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory): TransformerFactory { - return wrapCustomTransformerFactory(transformer, identity); + /** + * Emits a node with possible substitution. + * + * @param hint A hint as to the intended usage of the node. + * @param node The node to emit. + * @param emitCallback The callback used to emit the node or its substitute. + */ + function substituteNode(hint: EmitHint, node: Node) { + Debug.assert(state < TransformationState.Disposed, "Cannot substitute a node after the result is disposed."); + return node && isSubstitutionEnabled(node) && onSubstituteNode(hint, node) || node; } - - export function noEmitSubstitution(_hint: EmitHint, node: Node) { - return node; + /** + * Enables before/after emit notifications in the pretty printer for the provided SyntaxKind. + */ + function enableEmitNotification(kind: SyntaxKind) { + Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); + enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.EmitNotifications; } - - export function noEmitNotification(hint: EmitHint, node: Node, callback: (hint: EmitHint, node: Node) => void) { - callback(hint, node); + /** + * Determines whether before/after emit notifications should be raised in the pretty + * printer when it emits a node. + */ + function isEmitNotificationEnabled(node: Node) { + return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.EmitNotifications) !== 0 + || (getEmitFlags(node) & EmitFlags.AdviseOnEmitNode) !== 0; } - /** - * Transforms an array of SourceFiles by passing them through each transformer. + * Emits a node with possible emit notification. * - * @param resolver The emit resolver provided by the checker. - * @param host The emit host object used to interact with the file system. - * @param options Compiler options to surface in the `TransformationContext`. - * @param nodes An array of nodes to transform. - * @param transforms An array of `TransformerFactory` callbacks. - * @param allowDtsFiles A value indicating whether to allow the transformation of .d.ts files. + * @param hint A hint as to the intended usage of the node. + * @param node The node to emit. + * @param emitCallback The callback used to emit the node. */ - export function transformNodes(resolver: EmitResolver | undefined, host: EmitHost | undefined, options: CompilerOptions, nodes: readonly T[], transformers: readonly TransformerFactory[], allowDtsFiles: boolean): TransformationResult { - const enabledSyntaxKindFeatures = new Array(SyntaxKind.Count); - let lexicalEnvironmentVariableDeclarations: VariableDeclaration[]; - let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[]; - let lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; - let lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; - let lexicalEnvironmentStackOffset = 0; - let lexicalEnvironmentSuspended = false; - let emitHelpers: EmitHelper[] | undefined; - let onSubstituteNode: TransformationContext["onSubstituteNode"] = noEmitSubstitution; - let onEmitNode: TransformationContext["onEmitNode"] = noEmitNotification; - let state = TransformationState.Uninitialized; - const diagnostics: DiagnosticWithLocation[] = []; - - // The transformation context is provided to each transformer as part of transformer - // initialization. - const context: TransformationContext = { - getCompilerOptions: () => options, - getEmitResolver: () => resolver!, // TODO: GH#18217 - getEmitHost: () => host!, // TODO: GH#18217 - startLexicalEnvironment, - suspendLexicalEnvironment, - resumeLexicalEnvironment, - endLexicalEnvironment, - hoistVariableDeclaration, - hoistFunctionDeclaration, - requestEmitHelper, - readEmitHelpers, - enableSubstitution, - enableEmitNotification, - isSubstitutionEnabled, - isEmitNotificationEnabled, - get onSubstituteNode() { return onSubstituteNode; }, - set onSubstituteNode(value) { - Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); - Debug.assert(value !== undefined, "Value must not be 'undefined'"); - onSubstituteNode = value; - }, - get onEmitNode() { return onEmitNode; }, - set onEmitNode(value) { - Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); - Debug.assert(value !== undefined, "Value must not be 'undefined'"); - onEmitNode = value; - }, - addDiagnostic(diag) { - diagnostics.push(diag); + function emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { + Debug.assert(state < TransformationState.Disposed, "Cannot invoke TransformationResult callbacks after the result is disposed."); + if (node) { + if (isEmitNotificationEnabled(node)) { + onEmitNode(hint, node, emitCallback); } - }; - - // Ensure the parse tree is clean before applying transformations - for (const node of nodes) { - disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node))); - } - - performance.mark("beforeTransform"); - - // Chain together and initialize each transformer. - const transformersWithContext = transformers.map(t => t(context)); - const transformation = (node: T): T => { - for (const transform of transformersWithContext) { - node = transform(node); + else { + emitCallback(hint, node); } - return node; - }; - - // prevent modification of transformation hooks. - state = TransformationState.Initialized; - - // Transform each node. - const transformed = map(nodes, allowDtsFiles ? transformation : transformRoot); - - // prevent modification of the lexical environment. - state = TransformationState.Completed; - - performance.mark("afterTransform"); - performance.measure("transformTime", "beforeTransform", "afterTransform"); - - return { - transformed, - substituteNode, - emitNodeWithNotification, - dispose, - diagnostics - }; - - function transformRoot(node: T) { - return node && (!isSourceFile(node) || !node.isDeclarationFile) ? transformation(node) : node; - } - - /** - * Enables expression substitutions in the pretty printer for the provided SyntaxKind. - */ - function enableSubstitution(kind: SyntaxKind) { - Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); - enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.Substitution; } - - /** - * Determines whether expression substitutions are enabled for the provided node. - */ - function isSubstitutionEnabled(node: Node) { - return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.Substitution) !== 0 - && (getEmitFlags(node) & EmitFlags.NoSubstitution) === 0; + } + /** + * Records a hoisted variable declaration for the provided name within a lexical environment. + */ + function hoistVariableDeclaration(name: Identifier): void { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + const decl = setEmitFlags(createVariableDeclaration(name), EmitFlags.NoNestedSourceMaps); + if (!lexicalEnvironmentVariableDeclarations) { + lexicalEnvironmentVariableDeclarations = [decl]; } - - /** - * Emits a node with possible substitution. - * - * @param hint A hint as to the intended usage of the node. - * @param node The node to emit. - * @param emitCallback The callback used to emit the node or its substitute. - */ - function substituteNode(hint: EmitHint, node: Node) { - Debug.assert(state < TransformationState.Disposed, "Cannot substitute a node after the result is disposed."); - return node && isSubstitutionEnabled(node) && onSubstituteNode(hint, node) || node; + else { + lexicalEnvironmentVariableDeclarations.push(decl); } - - /** - * Enables before/after emit notifications in the pretty printer for the provided SyntaxKind. - */ - function enableEmitNotification(kind: SyntaxKind) { - Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); - enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.EmitNotifications; + } + /** + * Records a hoisted function declaration within a lexical environment. + */ + function hoistFunctionDeclaration(func: FunctionDeclaration): void { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + if (!lexicalEnvironmentFunctionDeclarations) { + lexicalEnvironmentFunctionDeclarations = [func]; } - - /** - * Determines whether before/after emit notifications should be raised in the pretty - * printer when it emits a node. - */ - function isEmitNotificationEnabled(node: Node) { - return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.EmitNotifications) !== 0 - || (getEmitFlags(node) & EmitFlags.AdviseOnEmitNode) !== 0; + else { + lexicalEnvironmentFunctionDeclarations.push(func); } - - /** - * Emits a node with possible emit notification. - * - * @param hint A hint as to the intended usage of the node. - * @param node The node to emit. - * @param emitCallback The callback used to emit the node. - */ - function emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { - Debug.assert(state < TransformationState.Disposed, "Cannot invoke TransformationResult callbacks after the result is disposed."); - if (node) { - if (isEmitNotificationEnabled(node)) { - onEmitNode(hint, node, emitCallback); + } + /** + * Starts a new lexical environment. Any existing hoisted variable or function declarations + * are pushed onto a stack, and the related storage variables are reset. + */ + function startLexicalEnvironment(): void { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); + // Save the current lexical environment. Rather than resizing the array we adjust the + // stack size variable. This allows us to reuse existing array slots we've + // already allocated between transformations to avoid allocation and GC overhead during + // transformation. + lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentVariableDeclarations; + lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentFunctionDeclarations; + lexicalEnvironmentStackOffset++; + lexicalEnvironmentVariableDeclarations = undefined!; + lexicalEnvironmentFunctionDeclarations = undefined!; + } + /** Suspends the current lexical environment, usually after visiting a parameter list. */ + function suspendLexicalEnvironment(): void { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is already suspended."); + lexicalEnvironmentSuspended = true; + } + /** Resumes a suspended lexical environment, usually before visiting a function body. */ + function resumeLexicalEnvironment(): void { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + Debug.assert(lexicalEnvironmentSuspended, "Lexical environment is not suspended."); + lexicalEnvironmentSuspended = false; + } + /** + * Ends a lexical environment. The previous set of hoisted declarations are restored and + * any hoisted declarations added in this environment are returned. + */ + function endLexicalEnvironment(): Statement[] | undefined { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); + let statements: Statement[] | undefined; + if (lexicalEnvironmentVariableDeclarations || lexicalEnvironmentFunctionDeclarations) { + if (lexicalEnvironmentFunctionDeclarations) { + statements = [...lexicalEnvironmentFunctionDeclarations]; + } + if (lexicalEnvironmentVariableDeclarations) { + const statement = createVariableStatement( + /*modifiers*/ undefined, createVariableDeclarationList(lexicalEnvironmentVariableDeclarations)); + setEmitFlags(statement, EmitFlags.CustomPrologue); + if (!statements) { + statements = [statement]; } else { - emitCallback(hint, node); + statements.push(statement); } } } - - /** - * Records a hoisted variable declaration for the provided name within a lexical environment. - */ - function hoistVariableDeclaration(name: Identifier): void { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); - const decl = setEmitFlags(createVariableDeclaration(name), EmitFlags.NoNestedSourceMaps); - if (!lexicalEnvironmentVariableDeclarations) { - lexicalEnvironmentVariableDeclarations = [decl]; - } - else { - lexicalEnvironmentVariableDeclarations.push(decl); - } + // Restore the previous lexical environment. + lexicalEnvironmentStackOffset--; + lexicalEnvironmentVariableDeclarations = lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset]; + lexicalEnvironmentFunctionDeclarations = lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset]; + if (lexicalEnvironmentStackOffset === 0) { + lexicalEnvironmentVariableDeclarationsStack = []; + lexicalEnvironmentFunctionDeclarationsStack = []; } - - /** - * Records a hoisted function declaration within a lexical environment. - */ - function hoistFunctionDeclaration(func: FunctionDeclaration): void { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); - if (!lexicalEnvironmentFunctionDeclarations) { - lexicalEnvironmentFunctionDeclarations = [func]; - } - else { - lexicalEnvironmentFunctionDeclarations.push(func); + return statements; + } + function requestEmitHelper(helper: EmitHelper): void { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); + Debug.assert(!helper.scoped, "Cannot request a scoped emit helper."); + emitHelpers = append(emitHelpers, helper); + } + function readEmitHelpers(): EmitHelper[] | undefined { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); + const helpers = emitHelpers; + emitHelpers = undefined; + return helpers; + } + function dispose() { + if (state < TransformationState.Disposed) { + // Clean up emit nodes on parse tree + for (const node of nodes) { + disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node))); } - } - - /** - * Starts a new lexical environment. Any existing hoisted variable or function declarations - * are pushed onto a stack, and the related storage variables are reset. - */ - function startLexicalEnvironment(): void { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); - Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); - - // Save the current lexical environment. Rather than resizing the array we adjust the - // stack size variable. This allows us to reuse existing array slots we've - // already allocated between transformations to avoid allocation and GC overhead during - // transformation. - lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentVariableDeclarations; - lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentFunctionDeclarations; - lexicalEnvironmentStackOffset++; + // Release references to external entries for GC purposes. lexicalEnvironmentVariableDeclarations = undefined!; + lexicalEnvironmentVariableDeclarationsStack = undefined!; lexicalEnvironmentFunctionDeclarations = undefined!; - } - - /** Suspends the current lexical environment, usually after visiting a parameter list. */ - function suspendLexicalEnvironment(): void { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); - Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is already suspended."); - lexicalEnvironmentSuspended = true; - } - - /** Resumes a suspended lexical environment, usually before visiting a function body. */ - function resumeLexicalEnvironment(): void { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); - Debug.assert(lexicalEnvironmentSuspended, "Lexical environment is not suspended."); - lexicalEnvironmentSuspended = false; - } - - /** - * Ends a lexical environment. The previous set of hoisted declarations are restored and - * any hoisted declarations added in this environment are returned. - */ - function endLexicalEnvironment(): Statement[] | undefined { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); - Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); - - let statements: Statement[] | undefined; - if (lexicalEnvironmentVariableDeclarations || lexicalEnvironmentFunctionDeclarations) { - if (lexicalEnvironmentFunctionDeclarations) { - statements = [...lexicalEnvironmentFunctionDeclarations]; - } - - if (lexicalEnvironmentVariableDeclarations) { - const statement = createVariableStatement( - /*modifiers*/ undefined, - createVariableDeclarationList(lexicalEnvironmentVariableDeclarations) - ); - - setEmitFlags(statement, EmitFlags.CustomPrologue); - - if (!statements) { - statements = [statement]; - } - else { - statements.push(statement); - } - } - } - - // Restore the previous lexical environment. - lexicalEnvironmentStackOffset--; - lexicalEnvironmentVariableDeclarations = lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset]; - lexicalEnvironmentFunctionDeclarations = lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset]; - if (lexicalEnvironmentStackOffset === 0) { - lexicalEnvironmentVariableDeclarationsStack = []; - lexicalEnvironmentFunctionDeclarationsStack = []; - } - return statements; - } - - function requestEmitHelper(helper: EmitHelper): void { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); - Debug.assert(!helper.scoped, "Cannot request a scoped emit helper."); - emitHelpers = append(emitHelpers, helper); - } - - function readEmitHelpers(): EmitHelper[] | undefined { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); - const helpers = emitHelpers; + lexicalEnvironmentFunctionDeclarationsStack = undefined!; + onSubstituteNode = undefined!; + onEmitNode = undefined!; emitHelpers = undefined; - return helpers; - } - - function dispose() { - if (state < TransformationState.Disposed) { - // Clean up emit nodes on parse tree - for (const node of nodes) { - disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node))); - } - - // Release references to external entries for GC purposes. - lexicalEnvironmentVariableDeclarations = undefined!; - lexicalEnvironmentVariableDeclarationsStack = undefined!; - lexicalEnvironmentFunctionDeclarations = undefined!; - lexicalEnvironmentFunctionDeclarationsStack = undefined!; - onSubstituteNode = undefined!; - onEmitNode = undefined!; - emitHelpers = undefined; - - // Prevent further use of the transformation result. - state = TransformationState.Disposed; - } + // Prevent further use of the transformation result. + state = TransformationState.Disposed; } } } diff --git a/src/compiler/transformers/classFields.ts b/src/compiler/transformers/classFields.ts index da4a5b08a0a4b..879a13c97d69b 100644 --- a/src/compiler/transformers/classFields.ts +++ b/src/compiler/transformers/classFields.ts @@ -1,522 +1,415 @@ +import { TransformationContext, Identifier, Expression, Statement, chainBundle, SourceFile, ScriptTarget, visitEachChild, addEmitHelpers, Node, VisitResult, TransformFlags, SyntaxKind, ClassExpression, ClassDeclaration, VariableStatement, PropertyDeclaration, ComputedPropertyName, some, updateComputedPropertyName, inlineExpressions, Debug, isSimpleInlineableExpression, forEach, isPropertyDeclaration, getEffectiveBaseTypeNode, skipOuterExpressions, updateClassDeclaration, visitNodes, isHeritageClause, createExpressionStatement, getProperties, getInternalName, isClassDeclaration, getOriginalNode, updateClassExpression, NodeCheckFlags, createTempVariable, getSynthesizedClone, GeneratedIdentifierFlags, getOriginalNodeId, setEmitFlags, EmitFlags, getEmitFlags, startOnNewLine, createAssignment, addRange, map, ClassElement, isClassElement, setTextRange, createNodeArray, visitNode, getFirstConstructorWithBody, isConstructorDeclaration, isInitializedProperty, visitParameterList, setOriginalNode, createConstructor, ConstructorDeclaration, visitFunctionBody, createCall, createSuper, createSpread, createIdentifier, addPrologueDirectivesAndInitialSuperCall, findIndex, isParameterPropertyDeclaration, isStatement, createThis, mergeLexicalEnvironment, createBlock, LeftHandSideExpression, setSourceMapRange, moveRangePastModifiers, setCommentRange, isComputedPropertyName, getGeneratedNameForNode, isExpression, hasModifier, ModifierFlags, isIdentifier, createVoidZero, createMemberAccessForPropertyName, createStringLiteral, unescapeLeadingUnderscores, createPropertyDescriptor, createObjectDefinePropertyCall, EmitHint, PropertyName, skipPartiallyEmittedExpressions, isAssignmentExpression, isGeneratedIdentifier } from "../ts"; /*@internal*/ -namespace ts { - const enum ClassPropertySubstitutionFlags { - /** - * Enables substitutions for class expressions with static fields - * which have initializers that reference the class name. - */ - ClassAliases = 1 << 0, +const enum ClassPropertySubstitutionFlags { + /** + * Enables substitutions for class expressions with static fields + * which have initializers that reference the class name. + */ + ClassAliases = 1 << 0 +} +/** + * Transforms ECMAScript Class Syntax. + * TypeScript parameter property syntax is transformed in the TypeScript transformer. + * For now, this transforms public field declarations using TypeScript class semantics, + * where declarations are elided and initializers are transformed as assignments in the constructor. + * When --useDefineForClassFields is on, this transforms to ECMAScript semantics, with Object.defineProperty. + */ +/* @internal */ +export function transformClassFields(context: TransformationContext) { + const { hoistVariableDeclaration, endLexicalEnvironment, resumeLexicalEnvironment } = context; + const resolver = context.getEmitResolver(); + const previousOnSubstituteNode = context.onSubstituteNode; + context.onSubstituteNode = onSubstituteNode; + let enabledSubstitutions: ClassPropertySubstitutionFlags; + let classAliases: Identifier[]; + /** + * Tracks what computed name expressions originating from elided names must be inlined + * at the next execution site, in document order + */ + let pendingExpressions: Expression[] | undefined; + /** + * Tracks what computed name expression statements and static property initializers must be + * emitted at the next execution site, in document order (for decorated classes). + */ + let pendingStatements: Statement[] | undefined; + return chainBundle(transformSourceFile); + function transformSourceFile(node: SourceFile) { + const options = context.getCompilerOptions(); + if (node.isDeclarationFile + || options.useDefineForClassFields && options.target === ScriptTarget.ESNext) { + return node; + } + const visited = visitEachChild(node, visitor, context); + addEmitHelpers(visited, context.readEmitHelpers()); + return visited; + } + function visitor(node: Node): VisitResult { + if (!(node.transformFlags & TransformFlags.ContainsClassFields)) + return node; + switch (node.kind) { + case SyntaxKind.ClassExpression: + return visitClassExpression((node as ClassExpression)); + case SyntaxKind.ClassDeclaration: + return visitClassDeclaration((node as ClassDeclaration)); + case SyntaxKind.VariableStatement: + return visitVariableStatement((node as VariableStatement)); + } + return visitEachChild(node, visitor, context); } /** - * Transforms ECMAScript Class Syntax. - * TypeScript parameter property syntax is transformed in the TypeScript transformer. - * For now, this transforms public field declarations using TypeScript class semantics, - * where declarations are elided and initializers are transformed as assignments in the constructor. - * When --useDefineForClassFields is on, this transforms to ECMAScript semantics, with Object.defineProperty. + * Visits the members of a class that has fields. + * + * @param node The node to visit. */ - export function transformClassFields(context: TransformationContext) { - const { - hoistVariableDeclaration, - endLexicalEnvironment, - resumeLexicalEnvironment - } = context; - const resolver = context.getEmitResolver(); - - const previousOnSubstituteNode = context.onSubstituteNode; - context.onSubstituteNode = onSubstituteNode; - - let enabledSubstitutions: ClassPropertySubstitutionFlags; - - let classAliases: Identifier[]; - - /** - * Tracks what computed name expressions originating from elided names must be inlined - * at the next execution site, in document order - */ - let pendingExpressions: Expression[] | undefined; - - /** - * Tracks what computed name expression statements and static property initializers must be - * emitted at the next execution site, in document order (for decorated classes). - */ - let pendingStatements: Statement[] | undefined; - - return chainBundle(transformSourceFile); - - function transformSourceFile(node: SourceFile) { - const options = context.getCompilerOptions(); - if (node.isDeclarationFile - || options.useDefineForClassFields && options.target === ScriptTarget.ESNext) { + function classElementVisitor(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.Constructor: + // Constructors for classes using class fields are transformed in + // `visitClassDeclaration` or `visitClassExpression`. + return undefined; + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.MethodDeclaration: + // Visit the name of the member (if it's a computed property name). + return visitEachChild(node, classElementVisitor, context); + case SyntaxKind.PropertyDeclaration: + return visitPropertyDeclaration((node as PropertyDeclaration)); + case SyntaxKind.ComputedPropertyName: + return visitComputedPropertyName((node as ComputedPropertyName)); + case SyntaxKind.SemicolonClassElement: return node; - } - const visited = visitEachChild(node, visitor, context); - addEmitHelpers(visited, context.readEmitHelpers()); - return visited; + default: + return visitor(node); } - - function visitor(node: Node): VisitResult { - if (!(node.transformFlags & TransformFlags.ContainsClassFields)) return node; - - switch (node.kind) { - case SyntaxKind.ClassExpression: - return visitClassExpression(node as ClassExpression); - case SyntaxKind.ClassDeclaration: - return visitClassDeclaration(node as ClassDeclaration); - case SyntaxKind.VariableStatement: - return visitVariableStatement(node as VariableStatement); - } - return visitEachChild(node, visitor, context); + } + function visitVariableStatement(node: VariableStatement) { + const savedPendingStatements = pendingStatements; + pendingStatements = []; + const visitedNode = visitEachChild(node, visitor, context); + const statement = some(pendingStatements) ? + [visitedNode, ...pendingStatements] : + visitedNode; + pendingStatements = savedPendingStatements; + return statement; + } + function visitComputedPropertyName(name: ComputedPropertyName) { + let node = visitEachChild(name, visitor, context); + if (some(pendingExpressions)) { + const expressions = pendingExpressions; + expressions.push(name.expression); + pendingExpressions = []; + node = updateComputedPropertyName(node, inlineExpressions(expressions)); } - - /** - * Visits the members of a class that has fields. - * - * @param node The node to visit. - */ - function classElementVisitor(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.Constructor: - // Constructors for classes using class fields are transformed in - // `visitClassDeclaration` or `visitClassExpression`. - return undefined; - - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.MethodDeclaration: - // Visit the name of the member (if it's a computed property name). - return visitEachChild(node, classElementVisitor, context); - - case SyntaxKind.PropertyDeclaration: - return visitPropertyDeclaration(node as PropertyDeclaration); - - case SyntaxKind.ComputedPropertyName: - return visitComputedPropertyName(node as ComputedPropertyName); - - case SyntaxKind.SemicolonClassElement: - return node; - - default: - return visitor(node); - } + return node; + } + function visitPropertyDeclaration(node: PropertyDeclaration) { + Debug.assert(!some(node.decorators)); + // Create a temporary variable to store a computed property name (if necessary). + // If it's not inlineable, then we emit an expression after the class which assigns + // the property name to the temporary variable. + const expr = getPropertyNameExpressionIfNeeded(node.name, !!node.initializer || !!context.getCompilerOptions().useDefineForClassFields); + if (expr && !isSimpleInlineableExpression(expr)) { + (pendingExpressions || (pendingExpressions = [])).push(expr); } - - function visitVariableStatement(node: VariableStatement) { - const savedPendingStatements = pendingStatements; - pendingStatements = []; - - const visitedNode = visitEachChild(node, visitor, context); - const statement = some(pendingStatements) ? - [visitedNode, ...pendingStatements] : - visitedNode; - - pendingStatements = savedPendingStatements; - return statement; + return undefined; + } + function visitClassDeclaration(node: ClassDeclaration) { + if (!forEach(node.members, isPropertyDeclaration)) { + return visitEachChild(node, visitor, context); } - - function visitComputedPropertyName(name: ComputedPropertyName) { - let node = visitEachChild(name, visitor, context); - if (some(pendingExpressions)) { - const expressions = pendingExpressions; - expressions.push(name.expression); - pendingExpressions = []; - node = updateComputedPropertyName( - node, - inlineExpressions(expressions) - ); - } - return node; + const savedPendingExpressions = pendingExpressions; + pendingExpressions = undefined; + const extendsClauseElement = getEffectiveBaseTypeNode(node); + const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword); + const statements: Statement[] = [ + updateClassDeclaration(node, + /*decorators*/ undefined, node.modifiers, node.name, + /*typeParameters*/ undefined, visitNodes(node.heritageClauses, visitor, isHeritageClause), transformClassMembers(node, isDerivedClass)) + ]; + // Write any pending expressions from elided or moved computed property names + if (some(pendingExpressions)) { + statements.push(createExpressionStatement(inlineExpressions(pendingExpressions))); } - - function visitPropertyDeclaration(node: PropertyDeclaration) { - Debug.assert(!some(node.decorators)); - // Create a temporary variable to store a computed property name (if necessary). - // If it's not inlineable, then we emit an expression after the class which assigns - // the property name to the temporary variable. - const expr = getPropertyNameExpressionIfNeeded(node.name, !!node.initializer || !!context.getCompilerOptions().useDefineForClassFields); - if (expr && !isSimpleInlineableExpression(expr)) { - (pendingExpressions || (pendingExpressions = [])).push(expr); - } - return undefined; + pendingExpressions = savedPendingExpressions; + // Emit static property assignment. Because classDeclaration is lexically evaluated, + // it is safe to emit static property assignment after classDeclaration + // From ES6 specification: + // HasLexicalDeclaration (N) : Determines if the argument identifier has a binding in this environment record that was created using + // a lexical declaration such as a LexicalDeclaration or a ClassDeclaration. + const staticProperties = getProperties(node, /*requireInitializer*/ true, /*isStatic*/ true); + if (some(staticProperties)) { + addPropertyStatements(statements, staticProperties, getInternalName(node)); } - - function visitClassDeclaration(node: ClassDeclaration) { - if (!forEach(node.members, isPropertyDeclaration)) { - return visitEachChild(node, visitor, context); - } - - const savedPendingExpressions = pendingExpressions; - pendingExpressions = undefined; - - const extendsClauseElement = getEffectiveBaseTypeNode(node); - const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword); - - const statements: Statement[] = [ - updateClassDeclaration( - node, - /*decorators*/ undefined, - node.modifiers, - node.name, - /*typeParameters*/ undefined, - visitNodes(node.heritageClauses, visitor, isHeritageClause), - transformClassMembers(node, isDerivedClass) - ) - ]; - - // Write any pending expressions from elided or moved computed property names - if (some(pendingExpressions)) { - statements.push(createExpressionStatement(inlineExpressions(pendingExpressions))); - } - - pendingExpressions = savedPendingExpressions; - - // Emit static property assignment. Because classDeclaration is lexically evaluated, - // it is safe to emit static property assignment after classDeclaration - // From ES6 specification: - // HasLexicalDeclaration (N) : Determines if the argument identifier has a binding in this environment record that was created using - // a lexical declaration such as a LexicalDeclaration or a ClassDeclaration. - const staticProperties = getProperties(node, /*requireInitializer*/ true, /*isStatic*/ true); - if (some(staticProperties)) { - addPropertyStatements(statements, staticProperties, getInternalName(node)); - } - - return statements; + return statements; + } + function visitClassExpression(node: ClassExpression): Expression { + if (!forEach(node.members, isPropertyDeclaration)) { + return visitEachChild(node, visitor, context); } - - function visitClassExpression(node: ClassExpression): Expression { - if (!forEach(node.members, isPropertyDeclaration)) { - return visitEachChild(node, visitor, context); - } - const savedPendingExpressions = pendingExpressions; - pendingExpressions = undefined; - - // If this class expression is a transformation of a decorated class declaration, - // then we want to output the pendingExpressions as statements, not as inlined - // expressions with the class statement. - // - // In this case, we use pendingStatements to produce the same output as the - // class declaration transformation. The VariableStatement visitor will insert - // these statements after the class expression variable statement. - const isDecoratedClassDeclaration = isClassDeclaration(getOriginalNode(node)); - - const staticProperties = getProperties(node, /*requireInitializer*/ true, /*isStatic*/ true); - const extendsClauseElement = getEffectiveBaseTypeNode(node); - const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword); - - const classExpression = updateClassExpression( - node, - node.modifiers, - node.name, - /*typeParameters*/ undefined, - visitNodes(node.heritageClauses, visitor, isHeritageClause), - transformClassMembers(node, isDerivedClass) - ); - - if (some(staticProperties) || some(pendingExpressions)) { - if (isDecoratedClassDeclaration) { - Debug.assertDefined(pendingStatements, "Decorated classes transformed by TypeScript are expected to be within a variable declaration."); - - // Write any pending expressions from elided or moved computed property names - if (pendingStatements && pendingExpressions && some(pendingExpressions)) { - pendingStatements.push(createExpressionStatement(inlineExpressions(pendingExpressions))); - } - pendingExpressions = savedPendingExpressions; - - if (pendingStatements && some(staticProperties)) { - addPropertyStatements(pendingStatements, staticProperties, getInternalName(node)); - } - return classExpression; + const savedPendingExpressions = pendingExpressions; + pendingExpressions = undefined; + // If this class expression is a transformation of a decorated class declaration, + // then we want to output the pendingExpressions as statements, not as inlined + // expressions with the class statement. + // + // In this case, we use pendingStatements to produce the same output as the + // class declaration transformation. The VariableStatement visitor will insert + // these statements after the class expression variable statement. + const isDecoratedClassDeclaration = isClassDeclaration(getOriginalNode(node)); + const staticProperties = getProperties(node, /*requireInitializer*/ true, /*isStatic*/ true); + const extendsClauseElement = getEffectiveBaseTypeNode(node); + const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword); + const classExpression = updateClassExpression(node, node.modifiers, node.name, + /*typeParameters*/ undefined, visitNodes(node.heritageClauses, visitor, isHeritageClause), transformClassMembers(node, isDerivedClass)); + if (some(staticProperties) || some(pendingExpressions)) { + if (isDecoratedClassDeclaration) { + Debug.assertDefined(pendingStatements, "Decorated classes transformed by TypeScript are expected to be within a variable declaration."); + // Write any pending expressions from elided or moved computed property names + if (pendingStatements && pendingExpressions && some(pendingExpressions)) { + pendingStatements.push(createExpressionStatement(inlineExpressions(pendingExpressions))); } - else { - const expressions: Expression[] = []; - const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference; - const temp = createTempVariable(hoistVariableDeclaration, !!isClassWithConstructorReference); - if (isClassWithConstructorReference) { - // record an alias as the class name is not in scope for statics. - enableSubstitutionForClassAliases(); - const alias = getSynthesizedClone(temp); - alias.autoGenerateFlags &= ~GeneratedIdentifierFlags.ReservedInNestedScopes; - classAliases[getOriginalNodeId(node)] = alias; - } - - // To preserve the behavior of the old emitter, we explicitly indent - // the body of a class with static initializers. - setEmitFlags(classExpression, EmitFlags.Indented | getEmitFlags(classExpression)); - expressions.push(startOnNewLine(createAssignment(temp, classExpression))); - // Add any pending expressions leftover from elided or relocated computed property names - addRange(expressions, map(pendingExpressions, startOnNewLine)); - addRange(expressions, generateInitializedPropertyExpressions(staticProperties, temp)); - expressions.push(startOnNewLine(temp)); - - pendingExpressions = savedPendingExpressions; - return inlineExpressions(expressions); + pendingExpressions = savedPendingExpressions; + if (pendingStatements && some(staticProperties)) { + addPropertyStatements(pendingStatements, staticProperties, getInternalName(node)); } + return classExpression; } - - pendingExpressions = savedPendingExpressions; - return classExpression; - } - - function transformClassMembers(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) { - const members: ClassElement[] = []; - const constructor = transformConstructor(node, isDerivedClass); - if (constructor) { - members.push(constructor); + else { + const expressions: Expression[] = []; + const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference; + const temp = createTempVariable(hoistVariableDeclaration, !!isClassWithConstructorReference); + if (isClassWithConstructorReference) { + // record an alias as the class name is not in scope for statics. + enableSubstitutionForClassAliases(); + const alias = getSynthesizedClone(temp); + alias.autoGenerateFlags &= ~GeneratedIdentifierFlags.ReservedInNestedScopes; + classAliases[getOriginalNodeId(node)] = alias; + } + // To preserve the behavior of the old emitter, we explicitly indent + // the body of a class with static initializers. + setEmitFlags(classExpression, EmitFlags.Indented | getEmitFlags(classExpression)); + expressions.push(startOnNewLine(createAssignment(temp, classExpression))); + // Add any pending expressions leftover from elided or relocated computed property names + addRange(expressions, map(pendingExpressions, startOnNewLine)); + addRange(expressions, generateInitializedPropertyExpressions(staticProperties, temp)); + expressions.push(startOnNewLine(temp)); + pendingExpressions = savedPendingExpressions; + return inlineExpressions(expressions); } - addRange(members, visitNodes(node.members, classElementVisitor, isClassElement)); - return setTextRange(createNodeArray(members), /*location*/ node.members); } - - function transformConstructor(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) { - const constructor = visitNode(getFirstConstructorWithBody(node), visitor, isConstructorDeclaration); - const containsProperty = forEach(node.members, m => isInitializedProperty(m, /*requireInitializer*/ !context.getCompilerOptions().useDefineForClassFields)); - if (!containsProperty) { - return constructor; - } - const parameters = visitParameterList(constructor ? constructor.parameters : undefined, visitor, context); - const body = transformConstructorBody(node, constructor, isDerivedClass); - if (!body) { - return undefined; - } - return startOnNewLine( - setOriginalNode( - setTextRange( - createConstructor( - /*decorators*/ undefined, - /*modifiers*/ undefined, - parameters, - body - ), - constructor || node - ), - constructor - ) - ); + pendingExpressions = savedPendingExpressions; + return classExpression; + } + function transformClassMembers(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) { + const members: ClassElement[] = []; + const constructor = transformConstructor(node, isDerivedClass); + if (constructor) { + members.push(constructor); } - - function transformConstructorBody(node: ClassDeclaration | ClassExpression, constructor: ConstructorDeclaration | undefined, isDerivedClass: boolean) { - const useDefineForClassFields = context.getCompilerOptions().useDefineForClassFields; - const properties = getProperties(node, /*requireInitializer*/ !useDefineForClassFields, /*isStatic*/ false); - - // Only generate synthetic constructor when there are property initializers to move. - if (!constructor && !some(properties)) { - return visitFunctionBody(/*node*/ undefined, visitor, context); - } - - resumeLexicalEnvironment(); - - let indexOfFirstStatement = 0; - let statements: Statement[] = []; - - if (!constructor && isDerivedClass) { - // Add a synthetic `super` call: - // - // super(...arguments); - // - statements.push( - createExpressionStatement( - createCall( - createSuper(), - /*typeArguments*/ undefined, - [createSpread(createIdentifier("arguments"))] - ) - ) - ); - } - - if (constructor) { - indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(constructor, statements, visitor); - } - // Add the property initializers. Transforms this: - // - // public x = 1; - // - // Into this: + addRange(members, visitNodes(node.members, classElementVisitor, isClassElement)); + return setTextRange(createNodeArray(members), /*location*/ node.members); + } + function transformConstructor(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) { + const constructor = visitNode(getFirstConstructorWithBody(node), visitor, isConstructorDeclaration); + const containsProperty = forEach(node.members, m => isInitializedProperty(m, /*requireInitializer*/ !context.getCompilerOptions().useDefineForClassFields)); + if (!containsProperty) { + return constructor; + } + const parameters = visitParameterList(constructor ? constructor.parameters : undefined, visitor, context); + const body = transformConstructorBody(node, constructor, isDerivedClass); + if (!body) { + return undefined; + } + return startOnNewLine(setOriginalNode(setTextRange(createConstructor( + /*decorators*/ undefined, + /*modifiers*/ undefined, parameters, body), constructor || node), constructor)); + } + function transformConstructorBody(node: ClassDeclaration | ClassExpression, constructor: ConstructorDeclaration | undefined, isDerivedClass: boolean) { + const useDefineForClassFields = context.getCompilerOptions().useDefineForClassFields; + const properties = getProperties(node, /*requireInitializer*/ !useDefineForClassFields, /*isStatic*/ false); + // Only generate synthetic constructor when there are property initializers to move. + if (!constructor && !some(properties)) { + return visitFunctionBody(/*node*/ undefined, visitor, context); + } + resumeLexicalEnvironment(); + let indexOfFirstStatement = 0; + let statements: Statement[] = []; + if (!constructor && isDerivedClass) { + // Add a synthetic `super` call: // - // constructor() { - // this.x = 1; - // } + // super(...arguments); // - if (constructor?.body) { - let afterParameterProperties = findIndex(constructor.body.statements, s => !isParameterPropertyDeclaration(getOriginalNode(s), constructor), indexOfFirstStatement); - if (afterParameterProperties === -1) { - afterParameterProperties = constructor.body.statements.length; - } - if (afterParameterProperties > indexOfFirstStatement) { - if (!useDefineForClassFields) { - addRange(statements, visitNodes(constructor.body.statements, visitor, isStatement, indexOfFirstStatement, afterParameterProperties - indexOfFirstStatement)); - } - indexOfFirstStatement = afterParameterProperties; - } + statements.push(createExpressionStatement(createCall(createSuper(), + /*typeArguments*/ undefined, [createSpread(createIdentifier("arguments"))]))); + } + if (constructor) { + indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(constructor, statements, visitor); + } + // Add the property initializers. Transforms this: + // + // public x = 1; + // + // Into this: + // + // constructor() { + // this.x = 1; + // } + // + if (constructor?.body) { + let afterParameterProperties = findIndex(constructor.body.statements, s => !isParameterPropertyDeclaration(getOriginalNode(s), constructor), indexOfFirstStatement); + if (afterParameterProperties === -1) { + afterParameterProperties = constructor.body.statements.length; } - addPropertyStatements(statements, properties, createThis()); - - // Add existing statements, skipping the initial super call. - if (constructor) { - addRange(statements, visitNodes(constructor.body!.statements, visitor, isStatement, indexOfFirstStatement)); + if (afterParameterProperties > indexOfFirstStatement) { + if (!useDefineForClassFields) { + addRange(statements, visitNodes(constructor.body.statements, visitor, isStatement, indexOfFirstStatement, afterParameterProperties - indexOfFirstStatement)); + } + indexOfFirstStatement = afterParameterProperties; } - - statements = mergeLexicalEnvironment(statements, endLexicalEnvironment()); - - return setTextRange( - createBlock( - setTextRange( - createNodeArray(statements), - /*location*/ constructor ? constructor.body!.statements : node.members - ), - /*multiLine*/ true - ), - /*location*/ constructor ? constructor.body : undefined - ); } - - /** - * Generates assignment statements for property initializers. - * - * @param properties An array of property declarations to transform. - * @param receiver The receiver on which each property should be assigned. - */ - function addPropertyStatements(statements: Statement[], properties: readonly PropertyDeclaration[], receiver: LeftHandSideExpression) { - for (const property of properties) { - const statement = createExpressionStatement(transformInitializedProperty(property, receiver)); - setSourceMapRange(statement, moveRangePastModifiers(property)); - setCommentRange(statement, property); - setOriginalNode(statement, property); - statements.push(statement); - } + addPropertyStatements(statements, properties, createThis()); + // Add existing statements, skipping the initial super call. + if (constructor) { + addRange(statements, visitNodes(constructor.body!.statements, visitor, isStatement, indexOfFirstStatement)); } - - /** - * Generates assignment expressions for property initializers. - * - * @param properties An array of property declarations to transform. - * @param receiver The receiver on which each property should be assigned. - */ - function generateInitializedPropertyExpressions(properties: readonly PropertyDeclaration[], receiver: LeftHandSideExpression) { - const expressions: Expression[] = []; - for (const property of properties) { - const expression = transformInitializedProperty(property, receiver); - startOnNewLine(expression); - setSourceMapRange(expression, moveRangePastModifiers(property)); - setCommentRange(expression, property); - setOriginalNode(expression, property); - expressions.push(expression); - } - - return expressions; + statements = mergeLexicalEnvironment(statements, endLexicalEnvironment()); + return setTextRange(createBlock(setTextRange(createNodeArray(statements), + /*location*/ constructor ? constructor.body!.statements : node.members), + /*multiLine*/ true), + /*location*/ constructor ? constructor.body : undefined); + } + /** + * Generates assignment statements for property initializers. + * + * @param properties An array of property declarations to transform. + * @param receiver The receiver on which each property should be assigned. + */ + function addPropertyStatements(statements: Statement[], properties: readonly PropertyDeclaration[], receiver: LeftHandSideExpression) { + for (const property of properties) { + const statement = createExpressionStatement(transformInitializedProperty(property, receiver)); + setSourceMapRange(statement, moveRangePastModifiers(property)); + setCommentRange(statement, property); + setOriginalNode(statement, property); + statements.push(statement); } - - /** - * Transforms a property initializer into an assignment statement. - * - * @param property The property declaration. - * @param receiver The object receiving the property assignment. - */ - function transformInitializedProperty(property: PropertyDeclaration, receiver: LeftHandSideExpression) { - // We generate a name here in order to reuse the value cached by the relocated computed name expression (which uses the same generated name) - const emitAssignment = !context.getCompilerOptions().useDefineForClassFields; - const propertyName = isComputedPropertyName(property.name) && !isSimpleInlineableExpression(property.name.expression) - ? updateComputedPropertyName(property.name, getGeneratedNameForNode(property.name)) - : property.name; - - const initializer = property.initializer || emitAssignment ? visitNode(property.initializer, visitor, isExpression) - : hasModifier(getOriginalNode(property), ModifierFlags.ParameterPropertyModifier) && isIdentifier(propertyName) ? propertyName + } + /** + * Generates assignment expressions for property initializers. + * + * @param properties An array of property declarations to transform. + * @param receiver The receiver on which each property should be assigned. + */ + function generateInitializedPropertyExpressions(properties: readonly PropertyDeclaration[], receiver: LeftHandSideExpression) { + const expressions: Expression[] = []; + for (const property of properties) { + const expression = transformInitializedProperty(property, receiver); + startOnNewLine(expression); + setSourceMapRange(expression, moveRangePastModifiers(property)); + setCommentRange(expression, property); + setOriginalNode(expression, property); + expressions.push(expression); + } + return expressions; + } + /** + * Transforms a property initializer into an assignment statement. + * + * @param property The property declaration. + * @param receiver The object receiving the property assignment. + */ + function transformInitializedProperty(property: PropertyDeclaration, receiver: LeftHandSideExpression) { + // We generate a name here in order to reuse the value cached by the relocated computed name expression (which uses the same generated name) + const emitAssignment = !context.getCompilerOptions().useDefineForClassFields; + const propertyName = isComputedPropertyName(property.name) && !isSimpleInlineableExpression(property.name.expression) + ? updateComputedPropertyName(property.name, getGeneratedNameForNode(property.name)) + : property.name; + const initializer = property.initializer || emitAssignment ? visitNode(property.initializer, visitor, isExpression) + : hasModifier(getOriginalNode(property), ModifierFlags.ParameterPropertyModifier) && isIdentifier(propertyName) ? propertyName : createVoidZero(); - if (emitAssignment) { - const memberAccess = createMemberAccessForPropertyName(receiver, propertyName, /*location*/ propertyName); - return createAssignment(memberAccess, initializer); - } - else { - const name = isComputedPropertyName(propertyName) ? propertyName.expression - : isIdentifier(propertyName) ? createStringLiteral(unescapeLeadingUnderscores(propertyName.escapedText)) - : propertyName; - const descriptor = createPropertyDescriptor({ value: initializer, configurable: true, writable: true, enumerable: true }); - return createObjectDefinePropertyCall(receiver, name, descriptor); - } + if (emitAssignment) { + const memberAccess = createMemberAccessForPropertyName(receiver, propertyName, /*location*/ propertyName); + return createAssignment(memberAccess, initializer); } - - function enableSubstitutionForClassAliases() { - if ((enabledSubstitutions & ClassPropertySubstitutionFlags.ClassAliases) === 0) { - enabledSubstitutions |= ClassPropertySubstitutionFlags.ClassAliases; - - // We need to enable substitutions for identifiers. This allows us to - // substitute class names inside of a class declaration. - context.enableSubstitution(SyntaxKind.Identifier); - - // Keep track of class aliases. - classAliases = []; - } + else { + const name = isComputedPropertyName(propertyName) ? propertyName.expression + : isIdentifier(propertyName) ? createStringLiteral(unescapeLeadingUnderscores(propertyName.escapedText)) + : propertyName; + const descriptor = createPropertyDescriptor({ value: initializer, configurable: true, writable: true, enumerable: true }); + return createObjectDefinePropertyCall(receiver, name, descriptor); } - - /** - * Hooks node substitutions. - * - * @param hint The context for the emitter. - * @param node The node to substitute. - */ - function onSubstituteNode(hint: EmitHint, node: Node) { - node = previousOnSubstituteNode(hint, node); - if (hint === EmitHint.Expression) { - return substituteExpression(node as Expression); - } - return node; + } + function enableSubstitutionForClassAliases() { + if ((enabledSubstitutions & ClassPropertySubstitutionFlags.ClassAliases) === 0) { + enabledSubstitutions |= ClassPropertySubstitutionFlags.ClassAliases; + // We need to enable substitutions for identifiers. This allows us to + // substitute class names inside of a class declaration. + context.enableSubstitution(SyntaxKind.Identifier); + // Keep track of class aliases. + classAliases = []; } - - function substituteExpression(node: Expression) { - switch (node.kind) { - case SyntaxKind.Identifier: - return substituteExpressionIdentifier(node as Identifier); - } - return node; + } + /** + * Hooks node substitutions. + * + * @param hint The context for the emitter. + * @param node The node to substitute. + */ + function onSubstituteNode(hint: EmitHint, node: Node) { + node = previousOnSubstituteNode(hint, node); + if (hint === EmitHint.Expression) { + return substituteExpression((node as Expression)); } - - function substituteExpressionIdentifier(node: Identifier): Expression { - return trySubstituteClassAlias(node) || node; + return node; + } + function substituteExpression(node: Expression) { + switch (node.kind) { + case SyntaxKind.Identifier: + return substituteExpressionIdentifier((node as Identifier)); } - - function trySubstituteClassAlias(node: Identifier): Expression | undefined { - if (enabledSubstitutions & ClassPropertySubstitutionFlags.ClassAliases) { - if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ConstructorReferenceInClass) { - // Due to the emit for class decorators, any reference to the class from inside of the class body - // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind - // behavior of class names in ES6. - // Also, when emitting statics for class expressions, we must substitute a class alias for - // constructor references in static property initializers. - const declaration = resolver.getReferencedValueDeclaration(node); - if (declaration) { - const classAlias = classAliases[declaration.id!]; // TODO: GH#18217 - if (classAlias) { - const clone = getSynthesizedClone(classAlias); - setSourceMapRange(clone, node); - setCommentRange(clone, node); - return clone; - } + return node; + } + function substituteExpressionIdentifier(node: Identifier): Expression { + return trySubstituteClassAlias(node) || node; + } + function trySubstituteClassAlias(node: Identifier): Expression | undefined { + if (enabledSubstitutions & ClassPropertySubstitutionFlags.ClassAliases) { + if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ConstructorReferenceInClass) { + // Due to the emit for class decorators, any reference to the class from inside of the class body + // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind + // behavior of class names in ES6. + // Also, when emitting statics for class expressions, we must substitute a class alias for + // constructor references in static property initializers. + const declaration = resolver.getReferencedValueDeclaration(node); + if (declaration) { + const classAlias = classAliases[declaration.id!]; // TODO: GH#18217 + if (classAlias) { + const clone = getSynthesizedClone(classAlias); + setSourceMapRange(clone, node); + setCommentRange(clone, node); + return clone; } } } - - return undefined; } - - - /** - * If the name is a computed property, this function transforms it, then either returns an expression which caches the - * value of the result or the expression itself if the value is either unused or safe to inline into multiple locations - * @param shouldHoist Does the expression need to be reused? (ie, for an initializer or a decorator) - */ - function getPropertyNameExpressionIfNeeded(name: PropertyName, shouldHoist: boolean): Expression | undefined { - if (isComputedPropertyName(name)) { - const expression = visitNode(name.expression, visitor, isExpression); - const innerExpression = skipPartiallyEmittedExpressions(expression); - const inlinable = isSimpleInlineableExpression(innerExpression); - const alreadyTransformed = isAssignmentExpression(innerExpression) && isGeneratedIdentifier(innerExpression.left); - if (!alreadyTransformed && !inlinable && shouldHoist) { - const generatedName = getGeneratedNameForNode(name); - hoistVariableDeclaration(generatedName); - return createAssignment(generatedName, expression); - } - return (inlinable || isIdentifier(innerExpression)) ? undefined : expression; + return undefined; + } + /** + * If the name is a computed property, this function transforms it, then either returns an expression which caches the + * value of the result or the expression itself if the value is either unused or safe to inline into multiple locations + * @param shouldHoist Does the expression need to be reused? (ie, for an initializer or a decorator) + */ + function getPropertyNameExpressionIfNeeded(name: PropertyName, shouldHoist: boolean): Expression | undefined { + if (isComputedPropertyName(name)) { + const expression = visitNode(name.expression, visitor, isExpression); + const innerExpression = skipPartiallyEmittedExpressions(expression); + const inlinable = isSimpleInlineableExpression(innerExpression); + const alreadyTransformed = isAssignmentExpression(innerExpression) && isGeneratedIdentifier(innerExpression.left); + if (!alreadyTransformed && !inlinable && shouldHoist) { + const generatedName = getGeneratedNameForNode(name); + hoistVariableDeclaration(generatedName); + return createAssignment(generatedName, expression); } + return (inlinable || isIdentifier(innerExpression)) ? undefined : expression; } } - } diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index e6413af8e85ea..17a6ec25df842 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -1,1589 +1,1285 @@ +import { EmitHost, EmitResolver, SourceFile, DiagnosticWithLocation, transformNodes, CommentRange, stringContains, Node, getParseTreeNode, SyntaxKind, FunctionLike, ParameterDeclaration, concatenate, getTrailingCommentRanges, skipTrivia, getLeadingCommentRanges, last, getLeadingCommentRangesOfNode, forEach, NodeBuilderFlags, TransformationContext, Debug, GetSymbolAccessibilityDiagnostic, LateVisibilityPaintedStatement, VisitResult, ExportAssignment, Symbol, SymbolTracker, DeclarationName, AnyImportSyntax, createMap, ModuleDeclaration, SymbolFlags, length, getSourceFileOfNode, getOriginalNodeId, SymbolAccessibilityResult, SymbolAccessibility, pushIfUnique, createDiagnosticForNode, getTextOfNode, Diagnostics, declarationNameToString, Bundle, createBundle, map, isExternalOrCommonJsModule, isJsonSourceFile, isSourceFileJS, createNodeArray, visitNodes, updateSourceFileNode, createModuleDeclaration, createModifier, createLiteral, getResolvedExternalModuleName, createModuleBlock, setTextRange, mapDefined, createUnparsedSourceFile, getDirectoryPath, normalizeSlashes, getOutputPathsFor, FileReference, NodeArray, Statement, filter, isAnyImportSyntax, isExternalModule, createEmptyExports, arrayFrom, isImportEqualsDeclaration, isExternalModuleReference, isStringLiteralLike, isImportDeclaration, isStringLiteral, contains, toPath, pathIsRelative, getRelativePathToDirectoryOrUrl, startsWith, hasExtension, UnparsedSource, isUnparsedSource, BindingName, updateArrayBindingPattern, updateObjectBindingPattern, ArrayBindingElement, updateBindingElement, ModifierFlags, TypeNode, createGetSymbolAccessibilityDiagnosticForNode, updateParameter, createToken, FunctionDeclaration, MethodDeclaration, GetAccessorDeclaration, SetAccessorDeclaration, BindingElement, ConstructSignatureDeclaration, VariableDeclaration, MethodSignature, CallSignatureDeclaration, PropertyDeclaration, PropertySignature, hasModifier, visitNode, createKeywordTypeNode, NamedDeclaration, OmittedExpression, isOmittedExpression, isBindingPattern, some, AccessorDeclaration, getThisParameter, isSetAccessorDeclaration, getSetAccessorValueParameter, createParameter, append, emptyArray, TypeParameterDeclaration, isSourceFile, isTypeAliasDeclaration, isModuleDeclaration, isClassDeclaration, isInterfaceDeclaration, isFunctionLike, isIndexSignatureDeclaration, isMappedTypeNode, EntityNameOrEntityNameExpression, hasJSDocNodes, setCommentRange, getCommentRange, ImportEqualsDeclaration, ImportDeclaration, ExportDeclaration, ImportTypeNode, StringLiteral, getExternalModuleNameFromDeclaration, getExternalModuleImportEqualsDeclarationExpression, updateImportEqualsDeclaration, updateExternalModuleReference, updateImportDeclaration, updateImportClause, updateNamedImports, isLateVisibilityPaintedStatement, isArray, needsScopeMarker, isExternalModuleIndicator, isDeclaration, hasDynamicName, Declaration, isSemicolonClassElement, canProduceDiagnostics, isMethodDeclaration, isMethodSignature, createProperty, DeclarationDiagnosticProducing, isTypeQueryNode, isEntityName, isEntityNameExpression, visitEachChild, updateExpressionWithTypeArguments, parenthesizeTypeParameters, updateTypeReferenceNode, updateConstructSignature, createSignatureDeclaration, updateGetAccessor, updateSetAccessor, updateProperty, updatePropertySignature, updateMethodSignature, updateCallSignature, updateIndexSignature, updateTypeScriptVariableDeclaration, updateTypeParameterDeclaration, updateConditionalTypeNode, updateFunctionTypeNode, updateConstructorTypeNode, isLiteralImportTypeNode, updateImportTypeNode, updateLiteralTypeNode, isTypeNode, setOriginalNode, updateExportDeclaration, createOptimisticUniqueName, createVariableDeclaration, createVariableStatement, createVariableDeclarationList, NodeFlags, updateExportAssignment, getMutableClone, createModifiersFromModifierFlags, getModifierFlags, updateTypeAliasDeclaration, isTypeParameterDeclaration, updateInterfaceDeclaration, updateFunctionDeclaration, createIdentifier, NamespaceDeclaration, createSymbolTable, isPropertyAccessExpression, unescapeLeadingUnderscores, updateModuleDeclaration, createExportAssignment, isGlobalScopeAugmentation, updateModuleBlock, isExternalModuleAugmentation, ModuleBody, getFirstConstructorWithBody, compact, flatMap, BindingPattern, Identifier, getEffectiveBaseTypeNode, updateHeritageClause, updateClassDeclaration, updateEnumDeclaration, updateEnumMember, VariableStatement, updateVariableStatement, updateVariableDeclarationList, flatten, createGetSymbolAccessibilityDiagnosticForNodeName, LateBoundDeclaration, isExportAssignment, isExportDeclaration, Modifier, AllAccessorDeclarations, HeritageClause, InterfaceDeclaration, ClassDeclaration, TypeAliasDeclaration, EnumDeclaration, ConstructorDeclaration, IndexSignatureDeclaration, ExpressionWithTypeArguments, TypeReferenceNode, ConditionalTypeNode, FunctionTypeNode, ConstructorTypeNode } from "../ts"; +import { getModuleSpecifier } from "../ts.moduleSpecifiers"; +import * as ts from "../ts"; /*@internal*/ -namespace ts { - export function getDeclarationDiagnostics(host: EmitHost, resolver: EmitResolver, file: SourceFile | undefined): DiagnosticWithLocation[] | undefined { - const compilerOptions = host.getCompilerOptions(); - const result = transformNodes(resolver, host, compilerOptions, file ? [file] : host.getSourceFiles(), [transformDeclarations], /*allowDtsFiles*/ false); - return result.diagnostics; - } - - function hasInternalAnnotation(range: CommentRange, currentSourceFile: SourceFile) { - const comment = currentSourceFile.text.substring(range.pos, range.end); - return stringContains(comment, "@internal"); +export function getDeclarationDiagnostics(host: EmitHost, resolver: EmitResolver, file: SourceFile | undefined): DiagnosticWithLocation[] | undefined { + const compilerOptions = host.getCompilerOptions(); + const result = transformNodes(resolver, host, compilerOptions, file ? [file] : host.getSourceFiles(), [transformDeclarations], /*allowDtsFiles*/ false); + return result.diagnostics; +} +/* @internal */ +function hasInternalAnnotation(range: CommentRange, currentSourceFile: SourceFile) { + const comment = currentSourceFile.text.substring(range.pos, range.end); + return stringContains(comment, "@internal"); +} +/* @internal */ +export function isInternalDeclaration(node: Node, currentSourceFile: SourceFile) { + const parseTreeNode = getParseTreeNode(node); + if (parseTreeNode && parseTreeNode.kind === SyntaxKind.Parameter) { + const paramIdx = (parseTreeNode.parent as FunctionLike).parameters.indexOf((parseTreeNode as ParameterDeclaration)); + const previousSibling = paramIdx > 0 ? (parseTreeNode.parent as FunctionLike).parameters[paramIdx - 1] : undefined; + const text = currentSourceFile.text; + const commentRanges = previousSibling + ? concatenate( + // to handle + // ... parameters, /* @internal */ + // public param: string + getTrailingCommentRanges(text, skipTrivia(text, previousSibling.end + 1, /* stopAfterLineBreak */ false, /* stopAtComments */ true)), getLeadingCommentRanges(text, node.pos)) + : getTrailingCommentRanges(text, skipTrivia(text, node.pos, /* stopAfterLineBreak */ false, /* stopAtComments */ true)); + return commentRanges && commentRanges.length && hasInternalAnnotation(last(commentRanges), currentSourceFile); } - - export function isInternalDeclaration(node: Node, currentSourceFile: SourceFile) { - const parseTreeNode = getParseTreeNode(node); - if (parseTreeNode && parseTreeNode.kind === SyntaxKind.Parameter) { - const paramIdx = (parseTreeNode.parent as FunctionLike).parameters.indexOf(parseTreeNode as ParameterDeclaration); - const previousSibling = paramIdx > 0 ? (parseTreeNode.parent as FunctionLike).parameters[paramIdx - 1] : undefined; - const text = currentSourceFile.text; - const commentRanges = previousSibling - ? concatenate( - // to handle - // ... parameters, /* @internal */ - // public param: string - getTrailingCommentRanges(text, skipTrivia(text, previousSibling.end + 1, /* stopAfterLineBreak */ false, /* stopAtComments */ true)), - getLeadingCommentRanges(text, node.pos) - ) - : getTrailingCommentRanges(text, skipTrivia(text, node.pos, /* stopAfterLineBreak */ false, /* stopAtComments */ true)); - return commentRanges && commentRanges.length && hasInternalAnnotation(last(commentRanges), currentSourceFile); - } - const leadingCommentRanges = parseTreeNode && getLeadingCommentRangesOfNode(parseTreeNode, currentSourceFile); - return !!forEach(leadingCommentRanges, range => { - return hasInternalAnnotation(range, currentSourceFile); - }); + const leadingCommentRanges = parseTreeNode && getLeadingCommentRangesOfNode(parseTreeNode, currentSourceFile); + return !!forEach(leadingCommentRanges, range => { + return hasInternalAnnotation(range, currentSourceFile); + }); +} +/* @internal */ +const declarationEmitNodeBuilderFlags = NodeBuilderFlags.MultilineObjectLiterals | + NodeBuilderFlags.WriteClassExpressionAsTypeLiteral | + NodeBuilderFlags.UseTypeOfFunction | + NodeBuilderFlags.UseStructuralFallback | + NodeBuilderFlags.AllowEmptyTuple | + NodeBuilderFlags.GenerateNamesForShadowedTypeParams | + NodeBuilderFlags.NoTruncation; +/** + * Transforms a ts file into a .d.ts file + * This process requires type information, which is retrieved through the emit resolver. Because of this, + * in many places this transformer assumes it will be operating on parse tree nodes directly. + * This means that _no transforms should be allowed to occur before this one_. + */ +/* @internal */ +export function transformDeclarations(context: TransformationContext) { + const throwDiagnostic = () => Debug.fail("Diagnostic emitted without context"); + let getSymbolAccessibilityDiagnostic: GetSymbolAccessibilityDiagnostic = throwDiagnostic; + let needsDeclare = true; + let isBundledEmit = false; + let resultHasExternalModuleIndicator = false; + let needsScopeFixMarker = false; + let resultHasScopeMarker = false; + let enclosingDeclaration: Node; + let necessaryTypeReferences: ts.Map | undefined; + let lateMarkedStatements: LateVisibilityPaintedStatement[] | undefined; + let lateStatementReplacementMap: ts.Map>; + let suppressNewDiagnosticContexts: boolean; + let exportedModulesFromDeclarationEmit: Symbol[] | undefined; + const host = context.getEmitHost(); + const symbolTracker: SymbolTracker = { + trackSymbol, + reportInaccessibleThisError, + reportInaccessibleUniqueSymbolError, + reportPrivateInBaseOfClassExpression, + reportLikelyUnsafeImportRequiredError, + moduleResolverHost: host, + trackReferencedAmbientModule, + trackExternalModuleSymbolOfImportTypeNode + }; + let errorNameNode: DeclarationName | undefined; + let currentSourceFile: SourceFile; + let refs: ts.Map; + let libs: ts.Map; + let emittedImports: readonly AnyImportSyntax[] | undefined; // must be declared in container so it can be `undefined` while transformer's first pass + const resolver = context.getEmitResolver(); + const options = context.getCompilerOptions(); + const { noResolve, stripInternal } = options; + return transformRoot; + function recordTypeReferenceDirectivesIfNecessary(typeReferenceDirectives: readonly string[] | undefined): void { + if (!typeReferenceDirectives) { + return; + } + necessaryTypeReferences = necessaryTypeReferences || createMap(); + for (const ref of typeReferenceDirectives) { + necessaryTypeReferences.set(ref, true); + } } - - const declarationEmitNodeBuilderFlags = - NodeBuilderFlags.MultilineObjectLiterals | - NodeBuilderFlags.WriteClassExpressionAsTypeLiteral | - NodeBuilderFlags.UseTypeOfFunction | - NodeBuilderFlags.UseStructuralFallback | - NodeBuilderFlags.AllowEmptyTuple | - NodeBuilderFlags.GenerateNamesForShadowedTypeParams | - NodeBuilderFlags.NoTruncation; - - /** - * Transforms a ts file into a .d.ts file - * This process requires type information, which is retrieved through the emit resolver. Because of this, - * in many places this transformer assumes it will be operating on parse tree nodes directly. - * This means that _no transforms should be allowed to occur before this one_. - */ - export function transformDeclarations(context: TransformationContext) { - const throwDiagnostic = () => Debug.fail("Diagnostic emitted without context"); - let getSymbolAccessibilityDiagnostic: GetSymbolAccessibilityDiagnostic = throwDiagnostic; - let needsDeclare = true; - let isBundledEmit = false; - let resultHasExternalModuleIndicator = false; - let needsScopeFixMarker = false; - let resultHasScopeMarker = false; - let enclosingDeclaration: Node; - let necessaryTypeReferences: Map | undefined; - let lateMarkedStatements: LateVisibilityPaintedStatement[] | undefined; - let lateStatementReplacementMap: Map>; - let suppressNewDiagnosticContexts: boolean; - let exportedModulesFromDeclarationEmit: Symbol[] | undefined; - - const host = context.getEmitHost(); - const symbolTracker: SymbolTracker = { - trackSymbol, - reportInaccessibleThisError, - reportInaccessibleUniqueSymbolError, - reportPrivateInBaseOfClassExpression, - reportLikelyUnsafeImportRequiredError, - moduleResolverHost: host, - trackReferencedAmbientModule, - trackExternalModuleSymbolOfImportTypeNode - }; - let errorNameNode: DeclarationName | undefined; - - let currentSourceFile: SourceFile; - let refs: Map; - let libs: Map; - let emittedImports: readonly AnyImportSyntax[] | undefined; // must be declared in container so it can be `undefined` while transformer's first pass - const resolver = context.getEmitResolver(); - const options = context.getCompilerOptions(); - const { noResolve, stripInternal } = options; - return transformRoot; - - function recordTypeReferenceDirectivesIfNecessary(typeReferenceDirectives: readonly string[] | undefined): void { - if (!typeReferenceDirectives) { - return; - } - necessaryTypeReferences = necessaryTypeReferences || createMap(); - for (const ref of typeReferenceDirectives) { - necessaryTypeReferences.set(ref, true); - } + function trackReferencedAmbientModule(node: ModuleDeclaration, symbol: Symbol) { + // If it is visible via `// `, then we should just use that + const directives = resolver.getTypeReferenceDirectivesForSymbol(symbol, SymbolFlags.All); + if (length(directives)) { + return recordTypeReferenceDirectivesIfNecessary(directives); } - - function trackReferencedAmbientModule(node: ModuleDeclaration, symbol: Symbol) { - // If it is visible via `// `, then we should just use that - const directives = resolver.getTypeReferenceDirectivesForSymbol(symbol, SymbolFlags.All); - if (length(directives)) { - return recordTypeReferenceDirectivesIfNecessary(directives); - } - // Otherwise we should emit a path-based reference - const container = getSourceFileOfNode(node); - refs.set("" + getOriginalNodeId(container), container); - } - - function handleSymbolAccessibilityError(symbolAccessibilityResult: SymbolAccessibilityResult) { - if (symbolAccessibilityResult.accessibility === SymbolAccessibility.Accessible) { - // Add aliases back onto the possible imports list if they're not there so we can try them again with updated visibility info - if (symbolAccessibilityResult && symbolAccessibilityResult.aliasesToMakeVisible) { - if (!lateMarkedStatements) { - lateMarkedStatements = symbolAccessibilityResult.aliasesToMakeVisible; - } - else { - for (const ref of symbolAccessibilityResult.aliasesToMakeVisible) { - pushIfUnique(lateMarkedStatements, ref); - } - } + // Otherwise we should emit a path-based reference + const container = getSourceFileOfNode(node); + refs.set("" + getOriginalNodeId(container), container); + } + function handleSymbolAccessibilityError(symbolAccessibilityResult: SymbolAccessibilityResult) { + if (symbolAccessibilityResult.accessibility === SymbolAccessibility.Accessible) { + // Add aliases back onto the possible imports list if they're not there so we can try them again with updated visibility info + if (symbolAccessibilityResult && symbolAccessibilityResult.aliasesToMakeVisible) { + if (!lateMarkedStatements) { + lateMarkedStatements = symbolAccessibilityResult.aliasesToMakeVisible; } - - // TODO: Do all these accessibility checks inside/after the first pass in the checker when declarations are enabled, if possible - } - else { - // Report error - const errorInfo = getSymbolAccessibilityDiagnostic(symbolAccessibilityResult); - if (errorInfo) { - if (errorInfo.typeName) { - context.addDiagnostic(createDiagnosticForNode(symbolAccessibilityResult.errorNode || errorInfo.errorNode, - errorInfo.diagnosticMessage, - getTextOfNode(errorInfo.typeName), - symbolAccessibilityResult.errorSymbolName, - symbolAccessibilityResult.errorModuleName)); - } - else { - context.addDiagnostic(createDiagnosticForNode(symbolAccessibilityResult.errorNode || errorInfo.errorNode, - errorInfo.diagnosticMessage, - symbolAccessibilityResult.errorSymbolName, - symbolAccessibilityResult.errorModuleName)); + else { + for (const ref of symbolAccessibilityResult.aliasesToMakeVisible) { + pushIfUnique(lateMarkedStatements, ref); } } } + // TODO: Do all these accessibility checks inside/after the first pass in the checker when declarations are enabled, if possible } - - function trackExternalModuleSymbolOfImportTypeNode(symbol: Symbol) { - if (!isBundledEmit) { - (exportedModulesFromDeclarationEmit || (exportedModulesFromDeclarationEmit = [])).push(symbol); + else { + // Report error + const errorInfo = getSymbolAccessibilityDiagnostic(symbolAccessibilityResult); + if (errorInfo) { + if (errorInfo.typeName) { + context.addDiagnostic(createDiagnosticForNode(symbolAccessibilityResult.errorNode || errorInfo.errorNode, errorInfo.diagnosticMessage, getTextOfNode(errorInfo.typeName), symbolAccessibilityResult.errorSymbolName, symbolAccessibilityResult.errorModuleName)); + } + else { + context.addDiagnostic(createDiagnosticForNode(symbolAccessibilityResult.errorNode || errorInfo.errorNode, errorInfo.diagnosticMessage, symbolAccessibilityResult.errorSymbolName, symbolAccessibilityResult.errorModuleName)); + } } } - - function trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags) { - if (symbol.flags & SymbolFlags.TypeParameter) return; - handleSymbolAccessibilityError(resolver.isSymbolAccessible(symbol, enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ true)); - recordTypeReferenceDirectivesIfNecessary(resolver.getTypeReferenceDirectivesForSymbol(symbol, meaning)); - } - - function reportPrivateInBaseOfClassExpression(propertyName: string) { - if (errorNameNode) { - context.addDiagnostic( - createDiagnosticForNode(errorNameNode, Diagnostics.Property_0_of_exported_class_expression_may_not_be_private_or_protected, propertyName)); - } + } + function trackExternalModuleSymbolOfImportTypeNode(symbol: Symbol) { + if (!isBundledEmit) { + (exportedModulesFromDeclarationEmit || (exportedModulesFromDeclarationEmit = [])).push(symbol); } - - function reportInaccessibleUniqueSymbolError() { - if (errorNameNode) { - context.addDiagnostic(createDiagnosticForNode(errorNameNode, Diagnostics.The_inferred_type_of_0_references_an_inaccessible_1_type_A_type_annotation_is_necessary, - declarationNameToString(errorNameNode), - "unique symbol")); - } + } + function trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags) { + if (symbol.flags & SymbolFlags.TypeParameter) + return; + handleSymbolAccessibilityError(resolver.isSymbolAccessible(symbol, enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ true)); + recordTypeReferenceDirectivesIfNecessary(resolver.getTypeReferenceDirectivesForSymbol(symbol, meaning)); + } + function reportPrivateInBaseOfClassExpression(propertyName: string) { + if (errorNameNode) { + context.addDiagnostic(createDiagnosticForNode(errorNameNode, Diagnostics.Property_0_of_exported_class_expression_may_not_be_private_or_protected, propertyName)); } - - function reportInaccessibleThisError() { - if (errorNameNode) { - context.addDiagnostic(createDiagnosticForNode(errorNameNode, Diagnostics.The_inferred_type_of_0_references_an_inaccessible_1_type_A_type_annotation_is_necessary, - declarationNameToString(errorNameNode), - "this")); - } + } + function reportInaccessibleUniqueSymbolError() { + if (errorNameNode) { + context.addDiagnostic(createDiagnosticForNode(errorNameNode, Diagnostics.The_inferred_type_of_0_references_an_inaccessible_1_type_A_type_annotation_is_necessary, declarationNameToString(errorNameNode), "unique symbol")); } - - function reportLikelyUnsafeImportRequiredError(specifier: string) { - if (errorNameNode) { - context.addDiagnostic(createDiagnosticForNode(errorNameNode, Diagnostics.The_inferred_type_of_0_cannot_be_named_without_a_reference_to_1_This_is_likely_not_portable_A_type_annotation_is_necessary, - declarationNameToString(errorNameNode), - specifier)); - } + } + function reportInaccessibleThisError() { + if (errorNameNode) { + context.addDiagnostic(createDiagnosticForNode(errorNameNode, Diagnostics.The_inferred_type_of_0_references_an_inaccessible_1_type_A_type_annotation_is_necessary, declarationNameToString(errorNameNode), "this")); } - - function transformDeclarationsForJS(sourceFile: SourceFile, bundled?: boolean) { - const oldDiag = getSymbolAccessibilityDiagnostic; - getSymbolAccessibilityDiagnostic = (s) => ({ - diagnosticMessage: s.errorModuleName - ? Diagnostics.Declaration_emit_for_this_file_requires_using_private_name_0_from_module_1_An_explicit_type_annotation_may_unblock_declaration_emit - : Diagnostics.Declaration_emit_for_this_file_requires_using_private_name_0_An_explicit_type_annotation_may_unblock_declaration_emit, - errorNode: s.errorNode || sourceFile - }); - const result = resolver.getDeclarationStatementsForSourceFile(sourceFile, declarationEmitNodeBuilderFlags, symbolTracker, bundled); - getSymbolAccessibilityDiagnostic = oldDiag; - return result; - } - - function transformRoot(node: Bundle): Bundle; - function transformRoot(node: SourceFile): SourceFile; - function transformRoot(node: SourceFile | Bundle): SourceFile | Bundle; - function transformRoot(node: SourceFile | Bundle) { - if (node.kind === SyntaxKind.SourceFile && node.isDeclarationFile) { - return node; - } - - if (node.kind === SyntaxKind.Bundle) { - isBundledEmit = true; - refs = createMap(); - libs = createMap(); - let hasNoDefaultLib = false; - const bundle = createBundle(map(node.sourceFiles, - sourceFile => { - if (sourceFile.isDeclarationFile) return undefined!; // Omit declaration files from bundle results, too // TODO: GH#18217 - hasNoDefaultLib = hasNoDefaultLib || sourceFile.hasNoDefaultLib; - currentSourceFile = sourceFile; - enclosingDeclaration = sourceFile; - lateMarkedStatements = undefined; - suppressNewDiagnosticContexts = false; - lateStatementReplacementMap = createMap(); - getSymbolAccessibilityDiagnostic = throwDiagnostic; - needsScopeFixMarker = false; - resultHasScopeMarker = false; - collectReferences(sourceFile, refs); - collectLibs(sourceFile, libs); - if (isExternalOrCommonJsModule(sourceFile) || isJsonSourceFile(sourceFile)) { - resultHasExternalModuleIndicator = false; // unused in external module bundle emit (all external modules are within module blocks, therefore are known to be modules) - needsDeclare = false; - const statements = isSourceFileJS(sourceFile) ? createNodeArray(transformDeclarationsForJS(sourceFile, /*bundled*/ true)) : visitNodes(sourceFile.statements, visitDeclarationStatements); - const newFile = updateSourceFileNode(sourceFile, [createModuleDeclaration( - [], - [createModifier(SyntaxKind.DeclareKeyword)], - createLiteral(getResolvedExternalModuleName(context.getEmitHost(), sourceFile)), - createModuleBlock(setTextRange(createNodeArray(transformAndReplaceLatePaintedStatements(statements)), sourceFile.statements)) - )], /*isDeclarationFile*/ true, /*referencedFiles*/ [], /*typeReferences*/ [], /*hasNoDefaultLib*/ false, /*libReferences*/ []); - return newFile; - } - needsDeclare = true; - const updated = isSourceFileJS(sourceFile) ? createNodeArray(transformDeclarationsForJS(sourceFile)) : visitNodes(sourceFile.statements, visitDeclarationStatements); - return updateSourceFileNode(sourceFile, transformAndReplaceLatePaintedStatements(updated), /*isDeclarationFile*/ true, /*referencedFiles*/ [], /*typeReferences*/ [], /*hasNoDefaultLib*/ false, /*libReferences*/ []); - } - ), mapDefined(node.prepends, prepend => { - if (prepend.kind === SyntaxKind.InputFiles) { - const sourceFile = createUnparsedSourceFile(prepend, "dts", stripInternal); - hasNoDefaultLib = hasNoDefaultLib || !!sourceFile.hasNoDefaultLib; - collectReferences(sourceFile, refs); - recordTypeReferenceDirectivesIfNecessary(sourceFile.typeReferenceDirectives); - collectLibs(sourceFile, libs); - return sourceFile; - } - return prepend; - })); - bundle.syntheticFileReferences = []; - bundle.syntheticTypeReferences = getFileReferencesForUsedTypeReferences(); - bundle.syntheticLibReferences = getLibReferences(); - bundle.hasNoDefaultLib = hasNoDefaultLib; - const outputFilePath = getDirectoryPath(normalizeSlashes(getOutputPathsFor(node, host, /*forceDtsPaths*/ true).declarationFilePath!)); - const referenceVisitor = mapReferencesIntoArray(bundle.syntheticFileReferences as FileReference[], outputFilePath); - refs.forEach(referenceVisitor); - return bundle; - } - - // Single source file - needsDeclare = true; - needsScopeFixMarker = false; - resultHasScopeMarker = false; - enclosingDeclaration = node; - currentSourceFile = node; - getSymbolAccessibilityDiagnostic = throwDiagnostic; - isBundledEmit = false; - resultHasExternalModuleIndicator = false; - suppressNewDiagnosticContexts = false; - lateMarkedStatements = undefined; - lateStatementReplacementMap = createMap(); - necessaryTypeReferences = undefined; - refs = collectReferences(currentSourceFile, createMap()); - libs = collectLibs(currentSourceFile, createMap()); - const references: FileReference[] = []; - const outputFilePath = getDirectoryPath(normalizeSlashes(getOutputPathsFor(node, host, /*forceDtsPaths*/ true).declarationFilePath!)); - const referenceVisitor = mapReferencesIntoArray(references, outputFilePath); - let combinedStatements: NodeArray; - if (isSourceFileJS(currentSourceFile)) { - combinedStatements = createNodeArray(transformDeclarationsForJS(node)); - refs.forEach(referenceVisitor); - emittedImports = filter(combinedStatements, isAnyImportSyntax); - } - else { - const statements = visitNodes(node.statements, visitDeclarationStatements); - combinedStatements = setTextRange(createNodeArray(transformAndReplaceLatePaintedStatements(statements)), node.statements); - refs.forEach(referenceVisitor); - emittedImports = filter(combinedStatements, isAnyImportSyntax); - if (isExternalModule(node) && (!resultHasExternalModuleIndicator || (needsScopeFixMarker && !resultHasScopeMarker))) { - combinedStatements = setTextRange(createNodeArray([...combinedStatements, createEmptyExports()]), combinedStatements); + } + function reportLikelyUnsafeImportRequiredError(specifier: string) { + if (errorNameNode) { + context.addDiagnostic(createDiagnosticForNode(errorNameNode, Diagnostics.The_inferred_type_of_0_cannot_be_named_without_a_reference_to_1_This_is_likely_not_portable_A_type_annotation_is_necessary, declarationNameToString(errorNameNode), specifier)); + } + } + function transformDeclarationsForJS(sourceFile: SourceFile, bundled?: boolean) { + const oldDiag = getSymbolAccessibilityDiagnostic; + getSymbolAccessibilityDiagnostic = (s) => ({ + diagnosticMessage: s.errorModuleName + ? Diagnostics.Declaration_emit_for_this_file_requires_using_private_name_0_from_module_1_An_explicit_type_annotation_may_unblock_declaration_emit + : Diagnostics.Declaration_emit_for_this_file_requires_using_private_name_0_An_explicit_type_annotation_may_unblock_declaration_emit, + errorNode: s.errorNode || sourceFile + }); + const result = resolver.getDeclarationStatementsForSourceFile(sourceFile, declarationEmitNodeBuilderFlags, symbolTracker, bundled); + getSymbolAccessibilityDiagnostic = oldDiag; + return result; + } + function transformRoot(node: Bundle): Bundle; + function transformRoot(node: SourceFile): SourceFile; + function transformRoot(node: SourceFile | Bundle): SourceFile | Bundle; + function transformRoot(node: SourceFile | Bundle) { + if (node.kind === SyntaxKind.SourceFile && node.isDeclarationFile) { + return node; + } + if (node.kind === SyntaxKind.Bundle) { + isBundledEmit = true; + refs = createMap(); + libs = createMap(); + let hasNoDefaultLib = false; + const bundle = createBundle(map(node.sourceFiles, sourceFile => { + if (sourceFile.isDeclarationFile) + return undefined!; // Omit declaration files from bundle results, too // TODO: GH#18217 + hasNoDefaultLib = hasNoDefaultLib || sourceFile.hasNoDefaultLib; + currentSourceFile = sourceFile; + enclosingDeclaration = sourceFile; + lateMarkedStatements = undefined; + suppressNewDiagnosticContexts = false; + lateStatementReplacementMap = createMap(); + getSymbolAccessibilityDiagnostic = throwDiagnostic; + needsScopeFixMarker = false; + resultHasScopeMarker = false; + collectReferences(sourceFile, refs); + collectLibs(sourceFile, libs); + if (isExternalOrCommonJsModule(sourceFile) || isJsonSourceFile(sourceFile)) { + resultHasExternalModuleIndicator = false; // unused in external module bundle emit (all external modules are within module blocks, therefore are known to be modules) + needsDeclare = false; + const statements = isSourceFileJS(sourceFile) ? createNodeArray(transformDeclarationsForJS(sourceFile, /*bundled*/ true)) : visitNodes(sourceFile.statements, visitDeclarationStatements); + const newFile = updateSourceFileNode(sourceFile, [createModuleDeclaration([], [createModifier(SyntaxKind.DeclareKeyword)], createLiteral(getResolvedExternalModuleName(context.getEmitHost(), sourceFile)), createModuleBlock(setTextRange(createNodeArray(transformAndReplaceLatePaintedStatements(statements)), sourceFile.statements)))], /*isDeclarationFile*/ true, /*referencedFiles*/ [], /*typeReferences*/ [], /*hasNoDefaultLib*/ false, /*libReferences*/ []); + return newFile; } + needsDeclare = true; + const updated = isSourceFileJS(sourceFile) ? createNodeArray(transformDeclarationsForJS(sourceFile)) : visitNodes(sourceFile.statements, visitDeclarationStatements); + return updateSourceFileNode(sourceFile, transformAndReplaceLatePaintedStatements(updated), /*isDeclarationFile*/ true, /*referencedFiles*/ [], /*typeReferences*/ [], /*hasNoDefaultLib*/ false, /*libReferences*/ []); + }), mapDefined(node.prepends, prepend => { + if (prepend.kind === SyntaxKind.InputFiles) { + const sourceFile = createUnparsedSourceFile(prepend, "dts", stripInternal); + hasNoDefaultLib = hasNoDefaultLib || !!sourceFile.hasNoDefaultLib; + collectReferences(sourceFile, refs); + recordTypeReferenceDirectivesIfNecessary(sourceFile.typeReferenceDirectives); + collectLibs(sourceFile, libs); + return sourceFile; + } + return prepend; + })); + bundle.syntheticFileReferences = []; + bundle.syntheticTypeReferences = getFileReferencesForUsedTypeReferences(); + bundle.syntheticLibReferences = getLibReferences(); + bundle.hasNoDefaultLib = hasNoDefaultLib; + const outputFilePath = getDirectoryPath(normalizeSlashes((getOutputPathsFor(node, host, /*forceDtsPaths*/ true).declarationFilePath!))); + const referenceVisitor = mapReferencesIntoArray((bundle.syntheticFileReferences as FileReference[]), outputFilePath); + refs.forEach(referenceVisitor); + return bundle; + } + // Single source file + needsDeclare = true; + needsScopeFixMarker = false; + resultHasScopeMarker = false; + enclosingDeclaration = node; + currentSourceFile = node; + getSymbolAccessibilityDiagnostic = throwDiagnostic; + isBundledEmit = false; + resultHasExternalModuleIndicator = false; + suppressNewDiagnosticContexts = false; + lateMarkedStatements = undefined; + lateStatementReplacementMap = createMap(); + necessaryTypeReferences = undefined; + refs = collectReferences(currentSourceFile, createMap()); + libs = collectLibs(currentSourceFile, createMap()); + const references: FileReference[] = []; + const outputFilePath = getDirectoryPath(normalizeSlashes((getOutputPathsFor(node, host, /*forceDtsPaths*/ true).declarationFilePath!))); + const referenceVisitor = mapReferencesIntoArray(references, outputFilePath); + let combinedStatements: NodeArray; + if (isSourceFileJS(currentSourceFile)) { + combinedStatements = createNodeArray(transformDeclarationsForJS(node)); + refs.forEach(referenceVisitor); + emittedImports = filter(combinedStatements, isAnyImportSyntax); + } + else { + const statements = visitNodes(node.statements, visitDeclarationStatements); + combinedStatements = setTextRange(createNodeArray(transformAndReplaceLatePaintedStatements(statements)), node.statements); + refs.forEach(referenceVisitor); + emittedImports = filter(combinedStatements, isAnyImportSyntax); + if (isExternalModule(node) && (!resultHasExternalModuleIndicator || (needsScopeFixMarker && !resultHasScopeMarker))) { + combinedStatements = setTextRange(createNodeArray([...combinedStatements, createEmptyExports()]), combinedStatements); } - const updated = updateSourceFileNode(node, combinedStatements, /*isDeclarationFile*/ true, references, getFileReferencesForUsedTypeReferences(), node.hasNoDefaultLib, getLibReferences()); - updated.exportedModulesFromDeclarationEmit = exportedModulesFromDeclarationEmit; - return updated; - - function getLibReferences() { - return map(arrayFrom(libs.keys()), lib => ({ fileName: lib, pos: -1, end: -1 })); - } - - function getFileReferencesForUsedTypeReferences() { - return necessaryTypeReferences ? mapDefined(arrayFrom(necessaryTypeReferences.keys()), getFileReferenceForTypeName) : []; - } - - function getFileReferenceForTypeName(typeName: string): FileReference | undefined { - // Elide type references for which we have imports - if (emittedImports) { - for (const importStatement of emittedImports) { - if (isImportEqualsDeclaration(importStatement) && isExternalModuleReference(importStatement.moduleReference)) { - const expr = importStatement.moduleReference.expression; - if (isStringLiteralLike(expr) && expr.text === typeName) { - return undefined; - } - } - else if (isImportDeclaration(importStatement) && isStringLiteral(importStatement.moduleSpecifier) && importStatement.moduleSpecifier.text === typeName) { + } + const updated = updateSourceFileNode(node, combinedStatements, /*isDeclarationFile*/ true, references, getFileReferencesForUsedTypeReferences(), node.hasNoDefaultLib, getLibReferences()); + updated.exportedModulesFromDeclarationEmit = exportedModulesFromDeclarationEmit; + return updated; + function getLibReferences() { + return map(arrayFrom(libs.keys()), lib => ({ fileName: lib, pos: -1, end: -1 })); + } + function getFileReferencesForUsedTypeReferences() { + return necessaryTypeReferences ? mapDefined(arrayFrom(necessaryTypeReferences.keys()), getFileReferenceForTypeName) : []; + } + function getFileReferenceForTypeName(typeName: string): FileReference | undefined { + // Elide type references for which we have imports + if (emittedImports) { + for (const importStatement of emittedImports) { + if (isImportEqualsDeclaration(importStatement) && isExternalModuleReference(importStatement.moduleReference)) { + const expr = importStatement.moduleReference.expression; + if (isStringLiteralLike(expr) && expr.text === typeName) { return undefined; } } + else if (isImportDeclaration(importStatement) && isStringLiteral(importStatement.moduleSpecifier) && importStatement.moduleSpecifier.text === typeName) { + return undefined; + } } - return { fileName: typeName, pos: -1, end: -1 }; } - - function mapReferencesIntoArray(references: FileReference[], outputFilePath: string): (file: SourceFile) => void { - return file => { - let declFileName: string; - if (file.isDeclarationFile) { // Neither decl files or js should have their refs changed - declFileName = file.fileName; + return { fileName: typeName, pos: -1, end: -1 }; + } + function mapReferencesIntoArray(references: FileReference[], outputFilePath: string): (file: SourceFile) => void { + return file => { + let declFileName: string; + if (file.isDeclarationFile) { // Neither decl files or js should have their refs changed + declFileName = file.fileName; + } + else { + if (isBundledEmit && contains((node as Bundle).sourceFiles, file)) + return; // Omit references to files which are being merged + const paths = getOutputPathsFor(file, host, /*forceDtsPaths*/ true); + declFileName = paths.declarationFilePath || paths.jsFilePath || file.fileName; + } + if (declFileName) { + const specifier = getModuleSpecifier( + // We pathify the baseUrl since we pathify the other paths here, so we can still easily check if the other paths are within the baseUrl + // TODO: Should we _always_ be pathifying the baseUrl as we read it in? + { ...options, baseUrl: options.baseUrl && toPath(options.baseUrl, host.getCurrentDirectory(), host.getCanonicalFileName) }, currentSourceFile, toPath(outputFilePath, host.getCurrentDirectory(), host.getCanonicalFileName), toPath(declFileName, host.getCurrentDirectory(), host.getCanonicalFileName), host, host.getSourceFiles(), + /*preferences*/ undefined, host.redirectTargetsMap); + if (!pathIsRelative(specifier)) { + // If some compiler option/symlink/whatever allows access to the file containing the ambient module declaration + // via a non-relative name, emit a type reference directive to that non-relative name, rather than + // a relative path to the declaration file + recordTypeReferenceDirectivesIfNecessary([specifier]); + return; } - else { - if (isBundledEmit && contains((node as Bundle).sourceFiles, file)) return; // Omit references to files which are being merged - const paths = getOutputPathsFor(file, host, /*forceDtsPaths*/ true); - declFileName = paths.declarationFilePath || paths.jsFilePath || file.fileName; + let fileName = getRelativePathToDirectoryOrUrl(outputFilePath, declFileName, host.getCurrentDirectory(), host.getCanonicalFileName, + /*isAbsolutePathAnUrl*/ false); + if (startsWith(fileName, "./") && hasExtension(fileName)) { + fileName = fileName.substring(2); } - - if (declFileName) { - const specifier = moduleSpecifiers.getModuleSpecifier( - // We pathify the baseUrl since we pathify the other paths here, so we can still easily check if the other paths are within the baseUrl - // TODO: Should we _always_ be pathifying the baseUrl as we read it in? - { ...options, baseUrl: options.baseUrl && toPath(options.baseUrl, host.getCurrentDirectory(), host.getCanonicalFileName) }, - currentSourceFile, - toPath(outputFilePath, host.getCurrentDirectory(), host.getCanonicalFileName), - toPath(declFileName, host.getCurrentDirectory(), host.getCanonicalFileName), - host, - host.getSourceFiles(), - /*preferences*/ undefined, - host.redirectTargetsMap - ); - if (!pathIsRelative(specifier)) { - // If some compiler option/symlink/whatever allows access to the file containing the ambient module declaration - // via a non-relative name, emit a type reference directive to that non-relative name, rather than - // a relative path to the declaration file - recordTypeReferenceDirectivesIfNecessary([specifier]); - return; - } - - let fileName = getRelativePathToDirectoryOrUrl( - outputFilePath, - declFileName, - host.getCurrentDirectory(), - host.getCanonicalFileName, - /*isAbsolutePathAnUrl*/ false - ); - if (startsWith(fileName, "./") && hasExtension(fileName)) { - fileName = fileName.substring(2); - } - - // omit references to files from node_modules (npm may disambiguate module - // references when installing this package, making the path is unreliable). - if (startsWith(fileName, "node_modules/") || fileName.indexOf("/node_modules/") !== -1) { - return; - } - - references.push({ pos: -1, end: -1, fileName }); + // omit references to files from node_modules (npm may disambiguate module + // references when installing this package, making the path is unreliable). + if (startsWith(fileName, "node_modules/") || fileName.indexOf("/node_modules/") !== -1) { + return; } - }; - } - } - - function collectReferences(sourceFile: SourceFile | UnparsedSource, ret: Map) { - if (noResolve || (!isUnparsedSource(sourceFile) && isSourceFileJS(sourceFile))) return ret; - forEach(sourceFile.referencedFiles, f => { - const elem = host.getSourceFileFromReference(sourceFile, f); - if (elem) { - ret.set("" + getOriginalNodeId(elem), elem); + references.push({ pos: -1, end: -1, fileName }); } - }); - return ret; + }; } - - function collectLibs(sourceFile: SourceFile | UnparsedSource, ret: Map) { - forEach(sourceFile.libReferenceDirectives, ref => { - const lib = host.getLibFileFromReference(ref); - if (lib) { - ret.set(ref.fileName.toLocaleLowerCase(), true); - } - }); + } + function collectReferences(sourceFile: SourceFile | UnparsedSource, ret: ts.Map) { + if (noResolve || (!isUnparsedSource(sourceFile) && isSourceFileJS(sourceFile))) return ret; + forEach(sourceFile.referencedFiles, f => { + const elem = host.getSourceFileFromReference(sourceFile, f); + if (elem) { + ret.set("" + getOriginalNodeId(elem), elem); + } + }); + return ret; + } + function collectLibs(sourceFile: SourceFile | UnparsedSource, ret: ts.Map) { + forEach(sourceFile.libReferenceDirectives, ref => { + const lib = host.getLibFileFromReference(ref); + if (lib) { + ret.set(ref.fileName.toLocaleLowerCase(), true); + } + }); + return ret; + } + function filterBindingPatternInitializers(name: BindingName) { + if (name.kind === SyntaxKind.Identifier) { + return name; } - - function filterBindingPatternInitializers(name: BindingName) { - if (name.kind === SyntaxKind.Identifier) { - return name; + else { + if (name.kind === SyntaxKind.ArrayBindingPattern) { + return updateArrayBindingPattern(name, visitNodes(name.elements, visitBindingElement)); } else { - if (name.kind === SyntaxKind.ArrayBindingPattern) { - return updateArrayBindingPattern(name, visitNodes(name.elements, visitBindingElement)); - } - else { - return updateObjectBindingPattern(name, visitNodes(name.elements, visitBindingElement)); - } - } - - function visitBindingElement(elem: T): T; - function visitBindingElement(elem: ArrayBindingElement): ArrayBindingElement { - if (elem.kind === SyntaxKind.OmittedExpression) { - return elem; - } - return updateBindingElement(elem, elem.dotDotDotToken, elem.propertyName, filterBindingPatternInitializers(elem.name), shouldPrintWithInitializer(elem) ? elem.initializer : undefined); + return updateObjectBindingPattern(name, visitNodes(name.elements, visitBindingElement)); } } - - function ensureParameter(p: ParameterDeclaration, modifierMask?: ModifierFlags, type?: TypeNode): ParameterDeclaration { - let oldDiag: typeof getSymbolAccessibilityDiagnostic | undefined; - if (!suppressNewDiagnosticContexts) { - oldDiag = getSymbolAccessibilityDiagnostic; - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(p); - } - const newParam = updateParameter( - p, - /*decorators*/ undefined, - maskModifiers(p, modifierMask), - p.dotDotDotToken, - filterBindingPatternInitializers(p.name), - resolver.isOptionalParameter(p) ? (p.questionToken || createToken(SyntaxKind.QuestionToken)) : undefined, - ensureType(p, type || p.type, /*ignorePrivate*/ true), // Ignore private param props, since this type is going straight back into a param - ensureNoInitializer(p) - ); - if (!suppressNewDiagnosticContexts) { - getSymbolAccessibilityDiagnostic = oldDiag!; + function visitBindingElement(elem: T): T; + function visitBindingElement(elem: ArrayBindingElement): ArrayBindingElement { + if (elem.kind === SyntaxKind.OmittedExpression) { + return elem; } - return newParam; + return updateBindingElement(elem, elem.dotDotDotToken, elem.propertyName, filterBindingPatternInitializers(elem.name), shouldPrintWithInitializer(elem) ? elem.initializer : undefined); } - - function shouldPrintWithInitializer(node: Node) { - return canHaveLiteralInitializer(node) && resolver.isLiteralConstDeclaration(getParseTreeNode(node) as CanHaveLiteralInitializer); // TODO: Make safe + } + function ensureParameter(p: ParameterDeclaration, modifierMask?: ModifierFlags, type?: TypeNode): ParameterDeclaration { + let oldDiag: typeof getSymbolAccessibilityDiagnostic | undefined; + if (!suppressNewDiagnosticContexts) { + oldDiag = getSymbolAccessibilityDiagnostic; + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(p); } - - function ensureNoInitializer(node: CanHaveLiteralInitializer) { - if (shouldPrintWithInitializer(node)) { - return resolver.createLiteralConstValue(getParseTreeNode(node) as CanHaveLiteralInitializer, symbolTracker); // TODO: Make safe - } - return undefined; - } - - type HasInferredType = - | FunctionDeclaration - | MethodDeclaration - | GetAccessorDeclaration - | SetAccessorDeclaration - | BindingElement - | ConstructSignatureDeclaration - | VariableDeclaration - | MethodSignature - | CallSignatureDeclaration - | ParameterDeclaration - | PropertyDeclaration - | PropertySignature; - - function ensureType(node: HasInferredType, type: TypeNode | undefined, ignorePrivate?: boolean): TypeNode | undefined { - if (!ignorePrivate && hasModifier(node, ModifierFlags.Private)) { - // Private nodes emit no types (except private parameter properties, whose parameter types are actually visible) - return; - } - if (shouldPrintWithInitializer(node)) { - // Literal const declarations will have an initializer ensured rather than a type - return; - } - const shouldUseResolverType = node.kind === SyntaxKind.Parameter && - (resolver.isRequiredInitializedParameter(node) || - resolver.isOptionalUninitializedParameterProperty(node)); - if (type && !shouldUseResolverType) { - return visitNode(type, visitDeclarationSubtree); - } - if (!getParseTreeNode(node)) { - return type ? visitNode(type, visitDeclarationSubtree) : createKeywordTypeNode(SyntaxKind.AnyKeyword); - } - if (node.kind === SyntaxKind.SetAccessor) { - // Set accessors with no associated type node (from it's param or get accessor return) are `any` since they are never contextually typed right now - // (The inferred type here will be void, but the old declaration emitter printed `any`, so this replicates that) - return createKeywordTypeNode(SyntaxKind.AnyKeyword); - } - errorNameNode = node.name; - let oldDiag: typeof getSymbolAccessibilityDiagnostic; + const newParam = updateParameter(p, + /*decorators*/ undefined, maskModifiers(p, modifierMask), p.dotDotDotToken, filterBindingPatternInitializers(p.name), resolver.isOptionalParameter(p) ? (p.questionToken || createToken(SyntaxKind.QuestionToken)) : undefined, ensureType(p, type || p.type, /*ignorePrivate*/ true), // Ignore private param props, since this type is going straight back into a param + ensureNoInitializer(p)); + if (!suppressNewDiagnosticContexts) { + getSymbolAccessibilityDiagnostic = oldDiag!; + } + return newParam; + } + function shouldPrintWithInitializer(node: Node) { + return canHaveLiteralInitializer(node) && resolver.isLiteralConstDeclaration((getParseTreeNode(node) as CanHaveLiteralInitializer)); // TODO: Make safe + } + function ensureNoInitializer(node: CanHaveLiteralInitializer) { + if (shouldPrintWithInitializer(node)) { + return resolver.createLiteralConstValue((getParseTreeNode(node) as CanHaveLiteralInitializer), symbolTracker); // TODO: Make safe + } + return undefined; + } + type HasInferredType = FunctionDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | BindingElement | ConstructSignatureDeclaration | VariableDeclaration | MethodSignature | CallSignatureDeclaration | ParameterDeclaration | PropertyDeclaration | PropertySignature; + function ensureType(node: HasInferredType, type: TypeNode | undefined, ignorePrivate?: boolean): TypeNode | undefined { + if (!ignorePrivate && hasModifier(node, ModifierFlags.Private)) { + // Private nodes emit no types (except private parameter properties, whose parameter types are actually visible) + return; + } + if (shouldPrintWithInitializer(node)) { + // Literal const declarations will have an initializer ensured rather than a type + return; + } + const shouldUseResolverType = node.kind === SyntaxKind.Parameter && + (resolver.isRequiredInitializedParameter(node) || + resolver.isOptionalUninitializedParameterProperty(node)); + if (type && !shouldUseResolverType) { + return visitNode(type, visitDeclarationSubtree); + } + if (!getParseTreeNode(node)) { + return type ? visitNode(type, visitDeclarationSubtree) : createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + if (node.kind === SyntaxKind.SetAccessor) { + // Set accessors with no associated type node (from it's param or get accessor return) are `any` since they are never contextually typed right now + // (The inferred type here will be void, but the old declaration emitter printed `any`, so this replicates that) + return createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + errorNameNode = node.name; + let oldDiag: typeof getSymbolAccessibilityDiagnostic; + if (!suppressNewDiagnosticContexts) { + oldDiag = getSymbolAccessibilityDiagnostic; + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(node); + } + if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { + return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); + } + if (node.kind === SyntaxKind.Parameter + || node.kind === SyntaxKind.PropertyDeclaration + || node.kind === SyntaxKind.PropertySignature) { + if (!node.initializer) + return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker, shouldUseResolverType)); + return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker, shouldUseResolverType) || resolver.createTypeOfExpression(node.initializer, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); + } + return cleanup(resolver.createReturnTypeOfSignatureDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); + function cleanup(returnValue: TypeNode | undefined) { + errorNameNode = undefined; if (!suppressNewDiagnosticContexts) { - oldDiag = getSymbolAccessibilityDiagnostic; - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(node); - } - if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { - return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); - } - if (node.kind === SyntaxKind.Parameter - || node.kind === SyntaxKind.PropertyDeclaration - || node.kind === SyntaxKind.PropertySignature) { - if (!node.initializer) return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker, shouldUseResolverType)); - return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker, shouldUseResolverType) || resolver.createTypeOfExpression(node.initializer, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); - } - return cleanup(resolver.createReturnTypeOfSignatureDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); - - function cleanup(returnValue: TypeNode | undefined) { - errorNameNode = undefined; - if (!suppressNewDiagnosticContexts) { - getSymbolAccessibilityDiagnostic = oldDiag; - } - return returnValue || createKeywordTypeNode(SyntaxKind.AnyKeyword); + getSymbolAccessibilityDiagnostic = oldDiag; } + return returnValue || createKeywordTypeNode(SyntaxKind.AnyKeyword); } - - function isDeclarationAndNotVisible(node: NamedDeclaration) { - node = getParseTreeNode(node) as NamedDeclaration; - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.EnumDeclaration: - return !resolver.isDeclarationVisible(node); - // The following should be doing their own visibility checks based on filtering their members - case SyntaxKind.VariableDeclaration: - return !getBindingNameVisible(node as VariableDeclaration); - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ExportDeclaration: - case SyntaxKind.ExportAssignment: - return false; - } + } + function isDeclarationAndNotVisible(node: NamedDeclaration) { + node = (getParseTreeNode(node) as NamedDeclaration); + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.EnumDeclaration: + return !resolver.isDeclarationVisible(node); + // The following should be doing their own visibility checks based on filtering their members + case SyntaxKind.VariableDeclaration: + return !getBindingNameVisible((node as VariableDeclaration)); + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportDeclaration: + case SyntaxKind.ExportAssignment: + return false; + } + return false; + } + function getBindingNameVisible(elem: BindingElement | VariableDeclaration | OmittedExpression): boolean { + if (isOmittedExpression(elem)) { return false; } - - function getBindingNameVisible(elem: BindingElement | VariableDeclaration | OmittedExpression): boolean { - if (isOmittedExpression(elem)) { - return false; - } - if (isBindingPattern(elem.name)) { - // If any child binding pattern element has been marked visible (usually by collect linked aliases), then this is visible - return some(elem.name.elements, getBindingNameVisible); - } - else { - return resolver.isDeclarationVisible(elem); - } + if (isBindingPattern(elem.name)) { + // If any child binding pattern element has been marked visible (usually by collect linked aliases), then this is visible + return some(elem.name.elements, getBindingNameVisible); } - - function updateParamsList(node: Node, params: NodeArray, modifierMask?: ModifierFlags) { - if (hasModifier(node, ModifierFlags.Private)) { - return undefined!; // TODO: GH#18217 - } - const newParams = map(params, p => ensureParameter(p, modifierMask)); - if (!newParams) { - return undefined!; // TODO: GH#18217 + else { + return resolver.isDeclarationVisible(elem); + } + } + function updateParamsList(node: Node, params: NodeArray, modifierMask?: ModifierFlags) { + if (hasModifier(node, ModifierFlags.Private)) { + return undefined!; // TODO: GH#18217 + } + const newParams = map(params, p => ensureParameter(p, modifierMask)); + if (!newParams) { + return undefined!; // TODO: GH#18217 + } + return createNodeArray(newParams, params.hasTrailingComma); + } + function updateAccessorParamsList(input: AccessorDeclaration, isPrivate: boolean) { + let newParams: ParameterDeclaration[] | undefined; + if (!isPrivate) { + const thisParameter = getThisParameter(input); + if (thisParameter) { + newParams = [ensureParameter(thisParameter)]; } - return createNodeArray(newParams, params.hasTrailingComma); } - - function updateAccessorParamsList(input: AccessorDeclaration, isPrivate: boolean) { - let newParams: ParameterDeclaration[] | undefined; + if (isSetAccessorDeclaration(input)) { + let newValueParameter: ParameterDeclaration | undefined; if (!isPrivate) { - const thisParameter = getThisParameter(input); - if (thisParameter) { - newParams = [ensureParameter(thisParameter)]; + const valueParameter = getSetAccessorValueParameter(input); + if (valueParameter) { + const accessorType = getTypeAnnotationFromAllAccessorDeclarations(input, resolver.getAllAccessorDeclarations(input)); + newValueParameter = ensureParameter(valueParameter, /*modifierMask*/ undefined, accessorType); } } - if (isSetAccessorDeclaration(input)) { - let newValueParameter: ParameterDeclaration | undefined; - if (!isPrivate) { - const valueParameter = getSetAccessorValueParameter(input); - if (valueParameter) { - const accessorType = getTypeAnnotationFromAllAccessorDeclarations(input, resolver.getAllAccessorDeclarations(input)); - newValueParameter = ensureParameter(valueParameter, /*modifierMask*/ undefined, accessorType); - } - } - if (!newValueParameter) { - newValueParameter = createParameter( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - "value" - ); - } - newParams = append(newParams, newValueParameter); + if (!newValueParameter) { + newValueParameter = createParameter( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, "value"); } - return createNodeArray(newParams || emptyArray) as NodeArray; - } - - function ensureTypeParams(node: Node, params: NodeArray | undefined) { - return hasModifier(node, ModifierFlags.Private) ? undefined : visitNodes(params, visitDeclarationSubtree); - } - - function isEnclosingDeclaration(node: Node) { - return isSourceFile(node) - || isTypeAliasDeclaration(node) - || isModuleDeclaration(node) - || isClassDeclaration(node) - || isInterfaceDeclaration(node) - || isFunctionLike(node) - || isIndexSignatureDeclaration(node) - || isMappedTypeNode(node); - } - - function checkEntityNameVisibility(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node) { - const visibilityResult = resolver.isEntityNameVisible(entityName, enclosingDeclaration); - handleSymbolAccessibilityError(visibilityResult); - recordTypeReferenceDirectivesIfNecessary(resolver.getTypeReferenceDirectivesForEntityName(entityName)); - } - - function preserveJsDoc(updated: T, original: Node): T { - if (hasJSDocNodes(updated) && hasJSDocNodes(original)) { - updated.jsDoc = original.jsDoc; + newParams = append(newParams, newValueParameter); + } + return createNodeArray(newParams || emptyArray) as NodeArray; + } + function ensureTypeParams(node: Node, params: NodeArray | undefined) { + return hasModifier(node, ModifierFlags.Private) ? undefined : visitNodes(params, visitDeclarationSubtree); + } + function isEnclosingDeclaration(node: Node) { + return isSourceFile(node) + || isTypeAliasDeclaration(node) + || isModuleDeclaration(node) + || isClassDeclaration(node) + || isInterfaceDeclaration(node) + || isFunctionLike(node) + || isIndexSignatureDeclaration(node) + || isMappedTypeNode(node); + } + function checkEntityNameVisibility(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node) { + const visibilityResult = resolver.isEntityNameVisible(entityName, enclosingDeclaration); + handleSymbolAccessibilityError(visibilityResult); + recordTypeReferenceDirectivesIfNecessary(resolver.getTypeReferenceDirectivesForEntityName(entityName)); + } + function preserveJsDoc(updated: T, original: Node): T { + if (hasJSDocNodes(updated) && hasJSDocNodes(original)) { + updated.jsDoc = original.jsDoc; + } + return setCommentRange(updated, getCommentRange(original)); + } + function rewriteModuleSpecifier(parent: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration | ImportTypeNode, input: T | undefined): T | StringLiteral { + if (!input) + return undefined!; // TODO: GH#18217 + resultHasExternalModuleIndicator = resultHasExternalModuleIndicator || (parent.kind !== SyntaxKind.ModuleDeclaration && parent.kind !== SyntaxKind.ImportType); + if (isStringLiteralLike(input)) { + if (isBundledEmit) { + const newName = getExternalModuleNameFromDeclaration(context.getEmitHost(), resolver, parent); + if (newName) { + return createLiteral(newName); + } } - return setCommentRange(updated, getCommentRange(original)); - } - - function rewriteModuleSpecifier(parent: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration | ImportTypeNode, input: T | undefined): T | StringLiteral { - if (!input) return undefined!; // TODO: GH#18217 - resultHasExternalModuleIndicator = resultHasExternalModuleIndicator || (parent.kind !== SyntaxKind.ModuleDeclaration && parent.kind !== SyntaxKind.ImportType); - if (isStringLiteralLike(input)) { - if (isBundledEmit) { - const newName = getExternalModuleNameFromDeclaration(context.getEmitHost(), resolver, parent); - if (newName) { - return createLiteral(newName); - } + else { + const symbol = resolver.getSymbolOfExternalModuleSpecifier(input); + if (symbol) { + (exportedModulesFromDeclarationEmit || (exportedModulesFromDeclarationEmit = [])).push(symbol); } - else { - const symbol = resolver.getSymbolOfExternalModuleSpecifier(input); - if (symbol) { - (exportedModulesFromDeclarationEmit || (exportedModulesFromDeclarationEmit = [])).push(symbol); + } + } + return input; + } + function transformImportEqualsDeclaration(decl: ImportEqualsDeclaration) { + if (!resolver.isDeclarationVisible(decl)) + return; + if (decl.moduleReference.kind === SyntaxKind.ExternalModuleReference) { + // Rewrite external module names if necessary + const specifier = getExternalModuleImportEqualsDeclarationExpression(decl); + return updateImportEqualsDeclaration(decl, + /*decorators*/ undefined, decl.modifiers, decl.name, updateExternalModuleReference(decl.moduleReference, rewriteModuleSpecifier(decl, specifier))); + } + else { + const oldDiag = getSymbolAccessibilityDiagnostic; + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(decl); + checkEntityNameVisibility(decl.moduleReference, enclosingDeclaration); + getSymbolAccessibilityDiagnostic = oldDiag; + return decl; + } + } + function transformImportDeclaration(decl: ImportDeclaration) { + if (!decl.importClause) { + // import "mod" - possibly needed for side effects? (global interface patches, module augmentations, etc) + return updateImportDeclaration(decl, + /*decorators*/ undefined, decl.modifiers, decl.importClause, rewriteModuleSpecifier(decl, decl.moduleSpecifier)); + } + // The `importClause` visibility corresponds to the default's visibility. + const visibleDefaultBinding = decl.importClause && decl.importClause.name && resolver.isDeclarationVisible(decl.importClause) ? decl.importClause.name : undefined; + if (!decl.importClause.namedBindings) { + // No named bindings (either namespace or list), meaning the import is just default or should be elided + return visibleDefaultBinding && updateImportDeclaration(decl, /*decorators*/ undefined, decl.modifiers, updateImportClause(decl.importClause, visibleDefaultBinding, + /*namedBindings*/ undefined), rewriteModuleSpecifier(decl, decl.moduleSpecifier)); + } + if (decl.importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { + // Namespace import (optionally with visible default) + const namedBindings = resolver.isDeclarationVisible(decl.importClause.namedBindings) ? decl.importClause.namedBindings : /*namedBindings*/ undefined; + return visibleDefaultBinding || namedBindings ? updateImportDeclaration(decl, /*decorators*/ undefined, decl.modifiers, updateImportClause(decl.importClause, visibleDefaultBinding, namedBindings), rewriteModuleSpecifier(decl, decl.moduleSpecifier)) : undefined; + } + // Named imports (optionally with visible default) + const bindingList = mapDefined(decl.importClause.namedBindings.elements, b => resolver.isDeclarationVisible(b) ? b : undefined); + if ((bindingList && bindingList.length) || visibleDefaultBinding) { + return updateImportDeclaration(decl, + /*decorators*/ undefined, decl.modifiers, updateImportClause(decl.importClause, visibleDefaultBinding, bindingList && bindingList.length ? updateNamedImports(decl.importClause.namedBindings, bindingList) : undefined), rewriteModuleSpecifier(decl, decl.moduleSpecifier)); + } + // Nothing visible + } + function transformAndReplaceLatePaintedStatements(statements: NodeArray): NodeArray { + // This is a `while` loop because `handleSymbolAccessibilityError` can see additional import aliases marked as visible during + // error handling which must now be included in the output and themselves checked for errors. + // For example: + // ``` + // module A { + // export module Q {} + // import B = Q; + // import C = B; + // export import D = C; + // } + // ``` + // In such a scenario, only Q and D are initially visible, but we don't consider imports as private names - instead we say they if they are referenced they must + // be recorded. So while checking D's visibility we mark C as visible, then we must check C which in turn marks B, completing the chain of + // dependent imports and allowing a valid declaration file output. Today, this dependent alias marking only happens for internal import aliases. + while (length(lateMarkedStatements)) { + const i = lateMarkedStatements!.shift()!; + if (!isLateVisibilityPaintedStatement(i)) { + return Debug.fail(`Late replaced statement was found which is not handled by the declaration transformer!: ${(ts as any).SyntaxKind ? (ts as any).SyntaxKind[(i as any).kind] : (i as any).kind}`); + } + const priorNeedsDeclare = needsDeclare; + needsDeclare = i.parent && isSourceFile(i.parent) && !(isExternalModule(i.parent) && isBundledEmit); + const result = transformTopLevelDeclaration(i); + needsDeclare = priorNeedsDeclare; + lateStatementReplacementMap.set("" + getOriginalNodeId(i), result); + } + // And lastly, we need to get the final form of all those indetermine import declarations from before and add them to the output list + // (and remove them from the set to examine for outter declarations) + return visitNodes(statements, visitLateVisibilityMarkedStatements); + function visitLateVisibilityMarkedStatements(statement: Statement) { + if (isLateVisibilityPaintedStatement(statement)) { + const key = "" + getOriginalNodeId(statement); + if (lateStatementReplacementMap.has(key)) { + const result = lateStatementReplacementMap.get(key); + lateStatementReplacementMap.delete(key); + if (result) { + if (isArray(result) ? some(result, needsScopeMarker) : needsScopeMarker(result)) { + // Top-level declarations in .d.ts files are always considered exported even without a modifier unless there's an export assignment or specifier + needsScopeFixMarker = true; + } + if (isSourceFile(statement.parent) && (isArray(result) ? some(result, isExternalModuleIndicator) : isExternalModuleIndicator(result))) { + resultHasExternalModuleIndicator = true; + } } + return result; } } - return input; + return statement; } - - function transformImportEqualsDeclaration(decl: ImportEqualsDeclaration) { - if (!resolver.isDeclarationVisible(decl)) return; - if (decl.moduleReference.kind === SyntaxKind.ExternalModuleReference) { - // Rewrite external module names if necessary - const specifier = getExternalModuleImportEqualsDeclarationExpression(decl); - return updateImportEqualsDeclaration( - decl, - /*decorators*/ undefined, - decl.modifiers, - decl.name, - updateExternalModuleReference(decl.moduleReference, rewriteModuleSpecifier(decl, specifier)) - ); - } - else { - const oldDiag = getSymbolAccessibilityDiagnostic; - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(decl); - checkEntityNameVisibility(decl.moduleReference, enclosingDeclaration); - getSymbolAccessibilityDiagnostic = oldDiag; - return decl; + } + function visitDeclarationSubtree(input: Node): VisitResult { + if (shouldStripInternal(input)) + return; + if (isDeclaration(input)) { + if (isDeclarationAndNotVisible(input)) + return; + if (hasDynamicName(input) && !resolver.isLateBound((getParseTreeNode(input) as Declaration))) { + return; } } - - function transformImportDeclaration(decl: ImportDeclaration) { - if (!decl.importClause) { - // import "mod" - possibly needed for side effects? (global interface patches, module augmentations, etc) - return updateImportDeclaration( - decl, - /*decorators*/ undefined, - decl.modifiers, - decl.importClause, - rewriteModuleSpecifier(decl, decl.moduleSpecifier) - ); - } - // The `importClause` visibility corresponds to the default's visibility. - const visibleDefaultBinding = decl.importClause && decl.importClause.name && resolver.isDeclarationVisible(decl.importClause) ? decl.importClause.name : undefined; - if (!decl.importClause.namedBindings) { - // No named bindings (either namespace or list), meaning the import is just default or should be elided - return visibleDefaultBinding && updateImportDeclaration(decl, /*decorators*/ undefined, decl.modifiers, updateImportClause( - decl.importClause, - visibleDefaultBinding, - /*namedBindings*/ undefined - ), rewriteModuleSpecifier(decl, decl.moduleSpecifier)); - } - if (decl.importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { - // Namespace import (optionally with visible default) - const namedBindings = resolver.isDeclarationVisible(decl.importClause.namedBindings) ? decl.importClause.namedBindings : /*namedBindings*/ undefined; - return visibleDefaultBinding || namedBindings ? updateImportDeclaration(decl, /*decorators*/ undefined, decl.modifiers, updateImportClause( - decl.importClause, - visibleDefaultBinding, - namedBindings - ), rewriteModuleSpecifier(decl, decl.moduleSpecifier)) : undefined; - } - // Named imports (optionally with visible default) - const bindingList = mapDefined(decl.importClause.namedBindings.elements, b => resolver.isDeclarationVisible(b) ? b : undefined); - if ((bindingList && bindingList.length) || visibleDefaultBinding) { - return updateImportDeclaration( - decl, - /*decorators*/ undefined, - decl.modifiers, - updateImportClause( - decl.importClause, - visibleDefaultBinding, - bindingList && bindingList.length ? updateNamedImports(decl.importClause.namedBindings, bindingList) : undefined - ), - rewriteModuleSpecifier(decl, decl.moduleSpecifier) - ); + // Elide implementation signatures from overload sets + if (isFunctionLike(input) && resolver.isImplementationOfOverload(input)) + return; + // Elide semicolon class statements + if (isSemicolonClassElement(input)) + return; + let previousEnclosingDeclaration: typeof enclosingDeclaration; + if (isEnclosingDeclaration(input)) { + previousEnclosingDeclaration = enclosingDeclaration; + enclosingDeclaration = (input as Declaration); + } + const oldDiag = getSymbolAccessibilityDiagnostic; + // Setup diagnostic-related flags before first potential `cleanup` call, otherwise + // We'd see a TDZ violation at runtime + const canProduceDiagnostic = canProduceDiagnostics(input); + const oldWithinObjectLiteralType = suppressNewDiagnosticContexts; + let shouldEnterSuppressNewDiagnosticsContextContext = ((input.kind === SyntaxKind.TypeLiteral || input.kind === SyntaxKind.MappedType) && input.parent.kind !== SyntaxKind.TypeAliasDeclaration); + // Emit methods which are private as properties with no type information + if (isMethodDeclaration(input) || isMethodSignature(input)) { + if (hasModifier(input, ModifierFlags.Private)) { + if (input.symbol && input.symbol.declarations && input.symbol.declarations[0] !== input) + return; // Elide all but the first overload + return cleanup(createProperty(/*decorators*/ undefined, ensureModifiers(input), input.name, /*questionToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined)); } - // Nothing visible - } - - function transformAndReplaceLatePaintedStatements(statements: NodeArray): NodeArray { - // This is a `while` loop because `handleSymbolAccessibilityError` can see additional import aliases marked as visible during - // error handling which must now be included in the output and themselves checked for errors. - // For example: - // ``` - // module A { - // export module Q {} - // import B = Q; - // import C = B; - // export import D = C; - // } - // ``` - // In such a scenario, only Q and D are initially visible, but we don't consider imports as private names - instead we say they if they are referenced they must - // be recorded. So while checking D's visibility we mark C as visible, then we must check C which in turn marks B, completing the chain of - // dependent imports and allowing a valid declaration file output. Today, this dependent alias marking only happens for internal import aliases. - while (length(lateMarkedStatements)) { - const i = lateMarkedStatements!.shift()!; - if (!isLateVisibilityPaintedStatement(i)) { - return Debug.fail(`Late replaced statement was found which is not handled by the declaration transformer!: ${(ts as any).SyntaxKind ? (ts as any).SyntaxKind[(i as any).kind] : (i as any).kind}`); + } + if (canProduceDiagnostic && !suppressNewDiagnosticContexts) { + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode((input as DeclarationDiagnosticProducing)); + } + if (isTypeQueryNode(input)) { + checkEntityNameVisibility(input.exprName, enclosingDeclaration); + } + if (shouldEnterSuppressNewDiagnosticsContextContext) { + // We stop making new diagnostic contexts within object literal types. Unless it's an object type on the RHS of a type alias declaration. Then we do. + suppressNewDiagnosticContexts = true; + } + if (isProcessedComponent(input)) { + switch (input.kind) { + case SyntaxKind.ExpressionWithTypeArguments: { + if ((isEntityName(input.expression) || isEntityNameExpression(input.expression))) { + checkEntityNameVisibility(input.expression, enclosingDeclaration); + } + const node = visitEachChild(input, visitDeclarationSubtree, context); + return cleanup(updateExpressionWithTypeArguments(node, parenthesizeTypeParameters(node.typeArguments), node.expression)); } - const priorNeedsDeclare = needsDeclare; - needsDeclare = i.parent && isSourceFile(i.parent) && !(isExternalModule(i.parent) && isBundledEmit); - const result = transformTopLevelDeclaration(i); - needsDeclare = priorNeedsDeclare; - lateStatementReplacementMap.set("" + getOriginalNodeId(i), result); - } - - // And lastly, we need to get the final form of all those indetermine import declarations from before and add them to the output list - // (and remove them from the set to examine for outter declarations) - return visitNodes(statements, visitLateVisibilityMarkedStatements); - - function visitLateVisibilityMarkedStatements(statement: Statement) { - if (isLateVisibilityPaintedStatement(statement)) { - const key = "" + getOriginalNodeId(statement); - if (lateStatementReplacementMap.has(key)) { - const result = lateStatementReplacementMap.get(key); - lateStatementReplacementMap.delete(key); - if (result) { - if (isArray(result) ? some(result, needsScopeMarker) : needsScopeMarker(result)) { - // Top-level declarations in .d.ts files are always considered exported even without a modifier unless there's an export assignment or specifier - needsScopeFixMarker = true; - } - if (isSourceFile(statement.parent) && (isArray(result) ? some(result, isExternalModuleIndicator) : isExternalModuleIndicator(result))) { - resultHasExternalModuleIndicator = true; - } - } - return result; + case SyntaxKind.TypeReference: { + checkEntityNameVisibility(input.typeName, enclosingDeclaration); + const node = visitEachChild(input, visitDeclarationSubtree, context); + return cleanup(updateTypeReferenceNode(node, node.typeName, parenthesizeTypeParameters(node.typeArguments))); + } + case SyntaxKind.ConstructSignature: + return cleanup(updateConstructSignature(input, ensureTypeParams(input, input.typeParameters), updateParamsList(input, input.parameters), ensureType(input, input.type))); + case SyntaxKind.Constructor: { + const isPrivate = hasModifier(input, ModifierFlags.Private); + // A constructor declaration may not have a type annotation + const ctor = createSignatureDeclaration(SyntaxKind.Constructor, isPrivate ? undefined : ensureTypeParams(input, input.typeParameters), + // TODO: GH#18217 + isPrivate ? undefined! : updateParamsList(input, input.parameters, ModifierFlags.None), + /*type*/ undefined); + ctor.modifiers = createNodeArray(ensureModifiers(input)); + return cleanup(ctor); + } + case SyntaxKind.MethodDeclaration: { + const sig = (createSignatureDeclaration(SyntaxKind.MethodSignature, ensureTypeParams(input, input.typeParameters), updateParamsList(input, input.parameters), ensureType(input, input.type)) as MethodSignature); + sig.name = input.name; + sig.modifiers = createNodeArray(ensureModifiers(input)); + sig.questionToken = input.questionToken; + return cleanup(sig); + } + case SyntaxKind.GetAccessor: { + const isPrivate = hasModifier(input, ModifierFlags.Private); + const accessorType = getTypeAnnotationFromAllAccessorDeclarations(input, resolver.getAllAccessorDeclarations(input)); + return cleanup(updateGetAccessor(input, + /*decorators*/ undefined, ensureModifiers(input), input.name, updateAccessorParamsList(input, isPrivate), !isPrivate ? ensureType(input, accessorType) : undefined, + /*body*/ undefined)); + } + case SyntaxKind.SetAccessor: { + return cleanup(updateSetAccessor(input, + /*decorators*/ undefined, ensureModifiers(input), input.name, updateAccessorParamsList(input, hasModifier(input, ModifierFlags.Private)), + /*body*/ undefined)); + } + case SyntaxKind.PropertyDeclaration: + return cleanup(updateProperty(input, + /*decorators*/ undefined, ensureModifiers(input), input.name, input.questionToken, !hasModifier(input, ModifierFlags.Private) ? ensureType(input, input.type) : undefined, ensureNoInitializer(input))); + case SyntaxKind.PropertySignature: + return cleanup(updatePropertySignature(input, ensureModifiers(input), input.name, input.questionToken, !hasModifier(input, ModifierFlags.Private) ? ensureType(input, input.type) : undefined, ensureNoInitializer(input))); + case SyntaxKind.MethodSignature: { + return cleanup(updateMethodSignature(input, ensureTypeParams(input, input.typeParameters), updateParamsList(input, input.parameters), ensureType(input, input.type), input.name, input.questionToken)); + } + case SyntaxKind.CallSignature: { + return cleanup(updateCallSignature(input, ensureTypeParams(input, input.typeParameters), updateParamsList(input, input.parameters), ensureType(input, input.type))); + } + case SyntaxKind.IndexSignature: { + return cleanup(updateIndexSignature(input, + /*decorators*/ undefined, ensureModifiers(input), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree) || createKeywordTypeNode(SyntaxKind.AnyKeyword))); + } + case SyntaxKind.VariableDeclaration: { + if (isBindingPattern(input.name)) { + return recreateBindingPattern(input.name); } + shouldEnterSuppressNewDiagnosticsContextContext = true; + suppressNewDiagnosticContexts = true; // Variable declaration types also suppress new diagnostic contexts, provided the contexts wouldn't be made for binding pattern types + return cleanup(updateTypeScriptVariableDeclaration(input, input.name, /*exclaimationToken*/ undefined, ensureType(input, input.type), ensureNoInitializer(input))); + } + case SyntaxKind.TypeParameter: { + if (isPrivateMethodTypeParameter(input) && (input.default || input.constraint)) { + return cleanup(updateTypeParameterDeclaration(input, input.name, /*constraint*/ undefined, /*defaultType*/ undefined)); + } + return cleanup(visitEachChild(input, visitDeclarationSubtree, context)); + } + case SyntaxKind.ConditionalType: { + // We have to process conditional types in a special way because for visibility purposes we need to push a new enclosingDeclaration + // just for the `infer` types in the true branch. It's an implicit declaration scope that only applies to _part_ of the type. + const checkType = visitNode(input.checkType, visitDeclarationSubtree); + const extendsType = visitNode(input.extendsType, visitDeclarationSubtree); + const oldEnclosingDecl = enclosingDeclaration; + enclosingDeclaration = input.trueType; + const trueType = visitNode(input.trueType, visitDeclarationSubtree); + enclosingDeclaration = oldEnclosingDecl; + const falseType = visitNode(input.falseType, visitDeclarationSubtree); + return cleanup(updateConditionalTypeNode(input, checkType, extendsType, trueType, falseType)); + } + case SyntaxKind.FunctionType: { + return cleanup(updateFunctionTypeNode(input, visitNodes(input.typeParameters, visitDeclarationSubtree), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree))); + } + case SyntaxKind.ConstructorType: { + return cleanup(updateConstructorTypeNode(input, visitNodes(input.typeParameters, visitDeclarationSubtree), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree))); + } + case SyntaxKind.ImportType: { + if (!isLiteralImportTypeNode(input)) + return cleanup(input); + return cleanup(updateImportTypeNode(input, updateLiteralTypeNode(input.argument, rewriteModuleSpecifier(input, input.argument.literal)), input.qualifier, visitNodes(input.typeArguments, visitDeclarationSubtree, isTypeNode), input.isTypeOf)); } - return statement; + default: Debug.assertNever(input, `Attempted to process unhandled node kind: ${(ts as any).SyntaxKind[(input as any).kind]}`); } } - - function visitDeclarationSubtree(input: Node): VisitResult { - if (shouldStripInternal(input)) return; - if (isDeclaration(input)) { - if (isDeclarationAndNotVisible(input)) return; - if (hasDynamicName(input) && !resolver.isLateBound(getParseTreeNode(input) as Declaration)) { - return; - } + return cleanup(visitEachChild(input, visitDeclarationSubtree, context)); + function cleanup(returnValue: T | undefined): T | undefined { + if (returnValue && canProduceDiagnostic && hasDynamicName((input as Declaration))) { + checkName((input as DeclarationDiagnosticProducing)); } - - // Elide implementation signatures from overload sets - if (isFunctionLike(input) && resolver.isImplementationOfOverload(input)) return; - - // Elide semicolon class statements - if (isSemicolonClassElement(input)) return; - - let previousEnclosingDeclaration: typeof enclosingDeclaration; if (isEnclosingDeclaration(input)) { - previousEnclosingDeclaration = enclosingDeclaration; - enclosingDeclaration = input as Declaration; - } - const oldDiag = getSymbolAccessibilityDiagnostic; - - // Setup diagnostic-related flags before first potential `cleanup` call, otherwise - // We'd see a TDZ violation at runtime - const canProduceDiagnostic = canProduceDiagnostics(input); - const oldWithinObjectLiteralType = suppressNewDiagnosticContexts; - let shouldEnterSuppressNewDiagnosticsContextContext = ((input.kind === SyntaxKind.TypeLiteral || input.kind === SyntaxKind.MappedType) && input.parent.kind !== SyntaxKind.TypeAliasDeclaration); - - // Emit methods which are private as properties with no type information - if (isMethodDeclaration(input) || isMethodSignature(input)) { - if (hasModifier(input, ModifierFlags.Private)) { - if (input.symbol && input.symbol.declarations && input.symbol.declarations[0] !== input) return; // Elide all but the first overload - return cleanup(createProperty(/*decorators*/undefined, ensureModifiers(input), input.name, /*questionToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined)); - } + enclosingDeclaration = previousEnclosingDeclaration; } - if (canProduceDiagnostic && !suppressNewDiagnosticContexts) { - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(input as DeclarationDiagnosticProducing); - } - - if (isTypeQueryNode(input)) { - checkEntityNameVisibility(input.exprName, enclosingDeclaration); + getSymbolAccessibilityDiagnostic = oldDiag; } - if (shouldEnterSuppressNewDiagnosticsContextContext) { - // We stop making new diagnostic contexts within object literal types. Unless it's an object type on the RHS of a type alias declaration. Then we do. - suppressNewDiagnosticContexts = true; + suppressNewDiagnosticContexts = oldWithinObjectLiteralType; } - - if (isProcessedComponent(input)) { - switch (input.kind) { - case SyntaxKind.ExpressionWithTypeArguments: { - if ((isEntityName(input.expression) || isEntityNameExpression(input.expression))) { - checkEntityNameVisibility(input.expression, enclosingDeclaration); - } - const node = visitEachChild(input, visitDeclarationSubtree, context); - return cleanup(updateExpressionWithTypeArguments(node, parenthesizeTypeParameters(node.typeArguments), node.expression)); - } - case SyntaxKind.TypeReference: { - checkEntityNameVisibility(input.typeName, enclosingDeclaration); - const node = visitEachChild(input, visitDeclarationSubtree, context); - return cleanup(updateTypeReferenceNode(node, node.typeName, parenthesizeTypeParameters(node.typeArguments))); - } - case SyntaxKind.ConstructSignature: - return cleanup(updateConstructSignature( - input, - ensureTypeParams(input, input.typeParameters), - updateParamsList(input, input.parameters), - ensureType(input, input.type) - )); - case SyntaxKind.Constructor: { - const isPrivate = hasModifier(input, ModifierFlags.Private); - // A constructor declaration may not have a type annotation - const ctor = createSignatureDeclaration( - SyntaxKind.Constructor, - isPrivate ? undefined : ensureTypeParams(input, input.typeParameters), - // TODO: GH#18217 - isPrivate ? undefined! : updateParamsList(input, input.parameters, ModifierFlags.None), - /*type*/ undefined - ); - ctor.modifiers = createNodeArray(ensureModifiers(input)); - return cleanup(ctor); - } - case SyntaxKind.MethodDeclaration: { - const sig = createSignatureDeclaration( - SyntaxKind.MethodSignature, - ensureTypeParams(input, input.typeParameters), - updateParamsList(input, input.parameters), - ensureType(input, input.type) - ) as MethodSignature; - sig.name = input.name; - sig.modifiers = createNodeArray(ensureModifiers(input)); - sig.questionToken = input.questionToken; - return cleanup(sig); - } - case SyntaxKind.GetAccessor: { - const isPrivate = hasModifier(input, ModifierFlags.Private); - const accessorType = getTypeAnnotationFromAllAccessorDeclarations(input, resolver.getAllAccessorDeclarations(input)); - return cleanup(updateGetAccessor( - input, - /*decorators*/ undefined, - ensureModifiers(input), - input.name, - updateAccessorParamsList(input, isPrivate), - !isPrivate ? ensureType(input, accessorType) : undefined, - /*body*/ undefined)); - } - case SyntaxKind.SetAccessor: { - return cleanup(updateSetAccessor( - input, - /*decorators*/ undefined, - ensureModifiers(input), - input.name, - updateAccessorParamsList(input, hasModifier(input, ModifierFlags.Private)), - /*body*/ undefined)); - } - case SyntaxKind.PropertyDeclaration: - return cleanup(updateProperty( - input, - /*decorators*/ undefined, - ensureModifiers(input), - input.name, - input.questionToken, - !hasModifier(input, ModifierFlags.Private) ? ensureType(input, input.type) : undefined, - ensureNoInitializer(input) - )); - case SyntaxKind.PropertySignature: - return cleanup(updatePropertySignature( - input, - ensureModifiers(input), - input.name, - input.questionToken, - !hasModifier(input, ModifierFlags.Private) ? ensureType(input, input.type) : undefined, - ensureNoInitializer(input) - )); - case SyntaxKind.MethodSignature: { - return cleanup(updateMethodSignature( - input, - ensureTypeParams(input, input.typeParameters), - updateParamsList(input, input.parameters), - ensureType(input, input.type), - input.name, - input.questionToken - )); - } - case SyntaxKind.CallSignature: { - return cleanup(updateCallSignature( - input, - ensureTypeParams(input, input.typeParameters), - updateParamsList(input, input.parameters), - ensureType(input, input.type) - )); - } - case SyntaxKind.IndexSignature: { - return cleanup(updateIndexSignature( - input, - /*decorators*/ undefined, - ensureModifiers(input), - updateParamsList(input, input.parameters), - visitNode(input.type, visitDeclarationSubtree) || createKeywordTypeNode(SyntaxKind.AnyKeyword) - )); - } - case SyntaxKind.VariableDeclaration: { - if (isBindingPattern(input.name)) { - return recreateBindingPattern(input.name); - } - shouldEnterSuppressNewDiagnosticsContextContext = true; - suppressNewDiagnosticContexts = true; // Variable declaration types also suppress new diagnostic contexts, provided the contexts wouldn't be made for binding pattern types - return cleanup(updateTypeScriptVariableDeclaration(input, input.name, /*exclaimationToken*/ undefined, ensureType(input, input.type), ensureNoInitializer(input))); - } - case SyntaxKind.TypeParameter: { - if (isPrivateMethodTypeParameter(input) && (input.default || input.constraint)) { - return cleanup(updateTypeParameterDeclaration(input, input.name, /*constraint*/ undefined, /*defaultType*/ undefined)); - } - return cleanup(visitEachChild(input, visitDeclarationSubtree, context)); - } - case SyntaxKind.ConditionalType: { - // We have to process conditional types in a special way because for visibility purposes we need to push a new enclosingDeclaration - // just for the `infer` types in the true branch. It's an implicit declaration scope that only applies to _part_ of the type. - const checkType = visitNode(input.checkType, visitDeclarationSubtree); - const extendsType = visitNode(input.extendsType, visitDeclarationSubtree); - const oldEnclosingDecl = enclosingDeclaration; - enclosingDeclaration = input.trueType; - const trueType = visitNode(input.trueType, visitDeclarationSubtree); - enclosingDeclaration = oldEnclosingDecl; - const falseType = visitNode(input.falseType, visitDeclarationSubtree); - return cleanup(updateConditionalTypeNode(input, checkType, extendsType, trueType, falseType)); - } - case SyntaxKind.FunctionType: { - return cleanup(updateFunctionTypeNode(input, visitNodes(input.typeParameters, visitDeclarationSubtree), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree))); - } - case SyntaxKind.ConstructorType: { - return cleanup(updateConstructorTypeNode(input, visitNodes(input.typeParameters, visitDeclarationSubtree), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree))); - } - case SyntaxKind.ImportType: { - if (!isLiteralImportTypeNode(input)) return cleanup(input); - return cleanup(updateImportTypeNode( - input, - updateLiteralTypeNode(input.argument, rewriteModuleSpecifier(input, input.argument.literal)), - input.qualifier, - visitNodes(input.typeArguments, visitDeclarationSubtree, isTypeNode), - input.isTypeOf - )); - } - default: Debug.assertNever(input, `Attempted to process unhandled node kind: ${(ts as any).SyntaxKind[(input as any).kind]}`); - } + if (returnValue === input) { + return returnValue; } - - return cleanup(visitEachChild(input, visitDeclarationSubtree, context)); - - function cleanup(returnValue: T | undefined): T | undefined { - if (returnValue && canProduceDiagnostic && hasDynamicName(input as Declaration)) { - checkName(input as DeclarationDiagnosticProducing); - } - if (isEnclosingDeclaration(input)) { - enclosingDeclaration = previousEnclosingDeclaration; + return returnValue && setOriginalNode(preserveJsDoc(returnValue, input), input); + } + } + function isPrivateMethodTypeParameter(node: TypeParameterDeclaration) { + return node.parent.kind === SyntaxKind.MethodDeclaration && hasModifier(node.parent, ModifierFlags.Private); + } + function visitDeclarationStatements(input: Node): VisitResult { + if (!isPreservedDeclarationStatement(input)) { + // return undefined for unmatched kinds to omit them from the tree + return; + } + if (shouldStripInternal(input)) + return; + switch (input.kind) { + case SyntaxKind.ExportDeclaration: { + if (isSourceFile(input.parent)) { + resultHasExternalModuleIndicator = true; } - if (canProduceDiagnostic && !suppressNewDiagnosticContexts) { - getSymbolAccessibilityDiagnostic = oldDiag; + resultHasScopeMarker = true; + // Always visible if the parent node isn't dropped for being not visible + // Rewrite external module names if necessary + return updateExportDeclaration(input, /*decorators*/ undefined, input.modifiers, input.exportClause, rewriteModuleSpecifier(input, input.moduleSpecifier)); + } + case SyntaxKind.ExportAssignment: { + // Always visible if the parent node isn't dropped for being not visible + if (isSourceFile(input.parent)) { + resultHasExternalModuleIndicator = true; } - if (shouldEnterSuppressNewDiagnosticsContextContext) { - suppressNewDiagnosticContexts = oldWithinObjectLiteralType; + resultHasScopeMarker = true; + if (input.expression.kind === SyntaxKind.Identifier) { + return input; } - if (returnValue === input) { - return returnValue; + else { + const newId = createOptimisticUniqueName("_default"); + getSymbolAccessibilityDiagnostic = () => ({ + diagnosticMessage: Diagnostics.Default_export_of_the_module_has_or_is_using_private_name_0, + errorNode: input + }); + const varDecl = createVariableDeclaration(newId, resolver.createTypeOfExpression(input.expression, input, declarationEmitNodeBuilderFlags, symbolTracker), /*initializer*/ undefined); + const statement = createVariableStatement(needsDeclare ? [createModifier(SyntaxKind.DeclareKeyword)] : [], createVariableDeclarationList([varDecl], NodeFlags.Const)); + return [statement, updateExportAssignment(input, input.decorators, input.modifiers, newId)]; } - return returnValue && setOriginalNode(preserveJsDoc(returnValue, input), input); } } - - function isPrivateMethodTypeParameter(node: TypeParameterDeclaration) { - return node.parent.kind === SyntaxKind.MethodDeclaration && hasModifier(node.parent, ModifierFlags.Private); + const result = transformTopLevelDeclaration(input); + // Don't actually transform yet; just leave as original node - will be elided/swapped by late pass + lateStatementReplacementMap.set("" + getOriginalNodeId(input), result); + return input; + } + function stripExportModifiers(statement: Statement): Statement { + if (isImportEqualsDeclaration(statement) || hasModifier(statement, ModifierFlags.Default)) { + // `export import` statements should remain as-is, as imports are _not_ implicitly exported in an ambient namespace + // Likewise, `export default` classes and the like and just be `default`, so we preserve their `export` modifiers, too + return statement; } - - function visitDeclarationStatements(input: Node): VisitResult { - if (!isPreservedDeclarationStatement(input)) { - // return undefined for unmatched kinds to omit them from the tree - return; + const clone = getMutableClone(statement); + const modifiers = createModifiersFromModifierFlags(getModifierFlags(statement) & (ModifierFlags.All ^ ModifierFlags.Export)); + clone.modifiers = modifiers.length ? createNodeArray(modifiers) : undefined; + return clone; + } + function transformTopLevelDeclaration(input: LateVisibilityPaintedStatement) { + if (shouldStripInternal(input)) + return; + switch (input.kind) { + case SyntaxKind.ImportEqualsDeclaration: { + return transformImportEqualsDeclaration(input); } - if (shouldStripInternal(input)) return; - - switch (input.kind) { - case SyntaxKind.ExportDeclaration: { - if (isSourceFile(input.parent)) { - resultHasExternalModuleIndicator = true; + case SyntaxKind.ImportDeclaration: { + return transformImportDeclaration(input); + } + } + if (isDeclaration(input) && isDeclarationAndNotVisible(input)) + return; + // Elide implementation signatures from overload sets + if (isFunctionLike(input) && resolver.isImplementationOfOverload(input)) + return; + let previousEnclosingDeclaration: typeof enclosingDeclaration; + if (isEnclosingDeclaration(input)) { + previousEnclosingDeclaration = enclosingDeclaration; + enclosingDeclaration = (input as Declaration); + } + const canProdiceDiagnostic = canProduceDiagnostics(input); + const oldDiag = getSymbolAccessibilityDiagnostic; + if (canProdiceDiagnostic) { + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode((input as DeclarationDiagnosticProducing)); + } + const previousNeedsDeclare = needsDeclare; + switch (input.kind) { + case SyntaxKind.TypeAliasDeclaration: // Type aliases get `declare`d if need be (for legacy support), but that's all + return cleanup(updateTypeAliasDeclaration(input, + /*decorators*/ undefined, ensureModifiers(input), input.name, visitNodes(input.typeParameters, visitDeclarationSubtree, isTypeParameterDeclaration), visitNode(input.type, visitDeclarationSubtree, isTypeNode))); + case SyntaxKind.InterfaceDeclaration: { + return cleanup(updateInterfaceDeclaration(input, + /*decorators*/ undefined, ensureModifiers(input), input.name, ensureTypeParams(input, input.typeParameters), transformHeritageClauses(input.heritageClauses), visitNodes(input.members, visitDeclarationSubtree))); + } + case SyntaxKind.FunctionDeclaration: { + // Generators lose their generator-ness, excepting their return type + const clean = cleanup(updateFunctionDeclaration(input, + /*decorators*/ undefined, ensureModifiers(input), + /*asteriskToken*/ undefined, input.name, ensureTypeParams(input, input.typeParameters), updateParamsList(input, input.parameters), ensureType(input, input.type), + /*body*/ undefined)); + if (clean && resolver.isExpandoFunctionDeclaration(input)) { + const props = resolver.getPropertiesOfContainerFunction(input); + const fakespace = createModuleDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, clean.name || createIdentifier("_default"), createModuleBlock([]), NodeFlags.Namespace); + fakespace.flags ^= NodeFlags.Synthesized; // unset synthesized so it is usable as an enclosing declaration + fakespace.parent = (enclosingDeclaration as SourceFile | NamespaceDeclaration); + fakespace.locals = createSymbolTable(props); + fakespace.symbol = props[0].parent!; + const declarations = mapDefined(props, p => { + if (!isPropertyAccessExpression(p.valueDeclaration)) { + return undefined; // TODO GH#33569: Handle element access expressions that created late bound names (rather than silently omitting them) + } + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(p.valueDeclaration); + const type = resolver.createTypeOfDeclaration(p.valueDeclaration, fakespace, declarationEmitNodeBuilderFlags, symbolTracker); + getSymbolAccessibilityDiagnostic = oldDiag; + const varDecl = createVariableDeclaration(unescapeLeadingUnderscores(p.escapedName), type, /*initializer*/ undefined); + return createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([varDecl])); + }); + const namespaceDecl = createModuleDeclaration(/*decorators*/ undefined, ensureModifiers(input), (input.name!), createModuleBlock(declarations), NodeFlags.Namespace); + if (!hasModifier(clean, ModifierFlags.Default)) { + return [clean, namespaceDecl]; } - resultHasScopeMarker = true; - // Always visible if the parent node isn't dropped for being not visible - // Rewrite external module names if necessary - return updateExportDeclaration(input, /*decorators*/ undefined, input.modifiers, input.exportClause, rewriteModuleSpecifier(input, input.moduleSpecifier)); - } - case SyntaxKind.ExportAssignment: { - // Always visible if the parent node isn't dropped for being not visible + const modifiers = createModifiersFromModifierFlags((getModifierFlags(clean) & ~ModifierFlags.ExportDefault) | ModifierFlags.Ambient); + const cleanDeclaration = updateFunctionDeclaration(clean, + /*decorators*/ undefined, modifiers, + /*asteriskToken*/ undefined, clean.name, clean.typeParameters, clean.parameters, clean.type, + /*body*/ undefined); + const namespaceDeclaration = updateModuleDeclaration(namespaceDecl, + /*decorators*/ undefined, modifiers, namespaceDecl.name, namespaceDecl.body); + const exportDefaultDeclaration = createExportAssignment( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*isExportEquals*/ false, namespaceDecl.name); if (isSourceFile(input.parent)) { resultHasExternalModuleIndicator = true; } resultHasScopeMarker = true; - if (input.expression.kind === SyntaxKind.Identifier) { - return input; - } - else { - const newId = createOptimisticUniqueName("_default"); - getSymbolAccessibilityDiagnostic = () => ({ - diagnosticMessage: Diagnostics.Default_export_of_the_module_has_or_is_using_private_name_0, - errorNode: input - }); - const varDecl = createVariableDeclaration(newId, resolver.createTypeOfExpression(input.expression, input, declarationEmitNodeBuilderFlags, symbolTracker), /*initializer*/ undefined); - const statement = createVariableStatement(needsDeclare ? [createModifier(SyntaxKind.DeclareKeyword)] : [], createVariableDeclarationList([varDecl], NodeFlags.Const)); - return [statement, updateExportAssignment(input, input.decorators, input.modifiers, newId)]; - } - } - } - - const result = transformTopLevelDeclaration(input); - // Don't actually transform yet; just leave as original node - will be elided/swapped by late pass - lateStatementReplacementMap.set("" + getOriginalNodeId(input), result); - return input; - } - - function stripExportModifiers(statement: Statement): Statement { - if (isImportEqualsDeclaration(statement) || hasModifier(statement, ModifierFlags.Default)) { - // `export import` statements should remain as-is, as imports are _not_ implicitly exported in an ambient namespace - // Likewise, `export default` classes and the like and just be `default`, so we preserve their `export` modifiers, too - return statement; - } - const clone = getMutableClone(statement); - const modifiers = createModifiersFromModifierFlags(getModifierFlags(statement) & (ModifierFlags.All ^ ModifierFlags.Export)); - clone.modifiers = modifiers.length ? createNodeArray(modifiers) : undefined; - return clone; - } - - function transformTopLevelDeclaration(input: LateVisibilityPaintedStatement) { - if (shouldStripInternal(input)) return; - switch (input.kind) { - case SyntaxKind.ImportEqualsDeclaration: { - return transformImportEqualsDeclaration(input); + return [cleanDeclaration, namespaceDeclaration, exportDefaultDeclaration]; } - case SyntaxKind.ImportDeclaration: { - return transformImportDeclaration(input); + else { + return clean; } } - if (isDeclaration(input) && isDeclarationAndNotVisible(input)) return; - - // Elide implementation signatures from overload sets - if (isFunctionLike(input) && resolver.isImplementationOfOverload(input)) return; - - let previousEnclosingDeclaration: typeof enclosingDeclaration; - if (isEnclosingDeclaration(input)) { - previousEnclosingDeclaration = enclosingDeclaration; - enclosingDeclaration = input as Declaration; - } - - const canProdiceDiagnostic = canProduceDiagnostics(input); - const oldDiag = getSymbolAccessibilityDiagnostic; - if (canProdiceDiagnostic) { - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(input as DeclarationDiagnosticProducing); - } - - const previousNeedsDeclare = needsDeclare; - switch (input.kind) { - case SyntaxKind.TypeAliasDeclaration: // Type aliases get `declare`d if need be (for legacy support), but that's all - return cleanup(updateTypeAliasDeclaration( - input, - /*decorators*/ undefined, - ensureModifiers(input), - input.name, - visitNodes(input.typeParameters, visitDeclarationSubtree, isTypeParameterDeclaration), - visitNode(input.type, visitDeclarationSubtree, isTypeNode) - )); - case SyntaxKind.InterfaceDeclaration: { - return cleanup(updateInterfaceDeclaration( - input, - /*decorators*/ undefined, - ensureModifiers(input), - input.name, - ensureTypeParams(input, input.typeParameters), - transformHeritageClauses(input.heritageClauses), - visitNodes(input.members, visitDeclarationSubtree) - )); - } - case SyntaxKind.FunctionDeclaration: { - // Generators lose their generator-ness, excepting their return type - const clean = cleanup(updateFunctionDeclaration( - input, - /*decorators*/ undefined, - ensureModifiers(input), - /*asteriskToken*/ undefined, - input.name, - ensureTypeParams(input, input.typeParameters), - updateParamsList(input, input.parameters), - ensureType(input, input.type), - /*body*/ undefined - )); - if (clean && resolver.isExpandoFunctionDeclaration(input)) { - const props = resolver.getPropertiesOfContainerFunction(input); - const fakespace = createModuleDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, clean.name || createIdentifier("_default"), createModuleBlock([]), NodeFlags.Namespace); - fakespace.flags ^= NodeFlags.Synthesized; // unset synthesized so it is usable as an enclosing declaration - fakespace.parent = enclosingDeclaration as SourceFile | NamespaceDeclaration; - fakespace.locals = createSymbolTable(props); - fakespace.symbol = props[0].parent!; - const declarations = mapDefined(props, p => { - if (!isPropertyAccessExpression(p.valueDeclaration)) { - return undefined; // TODO GH#33569: Handle element access expressions that created late bound names (rather than silently omitting them) - } - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(p.valueDeclaration); - const type = resolver.createTypeOfDeclaration(p.valueDeclaration, fakespace, declarationEmitNodeBuilderFlags, symbolTracker); - getSymbolAccessibilityDiagnostic = oldDiag; - const varDecl = createVariableDeclaration(unescapeLeadingUnderscores(p.escapedName), type, /*initializer*/ undefined); - return createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([varDecl])); - }); - const namespaceDecl = createModuleDeclaration(/*decorators*/ undefined, ensureModifiers(input), input.name!, createModuleBlock(declarations), NodeFlags.Namespace); - - if (!hasModifier(clean, ModifierFlags.Default)) { - return [clean, namespaceDecl]; + case SyntaxKind.ModuleDeclaration: { + needsDeclare = false; + const inner = input.body; + if (inner && inner.kind === SyntaxKind.ModuleBlock) { + const oldNeedsScopeFix = needsScopeFixMarker; + const oldHasScopeFix = resultHasScopeMarker; + resultHasScopeMarker = false; + needsScopeFixMarker = false; + const statements = visitNodes(inner.statements, visitDeclarationStatements); + let lateStatements = transformAndReplaceLatePaintedStatements(statements); + if (input.flags & NodeFlags.Ambient) { + needsScopeFixMarker = false; // If it was `declare`'d everything is implicitly exported already, ignore late printed "privates" + } + // With the final list of statements, there are 3 possibilities: + // 1. There's an export assignment or export declaration in the namespace - do nothing + // 2. Everything is exported and there are no export assignments or export declarations - strip all export modifiers + // 3. Some things are exported, some are not, and there's no marker - add an empty marker + if (!isGlobalScopeAugmentation(input) && !hasScopeMarker(lateStatements) && !resultHasScopeMarker) { + if (needsScopeFixMarker) { + lateStatements = createNodeArray([...lateStatements, createEmptyExports()]); } - - const modifiers = createModifiersFromModifierFlags((getModifierFlags(clean) & ~ModifierFlags.ExportDefault) | ModifierFlags.Ambient); - const cleanDeclaration = updateFunctionDeclaration( - clean, - /*decorators*/ undefined, - modifiers, - /*asteriskToken*/ undefined, - clean.name, - clean.typeParameters, - clean.parameters, - clean.type, - /*body*/ undefined - ); - - const namespaceDeclaration = updateModuleDeclaration( - namespaceDecl, - /*decorators*/ undefined, - modifiers, - namespaceDecl.name, - namespaceDecl.body - ); - - const exportDefaultDeclaration = createExportAssignment( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*isExportEquals*/ false, - namespaceDecl.name - ); - - if (isSourceFile(input.parent)) { - resultHasExternalModuleIndicator = true; + else { + lateStatements = visitNodes(lateStatements, stripExportModifiers); } - resultHasScopeMarker = true; - - return [cleanDeclaration, namespaceDeclaration, exportDefaultDeclaration]; - } - else { - return clean; } + const body = updateModuleBlock(inner, lateStatements); + needsDeclare = previousNeedsDeclare; + needsScopeFixMarker = oldNeedsScopeFix; + resultHasScopeMarker = oldHasScopeFix; + const mods = ensureModifiers(input); + return cleanup(updateModuleDeclaration(input, + /*decorators*/ undefined, mods, isExternalModuleAugmentation(input) ? rewriteModuleSpecifier(input, input.name) : input.name, body)); } - case SyntaxKind.ModuleDeclaration: { + else { + needsDeclare = previousNeedsDeclare; + const mods = ensureModifiers(input); needsDeclare = false; - const inner = input.body; - if (inner && inner.kind === SyntaxKind.ModuleBlock) { - const oldNeedsScopeFix = needsScopeFixMarker; - const oldHasScopeFix = resultHasScopeMarker; - resultHasScopeMarker = false; - needsScopeFixMarker = false; - const statements = visitNodes(inner.statements, visitDeclarationStatements); - let lateStatements = transformAndReplaceLatePaintedStatements(statements); - if (input.flags & NodeFlags.Ambient) { - needsScopeFixMarker = false; // If it was `declare`'d everything is implicitly exported already, ignore late printed "privates" + visitNode(inner, visitDeclarationStatements); + // eagerly transform nested namespaces (the nesting doesn't need any elision or painting done) + const id = "" + getOriginalNodeId((inner!)); // TODO: GH#18217 + const body = lateStatementReplacementMap.get(id); + lateStatementReplacementMap.delete(id); + return cleanup(updateModuleDeclaration(input, + /*decorators*/ undefined, mods, input.name, (body as ModuleBody))); + } + } + case SyntaxKind.ClassDeclaration: { + const modifiers = createNodeArray(ensureModifiers(input)); + const typeParameters = ensureTypeParams(input, input.typeParameters); + const ctor = getFirstConstructorWithBody(input); + let parameterProperties: readonly PropertyDeclaration[] | undefined; + if (ctor) { + const oldDiag = getSymbolAccessibilityDiagnostic; + parameterProperties = compact(flatMap(ctor.parameters, (param) => { + if (!hasModifier(param, ModifierFlags.ParameterPropertyModifier) || shouldStripInternal(param)) + return; + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(param); + if (param.name.kind === SyntaxKind.Identifier) { + return preserveJsDoc(createProperty( + /*decorators*/ undefined, ensureModifiers(param), param.name, param.questionToken, ensureType(param, param.type), ensureNoInitializer(param)), param); } - // With the final list of statements, there are 3 possibilities: - // 1. There's an export assignment or export declaration in the namespace - do nothing - // 2. Everything is exported and there are no export assignments or export declarations - strip all export modifiers - // 3. Some things are exported, some are not, and there's no marker - add an empty marker - if (!isGlobalScopeAugmentation(input) && !hasScopeMarker(lateStatements) && !resultHasScopeMarker) { - if (needsScopeFixMarker) { - lateStatements = createNodeArray([...lateStatements, createEmptyExports()]); - } - else { - lateStatements = visitNodes(lateStatements, stripExportModifiers); - } + else { + // Pattern - this is currently an error, but we emit declarations for it somewhat correctly + return walkBindingPattern(param.name); } - const body = updateModuleBlock(inner, lateStatements); - needsDeclare = previousNeedsDeclare; - needsScopeFixMarker = oldNeedsScopeFix; - resultHasScopeMarker = oldHasScopeFix; - const mods = ensureModifiers(input); - return cleanup(updateModuleDeclaration( - input, - /*decorators*/ undefined, - mods, - isExternalModuleAugmentation(input) ? rewriteModuleSpecifier(input, input.name) : input.name, - body - )); - } - else { - needsDeclare = previousNeedsDeclare; - const mods = ensureModifiers(input); - needsDeclare = false; - visitNode(inner, visitDeclarationStatements); - // eagerly transform nested namespaces (the nesting doesn't need any elision or painting done) - const id = "" + getOriginalNodeId(inner!); // TODO: GH#18217 - const body = lateStatementReplacementMap.get(id); - lateStatementReplacementMap.delete(id); - return cleanup(updateModuleDeclaration( - input, - /*decorators*/ undefined, - mods, - input.name, - body as ModuleBody - )); - } - } - case SyntaxKind.ClassDeclaration: { - const modifiers = createNodeArray(ensureModifiers(input)); - const typeParameters = ensureTypeParams(input, input.typeParameters); - const ctor = getFirstConstructorWithBody(input); - let parameterProperties: readonly PropertyDeclaration[] | undefined; - if (ctor) { - const oldDiag = getSymbolAccessibilityDiagnostic; - parameterProperties = compact(flatMap(ctor.parameters, (param) => { - if (!hasModifier(param, ModifierFlags.ParameterPropertyModifier) || shouldStripInternal(param)) return; - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(param); - if (param.name.kind === SyntaxKind.Identifier) { - return preserveJsDoc(createProperty( - /*decorators*/ undefined, - ensureModifiers(param), - param.name, - param.questionToken, - ensureType(param, param.type), - ensureNoInitializer(param)), param); - } - else { - // Pattern - this is currently an error, but we emit declarations for it somewhat correctly - return walkBindingPattern(param.name); - } - - function walkBindingPattern(pattern: BindingPattern) { - let elems: PropertyDeclaration[] | undefined; - for (const elem of pattern.elements) { - if (isOmittedExpression(elem)) continue; - if (isBindingPattern(elem.name)) { - elems = concatenate(elems, walkBindingPattern(elem.name)); - } - elems = elems || []; - elems.push(createProperty( - /*decorators*/ undefined, - ensureModifiers(param), - elem.name as Identifier, - /*questionToken*/ undefined, - ensureType(elem, /*type*/ undefined), - /*initializer*/ undefined - )); + function walkBindingPattern(pattern: BindingPattern) { + let elems: PropertyDeclaration[] | undefined; + for (const elem of pattern.elements) { + if (isOmittedExpression(elem)) + continue; + if (isBindingPattern(elem.name)) { + elems = concatenate(elems, walkBindingPattern(elem.name)); } - return elems; - } - })); - getSymbolAccessibilityDiagnostic = oldDiag; - } - const members = createNodeArray(concatenate(parameterProperties, visitNodes(input.members, visitDeclarationSubtree))); - - const extendsClause = getEffectiveBaseTypeNode(input); - if (extendsClause && !isEntityNameExpression(extendsClause.expression) && extendsClause.expression.kind !== SyntaxKind.NullKeyword) { - // We must add a temporary declaration for the extends clause expression - - const oldId = input.name ? unescapeLeadingUnderscores(input.name.escapedText) : "default"; - const newId = createOptimisticUniqueName(`${oldId}_base`); - getSymbolAccessibilityDiagnostic = () => ({ - diagnosticMessage: Diagnostics.extends_clause_of_exported_class_0_has_or_is_using_private_name_1, - errorNode: extendsClause, - typeName: input.name - }); - const varDecl = createVariableDeclaration(newId, resolver.createTypeOfExpression(extendsClause.expression, input, declarationEmitNodeBuilderFlags, symbolTracker), /*initializer*/ undefined); - const statement = createVariableStatement(needsDeclare ? [createModifier(SyntaxKind.DeclareKeyword)] : [], createVariableDeclarationList([varDecl], NodeFlags.Const)); - const heritageClauses = createNodeArray(map(input.heritageClauses, clause => { - if (clause.token === SyntaxKind.ExtendsKeyword) { - const oldDiag = getSymbolAccessibilityDiagnostic; - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(clause.types[0]); - const newClause = updateHeritageClause(clause, map(clause.types, t => updateExpressionWithTypeArguments(t, visitNodes(t.typeArguments, visitDeclarationSubtree), newId))); - getSymbolAccessibilityDiagnostic = oldDiag; - return newClause; + elems = elems || []; + elems.push(createProperty( + /*decorators*/ undefined, ensureModifiers(param), (elem.name as Identifier), + /*questionToken*/ undefined, ensureType(elem, /*type*/ undefined), + /*initializer*/ undefined)); } - return updateHeritageClause(clause, visitNodes(createNodeArray(filter(clause.types, t => isEntityNameExpression(t.expression) || t.expression.kind === SyntaxKind.NullKeyword)), visitDeclarationSubtree)); - })); - return [statement, cleanup(updateClassDeclaration( - input, - /*decorators*/ undefined, - modifiers, - input.name, - typeParameters, - heritageClauses, - members - ))!]; // TODO: GH#18217 - } - else { - const heritageClauses = transformHeritageClauses(input.heritageClauses); - return cleanup(updateClassDeclaration( - input, - /*decorators*/ undefined, - modifiers, - input.name, - typeParameters, - heritageClauses, - members - )); - } - } - case SyntaxKind.VariableStatement: { - return cleanup(transformVariableStatement(input)); - } - case SyntaxKind.EnumDeclaration: { - return cleanup(updateEnumDeclaration(input, /*decorators*/ undefined, createNodeArray(ensureModifiers(input)), input.name, createNodeArray(mapDefined(input.members, m => { - if (shouldStripInternal(m)) return; - // Rewrite enum values to their constants, if available - const constValue = resolver.getConstantValue(m); - return preserveJsDoc(updateEnumMember(m, m.name, constValue !== undefined ? createLiteral(constValue) : undefined), m); - })))); - } - } - // Anything left unhandled is an error, so this should be unreachable - return Debug.assertNever(input, `Unhandled top-level node in declaration emit: ${(ts as any).SyntaxKind[(input as any).kind]}`); - - function cleanup(node: T | undefined): T | undefined { - if (isEnclosingDeclaration(input)) { - enclosingDeclaration = previousEnclosingDeclaration; - } - if (canProdiceDiagnostic) { + return elems; + } + })); getSymbolAccessibilityDiagnostic = oldDiag; } - if (input.kind === SyntaxKind.ModuleDeclaration) { - needsDeclare = previousNeedsDeclare; - } - if (node as Node === input) { - return node; - } - return node && setOriginalNode(preserveJsDoc(node, input), input); - } - } - - function transformVariableStatement(input: VariableStatement) { - if (!forEach(input.declarationList.declarations, getBindingNameVisible)) return; - const nodes = visitNodes(input.declarationList.declarations, visitDeclarationSubtree); - if (!length(nodes)) return; - return updateVariableStatement(input, createNodeArray(ensureModifiers(input)), updateVariableDeclarationList(input.declarationList, nodes)); - } - - function recreateBindingPattern(d: BindingPattern): VariableDeclaration[] { - return flatten(mapDefined(d.elements, e => recreateBindingElement(e))); - } - - function recreateBindingElement(e: ArrayBindingElement) { - if (e.kind === SyntaxKind.OmittedExpression) { - return; - } - if (e.name) { - if (!getBindingNameVisible(e)) return; - if (isBindingPattern(e.name)) { - return recreateBindingPattern(e.name); + const members = createNodeArray(concatenate(parameterProperties, visitNodes(input.members, visitDeclarationSubtree))); + const extendsClause = getEffectiveBaseTypeNode(input); + if (extendsClause && !isEntityNameExpression(extendsClause.expression) && extendsClause.expression.kind !== SyntaxKind.NullKeyword) { + // We must add a temporary declaration for the extends clause expression + const oldId = input.name ? unescapeLeadingUnderscores(input.name.escapedText) : "default"; + const newId = createOptimisticUniqueName(`${oldId}_base`); + getSymbolAccessibilityDiagnostic = () => ({ + diagnosticMessage: Diagnostics.extends_clause_of_exported_class_0_has_or_is_using_private_name_1, + errorNode: extendsClause, + typeName: input.name + }); + const varDecl = createVariableDeclaration(newId, resolver.createTypeOfExpression(extendsClause.expression, input, declarationEmitNodeBuilderFlags, symbolTracker), /*initializer*/ undefined); + const statement = createVariableStatement(needsDeclare ? [createModifier(SyntaxKind.DeclareKeyword)] : [], createVariableDeclarationList([varDecl], NodeFlags.Const)); + const heritageClauses = createNodeArray(map(input.heritageClauses, clause => { + if (clause.token === SyntaxKind.ExtendsKeyword) { + const oldDiag = getSymbolAccessibilityDiagnostic; + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(clause.types[0]); + const newClause = updateHeritageClause(clause, map(clause.types, t => updateExpressionWithTypeArguments(t, visitNodes(t.typeArguments, visitDeclarationSubtree), newId))); + getSymbolAccessibilityDiagnostic = oldDiag; + return newClause; + } + return updateHeritageClause(clause, visitNodes(createNodeArray(filter(clause.types, t => isEntityNameExpression(t.expression) || t.expression.kind === SyntaxKind.NullKeyword)), visitDeclarationSubtree)); + })); + return [statement, (cleanup(updateClassDeclaration(input, + /*decorators*/ undefined, modifiers, input.name, typeParameters, heritageClauses, members))!)]; // TODO: GH#18217 } else { - return createVariableDeclaration(e.name, ensureType(e, /*type*/ undefined), /*initializer*/ undefined); + const heritageClauses = transformHeritageClauses(input.heritageClauses); + return cleanup(updateClassDeclaration(input, + /*decorators*/ undefined, modifiers, input.name, typeParameters, heritageClauses, members)); } } - } - - function checkName(node: DeclarationDiagnosticProducing) { - let oldDiag: typeof getSymbolAccessibilityDiagnostic | undefined; - if (!suppressNewDiagnosticContexts) { - oldDiag = getSymbolAccessibilityDiagnostic; - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNodeName(node); + case SyntaxKind.VariableStatement: { + return cleanup(transformVariableStatement(input)); } - errorNameNode = (node as NamedDeclaration).name; - Debug.assert(resolver.isLateBound(getParseTreeNode(node) as Declaration)); // Should only be called with dynamic names - const decl = node as NamedDeclaration as LateBoundDeclaration; - const entityName = decl.name.expression; - checkEntityNameVisibility(entityName, enclosingDeclaration); - if (!suppressNewDiagnosticContexts) { - getSymbolAccessibilityDiagnostic = oldDiag!; + case SyntaxKind.EnumDeclaration: { + return cleanup(updateEnumDeclaration(input, /*decorators*/ undefined, createNodeArray(ensureModifiers(input)), input.name, createNodeArray(mapDefined(input.members, m => { + if (shouldStripInternal(m)) + return; + // Rewrite enum values to their constants, if available + const constValue = resolver.getConstantValue(m); + return preserveJsDoc(updateEnumMember(m, m.name, constValue !== undefined ? createLiteral(constValue) : undefined), m); + })))); } - errorNameNode = undefined; - } - - function shouldStripInternal(node: Node) { - return !!stripInternal && !!node && isInternalDeclaration(node, currentSourceFile); - } - - function isScopeMarker(node: Node) { - return isExportAssignment(node) || isExportDeclaration(node); } - - function hasScopeMarker(statements: readonly Statement[]) { - return some(statements, isScopeMarker); - } - - function ensureModifiers(node: Node): readonly Modifier[] | undefined { - const currentFlags = getModifierFlags(node); - const newFlags = ensureModifierFlags(node); - if (currentFlags === newFlags) { - return node.modifiers; + // Anything left unhandled is an error, so this should be unreachable + return Debug.assertNever(input, `Unhandled top-level node in declaration emit: ${(ts as any).SyntaxKind[(input as any).kind]}`); + function cleanup(node: T | undefined): T | undefined { + if (isEnclosingDeclaration(input)) { + enclosingDeclaration = previousEnclosingDeclaration; } - return createModifiersFromModifierFlags(newFlags); - } - - function ensureModifierFlags(node: Node): ModifierFlags { - let mask = ModifierFlags.All ^ (ModifierFlags.Public | ModifierFlags.Async); // No async modifiers in declaration files - let additions = (needsDeclare && !isAlwaysType(node)) ? ModifierFlags.Ambient : ModifierFlags.None; - const parentIsFile = node.parent.kind === SyntaxKind.SourceFile; - if (!parentIsFile || (isBundledEmit && parentIsFile && isExternalModule(node.parent as SourceFile))) { - mask ^= ModifierFlags.Ambient; - additions = ModifierFlags.None; + if (canProdiceDiagnostic) { + getSymbolAccessibilityDiagnostic = oldDiag; } - return maskModifierFlags(node, mask, additions); - } - - function getTypeAnnotationFromAllAccessorDeclarations(node: AccessorDeclaration, accessors: AllAccessorDeclarations) { - let accessorType = getTypeAnnotationFromAccessor(node); - if (!accessorType && node !== accessors.firstAccessor) { - accessorType = getTypeAnnotationFromAccessor(accessors.firstAccessor); - // If we end up pulling the type from the second accessor, we also need to change the diagnostic context to get the expected error message - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(accessors.firstAccessor); + if (input.kind === SyntaxKind.ModuleDeclaration) { + needsDeclare = previousNeedsDeclare; } - if (!accessorType && accessors.secondAccessor && node !== accessors.secondAccessor) { - accessorType = getTypeAnnotationFromAccessor(accessors.secondAccessor); - // If we end up pulling the type from the second accessor, we also need to change the diagnostic context to get the expected error message - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(accessors.secondAccessor); + if ((node as Node) === input) { + return node; } - return accessorType; + return node && setOriginalNode(preserveJsDoc(node, input), input); } - - function transformHeritageClauses(nodes: NodeArray | undefined) { - return createNodeArray(filter(map(nodes, clause => updateHeritageClause(clause, visitNodes(createNodeArray(filter(clause.types, t => { - return isEntityNameExpression(t.expression) || (clause.token === SyntaxKind.ExtendsKeyword && t.expression.kind === SyntaxKind.NullKeyword); - })), visitDeclarationSubtree))), clause => clause.types && !!clause.types.length)); + } + function transformVariableStatement(input: VariableStatement) { + if (!forEach(input.declarationList.declarations, getBindingNameVisible)) + return; + const nodes = visitNodes(input.declarationList.declarations, visitDeclarationSubtree); + if (!length(nodes)) + return; + return updateVariableStatement(input, createNodeArray(ensureModifiers(input)), updateVariableDeclarationList(input.declarationList, nodes)); + } + function recreateBindingPattern(d: BindingPattern): VariableDeclaration[] { + return flatten(mapDefined(d.elements, e => recreateBindingElement(e))); + } + function recreateBindingElement(e: ArrayBindingElement) { + if (e.kind === SyntaxKind.OmittedExpression) { + return; + } + if (e.name) { + if (!getBindingNameVisible(e)) + return; + if (isBindingPattern(e.name)) { + return recreateBindingPattern(e.name); + } + else { + return createVariableDeclaration(e.name, ensureType(e, /*type*/ undefined), /*initializer*/ undefined); + } } } - - function isAlwaysType(node: Node) { - if (node.kind === SyntaxKind.InterfaceDeclaration) { - return true; + function checkName(node: DeclarationDiagnosticProducing) { + let oldDiag: typeof getSymbolAccessibilityDiagnostic | undefined; + if (!suppressNewDiagnosticContexts) { + oldDiag = getSymbolAccessibilityDiagnostic; + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNodeName(node); } - return false; + errorNameNode = (node as NamedDeclaration).name; + Debug.assert(resolver.isLateBound((getParseTreeNode(node) as Declaration))); // Should only be called with dynamic names + const decl = (node as NamedDeclaration as LateBoundDeclaration); + const entityName = decl.name.expression; + checkEntityNameVisibility(entityName, enclosingDeclaration); + if (!suppressNewDiagnosticContexts) { + getSymbolAccessibilityDiagnostic = oldDiag!; + } + errorNameNode = undefined; } - - // Elide "public" modifier, as it is the default - function maskModifiers(node: Node, modifierMask?: ModifierFlags, modifierAdditions?: ModifierFlags): Modifier[] { - return createModifiersFromModifierFlags(maskModifierFlags(node, modifierMask, modifierAdditions)); + function shouldStripInternal(node: Node) { + return !!stripInternal && !!node && isInternalDeclaration(node, currentSourceFile); } - - function maskModifierFlags(node: Node, modifierMask: ModifierFlags = ModifierFlags.All ^ ModifierFlags.Public, modifierAdditions: ModifierFlags = ModifierFlags.None): ModifierFlags { - let flags = (getModifierFlags(node) & modifierMask) | modifierAdditions; - if (flags & ModifierFlags.Default && !(flags & ModifierFlags.Export)) { - // A non-exported default is a nonsequitor - we usually try to remove all export modifiers - // from statements in ambient declarations; but a default export must retain its export modifier to be syntactically valid - flags ^= ModifierFlags.Export; - } - if (flags & ModifierFlags.Default && flags & ModifierFlags.Ambient) { - flags ^= ModifierFlags.Ambient; // `declare` is never required alongside `default` (and would be an error if printed) - } - return flags; + function isScopeMarker(node: Node) { + return isExportAssignment(node) || isExportDeclaration(node); } - - function getTypeAnnotationFromAccessor(accessor: AccessorDeclaration): TypeNode | undefined { - if (accessor) { - return accessor.kind === SyntaxKind.GetAccessor - ? accessor.type // Getter - return type - : accessor.parameters.length > 0 - ? accessor.parameters[0].type // Setter parameter type - : undefined; - } + function hasScopeMarker(statements: readonly Statement[]) { + return some(statements, isScopeMarker); } - - type CanHaveLiteralInitializer = VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration; - function canHaveLiteralInitializer(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - return !hasModifier(node, ModifierFlags.Private); - case SyntaxKind.Parameter: - case SyntaxKind.VariableDeclaration: - return true; + function ensureModifiers(node: Node): readonly Modifier[] | undefined { + const currentFlags = getModifierFlags(node); + const newFlags = ensureModifierFlags(node); + if (currentFlags === newFlags) { + return node.modifiers; } - return false; + return createModifiersFromModifierFlags(newFlags); } - - type ProcessedDeclarationStatement = - | FunctionDeclaration - | ModuleDeclaration - | ImportEqualsDeclaration - | InterfaceDeclaration - | ClassDeclaration - | TypeAliasDeclaration - | EnumDeclaration - | VariableStatement - | ImportDeclaration - | ExportDeclaration - | ExportAssignment; - - function isPreservedDeclarationStatement(node: Node): node is ProcessedDeclarationStatement { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.VariableStatement: - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ExportDeclaration: - case SyntaxKind.ExportAssignment: - return true; + function ensureModifierFlags(node: Node): ModifierFlags { + let mask = ModifierFlags.All ^ (ModifierFlags.Public | ModifierFlags.Async); // No async modifiers in declaration files + let additions = (needsDeclare && !isAlwaysType(node)) ? ModifierFlags.Ambient : ModifierFlags.None; + const parentIsFile = node.parent.kind === SyntaxKind.SourceFile; + if (!parentIsFile || (isBundledEmit && parentIsFile && isExternalModule((node.parent as SourceFile)))) { + mask ^= ModifierFlags.Ambient; + additions = ModifierFlags.None; } - return false; + return maskModifierFlags(node, mask, additions); } - - type ProcessedComponent = - | ConstructSignatureDeclaration - | ConstructorDeclaration - | MethodDeclaration - | GetAccessorDeclaration - | SetAccessorDeclaration - | PropertyDeclaration - | PropertySignature - | MethodSignature - | CallSignatureDeclaration - | IndexSignatureDeclaration - | VariableDeclaration - | TypeParameterDeclaration - | ExpressionWithTypeArguments - | TypeReferenceNode - | ConditionalTypeNode - | FunctionTypeNode - | ConstructorTypeNode - | ImportTypeNode; - - function isProcessedComponent(node: Node): node is ProcessedComponent { - switch (node.kind) { - case SyntaxKind.ConstructSignature: - case SyntaxKind.Constructor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.VariableDeclaration: - case SyntaxKind.TypeParameter: - case SyntaxKind.ExpressionWithTypeArguments: - case SyntaxKind.TypeReference: - case SyntaxKind.ConditionalType: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.ImportType: - return true; + function getTypeAnnotationFromAllAccessorDeclarations(node: AccessorDeclaration, accessors: AllAccessorDeclarations) { + let accessorType = getTypeAnnotationFromAccessor(node); + if (!accessorType && node !== accessors.firstAccessor) { + accessorType = getTypeAnnotationFromAccessor(accessors.firstAccessor); + // If we end up pulling the type from the second accessor, we also need to change the diagnostic context to get the expected error message + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(accessors.firstAccessor); } - return false; + if (!accessorType && accessors.secondAccessor && node !== accessors.secondAccessor) { + accessorType = getTypeAnnotationFromAccessor(accessors.secondAccessor); + // If we end up pulling the type from the second accessor, we also need to change the diagnostic context to get the expected error message + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(accessors.secondAccessor); + } + return accessorType; + } + function transformHeritageClauses(nodes: NodeArray | undefined) { + return createNodeArray(filter(map(nodes, clause => updateHeritageClause(clause, visitNodes(createNodeArray(filter(clause.types, t => { + return isEntityNameExpression(t.expression) || (clause.token === SyntaxKind.ExtendsKeyword && t.expression.kind === SyntaxKind.NullKeyword); + })), visitDeclarationSubtree))), clause => clause.types && !!clause.types.length)); + } +} +/* @internal */ +function isAlwaysType(node: Node) { + if (node.kind === SyntaxKind.InterfaceDeclaration) { + return true; + } + return false; +} +// Elide "public" modifier, as it is the default +/* @internal */ +function maskModifiers(node: Node, modifierMask?: ModifierFlags, modifierAdditions?: ModifierFlags): Modifier[] { + return createModifiersFromModifierFlags(maskModifierFlags(node, modifierMask, modifierAdditions)); +} +/* @internal */ +function maskModifierFlags(node: Node, modifierMask: ModifierFlags = ModifierFlags.All ^ ModifierFlags.Public, modifierAdditions: ModifierFlags = ModifierFlags.None): ModifierFlags { + let flags = (getModifierFlags(node) & modifierMask) | modifierAdditions; + if (flags & ModifierFlags.Default && !(flags & ModifierFlags.Export)) { + // A non-exported default is a nonsequitor - we usually try to remove all export modifiers + // from statements in ambient declarations; but a default export must retain its export modifier to be syntactically valid + flags ^= ModifierFlags.Export; + } + if (flags & ModifierFlags.Default && flags & ModifierFlags.Ambient) { + flags ^= ModifierFlags.Ambient; // `declare` is never required alongside `default` (and would be an error if printed) + } + return flags; +} +/* @internal */ +function getTypeAnnotationFromAccessor(accessor: AccessorDeclaration): TypeNode | undefined { + if (accessor) { + return accessor.kind === SyntaxKind.GetAccessor + ? accessor.type // Getter - return type + : accessor.parameters.length > 0 + ? accessor.parameters[0].type // Setter parameter type + : undefined; + } +} +/* @internal */ +type CanHaveLiteralInitializer = VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration; +/* @internal */ +function canHaveLiteralInitializer(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + return !hasModifier(node, ModifierFlags.Private); + case SyntaxKind.Parameter: + case SyntaxKind.VariableDeclaration: + return true; + } + return false; +} +/* @internal */ +type ProcessedDeclarationStatement = FunctionDeclaration | ModuleDeclaration | ImportEqualsDeclaration | InterfaceDeclaration | ClassDeclaration | TypeAliasDeclaration | EnumDeclaration | VariableStatement | ImportDeclaration | ExportDeclaration | ExportAssignment; +/* @internal */ +function isPreservedDeclarationStatement(node: Node): node is ProcessedDeclarationStatement { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.VariableStatement: + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportDeclaration: + case SyntaxKind.ExportAssignment: + return true; + } + return false; +} +/* @internal */ +type ProcessedComponent = ConstructSignatureDeclaration | ConstructorDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | PropertyDeclaration | PropertySignature | MethodSignature | CallSignatureDeclaration | IndexSignatureDeclaration | VariableDeclaration | TypeParameterDeclaration | ExpressionWithTypeArguments | TypeReferenceNode | ConditionalTypeNode | FunctionTypeNode | ConstructorTypeNode | ImportTypeNode; +/* @internal */ +function isProcessedComponent(node: Node): node is ProcessedComponent { + switch (node.kind) { + case SyntaxKind.ConstructSignature: + case SyntaxKind.Constructor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.VariableDeclaration: + case SyntaxKind.TypeParameter: + case SyntaxKind.ExpressionWithTypeArguments: + case SyntaxKind.TypeReference: + case SyntaxKind.ConditionalType: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.ImportType: + return true; } + return false; } diff --git a/src/compiler/transformers/declarations/diagnostics.ts b/src/compiler/transformers/declarations/diagnostics.ts index ac44f7efd15e5..1b84d1e739fd7 100644 --- a/src/compiler/transformers/declarations/diagnostics.ts +++ b/src/compiler/transformers/declarations/diagnostics.ts @@ -1,76 +1,152 @@ +import { SymbolAccessibilityResult, Node, DiagnosticMessage, DeclarationName, QualifiedName, VariableDeclaration, PropertyDeclaration, PropertySignature, BindingElement, SetAccessorDeclaration, GetAccessorDeclaration, ConstructSignatureDeclaration, CallSignatureDeclaration, MethodDeclaration, MethodSignature, FunctionDeclaration, ParameterDeclaration, TypeParameterDeclaration, ExpressionWithTypeArguments, ImportEqualsDeclaration, TypeAliasDeclaration, ConstructorDeclaration, IndexSignatureDeclaration, PropertyAccessExpression, isVariableDeclaration, isPropertyDeclaration, isPropertySignature, isBindingElement, isSetAccessor, isGetAccessor, isConstructSignatureDeclaration, isCallSignatureDeclaration, isMethodDeclaration, isMethodSignature, isFunctionDeclaration, isParameter, isTypeParameterDeclaration, isExpressionWithTypeArguments, isImportEqualsDeclaration, isTypeAliasDeclaration, isConstructorDeclaration, isIndexSignatureDeclaration, isPropertyAccessExpression, NamedDeclaration, hasModifier, ModifierFlags, SymbolAccessibility, Diagnostics, SyntaxKind, isParameterPropertyDeclaration, Debug, isHeritageClause, getNameOfDeclaration, Declaration } from "../../ts"; +import * as ts from "../../ts"; /* @internal */ -namespace ts { - export type GetSymbolAccessibilityDiagnostic = (symbolAccessibilityResult: SymbolAccessibilityResult) => (SymbolAccessibilityDiagnostic | undefined); - - export interface SymbolAccessibilityDiagnostic { - errorNode: Node; - diagnosticMessage: DiagnosticMessage; - typeName?: DeclarationName | QualifiedName; +export type GetSymbolAccessibilityDiagnostic = (symbolAccessibilityResult: SymbolAccessibilityResult) => (SymbolAccessibilityDiagnostic | undefined); +/* @internal */ +export interface SymbolAccessibilityDiagnostic { + errorNode: Node; + diagnosticMessage: DiagnosticMessage; + typeName?: DeclarationName | QualifiedName; +} +/* @internal */ +export type DeclarationDiagnosticProducing = VariableDeclaration | PropertyDeclaration | PropertySignature | BindingElement | SetAccessorDeclaration | GetAccessorDeclaration | ConstructSignatureDeclaration | CallSignatureDeclaration | MethodDeclaration | MethodSignature | FunctionDeclaration | ParameterDeclaration | TypeParameterDeclaration | ExpressionWithTypeArguments | ImportEqualsDeclaration | TypeAliasDeclaration | ConstructorDeclaration | IndexSignatureDeclaration | PropertyAccessExpression; +/* @internal */ +export function canProduceDiagnostics(node: Node): node is DeclarationDiagnosticProducing { + return isVariableDeclaration(node) || + isPropertyDeclaration(node) || + isPropertySignature(node) || + isBindingElement(node) || + isSetAccessor(node) || + isGetAccessor(node) || + isConstructSignatureDeclaration(node) || + isCallSignatureDeclaration(node) || + isMethodDeclaration(node) || + isMethodSignature(node) || + isFunctionDeclaration(node) || + isParameter(node) || + isTypeParameterDeclaration(node) || + isExpressionWithTypeArguments(node) || + isImportEqualsDeclaration(node) || + isTypeAliasDeclaration(node) || + isConstructorDeclaration(node) || + isIndexSignatureDeclaration(node) || + isPropertyAccessExpression(node); +} +/* @internal */ +export function createGetSymbolAccessibilityDiagnosticForNodeName(node: DeclarationDiagnosticProducing) { + if (isSetAccessor(node) || isGetAccessor(node)) { + return getAccessorNameVisibilityError; + } + else if (isMethodSignature(node) || isMethodDeclaration(node)) { + return getMethodNameVisibilityError; + } + else { + return createGetSymbolAccessibilityDiagnosticForNode(node); } - - export type DeclarationDiagnosticProducing = - | VariableDeclaration - | PropertyDeclaration - | PropertySignature - | BindingElement - | SetAccessorDeclaration - | GetAccessorDeclaration - | ConstructSignatureDeclaration - | CallSignatureDeclaration - | MethodDeclaration - | MethodSignature - | FunctionDeclaration - | ParameterDeclaration - | TypeParameterDeclaration - | ExpressionWithTypeArguments - | ImportEqualsDeclaration - | TypeAliasDeclaration - | ConstructorDeclaration - | IndexSignatureDeclaration - | PropertyAccessExpression; - - export function canProduceDiagnostics(node: Node): node is DeclarationDiagnosticProducing { - return isVariableDeclaration(node) || - isPropertyDeclaration(node) || - isPropertySignature(node) || - isBindingElement(node) || - isSetAccessor(node) || - isGetAccessor(node) || - isConstructSignatureDeclaration(node) || - isCallSignatureDeclaration(node) || - isMethodDeclaration(node) || - isMethodSignature(node) || - isFunctionDeclaration(node) || - isParameter(node) || - isTypeParameterDeclaration(node) || - isExpressionWithTypeArguments(node) || - isImportEqualsDeclaration(node) || - isTypeAliasDeclaration(node) || - isConstructorDeclaration(node) || - isIndexSignatureDeclaration(node) || - isPropertyAccessExpression(node); + function getAccessorNameVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult) { + const diagnosticMessage = getAccessorNameVisibilityDiagnosticMessage(symbolAccessibilityResult); + return diagnosticMessage !== undefined ? { + diagnosticMessage, + errorNode: node, + typeName: (node as NamedDeclaration).name + } : undefined; } - - export function createGetSymbolAccessibilityDiagnosticForNodeName(node: DeclarationDiagnosticProducing) { - if (isSetAccessor(node) || isGetAccessor(node)) { - return getAccessorNameVisibilityError; + function getAccessorNameVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult) { + if (hasModifier(node, ModifierFlags.Static)) { + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_private_name_1; } - else if (isMethodSignature(node) || isMethodDeclaration(node)) { - return getMethodNameVisibilityError; + else if (node.parent.kind === SyntaxKind.ClassDeclaration) { + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Public_property_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Public_property_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Public_property_0_of_exported_class_has_or_is_using_private_name_1; } else { - return createGetSymbolAccessibilityDiagnosticForNode(node); + return symbolAccessibilityResult.errorModuleName ? + Diagnostics.Property_0_of_exported_interface_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Property_0_of_exported_interface_has_or_is_using_private_name_1; + } + } + function getMethodNameVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic | undefined { + const diagnosticMessage = getMethodNameVisibilityDiagnosticMessage(symbolAccessibilityResult); + return diagnosticMessage !== undefined ? { + diagnosticMessage, + errorNode: node, + typeName: (node as NamedDeclaration).name + } : undefined; + } + function getMethodNameVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult) { + if (hasModifier(node, ModifierFlags.Static)) { + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Public_static_method_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Public_static_method_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Public_static_method_0_of_exported_class_has_or_is_using_private_name_1; + } + else if (node.parent.kind === SyntaxKind.ClassDeclaration) { + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Public_method_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Public_method_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Public_method_0_of_exported_class_has_or_is_using_private_name_1; + } + else { + return symbolAccessibilityResult.errorModuleName ? + Diagnostics.Method_0_of_exported_interface_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Method_0_of_exported_interface_has_or_is_using_private_name_1; + } + } +} +/* @internal */ +export function createGetSymbolAccessibilityDiagnosticForNode(node: DeclarationDiagnosticProducing): (symbolAccessibilityResult: SymbolAccessibilityResult) => SymbolAccessibilityDiagnostic | undefined { + if (isVariableDeclaration(node) || isPropertyDeclaration(node) || isPropertySignature(node) || isPropertyAccessExpression(node) || isBindingElement(node) || isConstructorDeclaration(node)) { + return getVariableDeclarationTypeVisibilityError; + } + else if (isSetAccessor(node) || isGetAccessor(node)) { + return getAccessorDeclarationTypeVisibilityError; + } + else if (isConstructSignatureDeclaration(node) || isCallSignatureDeclaration(node) || isMethodDeclaration(node) || isMethodSignature(node) || isFunctionDeclaration(node) || isIndexSignatureDeclaration(node)) { + return getReturnTypeVisibilityError; + } + else if (isParameter(node)) { + if (isParameterPropertyDeclaration(node, node.parent) && hasModifier(node.parent, ModifierFlags.Private)) { + return getVariableDeclarationTypeVisibilityError; } - function getAccessorNameVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult) { - const diagnosticMessage = getAccessorNameVisibilityDiagnosticMessage(symbolAccessibilityResult); - return diagnosticMessage !== undefined ? { - diagnosticMessage, - errorNode: node, - typeName: (node as NamedDeclaration).name - } : undefined; + return getParameterDeclarationTypeVisibilityError; + } + else if (isTypeParameterDeclaration(node)) { + return getTypeParameterConstraintVisibilityError; + } + else if (isExpressionWithTypeArguments(node)) { + return getHeritageClauseVisibilityError; + } + else if (isImportEqualsDeclaration(node)) { + return getImportEntityNameVisibilityError; + } + else if (isTypeAliasDeclaration(node)) { + return getTypeAliasDeclarationVisibilityError; + } + else { + return Debug.assertNever(node, `Attempted to set a declaration diagnostic context for unhandled node kind: ${(ts as any).SyntaxKind[(node as any).kind]}`); + } + function getVariableDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult) { + if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Exported_variable_0_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Exported_variable_0_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Exported_variable_0_has_or_is_using_private_name_1; } - - function getAccessorNameVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult) { + // This check is to ensure we don't report error on constructor parameter property as that error would be reported during parameter emit + // The only exception here is if the constructor was marked as private. we are not emitting the constructor parameters at all. + else if (node.kind === SyntaxKind.PropertyDeclaration || node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.PropertySignature || + (node.kind === SyntaxKind.Parameter && hasModifier(node.parent, ModifierFlags.Private))) { + // TODO(jfreeman): Deal with computed properties in error reporting. if (hasModifier(node, ModifierFlags.Static)) { return symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? @@ -78,7 +154,7 @@ namespace ts { Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_private_name_1; } - else if (node.parent.kind === SyntaxKind.ClassDeclaration) { + else if (node.parent.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.Parameter) { return symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? Diagnostics.Public_property_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : @@ -86,399 +162,269 @@ namespace ts { Diagnostics.Public_property_0_of_exported_class_has_or_is_using_private_name_1; } else { + // Interfaces cannot have types that cannot be named return symbolAccessibilityResult.errorModuleName ? Diagnostics.Property_0_of_exported_interface_has_or_is_using_name_1_from_private_module_2 : Diagnostics.Property_0_of_exported_interface_has_or_is_using_private_name_1; } } - - function getMethodNameVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic | undefined { - const diagnosticMessage = getMethodNameVisibilityDiagnosticMessage(symbolAccessibilityResult); - return diagnosticMessage !== undefined ? { - diagnosticMessage, - errorNode: node, - typeName: (node as NamedDeclaration).name - } : undefined; - } - - function getMethodNameVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult) { + } + function getVariableDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic | undefined { + const diagnosticMessage = getVariableDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult); + return diagnosticMessage !== undefined ? { + diagnosticMessage, + errorNode: node, + typeName: (node as NamedDeclaration).name + } : undefined; + } + function getAccessorDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic { + let diagnosticMessage: DiagnosticMessage; + if (node.kind === SyntaxKind.SetAccessor) { + // Getters can infer the return type from the returned expression, but setters cannot, so the + // "_from_external_module_1_but_cannot_be_named" case cannot occur. if (hasModifier(node, ModifierFlags.Static)) { - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Public_static_method_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Public_static_method_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Public_static_method_0_of_exported_class_has_or_is_using_private_name_1; - } - else if (node.parent.kind === SyntaxKind.ClassDeclaration) { - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Public_method_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Public_method_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Public_method_0_of_exported_class_has_or_is_using_private_name_1; + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + Diagnostics.Parameter_type_of_public_static_setter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_type_of_public_static_setter_0_from_exported_class_has_or_is_using_private_name_1; } else { - return symbolAccessibilityResult.errorModuleName ? - Diagnostics.Method_0_of_exported_interface_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Method_0_of_exported_interface_has_or_is_using_private_name_1; - } - } - } - - export function createGetSymbolAccessibilityDiagnosticForNode(node: DeclarationDiagnosticProducing): (symbolAccessibilityResult: SymbolAccessibilityResult) => SymbolAccessibilityDiagnostic | undefined { - if (isVariableDeclaration(node) || isPropertyDeclaration(node) || isPropertySignature(node) || isPropertyAccessExpression(node) || isBindingElement(node) || isConstructorDeclaration(node)) { - return getVariableDeclarationTypeVisibilityError; - } - else if (isSetAccessor(node) || isGetAccessor(node)) { - return getAccessorDeclarationTypeVisibilityError; - } - else if (isConstructSignatureDeclaration(node) || isCallSignatureDeclaration(node) || isMethodDeclaration(node) || isMethodSignature(node) || isFunctionDeclaration(node) || isIndexSignatureDeclaration(node)) { - return getReturnTypeVisibilityError; - } - else if (isParameter(node)) { - if (isParameterPropertyDeclaration(node, node.parent) && hasModifier(node.parent, ModifierFlags.Private)) { - return getVariableDeclarationTypeVisibilityError; + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + Diagnostics.Parameter_type_of_public_setter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_type_of_public_setter_0_from_exported_class_has_or_is_using_private_name_1; } - return getParameterDeclarationTypeVisibilityError; - } - else if (isTypeParameterDeclaration(node)) { - return getTypeParameterConstraintVisibilityError; - } - else if (isExpressionWithTypeArguments(node)) { - return getHeritageClauseVisibilityError; - } - else if (isImportEqualsDeclaration(node)) { - return getImportEntityNameVisibilityError; - } - else if (isTypeAliasDeclaration(node)) { - return getTypeAliasDeclarationVisibilityError; } else { - return Debug.assertNever(node, `Attempted to set a declaration diagnostic context for unhandled node kind: ${(ts as any).SyntaxKind[(node as any).kind]}`); - } - - function getVariableDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult) { - if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { - return symbolAccessibilityResult.errorModuleName ? + if (hasModifier(node, ModifierFlags.Static)) { + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Exported_variable_0_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Exported_variable_0_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Exported_variable_0_has_or_is_using_private_name_1; + Diagnostics.Return_type_of_public_static_getter_0_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Return_type_of_public_static_getter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Return_type_of_public_static_getter_0_from_exported_class_has_or_is_using_private_name_1; } - // This check is to ensure we don't report error on constructor parameter property as that error would be reported during parameter emit - // The only exception here is if the constructor was marked as private. we are not emitting the constructor parameters at all. - else if (node.kind === SyntaxKind.PropertyDeclaration || node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.PropertySignature || - (node.kind === SyntaxKind.Parameter && hasModifier(node.parent, ModifierFlags.Private))) { - // TODO(jfreeman): Deal with computed properties in error reporting. - if (hasModifier(node, ModifierFlags.Static)) { - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_private_name_1; - } - else if (node.parent.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.Parameter) { - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Public_property_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Public_property_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Public_property_0_of_exported_class_has_or_is_using_private_name_1; - } - else { - // Interfaces cannot have types that cannot be named - return symbolAccessibilityResult.errorModuleName ? - Diagnostics.Property_0_of_exported_interface_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Property_0_of_exported_interface_has_or_is_using_private_name_1; - } + else { + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Return_type_of_public_getter_0_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Return_type_of_public_getter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Return_type_of_public_getter_0_from_exported_class_has_or_is_using_private_name_1; } } - - function getVariableDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic | undefined { - const diagnosticMessage = getVariableDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult); - return diagnosticMessage !== undefined ? { - diagnosticMessage, - errorNode: node, - typeName: (node as NamedDeclaration).name - } : undefined; - } - - function getAccessorDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic { - let diagnosticMessage: DiagnosticMessage; - if (node.kind === SyntaxKind.SetAccessor) { - // Getters can infer the return type from the returned expression, but setters cannot, so the - // "_from_external_module_1_but_cannot_be_named" case cannot occur. - if (hasModifier(node, ModifierFlags.Static)) { - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - Diagnostics.Parameter_type_of_public_static_setter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_type_of_public_static_setter_0_from_exported_class_has_or_is_using_private_name_1; - } - else { - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - Diagnostics.Parameter_type_of_public_setter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_type_of_public_setter_0_from_exported_class_has_or_is_using_private_name_1; - } - } - else { + return { + diagnosticMessage, + errorNode: ((node as NamedDeclaration).name!), + typeName: (node as NamedDeclaration).name + }; + } + function getReturnTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic { + let diagnosticMessage: DiagnosticMessage; + switch (node.kind) { + case SyntaxKind.ConstructSignature: + // Interfaces cannot have return types that cannot be named + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + Diagnostics.Return_type_of_constructor_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_constructor_signature_from_exported_interface_has_or_is_using_private_name_0; + break; + case SyntaxKind.CallSignature: + // Interfaces cannot have return types that cannot be named + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + Diagnostics.Return_type_of_call_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_call_signature_from_exported_interface_has_or_is_using_private_name_0; + break; + case SyntaxKind.IndexSignature: + // Interfaces cannot have return types that cannot be named + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + Diagnostics.Return_type_of_index_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_index_signature_from_exported_interface_has_or_is_using_private_name_0; + break; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: if (hasModifier(node, ModifierFlags.Static)) { diagnosticMessage = symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Return_type_of_public_static_getter_0_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Return_type_of_public_static_getter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Return_type_of_public_static_getter_0_from_exported_class_has_or_is_using_private_name_1; + Diagnostics.Return_type_of_public_static_method_from_exported_class_has_or_is_using_name_0_from_external_module_1_but_cannot_be_named : + Diagnostics.Return_type_of_public_static_method_from_exported_class_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_public_static_method_from_exported_class_has_or_is_using_private_name_0; } - else { + else if (node.parent.kind === SyntaxKind.ClassDeclaration) { diagnosticMessage = symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Return_type_of_public_getter_0_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Return_type_of_public_getter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Return_type_of_public_getter_0_from_exported_class_has_or_is_using_private_name_1; + Diagnostics.Return_type_of_public_method_from_exported_class_has_or_is_using_name_0_from_external_module_1_but_cannot_be_named : + Diagnostics.Return_type_of_public_method_from_exported_class_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_public_method_from_exported_class_has_or_is_using_private_name_0; } - } - return { - diagnosticMessage, - errorNode: (node as NamedDeclaration).name!, - typeName: (node as NamedDeclaration).name - }; - } - - function getReturnTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic { - let diagnosticMessage: DiagnosticMessage; - switch (node.kind) { - case SyntaxKind.ConstructSignature: - // Interfaces cannot have return types that cannot be named - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - Diagnostics.Return_type_of_constructor_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_constructor_signature_from_exported_interface_has_or_is_using_private_name_0; - break; - - case SyntaxKind.CallSignature: - // Interfaces cannot have return types that cannot be named - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - Diagnostics.Return_type_of_call_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_call_signature_from_exported_interface_has_or_is_using_private_name_0; - break; - - case SyntaxKind.IndexSignature: + else { // Interfaces cannot have return types that cannot be named diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - Diagnostics.Return_type_of_index_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_index_signature_from_exported_interface_has_or_is_using_private_name_0; - break; - - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - if (hasModifier(node, ModifierFlags.Static)) { - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Return_type_of_public_static_method_from_exported_class_has_or_is_using_name_0_from_external_module_1_but_cannot_be_named : - Diagnostics.Return_type_of_public_static_method_from_exported_class_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_public_static_method_from_exported_class_has_or_is_using_private_name_0; - } - else if (node.parent.kind === SyntaxKind.ClassDeclaration) { - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Return_type_of_public_method_from_exported_class_has_or_is_using_name_0_from_external_module_1_but_cannot_be_named : - Diagnostics.Return_type_of_public_method_from_exported_class_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_public_method_from_exported_class_has_or_is_using_private_name_0; - } - else { - // Interfaces cannot have return types that cannot be named - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - Diagnostics.Return_type_of_method_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_method_from_exported_interface_has_or_is_using_private_name_0; - } - break; - - case SyntaxKind.FunctionDeclaration: - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Return_type_of_exported_function_has_or_is_using_name_0_from_external_module_1_but_cannot_be_named : - Diagnostics.Return_type_of_exported_function_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_exported_function_has_or_is_using_private_name_0; - break; - - default: - return Debug.fail("This is unknown kind for signature: " + node.kind); - } - - return { - diagnosticMessage, - errorNode: (node as NamedDeclaration).name || node - }; - } - - function getParameterDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic | undefined { - const diagnosticMessage: DiagnosticMessage = getParameterDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult); - return diagnosticMessage !== undefined ? { - diagnosticMessage, - errorNode: node, - typeName: (node as NamedDeclaration).name - } : undefined; + Diagnostics.Return_type_of_method_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_method_from_exported_interface_has_or_is_using_private_name_0; + } + break; + case SyntaxKind.FunctionDeclaration: + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Return_type_of_exported_function_has_or_is_using_name_0_from_external_module_1_but_cannot_be_named : + Diagnostics.Return_type_of_exported_function_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_exported_function_has_or_is_using_private_name_0; + break; + default: + return Debug.fail("This is unknown kind for signature: " + node.kind); } - - function getParameterDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult): DiagnosticMessage { - switch (node.parent.kind) { - case SyntaxKind.Constructor: + return { + diagnosticMessage, + errorNode: (node as NamedDeclaration).name || node + }; + } + function getParameterDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic | undefined { + const diagnosticMessage: DiagnosticMessage = getParameterDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult); + return diagnosticMessage !== undefined ? { + diagnosticMessage, + errorNode: node, + typeName: (node as NamedDeclaration).name + } : undefined; + } + function getParameterDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult): DiagnosticMessage { + switch (node.parent.kind) { + case SyntaxKind.Constructor: + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Parameter_0_of_constructor_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Parameter_0_of_constructor_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_constructor_from_exported_class_has_or_is_using_private_name_1; + case SyntaxKind.ConstructSignature: + case SyntaxKind.ConstructorType: + // Interfaces cannot have parameter types that cannot be named + return symbolAccessibilityResult.errorModuleName ? + Diagnostics.Parameter_0_of_constructor_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_constructor_signature_from_exported_interface_has_or_is_using_private_name_1; + case SyntaxKind.CallSignature: + // Interfaces cannot have parameter types that cannot be named + return symbolAccessibilityResult.errorModuleName ? + Diagnostics.Parameter_0_of_call_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_call_signature_from_exported_interface_has_or_is_using_private_name_1; + case SyntaxKind.IndexSignature: + // Interfaces cannot have parameter types that cannot be named + return symbolAccessibilityResult.errorModuleName ? + Diagnostics.Parameter_0_of_index_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_index_signature_from_exported_interface_has_or_is_using_private_name_1; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + if (hasModifier(node.parent, ModifierFlags.Static)) { return symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Parameter_0_of_constructor_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Parameter_0_of_constructor_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_constructor_from_exported_class_has_or_is_using_private_name_1; - - case SyntaxKind.ConstructSignature: - case SyntaxKind.ConstructorType: - // Interfaces cannot have parameter types that cannot be named - return symbolAccessibilityResult.errorModuleName ? - Diagnostics.Parameter_0_of_constructor_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_constructor_signature_from_exported_interface_has_or_is_using_private_name_1; - - case SyntaxKind.CallSignature: - // Interfaces cannot have parameter types that cannot be named - return symbolAccessibilityResult.errorModuleName ? - Diagnostics.Parameter_0_of_call_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_call_signature_from_exported_interface_has_or_is_using_private_name_1; - - case SyntaxKind.IndexSignature: - // Interfaces cannot have parameter types that cannot be named - return symbolAccessibilityResult.errorModuleName ? - Diagnostics.Parameter_0_of_index_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_index_signature_from_exported_interface_has_or_is_using_private_name_1; - - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - if (hasModifier(node.parent, ModifierFlags.Static)) { - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Parameter_0_of_public_static_method_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Parameter_0_of_public_static_method_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_public_static_method_from_exported_class_has_or_is_using_private_name_1; - } - else if (node.parent.parent.kind === SyntaxKind.ClassDeclaration) { - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Parameter_0_of_public_method_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Parameter_0_of_public_method_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_public_method_from_exported_class_has_or_is_using_private_name_1; - } - else { - // Interfaces cannot have parameter types that cannot be named - return symbolAccessibilityResult.errorModuleName ? - Diagnostics.Parameter_0_of_method_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_method_from_exported_interface_has_or_is_using_private_name_1; - } - - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionType: + Diagnostics.Parameter_0_of_public_static_method_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Parameter_0_of_public_static_method_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_public_static_method_from_exported_class_has_or_is_using_private_name_1; + } + else if (node.parent.parent.kind === SyntaxKind.ClassDeclaration) { return symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Parameter_0_of_exported_function_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Parameter_0_of_exported_function_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_exported_function_has_or_is_using_private_name_1; - case SyntaxKind.SetAccessor: - case SyntaxKind.GetAccessor: + Diagnostics.Parameter_0_of_public_method_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Parameter_0_of_public_method_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_public_method_from_exported_class_has_or_is_using_private_name_1; + } + else { + // Interfaces cannot have parameter types that cannot be named return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Parameter_0_of_accessor_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Parameter_0_of_accessor_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_accessor_has_or_is_using_private_name_1; - - default: - return Debug.fail(`Unknown parent for parameter: ${(ts as any).SyntaxKind[node.parent.kind]}`); - } - } - - function getTypeParameterConstraintVisibilityError(): SymbolAccessibilityDiagnostic { - // Type parameter constraints are named by user so we should always be able to name it - let diagnosticMessage: DiagnosticMessage; - switch (node.parent.kind) { - case SyntaxKind.ClassDeclaration: - diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_class_has_or_is_using_private_name_1; - break; - - case SyntaxKind.InterfaceDeclaration: - diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_interface_has_or_is_using_private_name_1; - break; - - case SyntaxKind.MappedType: - diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_mapped_object_type_is_using_private_name_1; - break; - - case SyntaxKind.ConstructorType: - case SyntaxKind.ConstructSignature: - diagnosticMessage = Diagnostics.Type_parameter_0_of_constructor_signature_from_exported_interface_has_or_is_using_private_name_1; - break; - - case SyntaxKind.CallSignature: - diagnosticMessage = Diagnostics.Type_parameter_0_of_call_signature_from_exported_interface_has_or_is_using_private_name_1; - break; - - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - if (hasModifier(node.parent, ModifierFlags.Static)) { - diagnosticMessage = Diagnostics.Type_parameter_0_of_public_static_method_from_exported_class_has_or_is_using_private_name_1; - } - else if (node.parent.parent.kind === SyntaxKind.ClassDeclaration) { - diagnosticMessage = Diagnostics.Type_parameter_0_of_public_method_from_exported_class_has_or_is_using_private_name_1; - } - else { - diagnosticMessage = Diagnostics.Type_parameter_0_of_method_from_exported_interface_has_or_is_using_private_name_1; - } - break; - - case SyntaxKind.FunctionType: - case SyntaxKind.FunctionDeclaration: - diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_function_has_or_is_using_private_name_1; - break; - - case SyntaxKind.TypeAliasDeclaration: - diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_type_alias_has_or_is_using_private_name_1; - break; - - default: - return Debug.fail("This is unknown parent for type parameter: " + node.parent.kind); - } - - return { - diagnosticMessage, - errorNode: node, - typeName: (node as NamedDeclaration).name - }; + Diagnostics.Parameter_0_of_method_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_method_from_exported_interface_has_or_is_using_private_name_1; + } + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionType: + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Parameter_0_of_exported_function_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Parameter_0_of_exported_function_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_exported_function_has_or_is_using_private_name_1; + case SyntaxKind.SetAccessor: + case SyntaxKind.GetAccessor: + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Parameter_0_of_accessor_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Parameter_0_of_accessor_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_accessor_has_or_is_using_private_name_1; + default: + return Debug.fail(`Unknown parent for parameter: ${(ts as any).SyntaxKind[node.parent.kind]}`); } - - function getHeritageClauseVisibilityError(): SymbolAccessibilityDiagnostic { - let diagnosticMessage: DiagnosticMessage; - // Heritage clause is written by user so it can always be named - if (node.parent.parent.kind === SyntaxKind.ClassDeclaration) { - // Class or Interface implemented/extended is inaccessible - diagnosticMessage = isHeritageClause(node.parent) && node.parent.token === SyntaxKind.ImplementsKeyword ? - Diagnostics.Implements_clause_of_exported_class_0_has_or_is_using_private_name_1 : - Diagnostics.extends_clause_of_exported_class_0_has_or_is_using_private_name_1; - } - else { - // interface is inaccessible - diagnosticMessage = Diagnostics.extends_clause_of_exported_interface_0_has_or_is_using_private_name_1; - } - - return { - diagnosticMessage, - errorNode: node, - typeName: getNameOfDeclaration(node.parent.parent as Declaration) - }; + } + function getTypeParameterConstraintVisibilityError(): SymbolAccessibilityDiagnostic { + // Type parameter constraints are named by user so we should always be able to name it + let diagnosticMessage: DiagnosticMessage; + switch (node.parent.kind) { + case SyntaxKind.ClassDeclaration: + diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_class_has_or_is_using_private_name_1; + break; + case SyntaxKind.InterfaceDeclaration: + diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_interface_has_or_is_using_private_name_1; + break; + case SyntaxKind.MappedType: + diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_mapped_object_type_is_using_private_name_1; + break; + case SyntaxKind.ConstructorType: + case SyntaxKind.ConstructSignature: + diagnosticMessage = Diagnostics.Type_parameter_0_of_constructor_signature_from_exported_interface_has_or_is_using_private_name_1; + break; + case SyntaxKind.CallSignature: + diagnosticMessage = Diagnostics.Type_parameter_0_of_call_signature_from_exported_interface_has_or_is_using_private_name_1; + break; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + if (hasModifier(node.parent, ModifierFlags.Static)) { + diagnosticMessage = Diagnostics.Type_parameter_0_of_public_static_method_from_exported_class_has_or_is_using_private_name_1; + } + else if (node.parent.parent.kind === SyntaxKind.ClassDeclaration) { + diagnosticMessage = Diagnostics.Type_parameter_0_of_public_method_from_exported_class_has_or_is_using_private_name_1; + } + else { + diagnosticMessage = Diagnostics.Type_parameter_0_of_method_from_exported_interface_has_or_is_using_private_name_1; + } + break; + case SyntaxKind.FunctionType: + case SyntaxKind.FunctionDeclaration: + diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_function_has_or_is_using_private_name_1; + break; + case SyntaxKind.TypeAliasDeclaration: + diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_type_alias_has_or_is_using_private_name_1; + break; + default: + return Debug.fail("This is unknown parent for type parameter: " + node.parent.kind); } - - function getImportEntityNameVisibilityError(): SymbolAccessibilityDiagnostic { - return { - diagnosticMessage: Diagnostics.Import_declaration_0_is_using_private_name_1, - errorNode: node, - typeName: (node as NamedDeclaration).name - }; + return { + diagnosticMessage, + errorNode: node, + typeName: (node as NamedDeclaration).name + }; + } + function getHeritageClauseVisibilityError(): SymbolAccessibilityDiagnostic { + let diagnosticMessage: DiagnosticMessage; + // Heritage clause is written by user so it can always be named + if (node.parent.parent.kind === SyntaxKind.ClassDeclaration) { + // Class or Interface implemented/extended is inaccessible + diagnosticMessage = isHeritageClause(node.parent) && node.parent.token === SyntaxKind.ImplementsKeyword ? + Diagnostics.Implements_clause_of_exported_class_0_has_or_is_using_private_name_1 : + Diagnostics.extends_clause_of_exported_class_0_has_or_is_using_private_name_1; } - - function getTypeAliasDeclarationVisibilityError(): SymbolAccessibilityDiagnostic { - return { - diagnosticMessage: Diagnostics.Exported_type_alias_0_has_or_is_using_private_name_1, - errorNode: (node as TypeAliasDeclaration).type, - typeName: (node as TypeAliasDeclaration).name - }; + else { + // interface is inaccessible + diagnosticMessage = Diagnostics.extends_clause_of_exported_interface_0_has_or_is_using_private_name_1; } + return { + diagnosticMessage, + errorNode: node, + typeName: getNameOfDeclaration((node.parent.parent as Declaration)) + }; + } + function getImportEntityNameVisibilityError(): SymbolAccessibilityDiagnostic { + return { + diagnosticMessage: Diagnostics.Import_declaration_0_is_using_private_name_1, + errorNode: node, + typeName: (node as NamedDeclaration).name + }; + } + function getTypeAliasDeclarationVisibilityError(): SymbolAccessibilityDiagnostic { + return { + diagnosticMessage: Diagnostics.Exported_type_alias_0_has_or_is_using_private_name_1, + errorNode: (node as TypeAliasDeclaration).type, + typeName: (node as TypeAliasDeclaration).name + }; } } diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index 23f19ef9da81f..4d5643dc937ec 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -1,537 +1,493 @@ +import { TransformationContext, Expression, BindingOrAssignmentElementTarget, TextRange, Node, BindingOrAssignmentElement, ArrayBindingOrAssignmentPattern, ObjectBindingOrAssignmentPattern, Identifier, VisitResult, VariableDeclaration, DestructuringAssignment, isDestructuringAssignment, isEmptyArrayLiteral, isEmptyObjectLiteral, visitNode, isExpression, isIdentifier, nodeIsSynthesized, some, aggregateTransformFlags, inlineExpressions, createOmittedExpression, setEmitFlags, EmitFlags, append, Debug, setTextRange, createAssignment, __String, getTargetOfBindingOrAssignmentElement, isBindingOrAssignmentPattern, BindingOrAssignmentPattern, getElementsOfBindingOrAssignmentPattern, tryGetPropertyNameOfBindingOrAssignmentElement, isComputedPropertyName, isLiteralExpression, forEach, ParameterDeclaration, BindingName, isVariableDeclaration, getInitializerOfBindingOrAssignmentElement, updateVariableDeclaration, createTempVariable, last, addRange, createVariableDeclaration, isBindingName, createVoidZero, isObjectBindingOrAssignmentPattern, isArrayBindingOrAssignmentPattern, isDeclarationBindingElement, getRestIndicatorOfBindingOrAssignmentElement, getPropertyNameOfBindingOrAssignmentElement, TransformFlags, ElementAccessExpression, createReadHelper, every, isOmittedExpression, createElementAccess, createArraySlice, createConditional, createTypeCheck, PropertyName, LeftHandSideExpression, isStringOrNumericLiteralLike, getSynthesizedClone, createIdentifier, idText, createPropertyAccess, isArrayBindingElement, createArrayBindingPattern, ArrayBindingElement, createArrayLiteral, map, convertToArrayAssignmentElement, isBindingElement, createObjectBindingPattern, BindingElement, createObjectLiteral, convertToObjectAssignmentElement, createBindingElement, UnscopedEmitHelper, createAdd, createLiteral, createCall, getUnscopedHelperName } from "../ts"; /*@internal*/ -namespace ts { - interface FlattenContext { - context: TransformationContext; - level: FlattenLevel; - downlevelIteration: boolean; - hoistTempVariables: boolean; - emitExpression: (value: Expression) => void; - emitBindingOrAssignment: (target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange, original: Node | undefined) => void; - createArrayBindingOrAssignmentPattern: (elements: BindingOrAssignmentElement[]) => ArrayBindingOrAssignmentPattern; - createObjectBindingOrAssignmentPattern: (elements: BindingOrAssignmentElement[]) => ObjectBindingOrAssignmentPattern; - createArrayBindingOrAssignmentElement: (node: Identifier) => BindingOrAssignmentElement; - visitor?: (node: Node) => VisitResult; - } - - export const enum FlattenLevel { - All, - ObjectRest, - } - - /** - * Flattens a DestructuringAssignment or a VariableDeclaration to an expression. - * - * @param node The node to flatten. - * @param visitor An optional visitor used to visit initializers. - * @param context The transformation context. - * @param level Indicates the extent to which flattening should occur. - * @param needsValue An optional value indicating whether the value from the right-hand-side of - * the destructuring assignment is needed as part of a larger expression. - * @param createAssignmentCallback An optional callback used to create the assignment expression. - */ - export function flattenDestructuringAssignment( - node: VariableDeclaration | DestructuringAssignment, - visitor: ((node: Node) => VisitResult) | undefined, - context: TransformationContext, - level: FlattenLevel, - needsValue?: boolean, - createAssignmentCallback?: (name: Identifier, value: Expression, location?: TextRange) => Expression): Expression { - let location: TextRange = node; - let value: Expression | undefined; - if (isDestructuringAssignment(node)) { - value = node.right; - while (isEmptyArrayLiteral(node.left) || isEmptyObjectLiteral(node.left)) { - if (isDestructuringAssignment(value)) { - location = node = value; - value = node.right; - } - else { - return visitNode(value, visitor, isExpression); - } - } - } - - let expressions: Expression[] | undefined; - const flattenContext: FlattenContext = { - context, - level, - downlevelIteration: !!context.getCompilerOptions().downlevelIteration, - hoistTempVariables: true, - emitExpression, - emitBindingOrAssignment, - createArrayBindingOrAssignmentPattern: makeArrayAssignmentPattern, - createObjectBindingOrAssignmentPattern: makeObjectAssignmentPattern, - createArrayBindingOrAssignmentElement: makeAssignmentElement, - visitor - }; - - if (value) { - value = visitNode(value, visitor, isExpression); - - if (isIdentifier(value) && bindingOrAssignmentElementAssignsToName(node, value.escapedText) || - bindingOrAssignmentElementContainsNonLiteralComputedName(node)) { - // If the right-hand value of the assignment is also an assignment target then - // we need to cache the right-hand value. - value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ false, location); - } - else if (needsValue) { - // If the right-hand value of the destructuring assignment needs to be preserved (as - // is the case when the destructuring assignment is part of a larger expression), - // then we need to cache the right-hand value. - // - // The source map location for the assignment should point to the entire binary - // expression. - value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ true, location); - } - else if (nodeIsSynthesized(node)) { - // Generally, the source map location for a destructuring assignment is the root - // expression. - // - // However, if the root expression is synthesized (as in the case - // of the initializer when transforming a ForOfStatement), then the source map - // location should point to the right-hand value of the expression. - location = value; +interface FlattenContext { + context: TransformationContext; + level: FlattenLevel; + downlevelIteration: boolean; + hoistTempVariables: boolean; + emitExpression: (value: Expression) => void; + emitBindingOrAssignment: (target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange, original: Node | undefined) => void; + createArrayBindingOrAssignmentPattern: (elements: BindingOrAssignmentElement[]) => ArrayBindingOrAssignmentPattern; + createObjectBindingOrAssignmentPattern: (elements: BindingOrAssignmentElement[]) => ObjectBindingOrAssignmentPattern; + createArrayBindingOrAssignmentElement: (node: Identifier) => BindingOrAssignmentElement; + visitor?: (node: Node) => VisitResult; +} +/* @internal */ +export const enum FlattenLevel { + All, + ObjectRest +} +/** + * Flattens a DestructuringAssignment or a VariableDeclaration to an expression. + * + * @param node The node to flatten. + * @param visitor An optional visitor used to visit initializers. + * @param context The transformation context. + * @param level Indicates the extent to which flattening should occur. + * @param needsValue An optional value indicating whether the value from the right-hand-side of + * the destructuring assignment is needed as part of a larger expression. + * @param createAssignmentCallback An optional callback used to create the assignment expression. + */ +/* @internal */ +export function flattenDestructuringAssignment(node: VariableDeclaration | DestructuringAssignment, visitor: ((node: Node) => VisitResult) | undefined, context: TransformationContext, level: FlattenLevel, needsValue?: boolean, createAssignmentCallback?: (name: Identifier, value: Expression, location?: TextRange) => Expression): Expression { + let location: TextRange = node; + let value: Expression | undefined; + if (isDestructuringAssignment(node)) { + value = node.right; + while (isEmptyArrayLiteral(node.left) || isEmptyObjectLiteral(node.left)) { + if (isDestructuringAssignment(value)) { + location = node = value; + value = node.right; } - } - - flattenBindingOrAssignmentElement(flattenContext, node, value, location, /*skipInitializer*/ isDestructuringAssignment(node)); - - if (value && needsValue) { - if (!some(expressions)) { - return value; + else { + return visitNode(value, visitor, isExpression); } - - expressions.push(value); - } - - return aggregateTransformFlags(inlineExpressions(expressions!)) || createOmittedExpression(); - - function emitExpression(expression: Expression) { - // NOTE: this completely disables source maps, but aligns with the behavior of - // `emitAssignment` in the old emitter. - setEmitFlags(expression, EmitFlags.NoNestedSourceMaps); - aggregateTransformFlags(expression); - expressions = append(expressions, expression); - } - - function emitBindingOrAssignment(target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange, original: Node) { - Debug.assertNode(target, createAssignmentCallback ? isIdentifier : isExpression); - const expression = createAssignmentCallback - ? createAssignmentCallback(target, value, location) - : setTextRange( - createAssignment(visitNode(target, visitor, isExpression), value), - location - ); - expression.original = original; - emitExpression(expression); } } - - function bindingOrAssignmentElementAssignsToName(element: BindingOrAssignmentElement, escapedName: __String): boolean { - const target = getTargetOfBindingOrAssignmentElement(element)!; // TODO: GH#18217 - if (isBindingOrAssignmentPattern(target)) { - return bindingOrAssignmentPatternAssignsToName(target, escapedName); - } - else if (isIdentifier(target)) { - return target.escapedText === escapedName; + let expressions: Expression[] | undefined; + const flattenContext: FlattenContext = { + context, + level, + downlevelIteration: !!context.getCompilerOptions().downlevelIteration, + hoistTempVariables: true, + emitExpression, + emitBindingOrAssignment, + createArrayBindingOrAssignmentPattern: makeArrayAssignmentPattern, + createObjectBindingOrAssignmentPattern: makeObjectAssignmentPattern, + createArrayBindingOrAssignmentElement: makeAssignmentElement, + visitor + }; + if (value) { + value = visitNode(value, visitor, isExpression); + if (isIdentifier(value) && bindingOrAssignmentElementAssignsToName(node, value.escapedText) || + bindingOrAssignmentElementContainsNonLiteralComputedName(node)) { + // If the right-hand value of the assignment is also an assignment target then + // we need to cache the right-hand value. + value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ false, location); + } + else if (needsValue) { + // If the right-hand value of the destructuring assignment needs to be preserved (as + // is the case when the destructuring assignment is part of a larger expression), + // then we need to cache the right-hand value. + // + // The source map location for the assignment should point to the entire binary + // expression. + value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ true, location); + } + else if (nodeIsSynthesized(node)) { + // Generally, the source map location for a destructuring assignment is the root + // expression. + // + // However, if the root expression is synthesized (as in the case + // of the initializer when transforming a ForOfStatement), then the source map + // location should point to the right-hand value of the expression. + location = value; } - return false; - } - - function bindingOrAssignmentPatternAssignsToName(pattern: BindingOrAssignmentPattern, escapedName: __String): boolean { - const elements = getElementsOfBindingOrAssignmentPattern(pattern); - for (const element of elements) { - if (bindingOrAssignmentElementAssignsToName(element, escapedName)) { - return true; - } + } + flattenBindingOrAssignmentElement(flattenContext, node, value, location, /*skipInitializer*/ isDestructuringAssignment(node)); + if (value && needsValue) { + if (!some(expressions)) { + return value; } - return false; + expressions.push(value); + } + return aggregateTransformFlags(inlineExpressions((expressions!))) || createOmittedExpression(); + function emitExpression(expression: Expression) { + // NOTE: this completely disables source maps, but aligns with the behavior of + // `emitAssignment` in the old emitter. + setEmitFlags(expression, EmitFlags.NoNestedSourceMaps); + aggregateTransformFlags(expression); + expressions = append(expressions, expression); + } + function emitBindingOrAssignment(target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange, original: Node) { + Debug.assertNode(target, createAssignmentCallback ? isIdentifier : isExpression); + const expression = createAssignmentCallback + ? createAssignmentCallback((target), value, location) + : setTextRange(createAssignment(visitNode((target), visitor, isExpression), value), location); + expression.original = original; + emitExpression(expression); + } +} +/* @internal */ +function bindingOrAssignmentElementAssignsToName(element: BindingOrAssignmentElement, escapedName: __String): boolean { + const target = (getTargetOfBindingOrAssignmentElement(element)!); // TODO: GH#18217 + if (isBindingOrAssignmentPattern(target)) { + return bindingOrAssignmentPatternAssignsToName(target, escapedName); } - - function bindingOrAssignmentElementContainsNonLiteralComputedName(element: BindingOrAssignmentElement): boolean { - const propertyName = tryGetPropertyNameOfBindingOrAssignmentElement(element); - if (propertyName && isComputedPropertyName(propertyName) && !isLiteralExpression(propertyName.expression)) { + else if (isIdentifier(target)) { + return target.escapedText === escapedName; + } + return false; +} +/* @internal */ +function bindingOrAssignmentPatternAssignsToName(pattern: BindingOrAssignmentPattern, escapedName: __String): boolean { + const elements = getElementsOfBindingOrAssignmentPattern(pattern); + for (const element of elements) { + if (bindingOrAssignmentElementAssignsToName(element, escapedName)) { return true; } - const target = getTargetOfBindingOrAssignmentElement(element); - return !!target && isBindingOrAssignmentPattern(target) && bindingOrAssignmentPatternContainsNonLiteralComputedName(target); - } - - function bindingOrAssignmentPatternContainsNonLiteralComputedName(pattern: BindingOrAssignmentPattern): boolean { - return !!forEach(getElementsOfBindingOrAssignmentPattern(pattern), bindingOrAssignmentElementContainsNonLiteralComputedName); - } - - /** - * Flattens a VariableDeclaration or ParameterDeclaration to one or more variable declarations. - * - * @param node The node to flatten. - * @param visitor An optional visitor used to visit initializers. - * @param context The transformation context. - * @param boundValue The value bound to the declaration. - * @param skipInitializer A value indicating whether to ignore the initializer of `node`. - * @param hoistTempVariables Indicates whether temporary variables should not be recorded in-line. - * @param level Indicates the extent to which flattening should occur. - */ - export function flattenDestructuringBinding( - node: VariableDeclaration | ParameterDeclaration, - visitor: (node: Node) => VisitResult, - context: TransformationContext, - level: FlattenLevel, - rval?: Expression, - hoistTempVariables = false, - skipInitializer?: boolean): VariableDeclaration[] { - let pendingExpressions: Expression[] | undefined; - const pendingDeclarations: { pendingExpressions?: Expression[], name: BindingName, value: Expression, location?: TextRange, original?: Node; }[] = []; - const declarations: VariableDeclaration[] = []; - const flattenContext: FlattenContext = { - context, - level, - downlevelIteration: !!context.getCompilerOptions().downlevelIteration, - hoistTempVariables, - emitExpression, - emitBindingOrAssignment, - createArrayBindingOrAssignmentPattern: makeArrayBindingPattern, - createObjectBindingOrAssignmentPattern: makeObjectBindingPattern, - createArrayBindingOrAssignmentElement: makeBindingElement, - visitor - }; - - if (isVariableDeclaration(node)) { - let initializer = getInitializerOfBindingOrAssignmentElement(node); - if (initializer && (isIdentifier(initializer) && bindingOrAssignmentElementAssignsToName(node, initializer.escapedText) || - bindingOrAssignmentElementContainsNonLiteralComputedName(node))) { - // If the right-hand value of the assignment is also an assignment target then - // we need to cache the right-hand value. - initializer = ensureIdentifier(flattenContext, initializer, /*reuseIdentifierExpressions*/ false, initializer); - node = updateVariableDeclaration(node, node.name, node.type, initializer); - } - } - - flattenBindingOrAssignmentElement(flattenContext, node, rval, node, skipInitializer); - if (pendingExpressions) { - const temp = createTempVariable(/*recordTempVariable*/ undefined); - if (hoistTempVariables) { - const value = inlineExpressions(pendingExpressions); - pendingExpressions = undefined; - emitBindingOrAssignment(temp, value, /*location*/ undefined, /*original*/ undefined); - } - else { - context.hoistVariableDeclaration(temp); - const pendingDeclaration = last(pendingDeclarations); - pendingDeclaration.pendingExpressions = append( - pendingDeclaration.pendingExpressions, - createAssignment(temp, pendingDeclaration.value) - ); - addRange(pendingDeclaration.pendingExpressions, pendingExpressions); - pendingDeclaration.value = temp; - } - } - for (const { pendingExpressions, name, value, location, original } of pendingDeclarations) { - const variable = createVariableDeclaration( - name, - /*type*/ undefined, - pendingExpressions ? inlineExpressions(append(pendingExpressions, value)) : value - ); - variable.original = original; - setTextRange(variable, location); - if (isIdentifier(name)) { - setEmitFlags(variable, EmitFlags.NoNestedSourceMaps); - } - aggregateTransformFlags(variable); - declarations.push(variable); - } - return declarations; - - function emitExpression(value: Expression) { - pendingExpressions = append(pendingExpressions, value); - } - - function emitBindingOrAssignment(target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange | undefined, original: Node | undefined) { - Debug.assertNode(target, isBindingName); - if (pendingExpressions) { - value = inlineExpressions(append(pendingExpressions, value)); - pendingExpressions = undefined; - } - pendingDeclarations.push({ pendingExpressions, name: target, value, location, original }); - } } - - /** - * Flattens a BindingOrAssignmentElement into zero or more bindings or assignments. - * - * @param flattenContext Options used to control flattening. - * @param element The element to flatten. - * @param value The current RHS value to assign to the element. - * @param location The location to use for source maps and comments. - * @param skipInitializer An optional value indicating whether to include the initializer - * for the element. - */ - function flattenBindingOrAssignmentElement( - flattenContext: FlattenContext, - element: BindingOrAssignmentElement, - value: Expression | undefined, - location: TextRange, - skipInitializer?: boolean) { - if (!skipInitializer) { - const initializer = visitNode(getInitializerOfBindingOrAssignmentElement(element), flattenContext.visitor, isExpression); - if (initializer) { - // Combine value and initializer - value = value ? createDefaultValueCheck(flattenContext, value, initializer, location) : initializer; - } - else if (!value) { - // Use 'void 0' in absence of value and initializer - value = createVoidZero(); - } - } - const bindingTarget = getTargetOfBindingOrAssignmentElement(element)!; // TODO: GH#18217 - if (isObjectBindingOrAssignmentPattern(bindingTarget)) { - flattenObjectBindingOrAssignmentPattern(flattenContext, element, bindingTarget, value!, location); + return false; +} +/* @internal */ +function bindingOrAssignmentElementContainsNonLiteralComputedName(element: BindingOrAssignmentElement): boolean { + const propertyName = tryGetPropertyNameOfBindingOrAssignmentElement(element); + if (propertyName && isComputedPropertyName(propertyName) && !isLiteralExpression(propertyName.expression)) { + return true; + } + const target = getTargetOfBindingOrAssignmentElement(element); + return !!target && isBindingOrAssignmentPattern(target) && bindingOrAssignmentPatternContainsNonLiteralComputedName(target); +} +/* @internal */ +function bindingOrAssignmentPatternContainsNonLiteralComputedName(pattern: BindingOrAssignmentPattern): boolean { + return !!forEach(getElementsOfBindingOrAssignmentPattern(pattern), bindingOrAssignmentElementContainsNonLiteralComputedName); +} +/** + * Flattens a VariableDeclaration or ParameterDeclaration to one or more variable declarations. + * + * @param node The node to flatten. + * @param visitor An optional visitor used to visit initializers. + * @param context The transformation context. + * @param boundValue The value bound to the declaration. + * @param skipInitializer A value indicating whether to ignore the initializer of `node`. + * @param hoistTempVariables Indicates whether temporary variables should not be recorded in-line. + * @param level Indicates the extent to which flattening should occur. + */ +/* @internal */ +export function flattenDestructuringBinding(node: VariableDeclaration | ParameterDeclaration, visitor: (node: Node) => VisitResult, context: TransformationContext, level: FlattenLevel, rval?: Expression, hoistTempVariables = false, skipInitializer?: boolean): VariableDeclaration[] { + let pendingExpressions: Expression[] | undefined; + const pendingDeclarations: { + pendingExpressions?: Expression[]; + name: BindingName; + value: Expression; + location?: TextRange; + original?: Node; + }[] = []; + const declarations: VariableDeclaration[] = []; + const flattenContext: FlattenContext = { + context, + level, + downlevelIteration: !!context.getCompilerOptions().downlevelIteration, + hoistTempVariables, + emitExpression, + emitBindingOrAssignment, + createArrayBindingOrAssignmentPattern: makeArrayBindingPattern, + createObjectBindingOrAssignmentPattern: makeObjectBindingPattern, + createArrayBindingOrAssignmentElement: makeBindingElement, + visitor + }; + if (isVariableDeclaration(node)) { + let initializer = getInitializerOfBindingOrAssignmentElement(node); + if (initializer && (isIdentifier(initializer) && bindingOrAssignmentElementAssignsToName(node, initializer.escapedText) || + bindingOrAssignmentElementContainsNonLiteralComputedName(node))) { + // If the right-hand value of the assignment is also an assignment target then + // we need to cache the right-hand value. + initializer = ensureIdentifier(flattenContext, initializer, /*reuseIdentifierExpressions*/ false, initializer); + node = updateVariableDeclaration(node, node.name, node.type, initializer); } - else if (isArrayBindingOrAssignmentPattern(bindingTarget)) { - flattenArrayBindingOrAssignmentPattern(flattenContext, element, bindingTarget, value!, location); + } + flattenBindingOrAssignmentElement(flattenContext, node, rval, node, skipInitializer); + if (pendingExpressions) { + const temp = createTempVariable(/*recordTempVariable*/ undefined); + if (hoistTempVariables) { + const value = inlineExpressions(pendingExpressions); + pendingExpressions = undefined; + emitBindingOrAssignment(temp, value, /*location*/ undefined, /*original*/ undefined); } else { - flattenContext.emitBindingOrAssignment(bindingTarget, value!, location, /*original*/ element); // TODO: GH#18217 + context.hoistVariableDeclaration(temp); + const pendingDeclaration = last(pendingDeclarations); + pendingDeclaration.pendingExpressions = append(pendingDeclaration.pendingExpressions, createAssignment(temp, pendingDeclaration.value)); + addRange(pendingDeclaration.pendingExpressions, pendingExpressions); + pendingDeclaration.value = temp; } } - - /** - * Flattens an ObjectBindingOrAssignmentPattern into zero or more bindings or assignments. - * - * @param flattenContext Options used to control flattening. - * @param parent The parent element of the pattern. - * @param pattern The ObjectBindingOrAssignmentPattern to flatten. - * @param value The current RHS value to assign to the element. - * @param location The location to use for source maps and comments. - */ - function flattenObjectBindingOrAssignmentPattern(flattenContext: FlattenContext, parent: BindingOrAssignmentElement, pattern: ObjectBindingOrAssignmentPattern, value: Expression, location: TextRange) { - const elements = getElementsOfBindingOrAssignmentPattern(pattern); - const numElements = elements.length; - if (numElements !== 1) { - // For anything other than a single-element destructuring we need to generate a temporary - // to ensure value is evaluated exactly once. Additionally, if we have zero elements - // we need to emit *something* to ensure that in case a 'var' keyword was already emitted, - // so in that case, we'll intentionally create that temporary. - const reuseIdentifierExpressions = !isDeclarationBindingElement(parent) || numElements !== 0; - value = ensureIdentifier(flattenContext, value, reuseIdentifierExpressions, location); + for (const { pendingExpressions, name, value, location, original } of pendingDeclarations) { + const variable = createVariableDeclaration(name, + /*type*/ undefined, pendingExpressions ? inlineExpressions(append(pendingExpressions, value)) : value); + variable.original = original; + setTextRange(variable, location); + if (isIdentifier(name)) { + setEmitFlags(variable, EmitFlags.NoNestedSourceMaps); + } + aggregateTransformFlags(variable); + declarations.push(variable); + } + return declarations; + function emitExpression(value: Expression) { + pendingExpressions = append(pendingExpressions, value); + } + function emitBindingOrAssignment(target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange | undefined, original: Node | undefined) { + Debug.assertNode(target, isBindingName); + if (pendingExpressions) { + value = inlineExpressions(append(pendingExpressions, value)); + pendingExpressions = undefined; } - let bindingElements: BindingOrAssignmentElement[] | undefined; - let computedTempVariables: Expression[] | undefined; - for (let i = 0; i < numElements; i++) { - const element = elements[i]; - if (!getRestIndicatorOfBindingOrAssignmentElement(element)) { - const propertyName = getPropertyNameOfBindingOrAssignmentElement(element)!; - if (flattenContext.level >= FlattenLevel.ObjectRest - && !(element.transformFlags & (TransformFlags.ContainsRestOrSpread | TransformFlags.ContainsObjectRestOrSpread)) - && !(getTargetOfBindingOrAssignmentElement(element)!.transformFlags & (TransformFlags.ContainsRestOrSpread | TransformFlags.ContainsObjectRestOrSpread)) - && !isComputedPropertyName(propertyName)) { - bindingElements = append(bindingElements, element); - } - else { - if (bindingElements) { - flattenContext.emitBindingOrAssignment(flattenContext.createObjectBindingOrAssignmentPattern(bindingElements), value, location, pattern); - bindingElements = undefined; - } - const rhsValue = createDestructuringPropertyAccess(flattenContext, value, propertyName); - if (isComputedPropertyName(propertyName)) { - computedTempVariables = append(computedTempVariables, (rhsValue as ElementAccessExpression).argumentExpression); - } - flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element); - } + pendingDeclarations.push({ pendingExpressions, name: (target), value, location, original }); + } +} +/** + * Flattens a BindingOrAssignmentElement into zero or more bindings or assignments. + * + * @param flattenContext Options used to control flattening. + * @param element The element to flatten. + * @param value The current RHS value to assign to the element. + * @param location The location to use for source maps and comments. + * @param skipInitializer An optional value indicating whether to include the initializer + * for the element. + */ +/* @internal */ +function flattenBindingOrAssignmentElement(flattenContext: FlattenContext, element: BindingOrAssignmentElement, value: Expression | undefined, location: TextRange, skipInitializer?: boolean) { + if (!skipInitializer) { + const initializer = visitNode(getInitializerOfBindingOrAssignmentElement(element), flattenContext.visitor, isExpression); + if (initializer) { + // Combine value and initializer + value = value ? createDefaultValueCheck(flattenContext, value, initializer, location) : initializer; + } + else if (!value) { + // Use 'void 0' in absence of value and initializer + value = createVoidZero(); + } + } + const bindingTarget = (getTargetOfBindingOrAssignmentElement(element)!); // TODO: GH#18217 + if (isObjectBindingOrAssignmentPattern(bindingTarget)) { + flattenObjectBindingOrAssignmentPattern(flattenContext, element, bindingTarget, value!, location); + } + else if (isArrayBindingOrAssignmentPattern(bindingTarget)) { + flattenArrayBindingOrAssignmentPattern(flattenContext, element, bindingTarget, value!, location); + } + else { + flattenContext.emitBindingOrAssignment(bindingTarget, value!, location, /*original*/ element); // TODO: GH#18217 + } +} +/** + * Flattens an ObjectBindingOrAssignmentPattern into zero or more bindings or assignments. + * + * @param flattenContext Options used to control flattening. + * @param parent The parent element of the pattern. + * @param pattern The ObjectBindingOrAssignmentPattern to flatten. + * @param value The current RHS value to assign to the element. + * @param location The location to use for source maps and comments. + */ +/* @internal */ +function flattenObjectBindingOrAssignmentPattern(flattenContext: FlattenContext, parent: BindingOrAssignmentElement, pattern: ObjectBindingOrAssignmentPattern, value: Expression, location: TextRange) { + const elements = getElementsOfBindingOrAssignmentPattern(pattern); + const numElements = elements.length; + if (numElements !== 1) { + // For anything other than a single-element destructuring we need to generate a temporary + // to ensure value is evaluated exactly once. Additionally, if we have zero elements + // we need to emit *something* to ensure that in case a 'var' keyword was already emitted, + // so in that case, we'll intentionally create that temporary. + const reuseIdentifierExpressions = !isDeclarationBindingElement(parent) || numElements !== 0; + value = ensureIdentifier(flattenContext, value, reuseIdentifierExpressions, location); + } + let bindingElements: BindingOrAssignmentElement[] | undefined; + let computedTempVariables: Expression[] | undefined; + for (let i = 0; i < numElements; i++) { + const element = elements[i]; + if (!getRestIndicatorOfBindingOrAssignmentElement(element)) { + const propertyName = (getPropertyNameOfBindingOrAssignmentElement(element)!); + if (flattenContext.level >= FlattenLevel.ObjectRest + && !(element.transformFlags & (TransformFlags.ContainsRestOrSpread | TransformFlags.ContainsObjectRestOrSpread)) + && !(getTargetOfBindingOrAssignmentElement(element)!.transformFlags & (TransformFlags.ContainsRestOrSpread | TransformFlags.ContainsObjectRestOrSpread)) + && !isComputedPropertyName(propertyName)) { + bindingElements = append(bindingElements, element); } - else if (i === numElements - 1) { + else { if (bindingElements) { flattenContext.emitBindingOrAssignment(flattenContext.createObjectBindingOrAssignmentPattern(bindingElements), value, location, pattern); bindingElements = undefined; } - const rhsValue = createRestCall(flattenContext.context, value, elements, computedTempVariables!, pattern); // TODO: GH#18217 - flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, element); + const rhsValue = createDestructuringPropertyAccess(flattenContext, value, propertyName); + if (isComputedPropertyName(propertyName)) { + computedTempVariables = append(computedTempVariables, (rhsValue as ElementAccessExpression).argumentExpression); + } + flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element); } } - if (bindingElements) { - flattenContext.emitBindingOrAssignment(flattenContext.createObjectBindingOrAssignmentPattern(bindingElements), value, location, pattern); + else if (i === numElements - 1) { + if (bindingElements) { + flattenContext.emitBindingOrAssignment(flattenContext.createObjectBindingOrAssignmentPattern(bindingElements), value, location, pattern); + bindingElements = undefined; + } + const rhsValue = createRestCall(flattenContext.context, value, elements, computedTempVariables!, pattern); // TODO: GH#18217 + flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, element); } } - - /** - * Flattens an ArrayBindingOrAssignmentPattern into zero or more bindings or assignments. - * - * @param flattenContext Options used to control flattening. - * @param parent The parent element of the pattern. - * @param pattern The ArrayBindingOrAssignmentPattern to flatten. - * @param value The current RHS value to assign to the element. - * @param location The location to use for source maps and comments. - */ - function flattenArrayBindingOrAssignmentPattern(flattenContext: FlattenContext, parent: BindingOrAssignmentElement, pattern: ArrayBindingOrAssignmentPattern, value: Expression, location: TextRange) { - const elements = getElementsOfBindingOrAssignmentPattern(pattern); - const numElements = elements.length; - if (flattenContext.level < FlattenLevel.ObjectRest && flattenContext.downlevelIteration) { - // Read the elements of the iterable into an array - value = ensureIdentifier( - flattenContext, - createReadHelper( - flattenContext.context, - value, - numElements > 0 && getRestIndicatorOfBindingOrAssignmentElement(elements[numElements - 1]) - ? undefined - : numElements, - location - ), - /*reuseIdentifierExpressions*/ false, - location - ); - } - else if (numElements !== 1 && (flattenContext.level < FlattenLevel.ObjectRest || numElements === 0) - || every(elements, isOmittedExpression)) { - // For anything other than a single-element destructuring we need to generate a temporary - // to ensure value is evaluated exactly once. Additionally, if we have zero elements - // we need to emit *something* to ensure that in case a 'var' keyword was already emitted, - // so in that case, we'll intentionally create that temporary. - // Or all the elements of the binding pattern are omitted expression such as "var [,] = [1,2]", - // then we will create temporary variable. - const reuseIdentifierExpressions = !isDeclarationBindingElement(parent) || numElements !== 0; - value = ensureIdentifier(flattenContext, value, reuseIdentifierExpressions, location); - } - let bindingElements: BindingOrAssignmentElement[] | undefined; - let restContainingElements: [Identifier, BindingOrAssignmentElement][] | undefined; - for (let i = 0; i < numElements; i++) { - const element = elements[i]; - if (flattenContext.level >= FlattenLevel.ObjectRest) { - // If an array pattern contains an ObjectRest, we must cache the result so that we - // can perform the ObjectRest destructuring in a different declaration - if (element.transformFlags & TransformFlags.ContainsObjectRestOrSpread) { - const temp = createTempVariable(/*recordTempVariable*/ undefined); - if (flattenContext.hoistTempVariables) { - flattenContext.context.hoistVariableDeclaration(temp); - } - - restContainingElements = append(restContainingElements, <[Identifier, BindingOrAssignmentElement]>[temp, element]); - bindingElements = append(bindingElements, flattenContext.createArrayBindingOrAssignmentElement(temp)); - } - else { - bindingElements = append(bindingElements, element); + if (bindingElements) { + flattenContext.emitBindingOrAssignment(flattenContext.createObjectBindingOrAssignmentPattern(bindingElements), value, location, pattern); + } +} +/** + * Flattens an ArrayBindingOrAssignmentPattern into zero or more bindings or assignments. + * + * @param flattenContext Options used to control flattening. + * @param parent The parent element of the pattern. + * @param pattern The ArrayBindingOrAssignmentPattern to flatten. + * @param value The current RHS value to assign to the element. + * @param location The location to use for source maps and comments. + */ +/* @internal */ +function flattenArrayBindingOrAssignmentPattern(flattenContext: FlattenContext, parent: BindingOrAssignmentElement, pattern: ArrayBindingOrAssignmentPattern, value: Expression, location: TextRange) { + const elements = getElementsOfBindingOrAssignmentPattern(pattern); + const numElements = elements.length; + if (flattenContext.level < FlattenLevel.ObjectRest && flattenContext.downlevelIteration) { + // Read the elements of the iterable into an array + value = ensureIdentifier(flattenContext, createReadHelper(flattenContext.context, value, numElements > 0 && getRestIndicatorOfBindingOrAssignmentElement(elements[numElements - 1]) + ? undefined + : numElements, location), + /*reuseIdentifierExpressions*/ false, location); + } + else if (numElements !== 1 && (flattenContext.level < FlattenLevel.ObjectRest || numElements === 0) + || every(elements, isOmittedExpression)) { + // For anything other than a single-element destructuring we need to generate a temporary + // to ensure value is evaluated exactly once. Additionally, if we have zero elements + // we need to emit *something* to ensure that in case a 'var' keyword was already emitted, + // so in that case, we'll intentionally create that temporary. + // Or all the elements of the binding pattern are omitted expression such as "var [,] = [1,2]", + // then we will create temporary variable. + const reuseIdentifierExpressions = !isDeclarationBindingElement(parent) || numElements !== 0; + value = ensureIdentifier(flattenContext, value, reuseIdentifierExpressions, location); + } + let bindingElements: BindingOrAssignmentElement[] | undefined; + let restContainingElements: [Identifier, BindingOrAssignmentElement][] | undefined; + for (let i = 0; i < numElements; i++) { + const element = elements[i]; + if (flattenContext.level >= FlattenLevel.ObjectRest) { + // If an array pattern contains an ObjectRest, we must cache the result so that we + // can perform the ObjectRest destructuring in a different declaration + if (element.transformFlags & TransformFlags.ContainsObjectRestOrSpread) { + const temp = createTempVariable(/*recordTempVariable*/ undefined); + if (flattenContext.hoistTempVariables) { + flattenContext.context.hoistVariableDeclaration(temp); } + restContainingElements = append(restContainingElements, (<[Identifier, BindingOrAssignmentElement]>[temp, element])); + bindingElements = append(bindingElements, flattenContext.createArrayBindingOrAssignmentElement(temp)); } - else if (isOmittedExpression(element)) { - continue; - } - else if (!getRestIndicatorOfBindingOrAssignmentElement(element)) { - const rhsValue = createElementAccess(value, i); - flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element); - } - else if (i === numElements - 1) { - const rhsValue = createArraySlice(value, i); - flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element); + else { + bindingElements = append(bindingElements, element); } } - if (bindingElements) { - flattenContext.emitBindingOrAssignment(flattenContext.createArrayBindingOrAssignmentPattern(bindingElements), value, location, pattern); + else if (isOmittedExpression(element)) { + continue; } - if (restContainingElements) { - for (const [id, element] of restContainingElements) { - flattenBindingOrAssignmentElement(flattenContext, element, id, element); - } - } - } - - /** - * Creates an expression used to provide a default value if a value is `undefined` at runtime. - * - * @param flattenContext Options used to control flattening. - * @param value The RHS value to test. - * @param defaultValue The default value to use if `value` is `undefined` at runtime. - * @param location The location to use for source maps and comments. - */ - function createDefaultValueCheck(flattenContext: FlattenContext, value: Expression, defaultValue: Expression, location: TextRange): Expression { - value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ true, location); - return createConditional(createTypeCheck(value, "undefined"), defaultValue, value); - } - - /** - * Creates either a PropertyAccessExpression or an ElementAccessExpression for the - * right-hand side of a transformed destructuring assignment. - * - * @link https://tc39.github.io/ecma262/#sec-runtime-semantics-keyeddestructuringassignmentevaluation - * - * @param flattenContext Options used to control flattening. - * @param value The RHS value that is the source of the property. - * @param propertyName The destructuring property name. - */ - function createDestructuringPropertyAccess(flattenContext: FlattenContext, value: Expression, propertyName: PropertyName): LeftHandSideExpression { - if (isComputedPropertyName(propertyName)) { - const argumentExpression = ensureIdentifier(flattenContext, visitNode(propertyName.expression, flattenContext.visitor), /*reuseIdentifierExpressions*/ false, /*location*/ propertyName); - return createElementAccess(value, argumentExpression); + else if (!getRestIndicatorOfBindingOrAssignmentElement(element)) { + const rhsValue = createElementAccess(value, i); + flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element); } - else if (isStringOrNumericLiteralLike(propertyName)) { - const argumentExpression = getSynthesizedClone(propertyName); - argumentExpression.text = argumentExpression.text; - return createElementAccess(value, argumentExpression); + else if (i === numElements - 1) { + const rhsValue = createArraySlice(value, i); + flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element); } - else { - const name = createIdentifier(idText(propertyName)); - return createPropertyAccess(value, name); + } + if (bindingElements) { + flattenContext.emitBindingOrAssignment(flattenContext.createArrayBindingOrAssignmentPattern(bindingElements), value, location, pattern); + } + if (restContainingElements) { + for (const [id, element] of restContainingElements) { + flattenBindingOrAssignmentElement(flattenContext, element, id, element); } } - - /** - * Ensures that there exists a declared identifier whose value holds the given expression. - * This function is useful to ensure that the expression's value can be read from in subsequent expressions. - * Unless 'reuseIdentifierExpressions' is false, 'value' will be returned if it is just an identifier. - * - * @param flattenContext Options used to control flattening. - * @param value the expression whose value needs to be bound. - * @param reuseIdentifierExpressions true if identifier expressions can simply be returned; - * false if it is necessary to always emit an identifier. - * @param location The location to use for source maps and comments. - */ - function ensureIdentifier(flattenContext: FlattenContext, value: Expression, reuseIdentifierExpressions: boolean, location: TextRange) { - if (isIdentifier(value) && reuseIdentifierExpressions) { - return value; +} +/** + * Creates an expression used to provide a default value if a value is `undefined` at runtime. + * + * @param flattenContext Options used to control flattening. + * @param value The RHS value to test. + * @param defaultValue The default value to use if `value` is `undefined` at runtime. + * @param location The location to use for source maps and comments. + */ +/* @internal */ +function createDefaultValueCheck(flattenContext: FlattenContext, value: Expression, defaultValue: Expression, location: TextRange): Expression { + value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ true, location); + return createConditional(createTypeCheck(value, "undefined"), defaultValue, value); +} +/** + * Creates either a PropertyAccessExpression or an ElementAccessExpression for the + * right-hand side of a transformed destructuring assignment. + * + * @link https://tc39.github.io/ecma262/#sec-runtime-semantics-keyeddestructuringassignmentevaluation + * + * @param flattenContext Options used to control flattening. + * @param value The RHS value that is the source of the property. + * @param propertyName The destructuring property name. + */ +/* @internal */ +function createDestructuringPropertyAccess(flattenContext: FlattenContext, value: Expression, propertyName: PropertyName): LeftHandSideExpression { + if (isComputedPropertyName(propertyName)) { + const argumentExpression = ensureIdentifier(flattenContext, visitNode(propertyName.expression, flattenContext.visitor), /*reuseIdentifierExpressions*/ false, /*location*/ propertyName); + return createElementAccess(value, argumentExpression); + } + else if (isStringOrNumericLiteralLike(propertyName)) { + const argumentExpression = getSynthesizedClone(propertyName); + argumentExpression.text = argumentExpression.text; + return createElementAccess(value, argumentExpression); + } + else { + const name = createIdentifier(idText(propertyName)); + return createPropertyAccess(value, name); + } +} +/** + * Ensures that there exists a declared identifier whose value holds the given expression. + * This function is useful to ensure that the expression's value can be read from in subsequent expressions. + * Unless 'reuseIdentifierExpressions' is false, 'value' will be returned if it is just an identifier. + * + * @param flattenContext Options used to control flattening. + * @param value the expression whose value needs to be bound. + * @param reuseIdentifierExpressions true if identifier expressions can simply be returned; + * false if it is necessary to always emit an identifier. + * @param location The location to use for source maps and comments. + */ +/* @internal */ +function ensureIdentifier(flattenContext: FlattenContext, value: Expression, reuseIdentifierExpressions: boolean, location: TextRange) { + if (isIdentifier(value) && reuseIdentifierExpressions) { + return value; + } + else { + const temp = createTempVariable(/*recordTempVariable*/ undefined); + if (flattenContext.hoistTempVariables) { + flattenContext.context.hoistVariableDeclaration(temp); + flattenContext.emitExpression(setTextRange(createAssignment(temp, value), location)); } else { - const temp = createTempVariable(/*recordTempVariable*/ undefined); - if (flattenContext.hoistTempVariables) { - flattenContext.context.hoistVariableDeclaration(temp); - flattenContext.emitExpression(setTextRange(createAssignment(temp, value), location)); - } - else { - flattenContext.emitBindingOrAssignment(temp, value, location, /*original*/ undefined); - } - return temp; + flattenContext.emitBindingOrAssignment(temp, value, location, /*original*/ undefined); } + return temp; } - - function makeArrayBindingPattern(elements: BindingOrAssignmentElement[]) { - Debug.assertEachNode(elements, isArrayBindingElement); - return createArrayBindingPattern(elements); - } - - function makeArrayAssignmentPattern(elements: BindingOrAssignmentElement[]) { - return createArrayLiteral(map(elements, convertToArrayAssignmentElement)); - } - - function makeObjectBindingPattern(elements: BindingOrAssignmentElement[]) { - Debug.assertEachNode(elements, isBindingElement); - return createObjectBindingPattern(elements); - } - - function makeObjectAssignmentPattern(elements: BindingOrAssignmentElement[]) { - return createObjectLiteral(map(elements, convertToObjectAssignmentElement)); - } - - function makeBindingElement(name: Identifier) { - return createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, name); - } - - function makeAssignmentElement(name: Identifier) { - return name; - } - - export const restHelper: UnscopedEmitHelper = { - name: "typescript:rest", - importName: "__rest", - scoped: false, - text: ` +} +/* @internal */ +function makeArrayBindingPattern(elements: BindingOrAssignmentElement[]) { + Debug.assertEachNode(elements, isArrayBindingElement); + return createArrayBindingPattern((elements)); +} +/* @internal */ +function makeArrayAssignmentPattern(elements: BindingOrAssignmentElement[]) { + return createArrayLiteral(map(elements, convertToArrayAssignmentElement)); +} +/* @internal */ +function makeObjectBindingPattern(elements: BindingOrAssignmentElement[]) { + Debug.assertEachNode(elements, isBindingElement); + return createObjectBindingPattern((elements)); +} +/* @internal */ +function makeObjectAssignmentPattern(elements: BindingOrAssignmentElement[]) { + return createObjectLiteral(map(elements, convertToObjectAssignmentElement)); +} +/* @internal */ +function makeBindingElement(name: Identifier) { + return createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, name); +} +/* @internal */ +function makeAssignmentElement(name: Identifier) { + return name; +} +/* @internal */ +export const restHelper: UnscopedEmitHelper = { + name: "typescript:rest", + importName: "__rest", + scoped: false, + text: ` var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) @@ -543,44 +499,32 @@ namespace ts { } return t; };` - }; - - /** Given value: o, propName: p, pattern: { a, b, ...p } from the original statement - * `{ a, b, ...p } = o`, create `p = __rest(o, ["a", "b"]);` - */ - function createRestCall(context: TransformationContext, value: Expression, elements: readonly BindingOrAssignmentElement[], computedTempVariables: readonly Expression[], location: TextRange): Expression { - context.requestEmitHelper(restHelper); - const propertyNames: Expression[] = []; - let computedTempVariableOffset = 0; - for (let i = 0; i < elements.length - 1; i++) { - const propertyName = getPropertyNameOfBindingOrAssignmentElement(elements[i]); - if (propertyName) { - if (isComputedPropertyName(propertyName)) { - const temp = computedTempVariables[computedTempVariableOffset]; - computedTempVariableOffset++; - // typeof _tmp === "symbol" ? _tmp : _tmp + "" - propertyNames.push( - createConditional( - createTypeCheck(temp, "symbol"), - temp, - createAdd(temp, createLiteral("")) - ) - ); - } - else { - propertyNames.push(createLiteral(propertyName)); - } +}; +/** Given value: o, propName: p, pattern: { a, b, ...p } from the original statement + * `{ a, b, ...p } = o`, create `p = __rest(o, ["a", "b"]);` + */ +/* @internal */ +function createRestCall(context: TransformationContext, value: Expression, elements: readonly BindingOrAssignmentElement[], computedTempVariables: readonly Expression[], location: TextRange): Expression { + context.requestEmitHelper(restHelper); + const propertyNames: Expression[] = []; + let computedTempVariableOffset = 0; + for (let i = 0; i < elements.length - 1; i++) { + const propertyName = getPropertyNameOfBindingOrAssignmentElement(elements[i]); + if (propertyName) { + if (isComputedPropertyName(propertyName)) { + const temp = computedTempVariables[computedTempVariableOffset]; + computedTempVariableOffset++; + // typeof _tmp === "symbol" ? _tmp : _tmp + "" + propertyNames.push(createConditional(createTypeCheck(temp, "symbol"), temp, createAdd(temp, createLiteral("")))); + } + else { + propertyNames.push(createLiteral(propertyName)); } } - return createCall( - getUnscopedHelperName("__rest"), - /*typeArguments*/ undefined, - [ - value, - setTextRange( - createArrayLiteral(propertyNames), - location - ) - ]); } + return createCall(getUnscopedHelperName("__rest"), + /*typeArguments*/ undefined, [ + value, + setTextRange(createArrayLiteral(propertyNames), location) + ]); } diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 2684b1acdd554..eba960a21d74b 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -1,4377 +1,3329 @@ +import { Identifier, ParameterDeclaration, IterationStatement, LabeledStatement, Statement, TransformationContext, SourceFile, VariableDeclaration, append, createVariableDeclaration, chainBundle, addEmitHelpers, Node, SyntaxKind, ReturnStatement, TransformFlags, isStatement, isIterationStatement, getEmitFlags, EmitFlags, VisitResult, ClassDeclaration, ClassExpression, FunctionDeclaration, ArrowFunction, FunctionExpression, VariableDeclarationList, SwitchStatement, CaseBlock, Block, BreakOrContinueStatement, DoStatement, WhileStatement, ForStatement, ForInStatement, ForOfStatement, ExpressionStatement, ObjectLiteralExpression, CatchClause, ShorthandPropertyAssignment, ComputedPropertyName, ArrayLiteralExpression, CallExpression, NewExpression, ParenthesizedExpression, BinaryExpression, LiteralExpression, StringLiteral, NumericLiteral, TaggedTemplateExpression, TemplateExpression, YieldExpression, SpreadElement, MetaProperty, MethodDeclaration, AccessorDeclaration, VariableStatement, visitEachChild, addStandardPrologue, addCustomPrologue, addRange, visitNodes, createVariableStatement, createVariableDeclarationList, mergeLexicalEnvironment, updateSourceFileNode, setTextRange, createNodeArray, concatenate, setOriginalNode, createReturn, createFileLevelUniqueName, createObjectLiteral, createPropertyAssignment, createIdentifier, visitNode, isExpression, createVoidZero, createUniqueName, isGeneratedIdentifier, idText, Expression, createLiteral, createBinary, getLocalName, startOnNewLine, hasModifier, ModifierFlags, createExportDefault, createExternalModuleExport, createEndOfDeclarationMarker, setEmitFlags, singleOrMany, getClassExtendsHeritageElement, createFunctionExpression, createParameter, createPartiallyEmittedExpression, skipTrivia, createParen, createCall, addSyntheticLeadingComment, ExpressionWithTypeArguments, createTokenRange, getInternalName, insertStatementsAfterStandardPrologue, createBlock, createExpressionStatement, getFirstConstructorWithBody, createFunctionDeclaration, ConstructorDeclaration, visitParameterList, FunctionBody, skipOuterExpressions, isExpressionStatement, isSuperCall, cast, isBinaryExpression, isCallExpression, setCommentRange, getCommentRange, IfStatement, lastOrUndefined, createThis, createLogicalOr, createLogicalAnd, createStrictInequality, createNull, createFunctionApply, isBindingPattern, getGeneratedNameForNode, FunctionLikeDeclaration, some, BindingPattern, insertStatementAfterCustomPrologue, flattenDestructuringBinding, FlattenLevel, createAssignment, createIf, createTypeCheck, getSynthesizedClone, getMutableClone, createTempVariable, createLoopVariable, createArrayLiteral, createFor, createLessThan, createPropertyAccess, createPostfixIncrement, createElementAccess, createSubtract, insertStatementsAfterCustomPrologue, setSourceMapRange, createConditional, Debug, SemicolonClassElement, getAllAccessorDeclarations, createEmptyStatement, LeftHandSideExpression, getSourceMapRange, isPropertyName, isComputedPropertyName, isIdentifier, createStringLiteral, unescapeLeadingUnderscores, createObjectDefinePropertyCall, createPropertyDescriptor, createMemberAccessForPropertyName, AllAccessorDeclarations, createExpressionForPropertyName, ObjectLiteralElementLike, createTrue, updateFunctionExpression, updateFunctionDeclaration, isModifier, TextRange, isClassLike, isBlock, moveRangeEnd, nodeIsSynthesized, rangeEndIsOnSameLineAsRangeStart, moveSyntheticComments, arrayIsEqualTo, setTokenSourceMapRange, updateExpressionStatement, updateParen, isDestructuringAssignment, flattenDestructuringAssignment, NodeFlags, inlineExpressions, flatMap, last, createRange, NodeCheckFlags, createMap, unwrapInnermostStatementOfLabel, restoreEnclosingLabel, liftToBlock, isVariableDeclarationList, firstOrUndefined, moveRangePos, aggregateTransformFlags, updateBlock, createValuesHelper, createLogicalNot, createTry, createCatchClause, createFunctionCall, createThrow, isObjectLiteralElementLike, isForStatement, isOmittedExpression, updateFor, isForInitializer, updateForOf, updateForIn, updateDo, updateWhile, getCombinedNodeFlags, createFalse, createToken, map, createStatement, createPrefix, createBreak, createYield, createTypeOf, CaseClause, createSwitch, createCaseBlock, createContinue, createCaseClause, BindingElement, PropertyAssignment, updateCatchClause, updateGetAccessor, updateSetAccessor, isSpreadElement, isSuperProperty, updateCall, isArrowFunction, isVariableStatement, first, filter, tryCast, isAssignmentExpression, isFunctionExpression, isReturnStatement, elementAt, recreateOuterExpressions, createCallBinding, createNew, NodeArray, flatten, spanMap, __String, createSpreadHelper, createSpreadArraysHelper, isArrayLiteralExpression, every, TokenFlags, createNumericLiteral, isNoSubstitutionTemplateLiteral, isExternalModule, TemplateLiteralLikeNode, getSourceTextOfNodeFromSourceFile, reduceLeft, createAdd, EmitHint, isFunctionLike, isInternalName, getParseTreeNode, NamedDeclaration, Declaration, PrimaryExpression, getNameOfDeclaration, ClassLikeDeclaration, getEnclosingBlockScopeContainer, isClassElement, ClassElement, singleOrUndefined, getUnscopedHelperName, UnscopedEmitHelper } from "../ts"; +import * as ts from "../ts"; /*@internal*/ -namespace ts { - const enum ES2015SubstitutionFlags { - /** Enables substitutions for captured `this` */ - CapturedThis = 1 << 0, - /** Enables substitutions for block-scoped bindings. */ - BlockScopedBindings = 1 << 1, - } - - /** - * If loop contains block scoped binding captured in some function then loop body is converted to a function. - * Lexical bindings declared in loop initializer will be passed into the loop body function as parameters, - * however if this binding is modified inside the body - this new value should be propagated back to the original binding. - * This is done by declaring new variable (out parameter holder) outside of the loop for every binding that is reassigned inside the body. - * On every iteration this variable is initialized with value of corresponding binding. - * At every point where control flow leaves the loop either explicitly (break/continue) or implicitly (at the end of loop body) - * we copy the value inside the loop to the out parameter holder. - * +const enum ES2015SubstitutionFlags { + /** Enables substitutions for captured `this` */ + CapturedThis = 1 << 0, + /** Enables substitutions for block-scoped bindings. */ + BlockScopedBindings = 1 << 1 +} +/** + * If loop contains block scoped binding captured in some function then loop body is converted to a function. + * Lexical bindings declared in loop initializer will be passed into the loop body function as parameters, + * however if this binding is modified inside the body - this new value should be propagated back to the original binding. + * This is done by declaring new variable (out parameter holder) outside of the loop for every binding that is reassigned inside the body. + * On every iteration this variable is initialized with value of corresponding binding. + * At every point where control flow leaves the loop either explicitly (break/continue) or implicitly (at the end of loop body) + * we copy the value inside the loop to the out parameter holder. + * + * for (let x;;) { + * let a = 1; + * let b = () => a; + * x++ + * if (...) break; + * ... + * } + * + * will be converted to + * + * var out_x; + * var loop = function(x) { + * var a = 1; + * var b = function() { return a; } + * x++; + * if (...) return out_x = x, "break"; + * ... + * out_x = x; + * } + * for (var x;;) { + * out_x = x; + * var state = loop(x); + * x = out_x; + * if (state === "break") break; + * } + * + * NOTE: values to out parameters are not copies if loop is abrupted with 'return' - in this case this will end the entire enclosing function + * so nobody can observe this new value. + */ +/* @internal */ +interface LoopOutParameter { + flags: LoopOutParameterFlags; + originalName: Identifier; + outParamName: Identifier; +} +/* @internal */ +const enum LoopOutParameterFlags { + Body = 1 << 0, + Initializer = 1 << 1 +} +/* @internal */ +const enum CopyDirection { + ToOriginal, + ToOutParameter +} +/* @internal */ +const enum Jump { + Break = 1 << 1, + Continue = 1 << 2, + Return = 1 << 3 +} +/* @internal */ +interface ConvertedLoopState { + /* + * set of labels that occurred inside the converted loop + * used to determine if labeled jump can be emitted as is or it should be dispatched to calling code + */ + labels?: ts.Map; + /* + * collection of labeled jumps that transfer control outside the converted loop. + * maps store association 'label -> labelMarker' where + * - label - value of label as it appear in code + * - label marker - return value that should be interpreted by calling code as 'jump to