namespace ts { export function isExternalModuleNameRelative(moduleName: string): boolean { // TypeScript 1.0 spec (April 2014): 11.2.1 // An external module name is "relative" if the first term is "." or "..". // Update: We also consider a path like `C:\foo.ts` "relative" because we do not search for it in `node_modules` or treat it as an ambient module. return pathIsRelative(moduleName) || isRootedDiskPath(moduleName); } export function sortAndDeduplicateDiagnostics(diagnostics: ReadonlyArray): SortedReadonlyArray { return sortAndDeduplicate(diagnostics, compareDiagnostics); } } /* @internal */ namespace ts { export const resolvingEmptyArray: never[] = [] as never[]; export const emptyMap = createMap() as ReadonlyMap & ReadonlyPragmaMap; export const emptyUnderscoreEscapedMap: ReadonlyUnderscoreEscapedMap = emptyMap as ReadonlyUnderscoreEscapedMap; export const externalHelpersModuleNameText = "tslib"; export const defaultMaximumTruncationLength = 160; export function getDeclarationOfKind(symbol: Symbol, kind: T["kind"]): T | undefined { const declarations = symbol.declarations; if (declarations) { for (const declaration of declarations) { if (declaration.kind === kind) { return declaration as T; } } } return undefined; } /** Create a new escaped identifier map. */ export function createUnderscoreEscapedMap(): UnderscoreEscapedMap { return new MapCtr() as UnderscoreEscapedMap; } export function hasEntries(map: ReadonlyUnderscoreEscapedMap | undefined): map is ReadonlyUnderscoreEscapedMap { return !!map && !!map.size; } export function createSymbolTable(symbols?: ReadonlyArray): SymbolTable { const result = createMap() as SymbolTable; if (symbols) { for (const symbol of symbols) { result.set(symbol.escapedName, symbol); } } return result; } const stringWriter = createSingleLineStringWriter(); function createSingleLineStringWriter(): EmitTextWriter { let str = ""; const writeText: (text: string) => void = text => str += text; return { getText: () => str, write: writeText, rawWrite: writeText, writeKeyword: writeText, writeOperator: writeText, writePunctuation: writeText, writeSpace: writeText, writeStringLiteral: writeText, writeLiteral: writeText, writeParameter: writeText, writeProperty: writeText, writeSymbol: (s, _) => writeText(s), writeTrailingSemicolon: writeText, writeComment: writeText, getTextPos: () => str.length, getLine: () => 0, getColumn: () => 0, getIndent: () => 0, isAtStartOfLine: () => false, // Completely ignore indentation for string writers. And map newlines to // a single space. writeLine: () => str += " ", increaseIndent: noop, decreaseIndent: noop, clear: () => str = "", trackSymbol: noop, reportInaccessibleThisError: noop, reportInaccessibleUniqueSymbolError: noop, reportPrivateInBaseOfClassExpression: noop, }; } 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 changesAffectModuleResolution(oldOptions: CompilerOptions, newOptions: CompilerOptions): boolean { return oldOptions.configFilePath !== newOptions.configFilePath || optionsHaveModuleResolutionChanges(oldOptions, newOptions); } export function optionsHaveModuleResolutionChanges(oldOptions: CompilerOptions, newOptions: CompilerOptions) { return moduleResolutionOptionDeclarations.some(o => !isJsonEqual(getCompilerOptionValue(oldOptions, o), getCompilerOptionValue(newOptions, o))); } /** * Iterates through the parent chain of a node and performs the callback on each parent until the callback * returns a truthy value, then returns that value. * If no such value is found, it applies the callback until the parent pointer is undefined or the callback returns "quit" * At that point findAncestor returns undefined. */ export function findAncestor(node: Node | undefined, callback: (element: Node) => element is T): T | undefined; export function findAncestor(node: Node | undefined, callback: (element: Node) => boolean | "quit"): Node | undefined; export function findAncestor(node: Node, callback: (element: Node) => boolean | "quit"): Node | undefined { while (node) { const result = callback(node); if (result === "quit") { return undefined; } else if (result) { return node; } node = node.parent; } return undefined; } export function forEachAncestor(node: Node, callback: (n: Node) => T | undefined | "quit"): T | undefined { while (true) { const res = callback(node); if (res === "quit") return undefined; if (res !== undefined) return res; if (isSourceFile(node)) return undefined; node = node.parent; } } /** * Calls `callback` for each entry in the map, returning the first truthy result. * Use `map.forEach` instead for normal iteration. */ export function forEachEntry(map: ReadonlyUnderscoreEscapedMap, callback: (value: T, key: __String) => U | undefined): U | undefined; export function forEachEntry(map: ReadonlyMap, callback: (value: T, key: string) => U | undefined): U | undefined; export function forEachEntry(map: ReadonlyUnderscoreEscapedMap | ReadonlyMap, callback: (value: T, key: (string & __String)) => U | undefined): U | undefined { const iterator = map.entries(); for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) { const [key, value] = iterResult.value; const result = callback(value, key as (string & __String)); if (result) { return result; } } return undefined; } /** `forEachEntry` for just keys. */ export function forEachKey(map: ReadonlyUnderscoreEscapedMap<{}>, callback: (key: __String) => T | undefined): T | undefined; export function forEachKey(map: ReadonlyMap<{}>, callback: (key: string) => T | undefined): T | undefined; export function forEachKey(map: ReadonlyUnderscoreEscapedMap<{}> | ReadonlyMap<{}>, callback: (key: string & __String) => T | undefined): T | undefined { const iterator = map.keys(); for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) { const result = callback(iterResult.value as string & __String); if (result) { return result; } } return undefined; } /** Copy entries from `source` to `target`. */ export function copyEntries(source: ReadonlyUnderscoreEscapedMap, target: UnderscoreEscapedMap): void; export function copyEntries(source: ReadonlyMap, target: Map): void; export function copyEntries | Map>(source: U, target: U): void { (source as Map).forEach((value, key) => { (target as Map).set(key, value); }); } /** * Creates a set from the elements of an array. * * @param array the array of input elements. */ export function arrayToSet(array: ReadonlyArray): Map; export function arrayToSet(array: ReadonlyArray, makeKey: (value: T) => string | undefined): Map; export function arrayToSet(array: ReadonlyArray, makeKey: (value: T) => __String | undefined): UnderscoreEscapedMap; export function arrayToSet(array: ReadonlyArray, makeKey?: (value: any) => string | __String | undefined): Map | UnderscoreEscapedMap { return arrayToMap(array, makeKey || (s => s), () => true); } export function cloneMap(map: SymbolTable): SymbolTable; export function cloneMap(map: ReadonlyMap): Map; export function cloneMap(map: ReadonlyUnderscoreEscapedMap): UnderscoreEscapedMap; export function cloneMap(map: ReadonlyMap | ReadonlyUnderscoreEscapedMap | SymbolTable): Map | UnderscoreEscapedMap | SymbolTable { const clone = createMap(); copyEntries(map as Map, clone); return clone; } export function usingSingleLineStringWriter(action: (writer: EmitTextWriter) => void): string { const oldString = stringWriter.getText(); try { action(stringWriter); return stringWriter.getText(); } finally { stringWriter.clear(); stringWriter.writeKeyword(oldString); } } export function getFullWidth(node: Node) { return node.end - node.pos; } export function getResolvedModule(sourceFile: SourceFile, moduleNameText: string): ResolvedModuleFull | undefined { return sourceFile && sourceFile.resolvedModules && sourceFile.resolvedModules.get(moduleNameText); } export function setResolvedModule(sourceFile: SourceFile, moduleNameText: string, resolvedModule: ResolvedModuleFull): void { if (!sourceFile.resolvedModules) { sourceFile.resolvedModules = createMap(); } sourceFile.resolvedModules.set(moduleNameText, resolvedModule); } export function setResolvedTypeReferenceDirective(sourceFile: SourceFile, typeReferenceDirectiveName: string, resolvedTypeReferenceDirective?: ResolvedTypeReferenceDirective): void { if (!sourceFile.resolvedTypeReferenceDirectiveNames) { sourceFile.resolvedTypeReferenceDirectiveNames = createMap(); } sourceFile.resolvedTypeReferenceDirectiveNames.set(typeReferenceDirectiveName, resolvedTypeReferenceDirective); } export function projectReferenceIsEqualTo(oldRef: ProjectReference, newRef: ProjectReference) { return oldRef.path === newRef.path && !oldRef.prepend === !newRef.prepend && !oldRef.circular === !newRef.circular; } export function moduleResolutionIsEqualTo(oldResolution: ResolvedModuleFull, newResolution: ResolvedModuleFull): boolean { return oldResolution.isExternalLibraryImport === newResolution.isExternalLibraryImport && oldResolution.extension === newResolution.extension && oldResolution.resolvedFileName === newResolution.resolvedFileName && oldResolution.originalPath === newResolution.originalPath && packageIdIsEqual(oldResolution.packageId, newResolution.packageId); } function packageIdIsEqual(a: PackageId | undefined, b: PackageId | undefined): boolean { return a === b || !!a && !!b && a.name === b.name && a.subModuleName === b.subModuleName && a.version === b.version; } export function packageIdToString({ name, subModuleName, version }: PackageId): string { const fullName = subModuleName ? `${name}/${subModuleName}` : name; return `${fullName}@${version}`; } export function typeDirectiveIsEqualTo(oldResolution: ResolvedTypeReferenceDirective, newResolution: ResolvedTypeReferenceDirective): boolean { return oldResolution.resolvedFileName === newResolution.resolvedFileName && oldResolution.primary === newResolution.primary; } export function hasChangesInResolutions( names: ReadonlyArray, newResolutions: ReadonlyArray, oldResolutions: ReadonlyMap | undefined, comparer: (oldResolution: T, newResolution: T) => boolean): boolean { Debug.assert(names.length === newResolutions.length); for (let i = 0; i < names.length; i++) { const newResolution = newResolutions[i]; const oldResolution = oldResolutions && oldResolutions.get(names[i]); const changed = oldResolution ? !newResolution || !comparer(oldResolution, newResolution) : newResolution; if (changed) { return true; } } return false; } // Returns true if this node contains a parse error anywhere underneath it. export function containsParseError(node: Node): boolean { aggregateChildData(node); return (node.flags & NodeFlags.ThisNodeOrAnySubNodesHasError) !== 0; } function aggregateChildData(node: Node): void { if (!(node.flags & NodeFlags.HasAggregatedChildData)) { // A node is considered to contain a parse error if: // a) the parser explicitly marked that it had an error // b) any of it's children reported that it had an error. const thisNodeOrAnySubNodesHasError = ((node.flags & NodeFlags.ThisNodeHasError) !== 0) || forEachChild(node, containsParseError); // If so, mark ourselves accordingly. if (thisNodeOrAnySubNodesHasError) { node.flags |= NodeFlags.ThisNodeOrAnySubNodesHasError; } // Also mark that we've propagated the child information to this node. This way we can // always consult the bit directly on this node without needing to check its children // again. node.flags |= NodeFlags.HasAggregatedChildData; } } export function getSourceFileOfNode(node: Node): SourceFile; export function getSourceFileOfNode(node: Node | undefined): SourceFile | undefined; export function getSourceFileOfNode(node: Node): SourceFile { while (node && node.kind !== SyntaxKind.SourceFile) { node = node.parent; } return node; } export function isStatementWithLocals(node: Node) { switch (node.kind) { case SyntaxKind.Block: case SyntaxKind.CaseBlock: case SyntaxKind.ForStatement: case SyntaxKind.ForInStatement: case SyntaxKind.ForOfStatement: return true; } return false; } export function getStartPositionOfLine(line: number, sourceFile: SourceFileLike): number { Debug.assert(line >= 0); return getLineStarts(sourceFile)[line]; } // This is a useful function for debugging purposes. export function nodePosToString(node: Node): string { const file = getSourceFileOfNode(node); const loc = getLineAndCharacterOfPosition(file, node.pos); return `${file.fileName}(${loc.line + 1},${loc.character + 1})`; } export function getEndLinePosition(line: number, sourceFile: SourceFileLike): number { Debug.assert(line >= 0); const lineStarts = getLineStarts(sourceFile); const lineIndex = line; const sourceText = sourceFile.text; if (lineIndex + 1 === lineStarts.length) { // last line - return EOF return sourceText.length - 1; } else { // current line start const start = lineStarts[lineIndex]; // take the start position of the next line - 1 = it should be some line break let pos = lineStarts[lineIndex + 1] - 1; Debug.assert(isLineBreak(sourceText.charCodeAt(pos))); // walk backwards skipping line breaks, stop the the beginning of current line. // i.e: // // $ <- end of line for this position should match the start position while (start <= pos && isLineBreak(sourceText.charCodeAt(pos))) { pos--; } return pos; } } /** * Returns a value indicating whether a name is unique globally or within the current file. * Note: This does not consider whether a name appears as a free identifier or not, so at the expression `x.y` this includes both `x` and `y`. */ export function isFileLevelUniqueName(sourceFile: SourceFile, name: string, hasGlobalName?: PrintHandlers["hasGlobalName"]): boolean { return !(hasGlobalName && hasGlobalName(name)) && !sourceFile.identifiers.has(name); } // Returns true if this node is missing from the actual source code. A 'missing' node is different // from 'undefined/defined'. When a node is undefined (which can happen for optional nodes // in the tree), it is definitely missing. However, a node may be defined, but still be // missing. This happens whenever the parser knows it needs to parse something, but can't // get anything in the source code that it expects at that location. For example: // // let a: ; // // Here, the Type in the Type-Annotation is not-optional (as there is a colon in the source // code). So the parser will attempt to parse out a type, and will create an actual node. // However, this node will be 'missing' in the sense that no actual source-code/tokens are // contained within it. export function nodeIsMissing(node: Node | undefined): boolean { if (node === undefined) { return true; } return node.pos === node.end && node.pos >= 0 && node.kind !== SyntaxKind.EndOfFileToken; } export function nodeIsPresent(node: Node | undefined): boolean { return !nodeIsMissing(node); } function insertStatementsAfterPrologue(to: T[], from: ReadonlyArray | undefined, isPrologueDirective: (node: Node) => boolean): T[] { if (from === undefined || from.length === 0) return to; let statementIndex = 0; // skip all prologue directives to insert at the correct position for (; statementIndex < to.length; ++statementIndex) { if (!isPrologueDirective(to[statementIndex])) { break; } } to.splice(statementIndex, 0, ...from); return to; } function insertStatementAfterPrologue(to: T[], statement: T | undefined, isPrologueDirective: (node: Node) => boolean): T[] { if (statement === undefined) return to; let statementIndex = 0; // skip all prologue directives to insert at the correct position for (; statementIndex < to.length; ++statementIndex) { if (!isPrologueDirective(to[statementIndex])) { break; } } to.splice(statementIndex, 0, statement); return to; } function isAnyPrologueDirective(node: Node) { return isPrologueDirective(node) || !!(getEmitFlags(node) & EmitFlags.CustomPrologue); } /** * Prepends statements to an array while taking care of prologue directives. */ export function insertStatementsAfterStandardPrologue(to: T[], from: ReadonlyArray | undefined): T[] { return insertStatementsAfterPrologue(to, from, isPrologueDirective); } export function insertStatementsAfterCustomPrologue(to: T[], from: ReadonlyArray | undefined): T[] { return insertStatementsAfterPrologue(to, from, isAnyPrologueDirective); } /** * Prepends statements to an array while taking care of prologue directives. */ export function insertStatementAfterStandardPrologue(to: T[], statement: T | undefined): T[] { return insertStatementAfterPrologue(to, statement, isPrologueDirective); } export function insertStatementAfterCustomPrologue(to: T[], statement: T | undefined): T[] { return insertStatementAfterPrologue(to, statement, isAnyPrologueDirective); } /** * Determine if the given comment is a triple-slash * * @return true if the comment is a triple-slash comment else false */ export function isRecognizedTripleSlashComment(text: string, commentPos: number, commentEnd: number) { // Verify this is /// comment, but do the regexp match only when we first can find /// in the comment text // so that we don't end up computing comment string and doing match for all // comments if (text.charCodeAt(commentPos + 1) === CharacterCodes.slash && commentPos + 2 < commentEnd && text.charCodeAt(commentPos + 2) === CharacterCodes.slash) { const textSubStr = text.substring(commentPos, commentEnd); return textSubStr.match(fullTripleSlashReferencePathRegEx) || textSubStr.match(fullTripleSlashAMDReferencePathRegEx) || textSubStr.match(fullTripleSlashReferenceTypeReferenceDirectiveRegEx) || textSubStr.match(defaultLibReferenceRegEx) ? true : false; } return false; } export function isPinnedComment(text: string, start: number) { return text.charCodeAt(start + 1) === CharacterCodes.asterisk && text.charCodeAt(start + 2) === CharacterCodes.exclamation; } export function getTokenPosOfNode(node: Node, sourceFile?: SourceFileLike, includeJsDoc?: boolean): number { // With nodes that have no width (i.e. 'Missing' nodes), we actually *don't* // want to skip trivia because this will launch us forward to the next token. if (nodeIsMissing(node)) { return node.pos; } if (isJSDocNode(node)) { return skipTrivia((sourceFile || getSourceFileOfNode(node)).text, node.pos, /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); } if (includeJsDoc && hasJSDocNodes(node)) { return getTokenPosOfNode(node.jsDoc![0]); } // For a syntax list, it is possible that one of its children has JSDocComment nodes, while // the syntax list itself considers them as normal trivia. Therefore if we simply skip // trivia for the list, we may have skipped the JSDocComment as well. So we should process its // first child to determine the actual position of its first token. if (node.kind === SyntaxKind.SyntaxList && (node)._children.length > 0) { return getTokenPosOfNode((node)._children[0], sourceFile, includeJsDoc); } return skipTrivia((sourceFile || getSourceFileOfNode(node)).text, node.pos); } export function getNonDecoratorTokenPosOfNode(node: Node, sourceFile?: SourceFileLike): number { if (nodeIsMissing(node) || !node.decorators) { return getTokenPosOfNode(node, sourceFile); } return skipTrivia((sourceFile || getSourceFileOfNode(node)).text, node.decorators.end); } export function getSourceTextOfNodeFromSourceFile(sourceFile: SourceFile, node: Node, includeTrivia = false): string { return getTextOfNodeFromSourceText(sourceFile.text, node, includeTrivia); } function isJSDocTypeExpressionOrChild(node: Node): boolean { return node.kind === SyntaxKind.JSDocTypeExpression || (node.parent && isJSDocTypeExpressionOrChild(node.parent)); } export function getTextOfNodeFromSourceText(sourceText: string, node: Node, includeTrivia = false): string { if (nodeIsMissing(node)) { return ""; } let text = sourceText.substring(includeTrivia ? node.pos : skipTrivia(sourceText, node.pos), node.end); if (isJSDocTypeExpressionOrChild(node)) { // strip space + asterisk at line start text = text.replace(/(^|\r?\n|\r)\s*\*\s*/g, "$1"); } return text; } export function getTextOfNode(node: Node, includeTrivia = false): string { return getSourceTextOfNodeFromSourceFile(getSourceFileOfNode(node), node, includeTrivia); } function getPos(range: Node) { return range.pos; } /** * Note: it is expected that the `nodeArray` and the `node` are within the same file. * For example, searching for a `SourceFile` in a `SourceFile[]` wouldn't work. */ export function indexOfNode(nodeArray: ReadonlyArray, node: Node) { return binarySearch(nodeArray, node, getPos, compareValues); } /** * Gets flags that control emit behavior of a node. */ export function getEmitFlags(node: Node): EmitFlags { const emitNode = node.emitNode; return emitNode && emitNode.flags || 0; } export function getLiteralText(node: LiteralLikeNode, sourceFile: SourceFile, neverAsciiEscape: boolean | undefined) { // If we don't need to downlevel and we can reach the original source text using // the node's parent reference, then simply get the text as it was originally written. if (!nodeIsSynthesized(node) && node.parent && !( (isNumericLiteral(node) && node.numericLiteralFlags & TokenFlags.ContainsSeparator) || isBigIntLiteral(node) )) { return getSourceTextOfNodeFromSourceFile(sourceFile, node); } // If a NoSubstitutionTemplateLiteral appears to have a substitution in it, the original text // had to include a backslash: `not \${a} substitution`. const escapeText = neverAsciiEscape || (getEmitFlags(node) & EmitFlags.NoAsciiEscaping) ? escapeString : escapeNonAsciiString; // If we can't reach the original source text, use the canonical form if it's a number, // or a (possibly escaped) quoted form of the original text if it's string-like. switch (node.kind) { case SyntaxKind.StringLiteral: if ((node).singleQuote) { return "'" + escapeText(node.text, CharacterCodes.singleQuote) + "'"; } else { return '"' + escapeText(node.text, CharacterCodes.doubleQuote) + '"'; } case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.TemplateHead: case SyntaxKind.TemplateMiddle: case SyntaxKind.TemplateTail: const rawText = (node).rawText || escapeTemplateSubstitution(escapeText(node.text, CharacterCodes.backtick)); switch (node.kind) { case SyntaxKind.NoSubstitutionTemplateLiteral: return "`" + rawText + "`"; case SyntaxKind.TemplateHead: // tslint:disable-next-line no-invalid-template-strings return "`" + rawText + "${"; case SyntaxKind.TemplateMiddle: // tslint:disable-next-line no-invalid-template-strings return "}" + rawText + "${"; case SyntaxKind.TemplateTail: return "}" + rawText + "`"; } break; case SyntaxKind.NumericLiteral: case SyntaxKind.BigIntLiteral: case SyntaxKind.RegularExpressionLiteral: return node.text; } return Debug.fail(`Literal kind '${node.kind}' not accounted for.`); } export function getTextOfConstantValue(value: string | number) { return isString(value) ? '"' + escapeNonAsciiString(value) + '"' : "" + value; } // Make an identifier from an external module name by extracting the string after the last "/" and replacing // all non-alphanumeric characters with underscores export function makeIdentifierFromModuleName(moduleName: string): string { return getBaseFileName(moduleName).replace(/^(\d)/, "_$1").replace(/\W/g, "_"); } export function isBlockOrCatchScoped(declaration: Declaration) { return (getCombinedNodeFlags(declaration) & NodeFlags.BlockScoped) !== 0 || isCatchClauseVariableDeclarationOrBindingElement(declaration); } export function isCatchClauseVariableDeclarationOrBindingElement(declaration: Declaration) { const node = getRootDeclaration(declaration); return node.kind === SyntaxKind.VariableDeclaration && node.parent.kind === SyntaxKind.CatchClause; } export function isAmbientModule(node: Node): node is AmbientModuleDeclaration { return isModuleDeclaration(node) && (node.name.kind === SyntaxKind.StringLiteral || isGlobalScopeAugmentation(node)); } export function isModuleWithStringLiteralName(node: Node): node is ModuleDeclaration { return isModuleDeclaration(node) && node.name.kind === SyntaxKind.StringLiteral; } export function isNonGlobalAmbientModule(node: Node): node is ModuleDeclaration & { name: StringLiteral } { return isModuleDeclaration(node) && isStringLiteral(node.name); } /** * An effective module (namespace) declaration is either * 1. An actual declaration: namespace X { ... } * 2. A Javascript declaration, which is: * An identifier in a nested property access expression: Y in `X.Y.Z = { ... }` */ export function isEffectiveModuleDeclaration(node: Node) { return isModuleDeclaration(node) || isIdentifier(node); } /** Given a symbol for a module, checks that it is a shorthand ambient module. */ export function isShorthandAmbientModuleSymbol(moduleSymbol: Symbol): boolean { return isShorthandAmbientModule(moduleSymbol.valueDeclaration); } function isShorthandAmbientModule(node: Node): boolean { // The only kind of module that can be missing a body is a shorthand ambient module. return node && node.kind === SyntaxKind.ModuleDeclaration && (!(node).body); } export function isBlockScopedContainerTopLevel(node: Node): boolean { return node.kind === SyntaxKind.SourceFile || node.kind === SyntaxKind.ModuleDeclaration || isFunctionLike(node); } export function isGlobalScopeAugmentation(module: ModuleDeclaration): boolean { return !!(module.flags & NodeFlags.GlobalAugmentation); } export function isExternalModuleAugmentation(node: Node): node is AmbientModuleDeclaration { return isAmbientModule(node) && isModuleAugmentationExternal(node); } export function isModuleAugmentationExternal(node: AmbientModuleDeclaration) { // external module augmentation is a ambient module declaration that is either: // - defined in the top level scope and source file is an external module // - defined inside ambient module declaration located in the top level scope and source file not an external module switch (node.parent.kind) { case SyntaxKind.SourceFile: return isExternalModule(node.parent); case SyntaxKind.ModuleBlock: return isAmbientModule(node.parent.parent) && isSourceFile(node.parent.parent.parent) && !isExternalModule(node.parent.parent.parent); } return false; } export function getNonAugmentationDeclaration(symbol: Symbol) { return find(symbol.declarations, d => !isExternalModuleAugmentation(d) && !(isModuleDeclaration(d) && isGlobalScopeAugmentation(d))); } export function isEffectiveExternalModule(node: SourceFile, compilerOptions: CompilerOptions) { return isExternalModule(node) || compilerOptions.isolatedModules || ((getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS) && !!node.commonJsModuleIndicator); } /** * Returns whether the source file will be treated as if it were in strict mode at runtime. */ export function isEffectiveStrictModeSourceFile(node: SourceFile, compilerOptions: CompilerOptions) { // We can only verify strict mode for JS/TS files switch (node.scriptKind) { case ScriptKind.JS: case ScriptKind.TS: case ScriptKind.JSX: case ScriptKind.TSX: break; default: return false; } // Strict mode does not matter for declaration files. if (node.isDeclarationFile) { return false; } // If `alwaysStrict` is set, then treat the file as strict. if (getStrictOptionValue(compilerOptions, "alwaysStrict")) { return true; } // Starting with a "use strict" directive indicates the file is strict. if (startsWithUseStrict(node.statements)) { return true; } if (isExternalModule(node) || compilerOptions.isolatedModules) { // ECMAScript Modules are always strict. if (getEmitModuleKind(compilerOptions) >= ModuleKind.ES2015) { return true; } // Other modules are strict unless otherwise specified. return !compilerOptions.noImplicitUseStrict; } return false; } export function isBlockScope(node: Node, parentNode: Node): boolean { switch (node.kind) { case SyntaxKind.SourceFile: case SyntaxKind.CaseBlock: case SyntaxKind.CatchClause: case SyntaxKind.ModuleDeclaration: case SyntaxKind.ForStatement: case SyntaxKind.ForInStatement: case SyntaxKind.ForOfStatement: case SyntaxKind.Constructor: case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: return true; case SyntaxKind.Block: // function block is not considered block-scope container // see comment in binder.ts: bind(...), case for SyntaxKind.Block return !isFunctionLike(parentNode); } return false; } export function isDeclarationWithTypeParameters(node: Node): node is DeclarationWithTypeParameters; export function isDeclarationWithTypeParameters(node: DeclarationWithTypeParameters): node is DeclarationWithTypeParameters { switch (node.kind) { case SyntaxKind.JSDocCallbackTag: case SyntaxKind.JSDocTypedefTag: case SyntaxKind.JSDocSignature: return true; default: assertType(node); return isDeclarationWithTypeParameterChildren(node); } } export function isDeclarationWithTypeParameterChildren(node: Node): node is DeclarationWithTypeParameterChildren; export function isDeclarationWithTypeParameterChildren(node: DeclarationWithTypeParameterChildren): node is DeclarationWithTypeParameterChildren { switch (node.kind) { case SyntaxKind.CallSignature: case SyntaxKind.ConstructSignature: case SyntaxKind.MethodSignature: case SyntaxKind.IndexSignature: case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: case SyntaxKind.JSDocFunctionType: case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.TypeAliasDeclaration: case SyntaxKind.JSDocTemplateTag: case SyntaxKind.FunctionDeclaration: case SyntaxKind.MethodDeclaration: case SyntaxKind.Constructor: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: return true; default: assertType(node); return false; } } export function isAnyImportSyntax(node: Node): node is AnyImportSyntax { switch (node.kind) { case SyntaxKind.ImportDeclaration: case SyntaxKind.ImportEqualsDeclaration: return true; default: return false; } } export function isLateVisibilityPaintedStatement(node: Node): node is LateVisibilityPaintedStatement { switch (node.kind) { case SyntaxKind.ImportDeclaration: case SyntaxKind.ImportEqualsDeclaration: case SyntaxKind.VariableStatement: case SyntaxKind.ClassDeclaration: case SyntaxKind.FunctionDeclaration: case SyntaxKind.ModuleDeclaration: case SyntaxKind.TypeAliasDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.EnumDeclaration: return true; default: return false; } } export function isAnyImportOrReExport(node: Node): node is AnyImportOrReExport { return isAnyImportSyntax(node) || isExportDeclaration(node); } // Gets the nearest enclosing block scope container that has the provided node // as a descendant, that is not the provided node. export function getEnclosingBlockScopeContainer(node: Node): Node { return findAncestor(node.parent, current => isBlockScope(current, current.parent))!; } // Return display name of an identifier // Computed property names will just be emitted as "[]", where is the source // text of the expression in the computed property. export function declarationNameToString(name: DeclarationName | QualifiedName | undefined) { return !name || getFullWidth(name) === 0 ? "(Missing)" : getTextOfNode(name); } export function getNameFromIndexInfo(info: IndexInfo): string | undefined { return info.declaration ? declarationNameToString(info.declaration.parameters[0].name) : undefined; } export function getTextOfPropertyName(name: PropertyName | NoSubstitutionTemplateLiteral): __String { switch (name.kind) { case SyntaxKind.Identifier: return name.escapedText; case SyntaxKind.StringLiteral: case SyntaxKind.NumericLiteral: case SyntaxKind.NoSubstitutionTemplateLiteral: return escapeLeadingUnderscores(name.text); case SyntaxKind.ComputedPropertyName: if (isStringOrNumericLiteralLike(name.expression)) return escapeLeadingUnderscores(name.expression.text); return Debug.fail("Text of property name cannot be read from non-literal-valued ComputedPropertyNames"); default: return Debug.assertNever(name); } } export function entityNameToString(name: EntityNameOrEntityNameExpression): string { switch (name.kind) { case SyntaxKind.Identifier: return getFullWidth(name) === 0 ? idText(name) : getTextOfNode(name); case SyntaxKind.QualifiedName: return entityNameToString(name.left) + "." + entityNameToString(name.right); case SyntaxKind.PropertyAccessExpression: return entityNameToString(name.expression) + "." + entityNameToString(name.name); default: throw Debug.assertNever(name); } } export function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): DiagnosticWithLocation { const sourceFile = getSourceFileOfNode(node); return createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0, arg1, arg2, arg3); } export function createDiagnosticForNodeArray(sourceFile: SourceFile, nodes: NodeArray, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): DiagnosticWithLocation { const start = skipTrivia(sourceFile.text, nodes.pos); return createFileDiagnostic(sourceFile, start, nodes.end - start, message, arg0, arg1, arg2, arg3); } export function createDiagnosticForNodeInSourceFile(sourceFile: SourceFile, node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): DiagnosticWithLocation { const span = getErrorSpanForNode(sourceFile, node); return createFileDiagnostic(sourceFile, span.start, span.length, message, arg0, arg1, arg2, arg3); } export function createDiagnosticForNodeFromMessageChain(node: Node, messageChain: DiagnosticMessageChain, relatedInformation?: DiagnosticRelatedInformation[]): DiagnosticWithLocation { const sourceFile = getSourceFileOfNode(node); const span = getErrorSpanForNode(sourceFile, node); return { file: sourceFile, start: span.start, length: span.length, code: messageChain.code, category: messageChain.category, messageText: messageChain.next ? messageChain : messageChain.messageText, relatedInformation }; } export function getSpanOfTokenAtPosition(sourceFile: SourceFile, pos: number): TextSpan { const scanner = createScanner(sourceFile.languageVersion, /*skipTrivia*/ true, sourceFile.languageVariant, sourceFile.text, /*onError:*/ undefined, pos); scanner.scan(); const start = scanner.getTokenPos(); return createTextSpanFromBounds(start, scanner.getTextPos()); } function getErrorSpanForArrowFunction(sourceFile: SourceFile, node: ArrowFunction): TextSpan { const pos = skipTrivia(sourceFile.text, node.pos); if (node.body && node.body.kind === SyntaxKind.Block) { const { line: startLine } = getLineAndCharacterOfPosition(sourceFile, node.body.pos); const { line: endLine } = getLineAndCharacterOfPosition(sourceFile, node.body.end); if (startLine < endLine) { // The arrow function spans multiple lines, // make the error span be the first line, inclusive. return createTextSpan(pos, getEndLinePosition(startLine, sourceFile) - pos + 1); } } return createTextSpanFromBounds(pos, node.end); } export function getErrorSpanForNode(sourceFile: SourceFile, node: Node): TextSpan { let errorNode: Node | undefined = node; switch (node.kind) { case SyntaxKind.SourceFile: const pos = skipTrivia(sourceFile.text, 0, /*stopAfterLineBreak*/ false); if (pos === sourceFile.text.length) { // file is empty - return span for the beginning of the file return createTextSpan(0, 0); } return getSpanOfTokenAtPosition(sourceFile, pos); // This list is a work in progress. Add missing node kinds to improve their error // spans. case SyntaxKind.VariableDeclaration: case SyntaxKind.BindingElement: case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.ModuleDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.EnumMember: case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.TypeAliasDeclaration: case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: errorNode = (node).name; break; case SyntaxKind.ArrowFunction: return getErrorSpanForArrowFunction(sourceFile, node); } if (errorNode === undefined) { // If we don't have a better node, then just set the error on the first token of // construct. return getSpanOfTokenAtPosition(sourceFile, node.pos); } const isMissing = nodeIsMissing(errorNode); const pos = isMissing || isJsxText(node) ? errorNode.pos : skipTrivia(sourceFile.text, errorNode.pos); // These asserts should all be satisfied for a properly constructed `errorNode`. if (isMissing) { Debug.assert(pos === errorNode.pos, "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809"); Debug.assert(pos === errorNode.end, "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809"); } else { Debug.assert(pos >= errorNode.pos, "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809"); Debug.assert(pos <= errorNode.end, "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809"); } return createTextSpanFromBounds(pos, errorNode.end); } export function isExternalOrCommonJsModule(file: SourceFile): boolean { return (file.externalModuleIndicator || file.commonJsModuleIndicator) !== undefined; } export function isJsonSourceFile(file: SourceFile): file is JsonSourceFile { return file.scriptKind === ScriptKind.JSON; } export function isEnumConst(node: EnumDeclaration): boolean { return !!(getCombinedModifierFlags(node) & ModifierFlags.Const); } export function isDeclarationReadonly(declaration: Declaration): boolean { return !!(getCombinedModifierFlags(declaration) & ModifierFlags.Readonly && !isParameterPropertyDeclaration(declaration)); } export function isVarConst(node: VariableDeclaration | VariableDeclarationList): boolean { return !!(getCombinedNodeFlags(node) & NodeFlags.Const); } export function isLet(node: Node): boolean { return !!(getCombinedNodeFlags(node) & NodeFlags.Let); } export function isSuperCall(n: Node): n is SuperCall { return n.kind === SyntaxKind.CallExpression && (n).expression.kind === SyntaxKind.SuperKeyword; } export function isImportCall(n: Node): n is ImportCall { return n.kind === SyntaxKind.CallExpression && (n).expression.kind === SyntaxKind.ImportKeyword; } export function isImportMeta(n: Node): n is ImportMetaProperty { return isMetaProperty(n) && n.keywordToken === SyntaxKind.ImportKeyword && n.name.escapedText === "meta"; } export function isLiteralImportTypeNode(n: Node): n is LiteralImportTypeNode { return isImportTypeNode(n) && isLiteralTypeNode(n.argument) && isStringLiteral(n.argument.literal); } export function isPrologueDirective(node: Node): node is PrologueDirective { return node.kind === SyntaxKind.ExpressionStatement && (node).expression.kind === SyntaxKind.StringLiteral; } export function getLeadingCommentRangesOfNode(node: Node, sourceFileOfNode: SourceFile) { return node.kind !== SyntaxKind.JsxText ? getLeadingCommentRanges(sourceFileOfNode.text, node.pos) : undefined; } export function getJSDocCommentRanges(node: Node, text: string) { const commentRanges = (node.kind === SyntaxKind.Parameter || node.kind === SyntaxKind.TypeParameter || node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.ArrowFunction || node.kind === SyntaxKind.ParenthesizedExpression) ? concatenate(getTrailingCommentRanges(text, node.pos), getLeadingCommentRanges(text, node.pos)) : getLeadingCommentRanges(text, node.pos); // True if the comment starts with '/**' but not if it is '/**/' return filter(commentRanges, comment => text.charCodeAt(comment.pos + 1) === CharacterCodes.asterisk && text.charCodeAt(comment.pos + 2) === CharacterCodes.asterisk && text.charCodeAt(comment.pos + 3) !== CharacterCodes.slash); } export const fullTripleSlashReferencePathRegEx = /^(\/\/\/\s*/; const fullTripleSlashReferenceTypeReferenceDirectiveRegEx = /^(\/\/\/\s*/; export const fullTripleSlashAMDReferencePathRegEx = /^(\/\/\/\s*/; const defaultLibReferenceRegEx = /^(\/\/\/\s*/; export function isPartOfTypeNode(node: Node): boolean { if (SyntaxKind.FirstTypeNode <= node.kind && node.kind <= SyntaxKind.LastTypeNode) { return true; } switch (node.kind) { case SyntaxKind.AnyKeyword: case SyntaxKind.UnknownKeyword: case SyntaxKind.NumberKeyword: case SyntaxKind.BigIntKeyword: case SyntaxKind.StringKeyword: case SyntaxKind.BooleanKeyword: case SyntaxKind.SymbolKeyword: case SyntaxKind.ObjectKeyword: case SyntaxKind.UndefinedKeyword: case SyntaxKind.NeverKeyword: return true; case SyntaxKind.VoidKeyword: return node.parent.kind !== SyntaxKind.VoidExpression; case SyntaxKind.ExpressionWithTypeArguments: return !isExpressionWithTypeArgumentsInClassExtendsClause(node); case SyntaxKind.TypeParameter: return node.parent.kind === SyntaxKind.MappedType || node.parent.kind === SyntaxKind.InferType; // Identifiers and qualified names may be type nodes, depending on their context. Climb // above them to find the lowest container case SyntaxKind.Identifier: // If the identifier is the RHS of a qualified name, then it's a type iff its parent is. if (node.parent.kind === SyntaxKind.QualifiedName && (node.parent).right === node) { node = node.parent; } else if (node.parent.kind === SyntaxKind.PropertyAccessExpression && (node.parent).name === node) { node = node.parent; } // At this point, node is either a qualified name or an identifier Debug.assert(node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName || node.kind === SyntaxKind.PropertyAccessExpression, "'node' was expected to be a qualified name, identifier or property access in 'isPartOfTypeNode'."); // falls through case SyntaxKind.QualifiedName: case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ThisKeyword: { const { parent } = node; if (parent.kind === SyntaxKind.TypeQuery) { return false; } if (parent.kind === SyntaxKind.ImportType) { return !(parent as ImportTypeNode).isTypeOf; } // Do not recursively call isPartOfTypeNode on the parent. In the example: // // let a: A.B.C; // // Calling isPartOfTypeNode would consider the qualified name A.B a type node. // Only C and A.B.C are type nodes. if (SyntaxKind.FirstTypeNode <= parent.kind && parent.kind <= SyntaxKind.LastTypeNode) { return true; } switch (parent.kind) { case SyntaxKind.ExpressionWithTypeArguments: return !isExpressionWithTypeArgumentsInClassExtendsClause(parent); case SyntaxKind.TypeParameter: return node === (parent).constraint; case SyntaxKind.JSDocTemplateTag: return node === (parent).constraint; case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: case SyntaxKind.Parameter: case SyntaxKind.VariableDeclaration: return node === (parent as HasType).type; case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: case SyntaxKind.Constructor: case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: return node === (parent).type; case SyntaxKind.CallSignature: case SyntaxKind.ConstructSignature: case SyntaxKind.IndexSignature: return node === (parent).type; case SyntaxKind.TypeAssertionExpression: return node === (parent).type; case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: return contains((parent).typeArguments, node); case SyntaxKind.TaggedTemplateExpression: // TODO (drosen): TaggedTemplateExpressions may eventually support type arguments. return false; } } } return false; } export function isChildOfNodeWithKind(node: Node, kind: SyntaxKind): boolean { while (node) { if (node.kind === kind) { return true; } node = node.parent; } return false; } // Warning: This has the same semantics as the forEach family of functions, // in that traversal terminates in the event that 'visitor' supplies a truthy value. export function forEachReturnStatement(body: Block, visitor: (stmt: ReturnStatement) => T): T | undefined { return traverse(body); function traverse(node: Node): T | undefined { switch (node.kind) { case SyntaxKind.ReturnStatement: return visitor(node); case SyntaxKind.CaseBlock: case SyntaxKind.Block: case SyntaxKind.IfStatement: case SyntaxKind.DoStatement: case SyntaxKind.WhileStatement: case SyntaxKind.ForStatement: case SyntaxKind.ForInStatement: case SyntaxKind.ForOfStatement: case SyntaxKind.WithStatement: case SyntaxKind.SwitchStatement: case SyntaxKind.CaseClause: case SyntaxKind.DefaultClause: case SyntaxKind.LabeledStatement: case SyntaxKind.TryStatement: case SyntaxKind.CatchClause: return forEachChild(node, traverse); } } } export function forEachYieldExpression(body: Block, visitor: (expr: YieldExpression) => void): void { return traverse(body); function traverse(node: Node): void { switch (node.kind) { case SyntaxKind.YieldExpression: visitor(node); const operand = (node).expression; if (operand) { traverse(operand); } return; case SyntaxKind.EnumDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.ModuleDeclaration: case SyntaxKind.TypeAliasDeclaration: case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: // These are not allowed inside a generator now, but eventually they may be allowed // as local types. Regardless, any yield statements contained within them should be // skipped in this traversal. return; default: if (isFunctionLike(node)) { if (node.name && node.name.kind === SyntaxKind.ComputedPropertyName) { // Note that we will not include methods/accessors of a class because they would require // first descending into the class. This is by design. traverse(node.name.expression); return; } } else if (!isPartOfTypeNode(node)) { // This is the general case, which should include mostly expressions and statements. // Also includes NodeArrays. forEachChild(node, traverse); } } } } /** * Gets the most likely element type for a TypeNode. This is not an exhaustive test * as it assumes a rest argument can only be an array type (either T[], or Array). * * @param node The type node. */ export function getRestParameterElementType(node: TypeNode | undefined) { if (node && node.kind === SyntaxKind.ArrayType) { return (node).elementType; } else if (node && node.kind === SyntaxKind.TypeReference) { return singleOrUndefined((node).typeArguments); } else { return undefined; } } export function getMembersOfDeclaration(node: Declaration): NodeArray | undefined { switch (node.kind) { case SyntaxKind.InterfaceDeclaration: case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: case SyntaxKind.TypeLiteral: return (node).members; case SyntaxKind.ObjectLiteralExpression: return (node).properties; } } export function isVariableLike(node: Node): node is VariableLikeDeclaration { if (node) { switch (node.kind) { case SyntaxKind.BindingElement: case SyntaxKind.EnumMember: case SyntaxKind.Parameter: case SyntaxKind.PropertyAssignment: case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: case SyntaxKind.ShorthandPropertyAssignment: case SyntaxKind.VariableDeclaration: return true; } } return false; } export function isVariableLikeOrAccessor(node: Node): node is AccessorDeclaration | VariableLikeDeclaration { return isVariableLike(node) || isAccessor(node); } export function isVariableDeclarationInVariableStatement(node: VariableDeclaration) { return node.parent.kind === SyntaxKind.VariableDeclarationList && node.parent.parent.kind === SyntaxKind.VariableStatement; } export function isValidESSymbolDeclaration(node: Node): node is VariableDeclaration | PropertyDeclaration | SignatureDeclaration { return isVariableDeclaration(node) ? isVarConst(node) && isIdentifier(node.name) && isVariableDeclarationInVariableStatement(node) : isPropertyDeclaration(node) ? hasReadonlyModifier(node) && hasStaticModifier(node) : isPropertySignature(node) && hasReadonlyModifier(node); } export function introducesArgumentsExoticObject(node: Node) { switch (node.kind) { case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: case SyntaxKind.Constructor: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: return true; } return false; } export function unwrapInnermostStatementOfLabel(node: LabeledStatement, beforeUnwrapLabelCallback?: (node: LabeledStatement) => void): Statement { while (true) { if (beforeUnwrapLabelCallback) { beforeUnwrapLabelCallback(node); } if (node.statement.kind !== SyntaxKind.LabeledStatement) { return node.statement; } node = node.statement; } } export function isFunctionBlock(node: Node): boolean { return node && node.kind === SyntaxKind.Block && isFunctionLike(node.parent); } export function isObjectLiteralMethod(node: Node): node is MethodDeclaration { return node && node.kind === SyntaxKind.MethodDeclaration && node.parent.kind === SyntaxKind.ObjectLiteralExpression; } export function isObjectLiteralOrClassExpressionMethod(node: Node): node is MethodDeclaration { return node.kind === SyntaxKind.MethodDeclaration && (node.parent.kind === SyntaxKind.ObjectLiteralExpression || node.parent.kind === SyntaxKind.ClassExpression); } export function isIdentifierTypePredicate(predicate: TypePredicate): predicate is IdentifierTypePredicate { return predicate && predicate.kind === TypePredicateKind.Identifier; } export function isThisTypePredicate(predicate: TypePredicate): predicate is ThisTypePredicate { return predicate && predicate.kind === TypePredicateKind.This; } export function getPropertyAssignment(objectLiteral: ObjectLiteralExpression, key: string, key2?: string): ReadonlyArray { return objectLiteral.properties.filter((property): property is PropertyAssignment => { if (property.kind === SyntaxKind.PropertyAssignment) { const propName = getTextOfPropertyName(property.name); return key === propName || (!!key2 && key2 === propName); } return false; }); } export function getTsConfigObjectLiteralExpression(tsConfigSourceFile: TsConfigSourceFile | undefined): ObjectLiteralExpression | undefined { if (tsConfigSourceFile && tsConfigSourceFile.statements.length) { const expression = tsConfigSourceFile.statements[0].expression; return tryCast(expression, isObjectLiteralExpression); } } export function getTsConfigPropArrayElementValue(tsConfigSourceFile: TsConfigSourceFile | undefined, propKey: string, elementValue: string): StringLiteral | undefined { return firstDefined(getTsConfigPropArray(tsConfigSourceFile, propKey), property => isArrayLiteralExpression(property.initializer) ? find(property.initializer.elements, (element): element is StringLiteral => isStringLiteral(element) && element.text === elementValue) : undefined); } export function getTsConfigPropArray(tsConfigSourceFile: TsConfigSourceFile | undefined, propKey: string): ReadonlyArray { const jsonObjectLiteral = getTsConfigObjectLiteralExpression(tsConfigSourceFile); return jsonObjectLiteral ? getPropertyAssignment(jsonObjectLiteral, propKey) : emptyArray; } export function getContainingFunction(node: Node): SignatureDeclaration | undefined { return findAncestor(node.parent, isFunctionLike); } export function getContainingFunctionDeclaration(node: Node): FunctionLikeDeclaration | undefined { return findAncestor(node.parent, isFunctionLikeDeclaration); } export function getContainingClass(node: Node): ClassLikeDeclaration | undefined { return findAncestor(node.parent, isClassLike); } export function getThisContainer(node: Node, includeArrowFunctions: boolean): Node { Debug.assert(node.kind !== SyntaxKind.SourceFile); while (true) { node = node.parent; if (!node) { return Debug.fail(); // If we never pass in a SourceFile, this should be unreachable, since we'll stop when we reach that. } switch (node.kind) { case SyntaxKind.ComputedPropertyName: // If the grandparent node is an object literal (as opposed to a class), // then the computed property is not a 'this' container. // A computed property name in a class needs to be a this container // so that we can error on it. if (isClassLike(node.parent.parent)) { return node; } // If this is a computed property, then the parent should not // make it a this container. The parent might be a property // in an object literal, like a method or accessor. But in order for // such a parent to be a this container, the reference must be in // the *body* of the container. node = node.parent; break; case SyntaxKind.Decorator: // Decorators are always applied outside of the body of a class or method. if (node.parent.kind === SyntaxKind.Parameter && isClassElement(node.parent.parent)) { // If the decorator's parent is a Parameter, we resolve the this container from // the grandparent class declaration. node = node.parent.parent; } else if (isClassElement(node.parent)) { // If the decorator's parent is a class element, we resolve the 'this' container // from the parent class declaration. node = node.parent; } break; case SyntaxKind.ArrowFunction: if (!includeArrowFunctions) { continue; } // falls through case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.ModuleDeclaration: case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: case SyntaxKind.Constructor: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.CallSignature: case SyntaxKind.ConstructSignature: case SyntaxKind.IndexSignature: case SyntaxKind.EnumDeclaration: case SyntaxKind.SourceFile: return node; } } } export function getNewTargetContainer(node: Node) { const container = getThisContainer(node, /*includeArrowFunctions*/ false); if (container) { switch (container.kind) { case SyntaxKind.Constructor: case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: return container; } } return undefined; } /** * Given an super call/property node, returns the closest node where * - a super call/property access is legal in the node and not legal in the parent node the node. * i.e. super call is legal in constructor but not legal in the class body. * - the container is an arrow function (so caller might need to call getSuperContainer again in case it needs to climb higher) * - a super call/property is definitely illegal in the container (but might be legal in some subnode) * i.e. super property access is illegal in function declaration but can be legal in the statement list */ export function getSuperContainer(node: Node, stopOnFunctions: boolean): Node { while (true) { node = node.parent; if (!node) { return node; } switch (node.kind) { case SyntaxKind.ComputedPropertyName: node = node.parent; break; case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: if (!stopOnFunctions) { continue; } // falls through case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: case SyntaxKind.Constructor: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: return node; case SyntaxKind.Decorator: // Decorators are always applied outside of the body of a class or method. if (node.parent.kind === SyntaxKind.Parameter && isClassElement(node.parent.parent)) { // If the decorator's parent is a Parameter, we resolve the this container from // the grandparent class declaration. node = node.parent.parent; } else if (isClassElement(node.parent)) { // If the decorator's parent is a class element, we resolve the 'this' container // from the parent class declaration. node = node.parent; } break; } } } export function getImmediatelyInvokedFunctionExpression(func: Node): CallExpression | undefined { if (func.kind === SyntaxKind.FunctionExpression || func.kind === SyntaxKind.ArrowFunction) { let prev = func; let parent = func.parent; while (parent.kind === SyntaxKind.ParenthesizedExpression) { prev = parent; parent = parent.parent; } if (parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === prev) { return parent as CallExpression; } } } export function isSuperOrSuperProperty(node: Node): node is SuperExpression | SuperProperty { return node.kind === SyntaxKind.SuperKeyword || isSuperProperty(node); } /** * Determines whether a node is a property or element access expression for `super`. */ export function isSuperProperty(node: Node): node is SuperProperty { const kind = node.kind; return (kind === SyntaxKind.PropertyAccessExpression || kind === SyntaxKind.ElementAccessExpression) && (node).expression.kind === SyntaxKind.SuperKeyword; } /** * Determines whether a node is a property or element access expression for `this`. */ export function isThisProperty(node: Node): boolean { const kind = node.kind; return (kind === SyntaxKind.PropertyAccessExpression || kind === SyntaxKind.ElementAccessExpression) && (node).expression.kind === SyntaxKind.ThisKeyword; } export function getEntityNameFromTypeNode(node: TypeNode): EntityNameOrEntityNameExpression | undefined { switch (node.kind) { case SyntaxKind.TypeReference: return (node).typeName; case SyntaxKind.ExpressionWithTypeArguments: return isEntityNameExpression((node).expression) ? (node).expression : undefined; case SyntaxKind.Identifier: case SyntaxKind.QualifiedName: return (node); } return undefined; } export function getInvokedExpression(node: CallLikeExpression): Expression { switch (node.kind) { case SyntaxKind.TaggedTemplateExpression: return node.tag; case SyntaxKind.JsxOpeningElement: case SyntaxKind.JsxSelfClosingElement: return node.tagName; default: return node.expression; } } export function nodeCanBeDecorated(node: ClassDeclaration): true; export function nodeCanBeDecorated(node: ClassElement, parent: Node): boolean; export function nodeCanBeDecorated(node: Node, parent: Node, grandparent: Node): boolean; export function nodeCanBeDecorated(node: Node, parent?: Node, grandparent?: Node): boolean { switch (node.kind) { case SyntaxKind.ClassDeclaration: // classes are valid targets return true; case SyntaxKind.PropertyDeclaration: // property declarations are valid if their parent is a class declaration. return parent!.kind === SyntaxKind.ClassDeclaration; case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.MethodDeclaration: // if this method has a body and its parent is a class declaration, this is a valid target. return (node).body !== undefined && parent!.kind === SyntaxKind.ClassDeclaration; case SyntaxKind.Parameter: // if the parameter's parent has a body and its grandparent is a class declaration, this is a valid target; return (parent).body !== undefined && (parent!.kind === SyntaxKind.Constructor || parent!.kind === SyntaxKind.MethodDeclaration || parent!.kind === SyntaxKind.SetAccessor) && grandparent!.kind === SyntaxKind.ClassDeclaration; } return false; } export function nodeIsDecorated(node: ClassDeclaration): boolean; export function nodeIsDecorated(node: ClassElement, parent: Node): boolean; export function nodeIsDecorated(node: Node, parent: Node, grandparent: Node): boolean; export function nodeIsDecorated(node: Node, parent?: Node, grandparent?: Node): boolean { return node.decorators !== undefined && nodeCanBeDecorated(node, parent!, grandparent!); // TODO: GH#18217 } export function nodeOrChildIsDecorated(node: ClassDeclaration): boolean; export function nodeOrChildIsDecorated(node: ClassElement, parent: Node): boolean; export function nodeOrChildIsDecorated(node: Node, parent: Node, grandparent: Node): boolean; export function nodeOrChildIsDecorated(node: Node, parent?: Node, grandparent?: Node): boolean { return nodeIsDecorated(node, parent!, grandparent!) || childIsDecorated(node, parent!); // TODO: GH#18217 } export function childIsDecorated(node: ClassDeclaration): boolean; export function childIsDecorated(node: Node, parent: Node): boolean; export function childIsDecorated(node: Node, parent?: Node): boolean { switch (node.kind) { case SyntaxKind.ClassDeclaration: return some((node).members, m => nodeOrChildIsDecorated(m, node, parent!)); // TODO: GH#18217 case SyntaxKind.MethodDeclaration: case SyntaxKind.SetAccessor: return some((node).parameters, p => nodeIsDecorated(p, node, parent!)); // TODO: GH#18217 default: return false; } } export function isJSXTagName(node: Node) { const { parent } = node; if (parent.kind === SyntaxKind.JsxOpeningElement || parent.kind === SyntaxKind.JsxSelfClosingElement || parent.kind === SyntaxKind.JsxClosingElement) { return (parent).tagName === node; } return false; } export function isExpressionNode(node: Node): boolean { switch (node.kind) { case SyntaxKind.SuperKeyword: case SyntaxKind.NullKeyword: case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: case SyntaxKind.RegularExpressionLiteral: case SyntaxKind.ArrayLiteralExpression: case SyntaxKind.ObjectLiteralExpression: case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: case SyntaxKind.TaggedTemplateExpression: case SyntaxKind.AsExpression: case SyntaxKind.TypeAssertionExpression: case SyntaxKind.NonNullExpression: case SyntaxKind.ParenthesizedExpression: case SyntaxKind.FunctionExpression: case SyntaxKind.ClassExpression: case SyntaxKind.ArrowFunction: case SyntaxKind.VoidExpression: case SyntaxKind.DeleteExpression: case SyntaxKind.TypeOfExpression: case SyntaxKind.PrefixUnaryExpression: case SyntaxKind.PostfixUnaryExpression: case SyntaxKind.BinaryExpression: case SyntaxKind.ConditionalExpression: case SyntaxKind.SpreadElement: case SyntaxKind.TemplateExpression: case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.OmittedExpression: case SyntaxKind.JsxElement: case SyntaxKind.JsxSelfClosingElement: case SyntaxKind.JsxFragment: case SyntaxKind.YieldExpression: case SyntaxKind.AwaitExpression: case SyntaxKind.MetaProperty: return true; case SyntaxKind.QualifiedName: while (node.parent.kind === SyntaxKind.QualifiedName) { node = node.parent; } return node.parent.kind === SyntaxKind.TypeQuery || isJSXTagName(node); case SyntaxKind.Identifier: if (node.parent.kind === SyntaxKind.TypeQuery || isJSXTagName(node)) { return true; } // falls through case SyntaxKind.NumericLiteral: case SyntaxKind.BigIntLiteral: case SyntaxKind.StringLiteral: case SyntaxKind.ThisKeyword: return isInExpressionContext(node); default: return false; } } export function isInExpressionContext(node: Node): boolean { const { parent } = node; switch (parent.kind) { case SyntaxKind.VariableDeclaration: case SyntaxKind.Parameter: case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: case SyntaxKind.EnumMember: case SyntaxKind.PropertyAssignment: case SyntaxKind.BindingElement: return (parent as HasInitializer).initializer === node; case SyntaxKind.ExpressionStatement: case SyntaxKind.IfStatement: case SyntaxKind.DoStatement: case SyntaxKind.WhileStatement: case SyntaxKind.ReturnStatement: case SyntaxKind.WithStatement: case SyntaxKind.SwitchStatement: case SyntaxKind.CaseClause: case SyntaxKind.ThrowStatement: return (parent).expression === node; case SyntaxKind.ForStatement: const forStatement = parent; return (forStatement.initializer === node && forStatement.initializer.kind !== SyntaxKind.VariableDeclarationList) || forStatement.condition === node || forStatement.incrementor === node; case SyntaxKind.ForInStatement: case SyntaxKind.ForOfStatement: const forInStatement = parent; return (forInStatement.initializer === node && forInStatement.initializer.kind !== SyntaxKind.VariableDeclarationList) || forInStatement.expression === node; case SyntaxKind.TypeAssertionExpression: case SyntaxKind.AsExpression: return node === (parent).expression; case SyntaxKind.TemplateSpan: return node === (parent).expression; case SyntaxKind.ComputedPropertyName: return node === (parent).expression; case SyntaxKind.Decorator: case SyntaxKind.JsxExpression: case SyntaxKind.JsxSpreadAttribute: case SyntaxKind.SpreadAssignment: return true; case SyntaxKind.ExpressionWithTypeArguments: return (parent).expression === node && isExpressionWithTypeArgumentsInClassExtendsClause(parent); case SyntaxKind.ShorthandPropertyAssignment: return (parent).objectAssignmentInitializer === node; default: return isExpressionNode(parent); } } export function isExternalModuleImportEqualsDeclaration(node: Node) { return node.kind === SyntaxKind.ImportEqualsDeclaration && (node).moduleReference.kind === SyntaxKind.ExternalModuleReference; } export function getExternalModuleImportEqualsDeclarationExpression(node: Node) { Debug.assert(isExternalModuleImportEqualsDeclaration(node)); return ((node).moduleReference).expression; } export function isInternalModuleImportEqualsDeclaration(node: Node): node is ImportEqualsDeclaration { return node.kind === SyntaxKind.ImportEqualsDeclaration && (node).moduleReference.kind !== SyntaxKind.ExternalModuleReference; } export function isSourceFileJS(file: SourceFile): boolean { return isInJSFile(file); } export function isSourceFileNotJS(file: SourceFile): boolean { return !isInJSFile(file); } export function isInJSFile(node: Node | undefined): boolean { return !!node && !!(node.flags & NodeFlags.JavaScriptFile); } export function isInJsonFile(node: Node | undefined): boolean { return !!node && !!(node.flags & NodeFlags.JsonFile); } export function isInJSDoc(node: Node | undefined): boolean { return !!node && !!(node.flags & NodeFlags.JSDoc); } export function isJSDocIndexSignature(node: TypeReferenceNode | ExpressionWithTypeArguments) { return isTypeReferenceNode(node) && isIdentifier(node.typeName) && node.typeName.escapedText === "Object" && node.typeArguments && node.typeArguments.length === 2 && (node.typeArguments[0].kind === SyntaxKind.StringKeyword || node.typeArguments[0].kind === SyntaxKind.NumberKeyword); } /** * Returns true if the node is a CallExpression to the identifier 'require' with * exactly one argument (of the form 'require("name")'). * This function does not test if the node is in a JavaScript file or not. */ export function isRequireCall(callExpression: Node, checkArgumentIsStringLiteralLike: true): callExpression is RequireOrImportCall & { expression: Identifier, arguments: [StringLiteralLike] }; export function isRequireCall(callExpression: Node, checkArgumentIsStringLiteralLike: boolean): callExpression is CallExpression; export function isRequireCall(callExpression: Node, checkArgumentIsStringLiteralLike: boolean): callExpression is CallExpression { if (callExpression.kind !== SyntaxKind.CallExpression) { return false; } const { expression, arguments: args } = callExpression as CallExpression; if (expression.kind !== SyntaxKind.Identifier || (expression as Identifier).escapedText !== "require") { return false; } if (args.length !== 1) { return false; } const arg = args[0]; return !checkArgumentIsStringLiteralLike || isStringLiteralLike(arg); } export function isSingleOrDoubleQuote(charCode: number) { return charCode === CharacterCodes.singleQuote || charCode === CharacterCodes.doubleQuote; } export function isStringDoubleQuoted(str: StringLiteralLike, sourceFile: SourceFile): boolean { return getSourceTextOfNodeFromSourceFile(sourceFile, str).charCodeAt(0) === CharacterCodes.doubleQuote; } export function getDeclarationOfExpando(node: Node): Node | undefined { if (!node.parent) { return undefined; } let name: Expression | BindingName | undefined; let decl: Node | undefined; if (isVariableDeclaration(node.parent) && node.parent.initializer === node) { if (!isInJSFile(node) && !isVarConst(node.parent)) { return undefined; } name = node.parent.name; decl = node.parent; } else if (isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.EqualsToken && node.parent.right === node) { name = node.parent.left; decl = name; } else if (isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.BarBarToken) { if (isVariableDeclaration(node.parent.parent) && node.parent.parent.initializer === node.parent) { name = node.parent.parent.name; decl = node.parent.parent; } else if (isBinaryExpression(node.parent.parent) && node.parent.parent.operatorToken.kind === SyntaxKind.EqualsToken && node.parent.parent.right === node.parent) { name = node.parent.parent.left; decl = name; } if (!name || !isEntityNameExpression(name) || !isSameEntityName(name, node.parent.left)) { return undefined; } } if (!name || !getExpandoInitializer(node, isPrototypeAccess(name))) { return undefined; } return decl; } export function isAssignmentDeclaration(decl: Declaration) { return isBinaryExpression(decl) || isPropertyAccessExpression(decl) || isIdentifier(decl) || isCallExpression(decl); } /** Get the initializer, taking into account defaulted Javascript initializers */ export function getEffectiveInitializer(node: HasExpressionInitializer) { if (isInJSFile(node) && node.initializer && isBinaryExpression(node.initializer) && node.initializer.operatorToken.kind === SyntaxKind.BarBarToken && node.name && isEntityNameExpression(node.name) && isSameEntityName(node.name, node.initializer.left)) { return node.initializer.right; } return node.initializer; } /** Get the declaration initializer when it is container-like (See getExpandoInitializer). */ export function getDeclaredExpandoInitializer(node: HasExpressionInitializer) { const init = getEffectiveInitializer(node); return init && getExpandoInitializer(init, isPrototypeAccess(node.name)); } function hasExpandoValueProperty(node: ObjectLiteralExpression, isPrototypeAssignment: boolean) { return forEach(node.properties, p => isPropertyAssignment(p) && isIdentifier(p.name) && p.name.escapedText === "value" && p.initializer && getExpandoInitializer(p.initializer, isPrototypeAssignment)); } /** * Get the assignment 'initializer' -- the righthand side-- when the initializer is container-like (See getExpandoInitializer). * We treat the right hand side of assignments with container-like initalizers as declarations. */ export function getAssignedExpandoInitializer(node: Node | undefined) { if (node && node.parent && isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.EqualsToken) { const isPrototypeAssignment = isPrototypeAccess(node.parent.left); return getExpandoInitializer(node.parent.right, isPrototypeAssignment) || getDefaultedExpandoInitializer(node.parent.left as EntityNameExpression, node.parent.right, isPrototypeAssignment); } if (node && isCallExpression(node) && isBindableObjectDefinePropertyCall(node)) { const result = hasExpandoValueProperty(node.arguments[2], node.arguments[1].text === "prototype"); if (result) { return result; } } } /** * Recognized expando initializers are: * 1. (function() {})() -- IIFEs * 2. function() { } -- Function expressions * 3. class { } -- Class expressions * 4. {} -- Empty object literals * 5. { ... } -- Non-empty object literals, when used to initialize a prototype, like `C.prototype = { m() { } }` * * This function returns the provided initializer, or undefined if it is not valid. */ export function getExpandoInitializer(initializer: Node, isPrototypeAssignment: boolean): Expression | undefined { if (isCallExpression(initializer)) { const e = skipParentheses(initializer.expression); return e.kind === SyntaxKind.FunctionExpression || e.kind === SyntaxKind.ArrowFunction ? initializer : undefined; } if (initializer.kind === SyntaxKind.FunctionExpression || initializer.kind === SyntaxKind.ClassExpression || initializer.kind === SyntaxKind.ArrowFunction) { return initializer as Expression; } if (isObjectLiteralExpression(initializer) && (initializer.properties.length === 0 || isPrototypeAssignment)) { return initializer; } } /** * A defaulted expando initializer matches the pattern * `Lhs = Lhs || ExpandoInitializer` * or `var Lhs = Lhs || ExpandoInitializer` * * The second Lhs is required to be the same as the first except that it may be prefixed with * 'window.', 'global.' or 'self.' The second Lhs is otherwise ignored by the binder and checker. */ function getDefaultedExpandoInitializer(name: EntityNameExpression, initializer: Expression, isPrototypeAssignment: boolean) { const e = isBinaryExpression(initializer) && initializer.operatorToken.kind === SyntaxKind.BarBarToken && getExpandoInitializer(initializer.right, isPrototypeAssignment); if (e && isSameEntityName(name, (initializer as BinaryExpression).left as EntityNameExpression)) { return e; } } export function isDefaultedExpandoInitializer(node: BinaryExpression) { const name = isVariableDeclaration(node.parent) ? node.parent.name : isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.EqualsToken ? node.parent.left : undefined; return name && getExpandoInitializer(node.right, isPrototypeAccess(name)) && isEntityNameExpression(name) && isSameEntityName(name, node.left); } /** Given an expando initializer, return its declaration name, or the left-hand side of the assignment if it's part of an assignment declaration. */ export function getNameOfExpando(node: Declaration): DeclarationName | undefined { if (isBinaryExpression(node.parent)) { const parent = (node.parent.operatorToken.kind === SyntaxKind.BarBarToken && isBinaryExpression(node.parent.parent)) ? node.parent.parent : node.parent; if (parent.operatorToken.kind === SyntaxKind.EqualsToken && isIdentifier(parent.left)) { return parent.left; } } else if (isVariableDeclaration(node.parent)) { return node.parent.name; } } /** * Is the 'declared' name the same as the one in the initializer? * @return true for identical entity names, as well as ones where the initializer is prefixed with * 'window', 'self' or 'global'. For example: * * var my = my || {} * var min = window.min || {} * my.app = self.my.app || class { } */ function isSameEntityName(name: Expression, initializer: Expression): boolean { if (isIdentifier(name) && isIdentifier(initializer)) { return name.escapedText === initializer.escapedText; } if (isIdentifier(name) && isPropertyAccessExpression(initializer)) { return (initializer.expression.kind as SyntaxKind.ThisKeyword === SyntaxKind.ThisKeyword || isIdentifier(initializer.expression) && (initializer.expression.escapedText === "window" as __String || initializer.expression.escapedText === "self" as __String || initializer.expression.escapedText === "global" as __String)) && isSameEntityName(name, initializer.name); } if (isPropertyAccessExpression(name) && isPropertyAccessExpression(initializer)) { return name.name.escapedText === initializer.name.escapedText && isSameEntityName(name.expression, initializer.expression); } return false; } export function getRightMostAssignedExpression(node: Expression): Expression { while (isAssignmentExpression(node, /*excludeCompoundAssignments*/ true)) { node = node.right; } return node; } export function isExportsIdentifier(node: Node) { return isIdentifier(node) && node.escapedText === "exports"; } export function isModuleExportsPropertyAccessExpression(node: Node) { return isPropertyAccessExpression(node) && isIdentifier(node.expression) && node.expression.escapedText === "module" && node.name.escapedText === "exports"; } /// Given a BinaryExpression, returns SpecialPropertyAssignmentKind for the various kinds of property /// assignments we treat as special in the binder export function getAssignmentDeclarationKind(expr: BinaryExpression | CallExpression): AssignmentDeclarationKind { const special = getAssignmentDeclarationKindWorker(expr); return special === AssignmentDeclarationKind.Property || isInJSFile(expr) ? special : AssignmentDeclarationKind.None; } export function isBindableObjectDefinePropertyCall(expr: CallExpression): expr is BindableObjectDefinePropertyCall { return length(expr.arguments) === 3 && isPropertyAccessExpression(expr.expression) && isIdentifier(expr.expression.expression) && idText(expr.expression.expression) === "Object" && idText(expr.expression.name) === "defineProperty" && isStringOrNumericLiteralLike(expr.arguments[1]) && isEntityNameExpression(expr.arguments[0]); } function getAssignmentDeclarationKindWorker(expr: BinaryExpression | CallExpression): AssignmentDeclarationKind { if (isCallExpression(expr)) { if (!isBindableObjectDefinePropertyCall(expr)) { return AssignmentDeclarationKind.None; } const entityName = expr.arguments[0]; if (isExportsIdentifier(entityName) || isModuleExportsPropertyAccessExpression(entityName)) { return AssignmentDeclarationKind.ObjectDefinePropertyExports; } if (isPropertyAccessExpression(entityName) && entityName.name.escapedText === "prototype" && isEntityNameExpression(entityName.expression)) { return AssignmentDeclarationKind.ObjectDefinePrototypeProperty; } return AssignmentDeclarationKind.ObjectDefinePropertyValue; } if (expr.operatorToken.kind !== SyntaxKind.EqualsToken || !isPropertyAccessExpression(expr.left)) { return AssignmentDeclarationKind.None; } const lhs = expr.left; if (isEntityNameExpression(lhs.expression) && lhs.name.escapedText === "prototype" && isObjectLiteralExpression(getInitializerOfBinaryExpression(expr))) { // F.prototype = { ... } return AssignmentDeclarationKind.Prototype; } return getAssignmentDeclarationPropertyAccessKind(lhs); } export function getAssignmentDeclarationPropertyAccessKind(lhs: PropertyAccessExpression): AssignmentDeclarationKind { if (lhs.expression.kind === SyntaxKind.ThisKeyword) { return AssignmentDeclarationKind.ThisProperty; } else if (isModuleExportsPropertyAccessExpression(lhs)) { // module.exports = expr return AssignmentDeclarationKind.ModuleExports; } else if (isEntityNameExpression(lhs.expression)) { if (isPrototypeAccess(lhs.expression)) { // F.G....prototype.x = expr return AssignmentDeclarationKind.PrototypeProperty; } let nextToLast = lhs; while (isPropertyAccessExpression(nextToLast.expression)) { nextToLast = nextToLast.expression; } Debug.assert(isIdentifier(nextToLast.expression)); const id = nextToLast.expression as Identifier; if (id.escapedText === "exports" || id.escapedText === "module" && nextToLast.name.escapedText === "exports") { // exports.name = expr OR module.exports.name = expr return AssignmentDeclarationKind.ExportsProperty; } // F.G...x = expr return AssignmentDeclarationKind.Property; } return AssignmentDeclarationKind.None; } export function getInitializerOfBinaryExpression(expr: BinaryExpression) { while (isBinaryExpression(expr.right)) { expr = expr.right; } return expr.right; } export function isPrototypePropertyAssignment(node: Node): boolean { return isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.PrototypeProperty; } export function isSpecialPropertyDeclaration(expr: PropertyAccessExpression): boolean { return isInJSFile(expr) && expr.parent && expr.parent.kind === SyntaxKind.ExpressionStatement && !!getJSDocTypeTag(expr.parent); } export function isFunctionSymbol(symbol: Symbol | undefined) { if (!symbol || !symbol.valueDeclaration) { return false; } const decl = symbol.valueDeclaration; return decl.kind === SyntaxKind.FunctionDeclaration || isVariableDeclaration(decl) && decl.initializer && isFunctionLike(decl.initializer); } export function importFromModuleSpecifier(node: StringLiteralLike): AnyValidImportOrReExport { return tryGetImportFromModuleSpecifier(node) || Debug.failBadSyntaxKind(node.parent); } export function tryGetImportFromModuleSpecifier(node: StringLiteralLike): AnyValidImportOrReExport | undefined { switch (node.parent.kind) { case SyntaxKind.ImportDeclaration: case SyntaxKind.ExportDeclaration: return node.parent as AnyValidImportOrReExport; case SyntaxKind.ExternalModuleReference: return (node.parent as ExternalModuleReference).parent as AnyValidImportOrReExport; case SyntaxKind.CallExpression: return isImportCall(node.parent) || isRequireCall(node.parent, /*checkArg*/ false) ? node.parent as RequireOrImportCall : undefined; case SyntaxKind.LiteralType: Debug.assert(isStringLiteral(node)); return tryCast(node.parent.parent, isImportTypeNode) as ValidImportTypeNode | undefined; default: return undefined; } } export function getExternalModuleName(node: AnyImportOrReExport | ImportTypeNode): Expression | undefined { switch (node.kind) { case SyntaxKind.ImportDeclaration: case SyntaxKind.ExportDeclaration: return node.moduleSpecifier; case SyntaxKind.ImportEqualsDeclaration: return node.moduleReference.kind === SyntaxKind.ExternalModuleReference ? node.moduleReference.expression : undefined; case SyntaxKind.ImportType: return isLiteralImportTypeNode(node) ? node.argument.literal : undefined; default: return Debug.assertNever(node); } } export function getNamespaceDeclarationNode(node: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration): ImportEqualsDeclaration | NamespaceImport | undefined { switch (node.kind) { case SyntaxKind.ImportDeclaration: return node.importClause && tryCast(node.importClause.namedBindings, isNamespaceImport); case SyntaxKind.ImportEqualsDeclaration: return node; case SyntaxKind.ExportDeclaration: return undefined; default: return Debug.assertNever(node); } } export function isDefaultImport(node: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration): boolean { return node.kind === SyntaxKind.ImportDeclaration && !!node.importClause && !!node.importClause.name; } export function hasQuestionToken(node: Node) { if (node) { switch (node.kind) { case SyntaxKind.Parameter: case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: case SyntaxKind.ShorthandPropertyAssignment: case SyntaxKind.PropertyAssignment: case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: return (node).questionToken !== undefined; } } return false; } export function isJSDocConstructSignature(node: Node) { const param = isJSDocFunctionType(node) ? firstOrUndefined(node.parameters) : undefined; const name = tryCast(param && param.name, isIdentifier); return !!name && name.escapedText === "new"; } export function isJSDocTypeAlias(node: Node): node is JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag { return node.kind === SyntaxKind.JSDocTypedefTag || node.kind === SyntaxKind.JSDocCallbackTag || node.kind === SyntaxKind.JSDocEnumTag; } export function isTypeAlias(node: Node): node is JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag | TypeAliasDeclaration { return isJSDocTypeAlias(node) || isTypeAliasDeclaration(node); } function getSourceOfAssignment(node: Node): Node | undefined { return isExpressionStatement(node) && node.expression && isBinaryExpression(node.expression) && node.expression.operatorToken.kind === SyntaxKind.EqualsToken ? node.expression.right : undefined; } function getSourceOfDefaultedAssignment(node: Node): Node | undefined { return isExpressionStatement(node) && isBinaryExpression(node.expression) && getAssignmentDeclarationKind(node.expression) !== AssignmentDeclarationKind.None && isBinaryExpression(node.expression.right) && node.expression.right.operatorToken.kind === SyntaxKind.BarBarToken ? node.expression.right.right : undefined; } export function getSingleInitializerOfVariableStatementOrPropertyDeclaration(node: Node): Expression | undefined { switch (node.kind) { case SyntaxKind.VariableStatement: const v = getSingleVariableOfVariableStatement(node); return v && v.initializer; case SyntaxKind.PropertyDeclaration: return (node as PropertyDeclaration).initializer; case SyntaxKind.PropertyAssignment: return (node as PropertyAssignment).initializer; } } function getSingleVariableOfVariableStatement(node: Node): VariableDeclaration | undefined { return isVariableStatement(node) ? firstOrUndefined(node.declarationList.declarations) : undefined; } function getNestedModuleDeclaration(node: Node): Node | undefined { return isModuleDeclaration(node) && node.body && node.body.kind === SyntaxKind.ModuleDeclaration ? node.body : undefined; } export function getJSDocCommentsAndTags(hostNode: Node): ReadonlyArray { let result: (JSDoc | JSDocTag)[] | undefined; // Pull parameter comments from declaring function as well if (isVariableLike(hostNode) && hasInitializer(hostNode) && hasJSDocNodes(hostNode.initializer!)) { result = append(result, last((hostNode.initializer as HasJSDoc).jsDoc!)); } let node: Node | undefined = hostNode; while (node && node.parent) { if (hasJSDocNodes(node)) { result = append(result, last(node.jsDoc!)); } if (node.kind === SyntaxKind.Parameter) { result = addRange(result, getJSDocParameterTags(node as ParameterDeclaration)); break; } if (node.kind === SyntaxKind.TypeParameter) { result = addRange(result, getJSDocTypeParameterTags(node as TypeParameterDeclaration)); break; } node = getNextJSDocCommentLocation(node); } return result || emptyArray; } function getNextJSDocCommentLocation(node: Node) { const parent = node.parent; if (parent.kind === SyntaxKind.PropertyAssignment || parent.kind === SyntaxKind.ExportAssignment || parent.kind === SyntaxKind.PropertyDeclaration || parent.kind === SyntaxKind.ExpressionStatement && node.kind === SyntaxKind.PropertyAccessExpression || getNestedModuleDeclaration(parent) || isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.EqualsToken) { return parent; } // Try to recognize this pattern when node is initializer of variable declaration and JSDoc comments are on containing variable statement. // /** // * @param {number} name // * @returns {number} // */ // var x = function(name) { return name.length; } else if (parent.parent && (getSingleVariableOfVariableStatement(parent.parent) === node || isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken)) { return parent.parent; } else if (parent.parent && parent.parent.parent && (getSingleVariableOfVariableStatement(parent.parent.parent) || getSingleInitializerOfVariableStatementOrPropertyDeclaration(parent.parent.parent) === node || getSourceOfDefaultedAssignment(parent.parent.parent))) { return parent.parent.parent; } } /** Does the opposite of `getJSDocParameterTags`: given a JSDoc parameter, finds the parameter corresponding to it. */ export function getParameterSymbolFromJSDoc(node: JSDocParameterTag): Symbol | undefined { if (node.symbol) { return node.symbol; } if (!isIdentifier(node.name)) { return undefined; } const name = node.name.escapedText; const decl = getHostSignatureFromJSDoc(node); if (!decl) { return undefined; } const parameter = find(decl.parameters, p => p.name.kind === SyntaxKind.Identifier && p.name.escapedText === name); return parameter && parameter.symbol; } export function getHostSignatureFromJSDoc(node: Node): SignatureDeclaration | undefined { return getHostSignatureFromJSDocHost(getJSDocHost(node)); } export function getHostSignatureFromJSDocHost(host: HasJSDoc): SignatureDeclaration | undefined { const decl = getSourceOfDefaultedAssignment(host) || getSourceOfAssignment(host) || getSingleInitializerOfVariableStatementOrPropertyDeclaration(host) || getSingleVariableOfVariableStatement(host) || getNestedModuleDeclaration(host) || host; return decl && isFunctionLike(decl) ? decl : undefined; } export function getJSDocHost(node: Node): HasJSDoc { return Debug.assertDefined(findAncestor(node.parent, isJSDoc)).parent; } export function getTypeParameterFromJsDoc(node: TypeParameterDeclaration & { parent: JSDocTemplateTag }): TypeParameterDeclaration | undefined { const name = node.name.escapedText; const { typeParameters } = (node.parent.parent.parent as SignatureDeclaration | InterfaceDeclaration | ClassDeclaration); return find(typeParameters!, p => p.name.escapedText === name); } export function hasRestParameter(s: SignatureDeclaration | JSDocSignature): boolean { const last = lastOrUndefined(s.parameters); return !!last && isRestParameter(last); } export function isRestParameter(node: ParameterDeclaration | JSDocParameterTag): boolean { const type = isJSDocParameterTag(node) ? (node.typeExpression && node.typeExpression.type) : node.type; return (node as ParameterDeclaration).dotDotDotToken !== undefined || !!type && type.kind === SyntaxKind.JSDocVariadicType; } export const enum AssignmentKind { None, Definite, Compound } export function getAssignmentTargetKind(node: Node): AssignmentKind { let parent = node.parent; while (true) { switch (parent.kind) { case SyntaxKind.BinaryExpression: const binaryOperator = (parent).operatorToken.kind; return isAssignmentOperator(binaryOperator) && (parent).left === node ? binaryOperator === SyntaxKind.EqualsToken ? AssignmentKind.Definite : AssignmentKind.Compound : AssignmentKind.None; case SyntaxKind.PrefixUnaryExpression: case SyntaxKind.PostfixUnaryExpression: const unaryOperator = (parent).operator; return unaryOperator === SyntaxKind.PlusPlusToken || unaryOperator === SyntaxKind.MinusMinusToken ? AssignmentKind.Compound : AssignmentKind.None; case SyntaxKind.ForInStatement: case SyntaxKind.ForOfStatement: return (parent).initializer === node ? AssignmentKind.Definite : AssignmentKind.None; case SyntaxKind.ParenthesizedExpression: case SyntaxKind.ArrayLiteralExpression: case SyntaxKind.SpreadElement: case SyntaxKind.NonNullExpression: node = parent; break; case SyntaxKind.ShorthandPropertyAssignment: if ((parent as ShorthandPropertyAssignment).name !== node) { return AssignmentKind.None; } node = parent.parent; break; case SyntaxKind.PropertyAssignment: if ((parent as ShorthandPropertyAssignment).name === node) { return AssignmentKind.None; } node = parent.parent; break; default: return AssignmentKind.None; } parent = node.parent; } } // A node is an assignment target if it is on the left hand side of an '=' token, if it is parented by a property // assignment in an object literal that is an assignment target, or if it is parented by an array literal that is // an assignment target. Examples include 'a = xxx', '{ p: a } = xxx', '[{ a }] = xxx'. // (Note that `p` is not a target in the above examples, only `a`.) export function isAssignmentTarget(node: Node): boolean { return getAssignmentTargetKind(node) !== AssignmentKind.None; } export type NodeWithPossibleHoistedDeclaration = | Block | VariableStatement | WithStatement | IfStatement | SwitchStatement | CaseBlock | CaseClause | DefaultClause | LabeledStatement | ForStatement | ForInStatement | ForOfStatement | DoStatement | WhileStatement | TryStatement | CatchClause; /** * Indicates whether a node could contain a `var` VariableDeclarationList that contributes to * the same `var` declaration scope as the node's parent. */ export function isNodeWithPossibleHoistedDeclaration(node: Node): node is NodeWithPossibleHoistedDeclaration { switch (node.kind) { case SyntaxKind.Block: case SyntaxKind.VariableStatement: case SyntaxKind.WithStatement: case SyntaxKind.IfStatement: case SyntaxKind.SwitchStatement: case SyntaxKind.CaseBlock: case SyntaxKind.CaseClause: case SyntaxKind.DefaultClause: case SyntaxKind.LabeledStatement: case SyntaxKind.ForStatement: case SyntaxKind.ForInStatement: case SyntaxKind.ForOfStatement: case SyntaxKind.DoStatement: case SyntaxKind.WhileStatement: case SyntaxKind.TryStatement: case SyntaxKind.CatchClause: return true; } return false; } export type ValueSignatureDeclaration = | FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | AccessorDeclaration | FunctionExpression | ArrowFunction; export function isValueSignatureDeclaration(node: Node): node is ValueSignatureDeclaration { return isFunctionExpression(node) || isArrowFunction(node) || isMethodOrAccessor(node) || isFunctionDeclaration(node) || isConstructorDeclaration(node); } function walkUp(node: Node, kind: SyntaxKind) { while (node && node.kind === kind) { node = node.parent; } return node; } export function walkUpParenthesizedTypes(node: Node) { return walkUp(node, SyntaxKind.ParenthesizedType); } export function walkUpParenthesizedExpressions(node: Node) { return walkUp(node, SyntaxKind.ParenthesizedExpression); } export function skipParentheses(node: Expression): Expression; export function skipParentheses(node: Node): Node; export function skipParentheses(node: Node): Node { while (node.kind === SyntaxKind.ParenthesizedExpression) { node = (node as ParenthesizedExpression).expression; } return node; } function skipParenthesesUp(node: Node): Node { while (node.kind === SyntaxKind.ParenthesizedExpression) { node = node.parent; } return node; } // a node is delete target iff. it is PropertyAccessExpression/ElementAccessExpression with parentheses skipped export function isDeleteTarget(node: Node): boolean { if (node.kind !== SyntaxKind.PropertyAccessExpression && node.kind !== SyntaxKind.ElementAccessExpression) { return false; } node = walkUpParenthesizedExpressions(node.parent); return node && node.kind === SyntaxKind.DeleteExpression; } export function isNodeDescendantOf(node: Node, ancestor: Node): boolean { while (node) { if (node === ancestor) return true; node = node.parent; } return false; } // True if `name` is the name of a declaration node export function isDeclarationName(name: Node): boolean { return !isSourceFile(name) && !isBindingPattern(name) && isDeclaration(name.parent) && name.parent.name === name; } // See GH#16030 export function getDeclarationFromName(name: Node): Declaration | undefined { const parent = name.parent; switch (name.kind) { case SyntaxKind.StringLiteral: case SyntaxKind.NumericLiteral: if (isComputedPropertyName(parent)) return parent.parent; // falls through case SyntaxKind.Identifier: if (isDeclaration(parent)) { return parent.name === name ? parent : undefined; } else if (isQualifiedName(parent)) { const tag = parent.parent; return isJSDocParameterTag(tag) && tag.name === parent ? tag : undefined; } else { const binExp = parent.parent; return isBinaryExpression(binExp) && getAssignmentDeclarationKind(binExp) !== AssignmentDeclarationKind.None && (binExp.left.symbol || binExp.symbol) && getNameOfDeclaration(binExp) === name ? binExp : undefined; } default: return undefined; } } export function isLiteralComputedPropertyDeclarationName(node: Node) { return (node.kind === SyntaxKind.StringLiteral || node.kind === SyntaxKind.NumericLiteral) && node.parent.kind === SyntaxKind.ComputedPropertyName && isDeclaration(node.parent.parent); } // Return true if the given identifier is classified as an IdentifierName export function isIdentifierName(node: Identifier): boolean { let parent = node.parent; switch (parent.kind) { case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.EnumMember: case SyntaxKind.PropertyAssignment: case SyntaxKind.PropertyAccessExpression: // Name in member declaration or property name in property access return (parent).name === node; case SyntaxKind.QualifiedName: // Name on right hand side of dot in a type query or type reference if ((parent).right === node) { while (parent.kind === SyntaxKind.QualifiedName) { parent = parent.parent; } return parent.kind === SyntaxKind.TypeQuery || parent.kind === SyntaxKind.TypeReference; } return false; case SyntaxKind.BindingElement: case SyntaxKind.ImportSpecifier: // Property name in binding element or import specifier return (parent).propertyName === node; case SyntaxKind.ExportSpecifier: case SyntaxKind.JsxAttribute: // Any name in an export specifier or JSX Attribute return true; } return false; } // An alias symbol is created by one of the following declarations: // import = ... // import from ... // import * as from ... // import { x as } from ... // export { x as } from ... // export = // export default // module.exports = export function isAliasSymbolDeclaration(node: Node): boolean { return node.kind === SyntaxKind.ImportEqualsDeclaration || node.kind === SyntaxKind.NamespaceExportDeclaration || node.kind === SyntaxKind.ImportClause && !!(node).name || node.kind === SyntaxKind.NamespaceImport || node.kind === SyntaxKind.ImportSpecifier || node.kind === SyntaxKind.ExportSpecifier || node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(node) || isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.ModuleExports && exportAssignmentIsAlias(node); } export function exportAssignmentIsAlias(node: ExportAssignment | BinaryExpression): boolean { const e = isExportAssignment(node) ? node.expression : node.right; return isEntityNameExpression(e) || isClassExpression(e); } export function getEffectiveBaseTypeNode(node: ClassLikeDeclaration | InterfaceDeclaration) { const baseType = getClassExtendsHeritageElement(node); if (baseType && isInJSFile(node)) { // Prefer an @augments tag because it may have type parameters. const tag = getJSDocAugmentsTag(node); if (tag) { return tag.class; } } return baseType; } export function getClassExtendsHeritageElement(node: ClassLikeDeclaration | InterfaceDeclaration) { const heritageClause = getHeritageClause(node.heritageClauses, SyntaxKind.ExtendsKeyword); return heritageClause && heritageClause.types.length > 0 ? heritageClause.types[0] : undefined; } export function getClassImplementsHeritageClauseElements(node: ClassLikeDeclaration) { const heritageClause = getHeritageClause(node.heritageClauses, SyntaxKind.ImplementsKeyword); return heritageClause ? heritageClause.types : undefined; } /** Returns the node in an `extends` or `implements` clause of a class or interface. */ export function getAllSuperTypeNodes(node: Node): ReadonlyArray { return isInterfaceDeclaration(node) ? getInterfaceBaseTypeNodes(node) || emptyArray : isClassLike(node) ? concatenate(singleElementArray(getEffectiveBaseTypeNode(node)), getClassImplementsHeritageClauseElements(node)) || emptyArray : emptyArray; } export function getInterfaceBaseTypeNodes(node: InterfaceDeclaration) { const heritageClause = getHeritageClause(node.heritageClauses, SyntaxKind.ExtendsKeyword); return heritageClause ? heritageClause.types : undefined; } export function getHeritageClause(clauses: NodeArray | undefined, kind: SyntaxKind) { if (clauses) { for (const clause of clauses) { if (clause.token === kind) { return clause; } } } return undefined; } export function getAncestor(node: Node | undefined, kind: SyntaxKind): Node | undefined { while (node) { if (node.kind === kind) { return node; } node = node.parent; } return undefined; } export function isKeyword(token: SyntaxKind): boolean { return SyntaxKind.FirstKeyword <= token && token <= SyntaxKind.LastKeyword; } export function isContextualKeyword(token: SyntaxKind): boolean { return SyntaxKind.FirstContextualKeyword <= token && token <= SyntaxKind.LastContextualKeyword; } export function isNonContextualKeyword(token: SyntaxKind): boolean { return isKeyword(token) && !isContextualKeyword(token); } export function isFutureReservedKeyword(token: SyntaxKind): boolean { return SyntaxKind.FirstFutureReservedWord <= token && token <= SyntaxKind.LastFutureReservedWord; } export function isStringANonContextualKeyword(name: string) { const token = stringToToken(name); return token !== undefined && isNonContextualKeyword(token); } export function isIdentifierANonContextualKeyword({ originalKeywordKind }: Identifier): boolean { return !!originalKeywordKind && !isContextualKeyword(originalKeywordKind); } export type TriviaKind = SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia | SyntaxKind.NewLineTrivia | SyntaxKind.WhitespaceTrivia | SyntaxKind.ShebangTrivia | SyntaxKind.ConflictMarkerTrivia; export function isTrivia(token: SyntaxKind): token is TriviaKind { return SyntaxKind.FirstTriviaToken <= token && token <= SyntaxKind.LastTriviaToken; } export const enum FunctionFlags { Normal = 0, // Function is a normal function Generator = 1 << 0, // Function is a generator function or async generator function Async = 1 << 1, // Function is an async function or an async generator function Invalid = 1 << 2, // Function is a signature or overload and does not have a body. AsyncGenerator = Async | Generator, // Function is an async generator function } export function getFunctionFlags(node: SignatureDeclaration | undefined) { if (!node) { return FunctionFlags.Invalid; } let flags = FunctionFlags.Normal; switch (node.kind) { case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.MethodDeclaration: if (node.asteriskToken) { flags |= FunctionFlags.Generator; } // falls through case SyntaxKind.ArrowFunction: if (hasModifier(node, ModifierFlags.Async)) { flags |= FunctionFlags.Async; } break; } if (!(node as FunctionLikeDeclaration).body) { flags |= FunctionFlags.Invalid; } return flags; } export function isAsyncFunction(node: Node): boolean { switch (node.kind) { case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: case SyntaxKind.MethodDeclaration: return (node).body !== undefined && (node).asteriskToken === undefined && hasModifier(node, ModifierFlags.Async); } return false; } export function isStringOrNumericLiteralLike(node: Node): node is StringLiteralLike | NumericLiteral { return isStringLiteralLike(node) || isNumericLiteral(node); } export function isSignedNumericLiteral(node: Node): node is PrefixUnaryExpression & { operand: NumericLiteral } { return isPrefixUnaryExpression(node) && (node.operator === SyntaxKind.PlusToken || node.operator === SyntaxKind.MinusToken) && isNumericLiteral(node.operand); } /** * A declaration has a dynamic name if all of the following are true: * 1. The declaration has a computed property name. * 2. The computed name is *not* expressed as a StringLiteral. * 3. The computed name is *not* expressed as a NumericLiteral. * 4. The computed name is *not* expressed as a PlusToken or MinusToken * immediately followed by a NumericLiteral. * 5. The computed name is *not* expressed as `Symbol.`, where `` * is a property of the Symbol constructor that denotes a built-in * Symbol. */ export function hasDynamicName(declaration: Declaration): declaration is DynamicNamedDeclaration { const name = getNameOfDeclaration(declaration); return !!name && isDynamicName(name); } export function isDynamicName(name: DeclarationName): boolean { return name.kind === SyntaxKind.ComputedPropertyName && !isStringOrNumericLiteralLike(name.expression) && !isSignedNumericLiteral(name.expression) && !isWellKnownSymbolSyntactically(name.expression); } /** * Checks if the expression is of the form: * Symbol.name * where Symbol is literally the word "Symbol", and name is any identifierName */ export function isWellKnownSymbolSyntactically(node: Expression): boolean { return isPropertyAccessExpression(node) && isESSymbolIdentifier(node.expression); } export function getPropertyNameForPropertyNameNode(name: PropertyName): __String | undefined { switch (name.kind) { case SyntaxKind.Identifier: return name.escapedText; case SyntaxKind.StringLiteral: case SyntaxKind.NumericLiteral: return escapeLeadingUnderscores(name.text); case SyntaxKind.ComputedPropertyName: const nameExpression = name.expression; if (isWellKnownSymbolSyntactically(nameExpression)) { return getPropertyNameForKnownSymbolName(idText((nameExpression).name)); } else if (isStringOrNumericLiteralLike(nameExpression)) { return escapeLeadingUnderscores(nameExpression.text); } return undefined; default: return Debug.assertNever(name); } } export type PropertyNameLiteral = Identifier | StringLiteralLike | NumericLiteral; export function isPropertyNameLiteral(node: Node): node is PropertyNameLiteral { switch (node.kind) { case SyntaxKind.Identifier: case SyntaxKind.StringLiteral: case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.NumericLiteral: return true; default: return false; } } export function getTextOfIdentifierOrLiteral(node: PropertyNameLiteral): string { return node.kind === SyntaxKind.Identifier ? idText(node) : node.text; } export function getEscapedTextOfIdentifierOrLiteral(node: PropertyNameLiteral): __String { return node.kind === SyntaxKind.Identifier ? node.escapedText : escapeLeadingUnderscores(node.text); } export function getPropertyNameForKnownSymbolName(symbolName: string): __String { return "__@" + symbolName as __String; } export function isKnownSymbol(symbol: Symbol): boolean { return startsWith(symbol.escapedName as string, "__@"); } /** * Includes the word "Symbol" with unicode escapes */ export function isESSymbolIdentifier(node: Node): boolean { return node.kind === SyntaxKind.Identifier && (node).escapedText === "Symbol"; } export function isPushOrUnshiftIdentifier(node: Identifier) { return node.escapedText === "push" || node.escapedText === "unshift"; } export function isParameterDeclaration(node: VariableLikeDeclaration) { const root = getRootDeclaration(node); return root.kind === SyntaxKind.Parameter; } export function getRootDeclaration(node: Node): Node { while (node.kind === SyntaxKind.BindingElement) { node = node.parent.parent; } return node; } export function nodeStartsNewLexicalEnvironment(node: Node): boolean { const kind = node.kind; return kind === SyntaxKind.Constructor || kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.FunctionDeclaration || kind === SyntaxKind.ArrowFunction || kind === SyntaxKind.MethodDeclaration || kind === SyntaxKind.GetAccessor || kind === SyntaxKind.SetAccessor || kind === SyntaxKind.ModuleDeclaration || kind === SyntaxKind.SourceFile; } export function nodeIsSynthesized(range: TextRange): boolean { return positionIsSynthesized(range.pos) || positionIsSynthesized(range.end); } export function getOriginalSourceFile(sourceFile: SourceFile) { return getParseTreeNode(sourceFile, isSourceFile) || sourceFile; } export const enum Associativity { Left, Right } export function getExpressionAssociativity(expression: Expression) { const operator = getOperator(expression); const hasArguments = expression.kind === SyntaxKind.NewExpression && (expression).arguments !== undefined; return getOperatorAssociativity(expression.kind, operator, hasArguments); } export function getOperatorAssociativity(kind: SyntaxKind, operator: SyntaxKind, hasArguments?: boolean) { switch (kind) { case SyntaxKind.NewExpression: return hasArguments ? Associativity.Left : Associativity.Right; case SyntaxKind.PrefixUnaryExpression: case SyntaxKind.TypeOfExpression: case SyntaxKind.VoidExpression: case SyntaxKind.DeleteExpression: case SyntaxKind.AwaitExpression: case SyntaxKind.ConditionalExpression: case SyntaxKind.YieldExpression: return Associativity.Right; case SyntaxKind.BinaryExpression: switch (operator) { case SyntaxKind.AsteriskAsteriskToken: case SyntaxKind.EqualsToken: case SyntaxKind.PlusEqualsToken: case SyntaxKind.MinusEqualsToken: case SyntaxKind.AsteriskAsteriskEqualsToken: case SyntaxKind.AsteriskEqualsToken: case SyntaxKind.SlashEqualsToken: case SyntaxKind.PercentEqualsToken: case SyntaxKind.LessThanLessThanEqualsToken: case SyntaxKind.GreaterThanGreaterThanEqualsToken: case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: case SyntaxKind.AmpersandEqualsToken: case SyntaxKind.CaretEqualsToken: case SyntaxKind.BarEqualsToken: return Associativity.Right; } } return Associativity.Left; } export function getExpressionPrecedence(expression: Expression) { const operator = getOperator(expression); const hasArguments = expression.kind === SyntaxKind.NewExpression && (expression).arguments !== undefined; return getOperatorPrecedence(expression.kind, operator, hasArguments); } export function getOperator(expression: Expression): SyntaxKind { if (expression.kind === SyntaxKind.BinaryExpression) { return (expression).operatorToken.kind; } else if (expression.kind === SyntaxKind.PrefixUnaryExpression || expression.kind === SyntaxKind.PostfixUnaryExpression) { return (expression).operator; } else { return expression.kind; } } export function getOperatorPrecedence(nodeKind: SyntaxKind, operatorKind: SyntaxKind, hasArguments?: boolean) { switch (nodeKind) { case SyntaxKind.CommaListExpression: return 0; case SyntaxKind.SpreadElement: return 1; case SyntaxKind.YieldExpression: return 2; case SyntaxKind.ConditionalExpression: return 4; case SyntaxKind.BinaryExpression: switch (operatorKind) { case SyntaxKind.CommaToken: return 0; case SyntaxKind.EqualsToken: case SyntaxKind.PlusEqualsToken: case SyntaxKind.MinusEqualsToken: case SyntaxKind.AsteriskAsteriskEqualsToken: case SyntaxKind.AsteriskEqualsToken: case SyntaxKind.SlashEqualsToken: case SyntaxKind.PercentEqualsToken: case SyntaxKind.LessThanLessThanEqualsToken: case SyntaxKind.GreaterThanGreaterThanEqualsToken: case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: case SyntaxKind.AmpersandEqualsToken: case SyntaxKind.CaretEqualsToken: case SyntaxKind.BarEqualsToken: return 3; default: return getBinaryOperatorPrecedence(operatorKind); } case SyntaxKind.PrefixUnaryExpression: case SyntaxKind.TypeOfExpression: case SyntaxKind.VoidExpression: case SyntaxKind.DeleteExpression: case SyntaxKind.AwaitExpression: return 16; case SyntaxKind.PostfixUnaryExpression: return 17; case SyntaxKind.CallExpression: return 18; case SyntaxKind.NewExpression: return hasArguments ? 19 : 18; case SyntaxKind.TaggedTemplateExpression: case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: return 19; case SyntaxKind.ThisKeyword: case SyntaxKind.SuperKeyword: case SyntaxKind.Identifier: case SyntaxKind.NullKeyword: case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: case SyntaxKind.NumericLiteral: case SyntaxKind.BigIntLiteral: case SyntaxKind.StringLiteral: case SyntaxKind.ArrayLiteralExpression: case SyntaxKind.ObjectLiteralExpression: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: case SyntaxKind.ClassExpression: case SyntaxKind.JsxElement: case SyntaxKind.JsxSelfClosingElement: case SyntaxKind.JsxFragment: case SyntaxKind.RegularExpressionLiteral: case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.TemplateExpression: case SyntaxKind.ParenthesizedExpression: case SyntaxKind.OmittedExpression: return 20; default: return -1; } } export function getBinaryOperatorPrecedence(kind: SyntaxKind): number { switch (kind) { case SyntaxKind.BarBarToken: return 5; case SyntaxKind.AmpersandAmpersandToken: return 6; case SyntaxKind.BarToken: return 7; case SyntaxKind.CaretToken: return 8; case SyntaxKind.AmpersandToken: return 9; case SyntaxKind.EqualsEqualsToken: case SyntaxKind.ExclamationEqualsToken: case SyntaxKind.EqualsEqualsEqualsToken: case SyntaxKind.ExclamationEqualsEqualsToken: return 10; case SyntaxKind.LessThanToken: case SyntaxKind.GreaterThanToken: case SyntaxKind.LessThanEqualsToken: case SyntaxKind.GreaterThanEqualsToken: case SyntaxKind.InstanceOfKeyword: case SyntaxKind.InKeyword: case SyntaxKind.AsKeyword: return 11; case SyntaxKind.LessThanLessThanToken: case SyntaxKind.GreaterThanGreaterThanToken: case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: return 12; case SyntaxKind.PlusToken: case SyntaxKind.MinusToken: return 13; case SyntaxKind.AsteriskToken: case SyntaxKind.SlashToken: case SyntaxKind.PercentToken: return 14; case SyntaxKind.AsteriskAsteriskToken: return 15; } // -1 is lower than all other precedences. Returning it will cause binary expression // parsing to stop. return -1; } export function createDiagnosticCollection(): DiagnosticCollection { let nonFileDiagnostics = [] as Diagnostic[] as SortedArray; // See GH#19873 const filesWithDiagnostics = [] as string[] as SortedArray; const fileDiagnostics = createMap>(); let hasReadNonFileDiagnostics = false; return { add, lookup, getGlobalDiagnostics, getDiagnostics, reattachFileDiagnostics }; function reattachFileDiagnostics(newFile: SourceFile): void { forEach(fileDiagnostics.get(newFile.fileName), diagnostic => diagnostic.file = newFile); } function lookup(diagnostic: Diagnostic): Diagnostic | undefined { let diagnostics: SortedArray | undefined; if (diagnostic.file) { diagnostics = fileDiagnostics.get(diagnostic.file.fileName); } else { diagnostics = nonFileDiagnostics; } if (!diagnostics) { return undefined; } const result = binarySearch(diagnostics, diagnostic, identity, compareDiagnosticsSkipRelatedInformation); if (result >= 0) { return diagnostics[result]; } return undefined; } function add(diagnostic: Diagnostic): void { let diagnostics: SortedArray | undefined; if (diagnostic.file) { diagnostics = fileDiagnostics.get(diagnostic.file.fileName); if (!diagnostics) { diagnostics = [] as Diagnostic[] as SortedArray; // See GH#19873 fileDiagnostics.set(diagnostic.file.fileName, diagnostics as SortedArray); insertSorted(filesWithDiagnostics, diagnostic.file.fileName, compareStringsCaseSensitive); } } else { // If we've already read the non-file diagnostics, do not modify the existing array. if (hasReadNonFileDiagnostics) { hasReadNonFileDiagnostics = false; nonFileDiagnostics = nonFileDiagnostics.slice() as SortedArray; } diagnostics = nonFileDiagnostics; } insertSorted(diagnostics, diagnostic, compareDiagnostics); } function getGlobalDiagnostics(): Diagnostic[] { hasReadNonFileDiagnostics = true; return nonFileDiagnostics; } function getDiagnostics(fileName: string): DiagnosticWithLocation[]; function getDiagnostics(): Diagnostic[]; function getDiagnostics(fileName?: string): Diagnostic[] { if (fileName) { return fileDiagnostics.get(fileName) || []; } const fileDiags: Diagnostic[] = flatMapToMutable(filesWithDiagnostics, f => fileDiagnostics.get(f)); if (!nonFileDiagnostics.length) { return fileDiags; } fileDiags.unshift(...nonFileDiagnostics); return fileDiags; } } const templateSubstitutionRegExp = /\$\{/g; function escapeTemplateSubstitution(str: string): string { return str.replace(templateSubstitutionRegExp, "\\${"); } // This consists of the first 19 unprintable ASCII characters, canonical escapes, lineSeparator, // paragraphSeparator, and nextLine. The latter three are just desirable to suppress new lines in // the language service. These characters should be escaped when printing, and if any characters are added, // the map below must be updated. Note that this regexp *does not* include the 'delete' character. // There is no reason for this other than that JSON.stringify does not handle it either. const doubleQuoteEscapedCharsRegExp = /[\\\"\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g; const singleQuoteEscapedCharsRegExp = /[\\\'\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g; // Template strings should be preserved as much as possible const backtickQuoteEscapedCharsRegExp = /[\\\`]/g; const escapedCharsMap = createMapFromTemplate({ "\t": "\\t", "\v": "\\v", "\f": "\\f", "\b": "\\b", "\r": "\\r", "\n": "\\n", "\\": "\\\\", "\"": "\\\"", "\'": "\\\'", "\`": "\\\`", "\u2028": "\\u2028", // lineSeparator "\u2029": "\\u2029", // paragraphSeparator "\u0085": "\\u0085" // nextLine }); /** * Based heavily on the abstract 'Quote'/'QuoteJSONString' operation from ECMA-262 (24.3.2.2), * but augmented for a few select characters (e.g. lineSeparator, paragraphSeparator, nextLine) * Note that this doesn't actually wrap the input in double quotes. */ export function escapeString(s: string, quoteChar?: CharacterCodes.doubleQuote | CharacterCodes.singleQuote | CharacterCodes.backtick): string { const escapedCharsRegExp = quoteChar === CharacterCodes.backtick ? backtickQuoteEscapedCharsRegExp : quoteChar === CharacterCodes.singleQuote ? singleQuoteEscapedCharsRegExp : doubleQuoteEscapedCharsRegExp; return s.replace(escapedCharsRegExp, getReplacement); } /** * Strip off existed single quotes or double quotes from a given string * * @return non-quoted string */ export function stripQuotes(name: string) { const length = name.length; if (length >= 2 && name.charCodeAt(0) === name.charCodeAt(length - 1) && startsWithQuote(name)) { return name.substring(1, length - 1); } return name; } export function startsWithQuote(name: string): boolean { return isSingleOrDoubleQuote(name.charCodeAt(0)); } function getReplacement(c: string, offset: number, input: string) { if (c.charCodeAt(0) === CharacterCodes.nullCharacter) { const lookAhead = input.charCodeAt(offset + c.length); if (lookAhead >= CharacterCodes._0 && lookAhead <= CharacterCodes._9) { // If the null character is followed by digits, print as a hex escape to prevent the result from parsing as an octal (which is forbidden in strict mode) return "\\x00"; } // Otherwise, keep printing a literal \0 for the null character return "\\0"; } return escapedCharsMap.get(c) || get16BitUnicodeEscapeSequence(c.charCodeAt(0)); } export function isIntrinsicJsxName(name: __String | string) { const ch = (name as string).charCodeAt(0); return (ch >= CharacterCodes.a && ch <= CharacterCodes.z) || stringContains((name as string), "-"); } function get16BitUnicodeEscapeSequence(charCode: number): string { const hexCharCode = charCode.toString(16).toUpperCase(); const paddedHexCode = ("0000" + hexCharCode).slice(-4); return "\\u" + paddedHexCode; } const nonAsciiCharacters = /[^\u0000-\u007F]/g; export function escapeNonAsciiString(s: string, quoteChar?: CharacterCodes.doubleQuote | CharacterCodes.singleQuote | CharacterCodes.backtick): string { s = escapeString(s, quoteChar); // Replace non-ASCII characters with '\uNNNN' escapes if any exist. // Otherwise just return the original string. return nonAsciiCharacters.test(s) ? s.replace(nonAsciiCharacters, c => get16BitUnicodeEscapeSequence(c.charCodeAt(0))) : s; } const indentStrings: string[] = ["", " "]; export function getIndentString(level: number) { if (indentStrings[level] === undefined) { indentStrings[level] = getIndentString(level - 1) + indentStrings[1]; } return indentStrings[level]; } export function getIndentSize() { return indentStrings[1].length; } export function createTextWriter(newLine: string): EmitTextWriter { let output: string; let indent: number; let lineStart: boolean; let lineCount: number; let linePos: number; function updateLineCountAndPosFor(s: string) { const lineStartsOfS = computeLineStarts(s); if (lineStartsOfS.length > 1) { lineCount = lineCount + lineStartsOfS.length - 1; linePos = output.length - s.length + last(lineStartsOfS); lineStart = (linePos - output.length) === 0; } else { lineStart = false; } } function write(s: string) { if (s && s.length) { if (lineStart) { s = getIndentString(indent) + s; lineStart = false; } output += s; updateLineCountAndPosFor(s); } } function reset(): void { output = ""; indent = 0; lineStart = true; lineCount = 0; linePos = 0; } function rawWrite(s: string) { if (s !== undefined) { output += s; updateLineCountAndPosFor(s); } } function writeLiteral(s: string) { if (s && s.length) { write(s); } } function writeLine() { if (!lineStart) { output += newLine; lineCount++; linePos = output.length; lineStart = true; } } function getTextPosWithWriteLine() { return lineStart ? output.length : (output.length + newLine.length); } reset(); return { write, rawWrite, writeLiteral, writeLine, increaseIndent: () => { indent++; }, decreaseIndent: () => { indent--; }, getIndent: () => indent, getTextPos: () => output.length, getLine: () => lineCount, getColumn: () => lineStart ? indent * getIndentSize() : output.length - linePos, getText: () => output, isAtStartOfLine: () => lineStart, clear: reset, reportInaccessibleThisError: noop, reportPrivateInBaseOfClassExpression: noop, reportInaccessibleUniqueSymbolError: noop, trackSymbol: noop, writeKeyword: write, writeOperator: write, writeParameter: write, writeProperty: write, writePunctuation: write, writeSpace: write, writeStringLiteral: write, writeSymbol: (s, _) => write(s), writeTrailingSemicolon: write, writeComment: write, getTextPosWithWriteLine }; } export function getTrailingSemicolonOmittingWriter(writer: EmitTextWriter): EmitTextWriter { let pendingTrailingSemicolon = false; function commitPendingTrailingSemicolon() { if (pendingTrailingSemicolon) { writer.writeTrailingSemicolon(";"); pendingTrailingSemicolon = false; } } return { ...writer, writeTrailingSemicolon() { pendingTrailingSemicolon = true; }, writeLiteral(s) { commitPendingTrailingSemicolon(); writer.writeLiteral(s); }, writeStringLiteral(s) { commitPendingTrailingSemicolon(); writer.writeStringLiteral(s); }, writeSymbol(s, sym) { commitPendingTrailingSemicolon(); writer.writeSymbol(s, sym); }, writePunctuation(s) { commitPendingTrailingSemicolon(); writer.writePunctuation(s); }, writeKeyword(s) { commitPendingTrailingSemicolon(); writer.writeKeyword(s); }, writeOperator(s) { commitPendingTrailingSemicolon(); writer.writeOperator(s); }, writeParameter(s) { commitPendingTrailingSemicolon(); writer.writeParameter(s); }, writeSpace(s) { commitPendingTrailingSemicolon(); writer.writeSpace(s); }, writeProperty(s) { commitPendingTrailingSemicolon(); writer.writeProperty(s); }, writeComment(s) { commitPendingTrailingSemicolon(); writer.writeComment(s); }, writeLine() { commitPendingTrailingSemicolon(); writer.writeLine(); }, increaseIndent() { commitPendingTrailingSemicolon(); writer.increaseIndent(); }, decreaseIndent() { commitPendingTrailingSemicolon(); writer.decreaseIndent(); } }; } export function getResolvedExternalModuleName(host: EmitHost, file: SourceFile, referenceFile?: SourceFile): string { return file.moduleName || getExternalModuleNameFromPath(host, file.fileName, referenceFile && referenceFile.fileName); } export function getExternalModuleNameFromDeclaration(host: EmitHost, resolver: EmitResolver, declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration | ImportTypeNode): string | undefined { const file = resolver.getExternalModuleFileFromDeclaration(declaration); if (!file || file.isDeclarationFile) { return undefined; } return getResolvedExternalModuleName(host, file); } /** * Resolves a local path to a path which is absolute to the base of the emit */ export function getExternalModuleNameFromPath(host: EmitHost, fileName: string, referencePath?: string): string { const getCanonicalFileName = (f: string) => host.getCanonicalFileName(f); const dir = toPath(referencePath ? getDirectoryPath(referencePath) : host.getCommonSourceDirectory(), host.getCurrentDirectory(), getCanonicalFileName); const filePath = getNormalizedAbsolutePath(fileName, host.getCurrentDirectory()); const relativePath = getRelativePathToDirectoryOrUrl(dir, filePath, dir, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); const extensionless = removeFileExtension(relativePath); return referencePath ? ensurePathIsNonModuleName(extensionless) : extensionless; } export function getOwnEmitOutputFilePath(fileName: string, host: EmitHost, extension: string) { const compilerOptions = host.getCompilerOptions(); let emitOutputFilePathWithoutExtension: string; if (compilerOptions.outDir) { emitOutputFilePathWithoutExtension = removeFileExtension(getSourceFilePathInNewDir(fileName, host, compilerOptions.outDir)); } else { emitOutputFilePathWithoutExtension = removeFileExtension(fileName); } return emitOutputFilePathWithoutExtension + extension; } export function getDeclarationEmitOutputFilePath(fileName: string, host: EmitHost) { return getDeclarationEmitOutputFilePathWorker(fileName, host.getCompilerOptions(), host.getCurrentDirectory(), host.getCommonSourceDirectory(), f => host.getCanonicalFileName(f)); } export function getDeclarationEmitOutputFilePathWorker(fileName: string, options: CompilerOptions, currentDirectory: string, commonSourceDirectory: string, getCanonicalFileName: GetCanonicalFileName): string { const outputDir = options.declarationDir || options.outDir; // Prefer declaration folder if specified const path = outputDir ? getSourceFilePathInNewDirWorker(fileName, outputDir, currentDirectory, commonSourceDirectory, getCanonicalFileName) : fileName; return removeFileExtension(path) + Extension.Dts; } export interface EmitFileNames { jsFilePath?: string | undefined; sourceMapFilePath?: string | undefined; declarationFilePath?: string | undefined; declarationMapPath?: string | undefined; buildInfoPath?: string | undefined; } /** * Gets the source files that are expected to have an emit output. * * Originally part of `forEachExpectedEmitFile`, this functionality was extracted to support * transformations. * * @param host An EmitHost. * @param targetSourceFile An optional target source file to emit. */ export function getSourceFilesToEmit(host: EmitHost, targetSourceFile?: SourceFile): ReadonlyArray { const options = host.getCompilerOptions(); const isSourceFileFromExternalLibrary = (file: SourceFile) => host.isSourceFileFromExternalLibrary(file); const getResolvedProjectReferenceToRedirect = (fileName: string) => host.getResolvedProjectReferenceToRedirect(fileName); if (options.outFile || options.out) { const moduleKind = getEmitModuleKind(options); const moduleEmitEnabled = options.emitDeclarationOnly || moduleKind === ModuleKind.AMD || moduleKind === ModuleKind.System; // Can emit only sources that are not declaration file and are either non module code or module with --module or --target es6 specified return filter(host.getSourceFiles(), sourceFile => (moduleEmitEnabled || !isExternalModule(sourceFile)) && sourceFileMayBeEmitted(sourceFile, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect)); } else { const sourceFiles = targetSourceFile === undefined ? host.getSourceFiles() : [targetSourceFile]; return filter(sourceFiles, sourceFile => sourceFileMayBeEmitted(sourceFile, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect)); } } /** Don't call this for `--outFile`, just for `--outDir` or plain emit. `--outFile` needs additional checks. */ export function sourceFileMayBeEmitted( sourceFile: SourceFile, options: CompilerOptions, isSourceFileFromExternalLibrary: (file: SourceFile) => boolean, getResolvedProjectReferenceToRedirect: (fileName: string) => ResolvedProjectReference | undefined ) { return !(options.noEmitForJsFiles && isSourceFileJS(sourceFile)) && !sourceFile.isDeclarationFile && !isSourceFileFromExternalLibrary(sourceFile) && !(isJsonSourceFile(sourceFile) && getResolvedProjectReferenceToRedirect(sourceFile.fileName)); } export function getSourceFilePathInNewDir(fileName: string, host: EmitHost, newDirPath: string): string { return getSourceFilePathInNewDirWorker(fileName, newDirPath, host.getCurrentDirectory(), host.getCommonSourceDirectory(), f => host.getCanonicalFileName(f)); } export function getSourceFilePathInNewDirWorker(fileName: string, newDirPath: string, currentDirectory: string, commonSourceDirectory: string, getCanonicalFileName: GetCanonicalFileName): string { let sourceFilePath = getNormalizedAbsolutePath(fileName, currentDirectory); const isSourceFileInCommonSourceDirectory = getCanonicalFileName(sourceFilePath).indexOf(getCanonicalFileName(commonSourceDirectory)) === 0; sourceFilePath = isSourceFileInCommonSourceDirectory ? sourceFilePath.substring(commonSourceDirectory.length) : sourceFilePath; return combinePaths(newDirPath, sourceFilePath); } export function writeFile(host: { writeFile: WriteFileCallback; }, diagnostics: DiagnosticCollection, fileName: string, data: string, writeByteOrderMark: boolean, sourceFiles?: ReadonlyArray) { host.writeFile(fileName, data, writeByteOrderMark, hostErrorMessage => { diagnostics.add(createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, fileName, hostErrorMessage)); }, sourceFiles); } export function getLineOfLocalPosition(currentSourceFile: SourceFile, pos: number) { return getLineAndCharacterOfPosition(currentSourceFile, pos).line; } export function getLineOfLocalPositionFromLineMap(lineMap: ReadonlyArray, pos: number) { return computeLineAndCharacterOfPosition(lineMap, pos).line; } export function getFirstConstructorWithBody(node: ClassLikeDeclaration): ConstructorDeclaration & { body: FunctionBody } | undefined { return find(node.members, (member): member is ConstructorDeclaration & { body: FunctionBody } => isConstructorDeclaration(member) && nodeIsPresent(member.body)); } export function getSetAccessorValueParameter(accessor: SetAccessorDeclaration): ParameterDeclaration | undefined { if (accessor && accessor.parameters.length > 0) { const hasThis = accessor.parameters.length === 2 && parameterIsThisKeyword(accessor.parameters[0]); return accessor.parameters[hasThis ? 1 : 0]; } } /** Get the type annotation for the value parameter. */ export function getSetAccessorTypeAnnotationNode(accessor: SetAccessorDeclaration): TypeNode | undefined { const parameter = getSetAccessorValueParameter(accessor); return parameter && parameter.type; } export function getThisParameter(signature: SignatureDeclaration | JSDocSignature): ParameterDeclaration | undefined { // callback tags do not currently support this parameters if (signature.parameters.length && !isJSDocSignature(signature)) { const thisParameter = signature.parameters[0]; if (parameterIsThisKeyword(thisParameter)) { return thisParameter; } } } export function parameterIsThisKeyword(parameter: ParameterDeclaration): boolean { return isThisIdentifier(parameter.name); } export function isThisIdentifier(node: Node | undefined): boolean { return !!node && node.kind === SyntaxKind.Identifier && identifierIsThisKeyword(node as Identifier); } export function identifierIsThisKeyword(id: Identifier): boolean { return id.originalKeywordKind === SyntaxKind.ThisKeyword; } export function getAllAccessorDeclarations(declarations: readonly Declaration[], accessor: AccessorDeclaration): AllAccessorDeclarations { // TODO: GH#18217 let firstAccessor!: AccessorDeclaration; let secondAccessor!: AccessorDeclaration; let getAccessor!: GetAccessorDeclaration; let setAccessor!: SetAccessorDeclaration; if (hasDynamicName(accessor)) { firstAccessor = accessor; if (accessor.kind === SyntaxKind.GetAccessor) { getAccessor = accessor; } else if (accessor.kind === SyntaxKind.SetAccessor) { setAccessor = accessor; } else { Debug.fail("Accessor has wrong kind"); } } else { forEach(declarations, member => { if (isAccessor(member) && hasModifier(member, ModifierFlags.Static) === hasModifier(accessor, ModifierFlags.Static)) { const memberName = getPropertyNameForPropertyNameNode(member.name); const accessorName = getPropertyNameForPropertyNameNode(accessor.name); if (memberName === accessorName) { if (!firstAccessor) { firstAccessor = member; } else if (!secondAccessor) { secondAccessor = member; } if (member.kind === SyntaxKind.GetAccessor && !getAccessor) { getAccessor = member; } if (member.kind === SyntaxKind.SetAccessor && !setAccessor) { setAccessor = member; } } } }); } return { firstAccessor, secondAccessor, getAccessor, setAccessor }; } /** * Gets the effective type annotation of a variable, parameter, or property. If the node was * parsed in a JavaScript file, gets the type annotation from JSDoc. */ export function getEffectiveTypeAnnotationNode(node: Node): TypeNode | undefined { const type = (node as HasType).type; if (type || !isInJSFile(node)) return type; return isJSDocPropertyLikeTag(node) ? node.typeExpression && node.typeExpression.type : getJSDocType(node); } export function getTypeAnnotationNode(node: Node): TypeNode | undefined { return (node as HasType).type; } /** * Gets the effective return type annotation of a signature. If the node was parsed in a * JavaScript file, gets the return type annotation from JSDoc. */ export function getEffectiveReturnTypeNode(node: SignatureDeclaration | JSDocSignature): TypeNode | undefined { return isJSDocSignature(node) ? node.type && node.type.typeExpression && node.type.typeExpression.type : node.type || (isInJSFile(node) ? getJSDocReturnType(node) : undefined); } export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray { return flatMap(getJSDocTags(node), tag => isNonTypeAliasTemplate(tag) ? tag.typeParameters : undefined); } /** template tags are only available when a typedef isn't already using them */ function isNonTypeAliasTemplate(tag: JSDocTag): tag is JSDocTemplateTag { return isJSDocTemplateTag(tag) && !(tag.parent.kind === SyntaxKind.JSDocComment && tag.parent.tags!.some(isJSDocTypeAlias)); } /** * Gets the effective type annotation of the value parameter of a set accessor. If the node * was parsed in a JavaScript file, gets the type annotation from JSDoc. */ export function getEffectiveSetAccessorTypeAnnotationNode(node: SetAccessorDeclaration): TypeNode | undefined { const parameter = getSetAccessorValueParameter(node); return parameter && getEffectiveTypeAnnotationNode(parameter); } export function emitNewLineBeforeLeadingComments(lineMap: ReadonlyArray, writer: EmitTextWriter, node: TextRange, leadingComments: ReadonlyArray | undefined) { emitNewLineBeforeLeadingCommentsOfPosition(lineMap, writer, node.pos, leadingComments); } export function emitNewLineBeforeLeadingCommentsOfPosition(lineMap: ReadonlyArray, writer: EmitTextWriter, pos: number, leadingComments: ReadonlyArray | undefined) { // If the leading comments start on different line than the start of node, write new line if (leadingComments && leadingComments.length && pos !== leadingComments[0].pos && getLineOfLocalPositionFromLineMap(lineMap, pos) !== getLineOfLocalPositionFromLineMap(lineMap, leadingComments[0].pos)) { writer.writeLine(); } } export function emitNewLineBeforeLeadingCommentOfPosition(lineMap: ReadonlyArray, writer: EmitTextWriter, pos: number, commentPos: number) { // If the leading comments start on different line than the start of node, write new line if (pos !== commentPos && getLineOfLocalPositionFromLineMap(lineMap, pos) !== getLineOfLocalPositionFromLineMap(lineMap, commentPos)) { writer.writeLine(); } } export function emitComments( text: string, lineMap: ReadonlyArray, writer: EmitTextWriter, comments: ReadonlyArray | undefined, leadingSeparator: boolean, trailingSeparator: boolean, newLine: string, writeComment: (text: string, lineMap: ReadonlyArray, writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) => void) { if (comments && comments.length > 0) { if (leadingSeparator) { writer.writeSpace(" "); } let emitInterveningSeparator = false; for (const comment of comments) { if (emitInterveningSeparator) { writer.writeSpace(" "); emitInterveningSeparator = false; } writeComment(text, lineMap, writer, comment.pos, comment.end, newLine); if (comment.hasTrailingNewLine) { writer.writeLine(); } else { emitInterveningSeparator = true; } } if (emitInterveningSeparator && trailingSeparator) { writer.writeSpace(" "); } } } /** * Detached comment is a comment at the top of file or function body that is separated from * the next statement by space. */ export function emitDetachedComments(text: string, lineMap: ReadonlyArray, writer: EmitTextWriter, writeComment: (text: string, lineMap: ReadonlyArray, writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) => void, node: TextRange, newLine: string, removeComments: boolean) { let leadingComments: CommentRange[] | undefined; let currentDetachedCommentInfo: { nodePos: number, detachedCommentEndPos: number } | undefined; if (removeComments) { // removeComments is true, only reserve pinned comment at the top of file // For example: // /*! Pinned Comment */ // // var x = 10; if (node.pos === 0) { leadingComments = filter(getLeadingCommentRanges(text, node.pos), isPinnedCommentLocal); } } else { // removeComments is false, just get detached as normal and bypass the process to filter comment leadingComments = getLeadingCommentRanges(text, node.pos); } if (leadingComments) { const detachedComments: CommentRange[] = []; let lastComment: CommentRange | undefined; for (const comment of leadingComments) { if (lastComment) { const lastCommentLine = getLineOfLocalPositionFromLineMap(lineMap, lastComment.end); const commentLine = getLineOfLocalPositionFromLineMap(lineMap, comment.pos); if (commentLine >= lastCommentLine + 2) { // There was a blank line between the last comment and this comment. This // comment is not part of the copyright comments. Return what we have so // far. break; } } detachedComments.push(comment); lastComment = comment; } if (detachedComments.length) { // All comments look like they could have been part of the copyright header. Make // sure there is at least one blank line between it and the node. If not, it's not // a copyright header. const lastCommentLine = getLineOfLocalPositionFromLineMap(lineMap, last(detachedComments).end); const nodeLine = getLineOfLocalPositionFromLineMap(lineMap, skipTrivia(text, node.pos)); if (nodeLine >= lastCommentLine + 2) { // Valid detachedComments emitNewLineBeforeLeadingComments(lineMap, writer, node, leadingComments); emitComments(text, lineMap, writer, detachedComments, /*leadingSeparator*/ false, /*trailingSeparator*/ true, newLine, writeComment); currentDetachedCommentInfo = { nodePos: node.pos, detachedCommentEndPos: last(detachedComments).end }; } } } return currentDetachedCommentInfo; function isPinnedCommentLocal(comment: CommentRange) { return isPinnedComment(text, comment.pos); } } export function writeCommentRange(text: string, lineMap: ReadonlyArray, writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) { if (text.charCodeAt(commentPos + 1) === CharacterCodes.asterisk) { const firstCommentLineAndCharacter = computeLineAndCharacterOfPosition(lineMap, commentPos); const lineCount = lineMap.length; let firstCommentLineIndent: number | undefined; for (let pos = commentPos, currentLine = firstCommentLineAndCharacter.line; pos < commentEnd; currentLine++) { const nextLineStart = (currentLine + 1) === lineCount ? text.length + 1 : lineMap[currentLine + 1]; if (pos !== commentPos) { // If we are not emitting first line, we need to write the spaces to adjust the alignment if (firstCommentLineIndent === undefined) { firstCommentLineIndent = calculateIndent(text, lineMap[firstCommentLineAndCharacter.line], commentPos); } // These are number of spaces writer is going to write at current indent const currentWriterIndentSpacing = writer.getIndent() * getIndentSize(); // Number of spaces we want to be writing // eg: Assume writer indent // module m { // /* starts at character 9 this is line 1 // * starts at character pos 4 line --1 = 8 - 8 + 3 // More left indented comment */ --2 = 8 - 8 + 2 // class c { } // } // module m { // /* this is line 1 -- Assume current writer indent 8 // * line --3 = 8 - 4 + 5 // More right indented comment */ --4 = 8 - 4 + 11 // class c { } // } const spacesToEmit = currentWriterIndentSpacing - firstCommentLineIndent + calculateIndent(text, pos, nextLineStart); if (spacesToEmit > 0) { let numberOfSingleSpacesToEmit = spacesToEmit % getIndentSize(); const indentSizeSpaceString = getIndentString((spacesToEmit - numberOfSingleSpacesToEmit) / getIndentSize()); // Write indent size string ( in eg 1: = "", 2: "" , 3: string with 8 spaces 4: string with 12 spaces writer.rawWrite(indentSizeSpaceString); // Emit the single spaces (in eg: 1: 3 spaces, 2: 2 spaces, 3: 1 space, 4: 3 spaces) while (numberOfSingleSpacesToEmit) { writer.rawWrite(" "); numberOfSingleSpacesToEmit--; } } else { // No spaces to emit write empty string writer.rawWrite(""); } } // Write the comment line text writeTrimmedCurrentLine(text, commentEnd, writer, newLine, pos, nextLineStart); pos = nextLineStart; } } else { // Single line comment of style //.... writer.writeComment(text.substring(commentPos, commentEnd)); } } function writeTrimmedCurrentLine(text: string, commentEnd: number, writer: EmitTextWriter, newLine: string, pos: number, nextLineStart: number) { const end = Math.min(commentEnd, nextLineStart - 1); const currentLineText = text.substring(pos, end).replace(/^\s+|\s+$/g, ""); if (currentLineText) { // trimmed forward and ending spaces text writer.writeComment(currentLineText); if (end !== commentEnd) { writer.writeLine(); } } else { // Empty string - make sure we write empty line writer.rawWrite(newLine); } } function calculateIndent(text: string, pos: number, end: number) { let currentLineIndent = 0; for (; pos < end && isWhiteSpaceSingleLine(text.charCodeAt(pos)); pos++) { if (text.charCodeAt(pos) === CharacterCodes.tab) { // Tabs = TabSize = indent size and go to next tabStop currentLineIndent += getIndentSize() - (currentLineIndent % getIndentSize()); } else { // Single space currentLineIndent++; } } return currentLineIndent; } export function hasModifiers(node: Node) { return getModifierFlags(node) !== ModifierFlags.None; } export function hasModifier(node: Node, flags: ModifierFlags): boolean { return !!getSelectedModifierFlags(node, flags); } export function hasStaticModifier(node: Node): boolean { return hasModifier(node, ModifierFlags.Static); } export function hasReadonlyModifier(node: Node): boolean { return hasModifier(node, ModifierFlags.Readonly); } export function getSelectedModifierFlags(node: Node, flags: ModifierFlags): ModifierFlags { return getModifierFlags(node) & flags; } export function getModifierFlags(node: Node): ModifierFlags { if (node.modifierFlagsCache & ModifierFlags.HasComputedFlags) { return node.modifierFlagsCache & ~ModifierFlags.HasComputedFlags; } const flags = getModifierFlagsNoCache(node); node.modifierFlagsCache = flags | ModifierFlags.HasComputedFlags; return flags; } export function getModifierFlagsNoCache(node: Node): ModifierFlags { let flags = ModifierFlags.None; if (node.modifiers) { for (const modifier of node.modifiers) { flags |= modifierToFlag(modifier.kind); } } if (node.flags & NodeFlags.NestedNamespace || (node.kind === SyntaxKind.Identifier && (node).isInJSDocNamespace)) { flags |= ModifierFlags.Export; } return flags; } export function modifierToFlag(token: SyntaxKind): ModifierFlags { switch (token) { case SyntaxKind.StaticKeyword: return ModifierFlags.Static; case SyntaxKind.PublicKeyword: return ModifierFlags.Public; case SyntaxKind.ProtectedKeyword: return ModifierFlags.Protected; case SyntaxKind.PrivateKeyword: return ModifierFlags.Private; case SyntaxKind.AbstractKeyword: return ModifierFlags.Abstract; case SyntaxKind.ExportKeyword: return ModifierFlags.Export; case SyntaxKind.DeclareKeyword: return ModifierFlags.Ambient; case SyntaxKind.ConstKeyword: return ModifierFlags.Const; case SyntaxKind.DefaultKeyword: return ModifierFlags.Default; case SyntaxKind.AsyncKeyword: return ModifierFlags.Async; case SyntaxKind.ReadonlyKeyword: return ModifierFlags.Readonly; } return ModifierFlags.None; } export function isLogicalOperator(token: SyntaxKind): boolean { return token === SyntaxKind.BarBarToken || token === SyntaxKind.AmpersandAmpersandToken || token === SyntaxKind.ExclamationToken; } export function isAssignmentOperator(token: SyntaxKind): boolean { return token >= SyntaxKind.FirstAssignment && token <= SyntaxKind.LastAssignment; } /** Get `C` given `N` if `N` is in the position `class C extends N` where `N` is an ExpressionWithTypeArguments. */ export function tryGetClassExtendingExpressionWithTypeArguments(node: Node): ClassLikeDeclaration | undefined { const cls = tryGetClassImplementingOrExtendingExpressionWithTypeArguments(node); return cls && !cls.isImplements ? cls.class : undefined; } export interface ClassImplementingOrExtendingExpressionWithTypeArguments { readonly class: ClassLikeDeclaration; readonly isImplements: boolean; } export function tryGetClassImplementingOrExtendingExpressionWithTypeArguments(node: Node): ClassImplementingOrExtendingExpressionWithTypeArguments | undefined { return isExpressionWithTypeArguments(node) && isHeritageClause(node.parent) && isClassLike(node.parent.parent) ? { class: node.parent.parent, isImplements: node.parent.token === SyntaxKind.ImplementsKeyword } : undefined; } export function isAssignmentExpression(node: Node, excludeCompoundAssignment: true): node is AssignmentExpression; export function isAssignmentExpression(node: Node, excludeCompoundAssignment?: false): node is AssignmentExpression; export function isAssignmentExpression(node: Node, excludeCompoundAssignment?: boolean): node is AssignmentExpression { return isBinaryExpression(node) && (excludeCompoundAssignment ? node.operatorToken.kind === SyntaxKind.EqualsToken : isAssignmentOperator(node.operatorToken.kind)) && isLeftHandSideExpression(node.left); } export function isDestructuringAssignment(node: Node): node is DestructuringAssignment { if (isAssignmentExpression(node, /*excludeCompoundAssignment*/ true)) { const kind = node.left.kind; return kind === SyntaxKind.ObjectLiteralExpression || kind === SyntaxKind.ArrayLiteralExpression; } return false; } export function isExpressionWithTypeArgumentsInClassExtendsClause(node: Node): node is ExpressionWithTypeArguments { return tryGetClassExtendingExpressionWithTypeArguments(node) !== undefined; } export function isEntityNameExpression(node: Node): node is EntityNameExpression { return node.kind === SyntaxKind.Identifier || isPropertyAccessEntityNameExpression(node); } export function isPropertyAccessEntityNameExpression(node: Node): node is PropertyAccessEntityNameExpression { return isPropertyAccessExpression(node) && isEntityNameExpression(node.expression); } export function tryGetPropertyAccessOrIdentifierToString(expr: Expression): string | undefined { if (isPropertyAccessExpression(expr)) { return tryGetPropertyAccessOrIdentifierToString(expr.expression) + "." + expr.name; } if (isIdentifier(expr)) { return unescapeLeadingUnderscores(expr.escapedText); } return undefined; } export function isPrototypeAccess(node: Node): node is PropertyAccessExpression { return isPropertyAccessExpression(node) && node.name.escapedText === "prototype"; } export function isRightSideOfQualifiedNameOrPropertyAccess(node: Node) { return (node.parent.kind === SyntaxKind.QualifiedName && (node.parent).right === node) || (node.parent.kind === SyntaxKind.PropertyAccessExpression && (node.parent).name === node); } export function isEmptyObjectLiteral(expression: Node): boolean { return expression.kind === SyntaxKind.ObjectLiteralExpression && (expression).properties.length === 0; } export function isEmptyArrayLiteral(expression: Node): boolean { return expression.kind === SyntaxKind.ArrayLiteralExpression && (expression).elements.length === 0; } export function getLocalSymbolForExportDefault(symbol: Symbol) { return isExportDefaultSymbol(symbol) ? symbol.declarations[0].localSymbol : undefined; } function isExportDefaultSymbol(symbol: Symbol): boolean { return symbol && length(symbol.declarations) > 0 && hasModifier(symbol.declarations[0], ModifierFlags.Default); } /** Return ".ts", ".d.ts", or ".tsx", if that is the extension. */ export function tryExtractTSExtension(fileName: string): string | undefined { return find(supportedTSExtensionsForExtractExtension, extension => fileExtensionIs(fileName, extension)); } /** * Replace each instance of non-ascii characters by one, two, three, or four escape sequences * representing the UTF-8 encoding of the character, and return the expanded char code list. */ function getExpandedCharCodes(input: string): number[] { const output: number[] = []; const length = input.length; for (let i = 0; i < length; i++) { const charCode = input.charCodeAt(i); // handle utf8 if (charCode < 0x80) { output.push(charCode); } else if (charCode < 0x800) { output.push((charCode >> 6) | 0B11000000); output.push((charCode & 0B00111111) | 0B10000000); } else if (charCode < 0x10000) { output.push((charCode >> 12) | 0B11100000); output.push(((charCode >> 6) & 0B00111111) | 0B10000000); output.push((charCode & 0B00111111) | 0B10000000); } else if (charCode < 0x20000) { output.push((charCode >> 18) | 0B11110000); output.push(((charCode >> 12) & 0B00111111) | 0B10000000); output.push(((charCode >> 6) & 0B00111111) | 0B10000000); output.push((charCode & 0B00111111) | 0B10000000); } else { Debug.assert(false, "Unexpected code point"); } } return output; } const base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; /** * Converts a string to a base-64 encoded ASCII string. */ export function convertToBase64(input: string): string { let result = ""; const charCodes = getExpandedCharCodes(input); let i = 0; const length = charCodes.length; let byte1: number, byte2: number, byte3: number, byte4: number; while (i < length) { // Convert every 6-bits in the input 3 character points // into a base64 digit byte1 = charCodes[i] >> 2; byte2 = (charCodes[i] & 0B00000011) << 4 | charCodes[i + 1] >> 4; byte3 = (charCodes[i + 1] & 0B00001111) << 2 | charCodes[i + 2] >> 6; byte4 = charCodes[i + 2] & 0B00111111; // We are out of characters in the input, set the extra // digits to 64 (padding character). if (i + 1 >= length) { byte3 = byte4 = 64; } else if (i + 2 >= length) { byte4 = 64; } // Write to the output result += base64Digits.charAt(byte1) + base64Digits.charAt(byte2) + base64Digits.charAt(byte3) + base64Digits.charAt(byte4); i += 3; } return result; } function getStringFromExpandedCharCodes(codes: number[]): string { let output = ""; let i = 0; const length = codes.length; while (i < length) { const charCode = codes[i]; if (charCode < 0x80) { output += String.fromCharCode(charCode); i++; } else if ((charCode & 0B11000000) === 0B11000000) { let value = charCode & 0B00111111; i++; let nextCode: number = codes[i]; while ((nextCode & 0B11000000) === 0B10000000) { value = (value << 6) | (nextCode & 0B00111111); i++; nextCode = codes[i]; } // `value` may be greater than 10FFFF (the maximum unicode codepoint) - JS will just make this into an invalid character for us output += String.fromCharCode(value); } else { // We don't want to kill the process when decoding fails (due to a following char byte not // following a leading char), so we just print the (bad) value output += String.fromCharCode(charCode); i++; } } return output; } export function base64encode(host: { base64encode?(input: string): string } | undefined, input: string): string { if (host && host.base64encode) { return host.base64encode(input); } return convertToBase64(input); } export function base64decode(host: { base64decode?(input: string): string } | undefined, input: string): string { if (host && host.base64decode) { return host.base64decode(input); } const length = input.length; const expandedCharCodes: number[] = []; let i = 0; while (i < length) { // Stop decoding once padding characters are present if (input.charCodeAt(i) === base64Digits.charCodeAt(64)) { break; } // convert 4 input digits into three characters, ignoring padding characters at the end const ch1 = base64Digits.indexOf(input[i]); const ch2 = base64Digits.indexOf(input[i + 1]); const ch3 = base64Digits.indexOf(input[i + 2]); const ch4 = base64Digits.indexOf(input[i + 3]); const code1 = ((ch1 & 0B00111111) << 2) | ((ch2 >> 4) & 0B00000011); const code2 = ((ch2 & 0B00001111) << 4) | ((ch3 >> 2) & 0B00001111); const code3 = ((ch3 & 0B00000011) << 6) | (ch4 & 0B00111111); if (code2 === 0 && ch3 !== 0) { // code2 decoded to zero, but ch3 was padding - elide code2 and code3 expandedCharCodes.push(code1); } else if (code3 === 0 && ch4 !== 0) { // code3 decoded to zero, but ch4 was padding, elide code3 expandedCharCodes.push(code1, code2); } else { expandedCharCodes.push(code1, code2, code3); } i += 4; } return getStringFromExpandedCharCodes(expandedCharCodes); } export function readJson(path: string, host: { readFile(fileName: string): string | undefined }): object { try { const jsonText = host.readFile(path); if (!jsonText) return {}; const result = parseConfigFileTextToJson(path, jsonText); if (result.error) { return {}; } return result.config; } catch (e) { // gracefully handle if readFile fails or returns not JSON return {}; } } export function directoryProbablyExists(directoryName: string, host: { directoryExists?: (directoryName: string) => boolean }): boolean { // if host does not support 'directoryExists' assume that directory will exist return !host.directoryExists || host.directoryExists(directoryName); } const carriageReturnLineFeed = "\r\n"; const lineFeed = "\n"; export function getNewLineCharacter(options: CompilerOptions | PrinterOptions, getNewLine?: () => string): string { switch (options.newLine) { case NewLineKind.CarriageReturnLineFeed: return carriageReturnLineFeed; case NewLineKind.LineFeed: return lineFeed; } return getNewLine ? getNewLine() : sys ? sys.newLine : carriageReturnLineFeed; } /** * Creates a new TextRange from the provided pos and end. * * @param pos The start position. * @param end The end position. */ export function createRange(pos: number, end: number = pos): TextRange { Debug.assert(end >= pos || end === -1); return { pos, end }; } /** * Creates a new TextRange from a provided range with a new end position. * * @param range A TextRange. * @param end The new end position. */ export function moveRangeEnd(range: TextRange, end: number): TextRange { return createRange(range.pos, end); } /** * Creates a new TextRange from a provided range with a new start position. * * @param range A TextRange. * @param pos The new Start position. */ export function moveRangePos(range: TextRange, pos: number): TextRange { return createRange(pos, range.end); } /** * Moves the start position of a range past any decorators. */ export function moveRangePastDecorators(node: Node): TextRange { return node.decorators && node.decorators.length > 0 ? moveRangePos(node, node.decorators.end) : node; } /** * Moves the start position of a range past any decorators or modifiers. */ export function moveRangePastModifiers(node: Node): TextRange { return node.modifiers && node.modifiers.length > 0 ? moveRangePos(node, node.modifiers.end) : moveRangePastDecorators(node); } /** * Determines whether a TextRange has the same start and end positions. * * @param range A TextRange. */ export function isCollapsedRange(range: TextRange) { return range.pos === range.end; } /** * Creates a new TextRange for a token at the provides start position. * * @param pos The start position. * @param token The token. */ export function createTokenRange(pos: number, token: SyntaxKind): TextRange { return createRange(pos, pos + tokenToString(token)!.length); } export function rangeIsOnSingleLine(range: TextRange, sourceFile: SourceFile) { return rangeStartIsOnSameLineAsRangeEnd(range, range, sourceFile); } export function rangeStartPositionsAreOnSameLine(range1: TextRange, range2: TextRange, sourceFile: SourceFile) { return positionsAreOnSameLine(getStartPositionOfRange(range1, sourceFile), getStartPositionOfRange(range2, sourceFile), sourceFile); } export function rangeEndPositionsAreOnSameLine(range1: TextRange, range2: TextRange, sourceFile: SourceFile) { return positionsAreOnSameLine(range1.end, range2.end, sourceFile); } export function rangeStartIsOnSameLineAsRangeEnd(range1: TextRange, range2: TextRange, sourceFile: SourceFile) { return positionsAreOnSameLine(getStartPositionOfRange(range1, sourceFile), range2.end, sourceFile); } export function rangeEndIsOnSameLineAsRangeStart(range1: TextRange, range2: TextRange, sourceFile: SourceFile) { return positionsAreOnSameLine(range1.end, getStartPositionOfRange(range2, sourceFile), sourceFile); } export function isNodeArrayMultiLine(list: NodeArray, sourceFile: SourceFile): boolean { return !positionsAreOnSameLine(list.pos, list.end, sourceFile); } export function positionsAreOnSameLine(pos1: number, pos2: number, sourceFile: SourceFile) { return pos1 === pos2 || getLineOfLocalPosition(sourceFile, pos1) === getLineOfLocalPosition(sourceFile, pos2); } export function getStartPositionOfRange(range: TextRange, sourceFile: SourceFile) { return positionIsSynthesized(range.pos) ? -1 : skipTrivia(sourceFile.text, range.pos); } /** * Determines whether a name was originally the declaration name of an enum or namespace * declaration. */ export function isDeclarationNameOfEnumOrNamespace(node: Identifier) { const parseNode = getParseTreeNode(node); if (parseNode) { switch (parseNode.parent.kind) { case SyntaxKind.EnumDeclaration: case SyntaxKind.ModuleDeclaration: return parseNode === (parseNode.parent).name; } } return false; } export function getInitializedVariables(node: VariableDeclarationList) { return filter(node.declarations, isInitializedVariable); } function isInitializedVariable(node: VariableDeclaration) { return node.initializer !== undefined; } export function isWatchSet(options: CompilerOptions) { // Firefox has Object.prototype.watch return options.watch && options.hasOwnProperty("watch"); } export function closeFileWatcher(watcher: FileWatcher) { watcher.close(); } export function getCheckFlags(symbol: Symbol): CheckFlags { return symbol.flags & SymbolFlags.Transient ? (symbol).checkFlags : 0; } export function getDeclarationModifierFlagsFromSymbol(s: Symbol): ModifierFlags { if (s.valueDeclaration) { const flags = getCombinedModifierFlags(s.valueDeclaration); return s.parent && s.parent.flags & SymbolFlags.Class ? flags : flags & ~ModifierFlags.AccessibilityModifier; } if (getCheckFlags(s) & CheckFlags.Synthetic) { const checkFlags = (s).checkFlags; const accessModifier = checkFlags & CheckFlags.ContainsPrivate ? ModifierFlags.Private : checkFlags & CheckFlags.ContainsPublic ? ModifierFlags.Public : ModifierFlags.Protected; const staticModifier = checkFlags & CheckFlags.ContainsStatic ? ModifierFlags.Static : 0; return accessModifier | staticModifier; } if (s.flags & SymbolFlags.Prototype) { return ModifierFlags.Public | ModifierFlags.Static; } return 0; } export function skipAlias(symbol: Symbol, checker: TypeChecker) { return symbol.flags & SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol; } /** See comment on `declareModuleMember` in `binder.ts`. */ export function getCombinedLocalAndExportSymbolFlags(symbol: Symbol): SymbolFlags { return symbol.exportSymbol ? symbol.exportSymbol.flags | symbol.flags : symbol.flags; } export function isWriteOnlyAccess(node: Node) { return accessKind(node) === AccessKind.Write; } export function isWriteAccess(node: Node) { return accessKind(node) !== AccessKind.Read; } const enum AccessKind { /** Only reads from a variable. */ Read, /** Only writes to a variable without using the result. E.g.: `x++;`. */ Write, /** Writes to a variable and uses the result as an expression. E.g.: `f(x++);`. */ ReadWrite } function accessKind(node: Node): AccessKind { const { parent } = node; if (!parent) return AccessKind.Read; switch (parent.kind) { case SyntaxKind.ParenthesizedExpression: return accessKind(parent); case SyntaxKind.PostfixUnaryExpression: case SyntaxKind.PrefixUnaryExpression: const { operator } = parent as PrefixUnaryExpression | PostfixUnaryExpression; return operator === SyntaxKind.PlusPlusToken || operator === SyntaxKind.MinusMinusToken ? writeOrReadWrite() : AccessKind.Read; case SyntaxKind.BinaryExpression: const { left, operatorToken } = parent as BinaryExpression; return left === node && isAssignmentOperator(operatorToken.kind) ? operatorToken.kind === SyntaxKind.EqualsToken ? AccessKind.Write : writeOrReadWrite() : AccessKind.Read; case SyntaxKind.PropertyAccessExpression: return (parent as PropertyAccessExpression).name !== node ? AccessKind.Read : accessKind(parent); case SyntaxKind.PropertyAssignment: { const parentAccess = accessKind(parent.parent); // In `({ x: varname }) = { x: 1 }`, the left `x` is a read, the right `x` is a write. return node === (parent as PropertyAssignment).name ? reverseAccessKind(parentAccess) : parentAccess; } case SyntaxKind.ShorthandPropertyAssignment: // Assume it's the local variable being accessed, since we don't check public properties for --noUnusedLocals. return node === (parent as ShorthandPropertyAssignment).objectAssignmentInitializer ? AccessKind.Read : accessKind(parent.parent); case SyntaxKind.ArrayLiteralExpression: return accessKind(parent); default: return AccessKind.Read; } function writeOrReadWrite(): AccessKind { // If grandparent is not an ExpressionStatement, this is used as an expression in addition to having a side effect. return parent.parent && skipParenthesesUp(parent.parent).kind === SyntaxKind.ExpressionStatement ? AccessKind.Write : AccessKind.ReadWrite; } } function reverseAccessKind(a: AccessKind): AccessKind { switch (a) { case AccessKind.Read: return AccessKind.Write; case AccessKind.Write: return AccessKind.Read; case AccessKind.ReadWrite: return AccessKind.ReadWrite; default: return Debug.assertNever(a); } } export function compareDataObjects(dst: any, src: any): boolean { if (!dst || !src || Object.keys(dst).length !== Object.keys(src).length) { return false; } for (const e in dst) { if (typeof dst[e] === "object") { if (!compareDataObjects(dst[e], src[e])) { return false; } } else if (typeof dst[e] !== "function") { if (dst[e] !== src[e]) { return false; } } } return true; } /** * clears already present map by calling onDeleteExistingValue callback before deleting that key/value */ export function clearMap(map: { forEach: Map["forEach"]; clear: Map["clear"]; }, onDeleteValue: (valueInMap: T, key: string) => void) { // Remove all map.forEach(onDeleteValue); map.clear(); } export interface MutateMapSkippingNewValuesOptions { onDeleteValue(existingValue: T, key: string): void; /** * If present this is called with the key when there is value for that key both in new map as well as existing map provided * Caller can then decide to update or remove this key. * If the key is removed, caller will get callback of createNewValue for that key. * If this callback is not provided, the value of such keys is not updated. */ onExistingValue?(existingValue: T, valueInNewMap: U, key: string): void; } /** * Mutates the map with newMap such that keys in map will be same as newMap. */ export function mutateMapSkippingNewValues( map: Map, newMap: ReadonlyMap, options: MutateMapSkippingNewValuesOptions ) { const { onDeleteValue, onExistingValue } = options; // Needs update map.forEach((existingValue, key) => { const valueInNewMap = newMap.get(key); // Not present any more in new map, remove it if (valueInNewMap === undefined) { map.delete(key); onDeleteValue(existingValue, key); } // If present notify about existing values else if (onExistingValue) { onExistingValue(existingValue, valueInNewMap, key); } }); } export interface MutateMapOptions extends MutateMapSkippingNewValuesOptions { createNewValue(key: string, valueInNewMap: U): T; } /** * Mutates the map with newMap such that keys in map will be same as newMap. */ export function mutateMap(map: Map, newMap: ReadonlyMap, options: MutateMapOptions) { // Needs update mutateMapSkippingNewValues(map, newMap, options); const { createNewValue } = options; // Add new values that are not already present newMap.forEach((valueInNewMap, key) => { if (!map.has(key)) { // New values map.set(key, createNewValue(key, valueInNewMap)); } }); } /** Calls `callback` on `directory` and every ancestor directory it has, returning the first defined result. */ export function forEachAncestorDirectory(directory: string, callback: (directory: string) => 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; } } // Return true if the given type is the constructor type for an abstract class export function isAbstractConstructorType(type: Type): boolean { return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && !!type.symbol && isAbstractConstructorSymbol(type.symbol); } export function isAbstractConstructorSymbol(symbol: Symbol): boolean { if (symbol.flags & SymbolFlags.Class) { const declaration = getClassLikeDeclarationOfSymbol(symbol); return !!declaration && hasModifier(declaration, ModifierFlags.Abstract); } return false; } export function getClassLikeDeclarationOfSymbol(symbol: Symbol): ClassLikeDeclaration | undefined { return find(symbol.declarations, isClassLike); } export function getObjectFlags(type: Type): ObjectFlags { return type.flags & TypeFlags.ObjectFlagsType ? (type).objectFlags : 0; } export function typeHasCallOrConstructSignatures(type: Type, checker: TypeChecker) { return checker.getSignaturesOfType(type, SignatureKind.Call).length !== 0 || checker.getSignaturesOfType(type, SignatureKind.Construct).length !== 0; } export function forSomeAncestorDirectory(directory: string, callback: (directory: string) => boolean): boolean { return !!forEachAncestorDirectory(directory, d => callback(d) ? true : undefined); } export function isUMDExportSymbol(symbol: Symbol | undefined): boolean { return !!symbol && !!symbol.declarations && !!symbol.declarations[0] && isNamespaceExportDeclaration(symbol.declarations[0]); } export function showModuleSpecifier({ moduleSpecifier }: ImportDeclaration): string { return isStringLiteral(moduleSpecifier) ? moduleSpecifier.text : getTextOfNode(moduleSpecifier); } export function getLastChild(node: Node): Node | undefined { let lastChild: Node | undefined; forEachChild(node, child => { if (nodeIsPresent(child)) lastChild = child; }, children => { // As an optimization, jump straight to the end of the list. for (let i = children.length - 1; i >= 0; i--) { if (nodeIsPresent(children[i])) { lastChild = children[i]; break; } } }); return lastChild; } /** Add a value to a set, and return true if it wasn't already present. */ export function addToSeen(seen: Map, key: string | number): boolean; export function addToSeen(seen: Map, key: string | number, value: T): boolean; export function addToSeen(seen: Map, key: string | number, value: T = true as any): boolean { key = String(key); if (seen.has(key)) { return false; } seen.set(key, value); return true; } export function isObjectTypeDeclaration(node: Node): node is ObjectTypeDeclaration { return isClassLike(node) || isInterfaceDeclaration(node) || isTypeLiteralNode(node); } export function isTypeNodeKind(kind: SyntaxKind) { return (kind >= SyntaxKind.FirstTypeNode && kind <= SyntaxKind.LastTypeNode) || kind === SyntaxKind.AnyKeyword || kind === SyntaxKind.UnknownKeyword || kind === SyntaxKind.NumberKeyword || kind === SyntaxKind.BigIntKeyword || kind === SyntaxKind.ObjectKeyword || kind === SyntaxKind.BooleanKeyword || kind === SyntaxKind.StringKeyword || kind === SyntaxKind.SymbolKeyword || kind === SyntaxKind.ThisKeyword || kind === SyntaxKind.VoidKeyword || kind === SyntaxKind.UndefinedKeyword || kind === SyntaxKind.NullKeyword || kind === SyntaxKind.NeverKeyword || kind === SyntaxKind.ExpressionWithTypeArguments || kind === SyntaxKind.JSDocAllType || kind === SyntaxKind.JSDocUnknownType || kind === SyntaxKind.JSDocNullableType || kind === SyntaxKind.JSDocNonNullableType || kind === SyntaxKind.JSDocOptionalType || kind === SyntaxKind.JSDocFunctionType || kind === SyntaxKind.JSDocVariadicType; } export function isAccessExpression(node: Node): node is AccessExpression { return node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.ElementAccessExpression; } export function isBundleFileTextLike(section: BundleFileSection): section is BundleFileTextLike { switch (section.kind) { case BundleFileSectionKind.Text: case BundleFileSectionKind.Internal: return true; default: return false; } } } namespace ts { export function getDefaultLibFileName(options: CompilerOptions): string { switch (options.target) { case ScriptTarget.ESNext: return "lib.esnext.full.d.ts"; case ScriptTarget.ES2020: return "lib.es2020.full.d.ts"; case ScriptTarget.ES2019: return "lib.es2019.full.d.ts"; case ScriptTarget.ES2018: return "lib.es2018.full.d.ts"; case ScriptTarget.ES2017: return "lib.es2017.full.d.ts"; case ScriptTarget.ES2016: return "lib.es2016.full.d.ts"; case ScriptTarget.ES2015: return "lib.es6.d.ts"; // We don't use lib.es2015.full.d.ts due to breaking change. default: return "lib.d.ts"; } } export function textSpanEnd(span: TextSpan) { return span.start + span.length; } export function textSpanIsEmpty(span: TextSpan) { return span.length === 0; } export function textSpanContainsPosition(span: TextSpan, position: number) { return position >= span.start && position < textSpanEnd(span); } /* @internal */ export function textRangeContainsPositionInclusive(span: TextRange, position: number): boolean { return position >= span.pos && position <= span.end; } // Returns true if 'span' contains 'other'. export function textSpanContainsTextSpan(span: TextSpan, other: TextSpan) { return other.start >= span.start && textSpanEnd(other) <= textSpanEnd(span); } export function textSpanOverlapsWith(span: TextSpan, other: TextSpan) { return textSpanOverlap(span, other) !== undefined; } export function textSpanOverlap(span1: TextSpan, span2: TextSpan): TextSpan | undefined { const overlap = textSpanIntersection(span1, span2); return overlap && overlap.length === 0 ? undefined : overlap; } export function textSpanIntersectsWithTextSpan(span: TextSpan, other: TextSpan) { return decodedTextSpanIntersectsWith(span.start, span.length, other.start, other.length); } export function textSpanIntersectsWith(span: TextSpan, start: number, length: number) { return decodedTextSpanIntersectsWith(span.start, span.length, start, length); } export function decodedTextSpanIntersectsWith(start1: number, length1: number, start2: number, length2: number) { const end1 = start1 + length1; const end2 = start2 + length2; return start2 <= end1 && end2 >= start1; } export function textSpanIntersectsWithPosition(span: TextSpan, position: number) { return position <= textSpanEnd(span) && position >= span.start; } export function textSpanIntersection(span1: TextSpan, span2: TextSpan): TextSpan | undefined { const start = Math.max(span1.start, span2.start); const end = Math.min(textSpanEnd(span1), textSpanEnd(span2)); return start <= end ? createTextSpanFromBounds(start, end) : undefined; } export function createTextSpan(start: number, length: number): TextSpan { if (start < 0) { throw new Error("start < 0"); } if (length < 0) { throw new Error("length < 0"); } return { start, length }; } export function createTextSpanFromBounds(start: number, end: number) { return createTextSpan(start, end - start); } export function textChangeRangeNewSpan(range: TextChangeRange) { return createTextSpan(range.span.start, range.newLength); } export function textChangeRangeIsUnchanged(range: TextChangeRange) { return textSpanIsEmpty(range.span) && range.newLength === 0; } export function createTextChangeRange(span: TextSpan, newLength: number): TextChangeRange { if (newLength < 0) { throw new Error("newLength < 0"); } return { span, newLength }; } export let unchangedTextChangeRange = createTextChangeRange(createTextSpan(0, 0), 0); /** * Called to merge all the changes that occurred across several versions of a script snapshot * into a single change. i.e. if a user keeps making successive edits to a script we will * have a text change from V1 to V2, V2 to V3, ..., Vn. * * This function will then merge those changes into a single change range valid between V1 and * Vn. */ export function collapseTextChangeRangesAcrossMultipleVersions(changes: ReadonlyArray): TextChangeRange { if (changes.length === 0) { return unchangedTextChangeRange; } if (changes.length === 1) { return changes[0]; } // We change from talking about { { oldStart, oldLength }, newLength } to { oldStart, oldEnd, newEnd } // as it makes things much easier to reason about. const change0 = changes[0]; let oldStartN = change0.span.start; let oldEndN = textSpanEnd(change0.span); let newEndN = oldStartN + change0.newLength; for (let i = 1; i < changes.length; i++) { const nextChange = changes[i]; // Consider the following case: // i.e. two edits. The first represents the text change range { { 10, 50 }, 30 }. i.e. The span starting // at 10, with length 50 is reduced to length 30. The second represents the text change range { { 30, 30 }, 40 }. // i.e. the span starting at 30 with length 30 is increased to length 40. // // 0 10 20 30 40 50 60 70 80 90 100 // ------------------------------------------------------------------------------------------------------- // | / // | /---- // T1 | /---- // | /---- // | /---- // ------------------------------------------------------------------------------------------------------- // | \ // | \ // T2 | \ // | \ // | \ // ------------------------------------------------------------------------------------------------------- // // Merging these turns out to not be too difficult. First, determining the new start of the change is trivial // it's just the min of the old and new starts. i.e.: // // 0 10 20 30 40 50 60 70 80 90 100 // ------------------------------------------------------------*------------------------------------------ // | / // | /---- // T1 | /---- // | /---- // | /---- // ----------------------------------------$-------------------$------------------------------------------ // . | \ // . | \ // T2 . | \ // . | \ // . | \ // ----------------------------------------------------------------------*-------------------------------- // // (Note the dots represent the newly inferred start. // Determining the new and old end is also pretty simple. Basically it boils down to paying attention to the // absolute positions at the asterisks, and the relative change between the dollar signs. Basically, we see // which if the two $'s precedes the other, and we move that one forward until they line up. in this case that // means: // // 0 10 20 30 40 50 60 70 80 90 100 // --------------------------------------------------------------------------------*---------------------- // | / // | /---- // T1 | /---- // | /---- // | /---- // ------------------------------------------------------------$------------------------------------------ // . | \ // . | \ // T2 . | \ // . | \ // . | \ // ----------------------------------------------------------------------*-------------------------------- // // In other words (in this case), we're recognizing that the second edit happened after where the first edit // ended with a delta of 20 characters (60 - 40). Thus, if we go back in time to where the first edit started // that's the same as if we started at char 80 instead of 60. // // As it so happens, the same logic applies if the second edit precedes the first edit. In that case rather // than pushing the first edit forward to match the second, we'll push the second edit forward to match the // first. // // In this case that means we have { oldStart: 10, oldEnd: 80, newEnd: 70 } or, in TextChangeRange // semantics: { { start: 10, length: 70 }, newLength: 60 } // // The math then works out as follows. // If we have { oldStart1, oldEnd1, newEnd1 } and { oldStart2, oldEnd2, newEnd2 } then we can compute the // final result like so: // // { // oldStart3: Min(oldStart1, oldStart2), // oldEnd3: Max(oldEnd1, oldEnd1 + (oldEnd2 - newEnd1)), // newEnd3: Max(newEnd2, newEnd2 + (newEnd1 - oldEnd2)) // } const oldStart1 = oldStartN; const oldEnd1 = oldEndN; const newEnd1 = newEndN; const oldStart2 = nextChange.span.start; const oldEnd2 = textSpanEnd(nextChange.span); const newEnd2 = oldStart2 + nextChange.newLength; oldStartN = Math.min(oldStart1, oldStart2); oldEndN = Math.max(oldEnd1, oldEnd1 + (oldEnd2 - newEnd1)); newEndN = Math.max(newEnd2, newEnd2 + (newEnd1 - oldEnd2)); } return createTextChangeRange(createTextSpanFromBounds(oldStartN, oldEndN), /*newLength*/ newEndN - oldStartN); } export function getTypeParameterOwner(d: Declaration): Declaration | undefined { if (d && d.kind === SyntaxKind.TypeParameter) { for (let current: Node = d; current; current = current.parent) { if (isFunctionLike(current) || isClassLike(current) || current.kind === SyntaxKind.InterfaceDeclaration) { return current; } } } } export type ParameterPropertyDeclaration = ParameterDeclaration & { parent: ConstructorDeclaration, name: Identifier }; export function isParameterPropertyDeclaration(node: Node): node is ParameterPropertyDeclaration { return hasModifier(node, ModifierFlags.ParameterPropertyModifier) && node.parent.kind === SyntaxKind.Constructor; } export function isEmptyBindingPattern(node: BindingName): node is BindingPattern { if (isBindingPattern(node)) { return every(node.elements, isEmptyBindingElement); } return false; } export function isEmptyBindingElement(node: BindingElement): boolean { if (isOmittedExpression(node)) { return true; } return isEmptyBindingPattern(node.name); } export function walkUpBindingElementsAndPatterns(binding: BindingElement): VariableDeclaration | ParameterDeclaration { let node = binding.parent; while (isBindingElement(node.parent)) { node = node.parent.parent; } return node.parent; } function getCombinedFlags(node: Node, getFlags: (n: Node) => number): number { if (isBindingElement(node)) { node = walkUpBindingElementsAndPatterns(node); } let flags = getFlags(node); if (node.kind === SyntaxKind.VariableDeclaration) { node = node.parent; } if (node && node.kind === SyntaxKind.VariableDeclarationList) { flags |= getFlags(node); node = node.parent; } if (node && node.kind === SyntaxKind.VariableStatement) { flags |= getFlags(node); } return flags; } export function getCombinedModifierFlags(node: Declaration): ModifierFlags { return getCombinedFlags(node, getModifierFlags); } // Returns the node flags for this node and all relevant parent nodes. This is done so that // nodes like variable declarations and binding elements can returned a view of their flags // that includes the modifiers from their container. i.e. flags like export/declare aren't // stored on the variable declaration directly, but on the containing variable statement // (if it has one). Similarly, flags for let/const are store on the variable declaration // list. By calling this function, all those flags are combined so that the client can treat // the node as if it actually had those flags. export function getCombinedNodeFlags(node: Node): NodeFlags { return getCombinedFlags(node, n => n.flags); } /** * Checks to see if the locale is in the appropriate format, * and if it is, attempts to set the appropriate language. */ export function validateLocaleAndSetLanguage( locale: string, sys: { getExecutingFilePath(): string, resolvePath(path: string): string, fileExists(fileName: string): boolean, readFile(fileName: string): string | undefined }, errors?: Push) { const matchResult = /^([a-z]+)([_\-]([a-z]+))?$/.exec(locale.toLowerCase()); if (!matchResult) { if (errors) { errors.push(createCompilerDiagnostic(Diagnostics.Locale_must_be_of_the_form_language_or_language_territory_For_example_0_or_1, "en", "ja-jp")); } return; } const language = matchResult[1]; const territory = matchResult[3]; // First try the entire locale, then fall back to just language if that's all we have. // Either ways do not fail, and fallback to the English diagnostic strings. if (!trySetLanguageAndTerritory(language, territory, errors)) { trySetLanguageAndTerritory(language, /*territory*/ undefined, errors); } // Set the UI locale for string collation setUILocale(locale); function trySetLanguageAndTerritory(language: string, territory: string | undefined, errors?: Push): boolean { const compilerFilePath = normalizePath(sys.getExecutingFilePath()); const containingDirectoryPath = getDirectoryPath(compilerFilePath); let filePath = combinePaths(containingDirectoryPath, language); if (territory) { filePath = filePath + "-" + territory; } filePath = sys.resolvePath(combinePaths(filePath, "diagnosticMessages.generated.json")); if (!sys.fileExists(filePath)) { return false; } // TODO: Add codePage support for readFile? let fileContents: string | undefined = ""; try { fileContents = sys.readFile(filePath); } catch (e) { if (errors) { errors.push(createCompilerDiagnostic(Diagnostics.Unable_to_open_file_0, filePath)); } return false; } try { // tslint:disable-next-line no-unnecessary-qualifier (making clear this is a global mutation!) ts.localizedDiagnosticMessages = JSON.parse(fileContents!); } catch { if (errors) { errors.push(createCompilerDiagnostic(Diagnostics.Corrupted_locale_file_0, filePath)); } return false; } return true; } } export function getOriginalNode(node: Node): Node; export function getOriginalNode(node: Node, nodeTest: (node: Node) => node is T): T; export function getOriginalNode(node: Node | undefined): Node | undefined; export function getOriginalNode(node: Node | undefined, nodeTest: (node: Node | undefined) => node is T): T | undefined; export function getOriginalNode(node: Node | undefined, nodeTest?: (node: Node | undefined) => boolean): Node | undefined { if (node) { while (node.original !== undefined) { node = node.original; } } return !nodeTest || nodeTest(node) ? node : undefined; } /** * Gets a value indicating whether a node originated in the parse tree. * * @param node The node to test. */ export function isParseTreeNode(node: Node): boolean { return (node.flags & NodeFlags.Synthesized) === 0; } /** * Gets the original parse tree node for a node. * * @param node The original node. * @returns The original parse tree node if found; otherwise, undefined. */ export function getParseTreeNode(node: Node): Node; /** * Gets the original parse tree node for a node. * * @param node The original node. * @param nodeTest A callback used to ensure the correct type of parse tree node is returned. * @returns The original parse tree node if found; otherwise, undefined. */ export function getParseTreeNode(node: Node | undefined, nodeTest?: (node: Node) => node is T): T | undefined; export function getParseTreeNode(node: Node | undefined, nodeTest?: (node: Node) => boolean): Node | undefined { if (node === undefined || isParseTreeNode(node)) { return node; } node = getOriginalNode(node); if (isParseTreeNode(node) && (!nodeTest || nodeTest(node))) { return node; } return undefined; } /** Add an extra underscore to identifiers that start with two underscores to avoid issues with magic names like '__proto__' */ export function escapeLeadingUnderscores(identifier: string): __String { return (identifier.length >= 2 && identifier.charCodeAt(0) === CharacterCodes._ && identifier.charCodeAt(1) === CharacterCodes._ ? "_" + identifier : identifier) as __String; } /** * Remove extra underscore from escaped identifier text content. * * @param identifier The escaped identifier text. * @returns The unescaped identifier text. */ export function unescapeLeadingUnderscores(identifier: __String): string { const id = identifier as string; return id.length >= 3 && id.charCodeAt(0) === CharacterCodes._ && id.charCodeAt(1) === CharacterCodes._ && id.charCodeAt(2) === CharacterCodes._ ? id.substr(1) : id; } export function idText(identifier: Identifier): string { return unescapeLeadingUnderscores(identifier.escapedText); } export function symbolName(symbol: Symbol): string { return unescapeLeadingUnderscores(symbol.escapedName); } /** * A JSDocTypedef tag has an _optional_ name field - if a name is not directly present, we should * attempt to draw the name from the node the declaration is on (as that declaration is what its' symbol * will be merged with) */ function nameForNamelessJSDocTypedef(declaration: JSDocTypedefTag | JSDocEnumTag): Identifier | undefined { const hostNode = declaration.parent.parent; if (!hostNode) { return undefined; } // Covers classes, functions - any named declaration host node if (isDeclaration(hostNode)) { return getDeclarationIdentifier(hostNode); } // Covers remaining cases (returning undefined if none match). switch (hostNode.kind) { case SyntaxKind.VariableStatement: if (hostNode.declarationList && hostNode.declarationList.declarations[0]) { return getDeclarationIdentifier(hostNode.declarationList.declarations[0]); } break; case SyntaxKind.ExpressionStatement: let expr = hostNode.expression; if (expr.kind === SyntaxKind.BinaryExpression && (expr as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { expr = (expr as BinaryExpression).left; } switch (expr.kind) { case SyntaxKind.PropertyAccessExpression: return (expr as PropertyAccessExpression).name; case SyntaxKind.ElementAccessExpression: const arg = (expr as ElementAccessExpression).argumentExpression; if (isIdentifier(arg)) { return arg; } } break; case SyntaxKind.ParenthesizedExpression: { return getDeclarationIdentifier(hostNode.expression); } case SyntaxKind.LabeledStatement: { if (isDeclaration(hostNode.statement) || isExpression(hostNode.statement)) { return getDeclarationIdentifier(hostNode.statement); } break; } } } function getDeclarationIdentifier(node: Declaration | Expression): Identifier | undefined { const name = getNameOfDeclaration(node); return name && isIdentifier(name) ? name : undefined; } export function getNameOfJSDocTypedef(declaration: JSDocTypedefTag): Identifier | undefined { return declaration.name || nameForNamelessJSDocTypedef(declaration); } /** @internal */ export function isNamedDeclaration(node: Node): node is NamedDeclaration & { name: DeclarationName } { return !!(node as NamedDeclaration).name; // A 'name' property should always be a DeclarationName. } /** @internal */ export function getNonAssignedNameOfDeclaration(declaration: Declaration | Expression): DeclarationName | undefined { switch (declaration.kind) { case SyntaxKind.Identifier: return declaration as Identifier; case SyntaxKind.JSDocPropertyTag: case SyntaxKind.JSDocParameterTag: { const { name } = declaration as JSDocPropertyLikeTag; if (name.kind === SyntaxKind.QualifiedName) { return name.right; } break; } case SyntaxKind.CallExpression: case SyntaxKind.BinaryExpression: { const expr = declaration as BinaryExpression | CallExpression; switch (getAssignmentDeclarationKind(expr)) { case AssignmentDeclarationKind.ExportsProperty: case AssignmentDeclarationKind.ThisProperty: case AssignmentDeclarationKind.Property: case AssignmentDeclarationKind.PrototypeProperty: return ((expr as BinaryExpression).left as PropertyAccessExpression).name; case AssignmentDeclarationKind.ObjectDefinePropertyValue: case AssignmentDeclarationKind.ObjectDefinePropertyExports: case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: return (expr as BindableObjectDefinePropertyCall).arguments[1]; default: return undefined; } } case SyntaxKind.JSDocTypedefTag: return getNameOfJSDocTypedef(declaration as JSDocTypedefTag); case SyntaxKind.JSDocEnumTag: return nameForNamelessJSDocTypedef(declaration as JSDocEnumTag); case SyntaxKind.ExportAssignment: { const { expression } = declaration as ExportAssignment; return isIdentifier(expression) ? expression : undefined; } } return (declaration as NamedDeclaration).name; } export function getNameOfDeclaration(declaration: Declaration | Expression): DeclarationName | undefined { if (declaration === undefined) return undefined; return getNonAssignedNameOfDeclaration(declaration) || (isFunctionExpression(declaration) || isClassExpression(declaration) ? getAssignedName(declaration) : undefined); } function getAssignedName(node: Node): DeclarationName | undefined { if (!node.parent) { return undefined; } else if (isPropertyAssignment(node.parent) || isBindingElement(node.parent)) { return node.parent.name; } else if (isBinaryExpression(node.parent) && node === node.parent.right) { if (isIdentifier(node.parent.left)) { return node.parent.left; } else if (isPropertyAccessExpression(node.parent.left)) { return node.parent.left.name; } } else if (isVariableDeclaration(node.parent) && isIdentifier(node.parent.name)) { return node.parent.name; } } /** * Gets the JSDoc parameter tags for the node if present. * * @remarks Returns any JSDoc param tag whose name matches the provided * parameter, whether a param tag on a containing function * expression, or a param tag on a variable declaration whose * initializer is the containing function. The tags closest to the * node are returned first, so in the previous example, the param * tag on the containing function expression would be first. * * For binding patterns, parameter tags are matched by position. */ export function getJSDocParameterTags(param: ParameterDeclaration): ReadonlyArray { if (param.name) { if (isIdentifier(param.name)) { const name = param.name.escapedText; return getJSDocTags(param.parent).filter((tag): tag is JSDocParameterTag => isJSDocParameterTag(tag) && isIdentifier(tag.name) && tag.name.escapedText === name); } else { const i = param.parent.parameters.indexOf(param); Debug.assert(i > -1, "Parameters should always be in their parents' parameter list"); const paramTags = getJSDocTags(param.parent).filter(isJSDocParameterTag); if (i < paramTags.length) { return [paramTags[i]]; } } } // return empty array for: out-of-order binding patterns and JSDoc function syntax, which has un-named parameters return emptyArray; } /** * Gets the JSDoc type parameter tags for the node if present. * * @remarks Returns any JSDoc template tag whose names match the provided * parameter, whether a template tag on a containing function * expression, or a template tag on a variable declaration whose * initializer is the containing function. The tags closest to the * node are returned first, so in the previous example, the template * tag on the containing function expression would be first. */ export function getJSDocTypeParameterTags(param: TypeParameterDeclaration): ReadonlyArray { const name = param.name.escapedText; return getJSDocTags(param.parent).filter((tag): tag is JSDocTemplateTag => isJSDocTemplateTag(tag) && tag.typeParameters.some(tp => tp.name.escapedText === name)); } /** * Return true if the node has JSDoc parameter tags. * * @remarks Includes parameter tags that are not directly on the node, * for example on a variable declaration whose initializer is a function expression. */ export function hasJSDocParameterTags(node: FunctionLikeDeclaration | SignatureDeclaration): boolean { return !!getFirstJSDocTag(node, isJSDocParameterTag); } /** Gets the JSDoc augments tag for the node if present */ export function getJSDocAugmentsTag(node: Node): JSDocAugmentsTag | undefined { return getFirstJSDocTag(node, isJSDocAugmentsTag); } /** Gets the JSDoc class tag for the node if present */ export function getJSDocClassTag(node: Node): JSDocClassTag | undefined { return getFirstJSDocTag(node, isJSDocClassTag); } /** Gets the JSDoc enum tag for the node if present */ export function getJSDocEnumTag(node: Node): JSDocEnumTag | undefined { return getFirstJSDocTag(node, isJSDocEnumTag); } /** Gets the JSDoc this tag for the node if present */ export function getJSDocThisTag(node: Node): JSDocThisTag | undefined { return getFirstJSDocTag(node, isJSDocThisTag); } /** Gets the JSDoc return tag for the node if present */ export function getJSDocReturnTag(node: Node): JSDocReturnTag | undefined { return getFirstJSDocTag(node, isJSDocReturnTag); } /** Gets the JSDoc template tag for the node if present */ export function getJSDocTemplateTag(node: Node): JSDocTemplateTag | undefined { return getFirstJSDocTag(node, isJSDocTemplateTag); } /** Gets the JSDoc type tag for the node if present and valid */ export function getJSDocTypeTag(node: Node): JSDocTypeTag | undefined { // We should have already issued an error if there were multiple type jsdocs, so just use the first one. const tag = getFirstJSDocTag(node, isJSDocTypeTag); if (tag && tag.typeExpression && tag.typeExpression.type) { return tag; } return undefined; } /** * Gets the type node for the node if provided via JSDoc. * * @remarks The search includes any JSDoc param tag that relates * to the provided parameter, for example a type tag on the * parameter itself, or a param tag on a containing function * expression, or a param tag on a variable declaration whose * initializer is the containing function. The tags closest to the * node are examined first, so in the previous example, the type * tag directly on the node would be returned. */ export function getJSDocType(node: Node): TypeNode | undefined { let tag: JSDocTypeTag | JSDocParameterTag | undefined = getFirstJSDocTag(node, isJSDocTypeTag); if (!tag && isParameter(node)) { tag = find(getJSDocParameterTags(node), tag => !!tag.typeExpression); } return tag && tag.typeExpression && tag.typeExpression.type; } /** * Gets the return type node for the node if provided via JSDoc return tag or type tag. * * @remarks `getJSDocReturnTag` just gets the whole JSDoc tag. This function * gets the type from inside the braces, after the fat arrow, etc. */ export function getJSDocReturnType(node: Node): TypeNode | undefined { const returnTag = getJSDocReturnTag(node); if (returnTag && returnTag.typeExpression) { return returnTag.typeExpression.type; } const typeTag = getJSDocTypeTag(node); if (typeTag && typeTag.typeExpression) { const type = typeTag.typeExpression.type; if (isTypeLiteralNode(type)) { const sig = find(type.members, isCallSignatureDeclaration); return sig && sig.type; } if (isFunctionTypeNode(type)) { return type.type; } } } /** Get all JSDoc tags related to a node, including those on parent nodes. */ export function getJSDocTags(node: Node): ReadonlyArray { let tags = (node as JSDocContainer).jsDocCache; // If cache is 'null', that means we did the work of searching for JSDoc tags and came up with nothing. if (tags === undefined) { const comments = getJSDocCommentsAndTags(node); Debug.assert(comments.length < 2 || comments[0] !== comments[1]); (node as JSDocContainer).jsDocCache = tags = flatMap(comments, j => isJSDoc(j) ? j.tags : j); } return tags; } /** Get the first JSDoc tag of a specified kind, or undefined if not present. */ function getFirstJSDocTag(node: Node, predicate: (tag: JSDocTag) => tag is T): T | undefined { return find(getJSDocTags(node), predicate); } /** Gets all JSDoc tags of a specified kind, or undefined if not present. */ export function getAllJSDocTagsOfKind(node: Node, kind: SyntaxKind): ReadonlyArray { return getJSDocTags(node).filter(doc => doc.kind === kind); } /** * Gets the effective type parameters. If the node was parsed in a * JavaScript file, gets the type parameters from the `@template` tag from JSDoc. */ export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray { if (isJSDocSignature(node)) { return emptyArray; } if (isJSDocTypeAlias(node)) { Debug.assert(node.parent.kind === SyntaxKind.JSDocComment); return flatMap(node.parent.tags, tag => isJSDocTemplateTag(tag) ? tag.typeParameters : undefined); } if (node.typeParameters) { return node.typeParameters; } if (isInJSFile(node)) { const decls = getJSDocTypeParameterDeclarations(node); if (decls.length) { return decls; } const typeTag = getJSDocType(node); if (typeTag && isFunctionTypeNode(typeTag) && typeTag.typeParameters) { return typeTag.typeParameters; } } return emptyArray; } export function getEffectiveConstraintOfTypeParameter(node: TypeParameterDeclaration): TypeNode | undefined { return node.constraint ? node.constraint : isJSDocTemplateTag(node.parent) && node === node.parent.typeParameters[0] ? node.parent.constraint : undefined; } } // Simple node tests of the form `node.kind === SyntaxKind.Foo`. namespace ts { // Literals export function isNumericLiteral(node: Node): node is NumericLiteral { return node.kind === SyntaxKind.NumericLiteral; } export function isBigIntLiteral(node: Node): node is BigIntLiteral { return node.kind === SyntaxKind.BigIntLiteral; } export function isStringLiteral(node: Node): node is StringLiteral { return node.kind === SyntaxKind.StringLiteral; } export function isJsxText(node: Node): node is JsxText { return node.kind === SyntaxKind.JsxText; } export function isRegularExpressionLiteral(node: Node): node is RegularExpressionLiteral { return node.kind === SyntaxKind.RegularExpressionLiteral; } export function isNoSubstitutionTemplateLiteral(node: Node): node is NoSubstitutionTemplateLiteral { return node.kind === SyntaxKind.NoSubstitutionTemplateLiteral; } // Pseudo-literals export function isTemplateHead(node: Node): node is TemplateHead { return node.kind === SyntaxKind.TemplateHead; } export function isTemplateMiddle(node: Node): node is TemplateMiddle { return node.kind === SyntaxKind.TemplateMiddle; } export function isTemplateTail(node: Node): node is TemplateTail { return node.kind === SyntaxKind.TemplateTail; } export function isIdentifier(node: Node): node is Identifier { return node.kind === SyntaxKind.Identifier; } // Names export function isQualifiedName(node: Node): node is QualifiedName { return node.kind === SyntaxKind.QualifiedName; } export function isComputedPropertyName(node: Node): node is ComputedPropertyName { return node.kind === SyntaxKind.ComputedPropertyName; } // Signature elements export function isTypeParameterDeclaration(node: Node): node is TypeParameterDeclaration { return node.kind === SyntaxKind.TypeParameter; } export function isParameter(node: Node): node is ParameterDeclaration { return node.kind === SyntaxKind.Parameter; } export function isDecorator(node: Node): node is Decorator { return node.kind === SyntaxKind.Decorator; } // TypeMember export function isPropertySignature(node: Node): node is PropertySignature { return node.kind === SyntaxKind.PropertySignature; } export function isPropertyDeclaration(node: Node): node is PropertyDeclaration { return node.kind === SyntaxKind.PropertyDeclaration; } export function isMethodSignature(node: Node): node is MethodSignature { return node.kind === SyntaxKind.MethodSignature; } export function isMethodDeclaration(node: Node): node is MethodDeclaration { return node.kind === SyntaxKind.MethodDeclaration; } export function isConstructorDeclaration(node: Node): node is ConstructorDeclaration { return node.kind === SyntaxKind.Constructor; } export function isGetAccessorDeclaration(node: Node): node is GetAccessorDeclaration { return node.kind === SyntaxKind.GetAccessor; } export function isSetAccessorDeclaration(node: Node): node is SetAccessorDeclaration { return node.kind === SyntaxKind.SetAccessor; } export function isCallSignatureDeclaration(node: Node): node is CallSignatureDeclaration { return node.kind === SyntaxKind.CallSignature; } export function isConstructSignatureDeclaration(node: Node): node is ConstructSignatureDeclaration { return node.kind === SyntaxKind.ConstructSignature; } export function isIndexSignatureDeclaration(node: Node): node is IndexSignatureDeclaration { return node.kind === SyntaxKind.IndexSignature; } /* @internal */ export function isGetOrSetAccessorDeclaration(node: Node): node is AccessorDeclaration { return node.kind === SyntaxKind.SetAccessor || node.kind === SyntaxKind.GetAccessor; } // Type export function isTypePredicateNode(node: Node): node is TypePredicateNode { return node.kind === SyntaxKind.TypePredicate; } export function isTypeReferenceNode(node: Node): node is TypeReferenceNode { return node.kind === SyntaxKind.TypeReference; } export function isFunctionTypeNode(node: Node): node is FunctionTypeNode { return node.kind === SyntaxKind.FunctionType; } export function isConstructorTypeNode(node: Node): node is ConstructorTypeNode { return node.kind === SyntaxKind.ConstructorType; } export function isTypeQueryNode(node: Node): node is TypeQueryNode { return node.kind === SyntaxKind.TypeQuery; } export function isTypeLiteralNode(node: Node): node is TypeLiteralNode { return node.kind === SyntaxKind.TypeLiteral; } export function isArrayTypeNode(node: Node): node is ArrayTypeNode { return node.kind === SyntaxKind.ArrayType; } export function isTupleTypeNode(node: Node): node is TupleTypeNode { return node.kind === SyntaxKind.TupleType; } export function isUnionTypeNode(node: Node): node is UnionTypeNode { return node.kind === SyntaxKind.UnionType; } export function isIntersectionTypeNode(node: Node): node is IntersectionTypeNode { return node.kind === SyntaxKind.IntersectionType; } export function isConditionalTypeNode(node: Node): node is ConditionalTypeNode { return node.kind === SyntaxKind.ConditionalType; } export function isInferTypeNode(node: Node): node is InferTypeNode { return node.kind === SyntaxKind.InferType; } export function isParenthesizedTypeNode(node: Node): node is ParenthesizedTypeNode { return node.kind === SyntaxKind.ParenthesizedType; } export function isThisTypeNode(node: Node): node is ThisTypeNode { return node.kind === SyntaxKind.ThisType; } export function isTypeOperatorNode(node: Node): node is TypeOperatorNode { return node.kind === SyntaxKind.TypeOperator; } export function isIndexedAccessTypeNode(node: Node): node is IndexedAccessTypeNode { return node.kind === SyntaxKind.IndexedAccessType; } export function isMappedTypeNode(node: Node): node is MappedTypeNode { return node.kind === SyntaxKind.MappedType; } export function isLiteralTypeNode(node: Node): node is LiteralTypeNode { return node.kind === SyntaxKind.LiteralType; } export function isImportTypeNode(node: Node): node is ImportTypeNode { return node.kind === SyntaxKind.ImportType; } // Binding patterns export function isObjectBindingPattern(node: Node): node is ObjectBindingPattern { return node.kind === SyntaxKind.ObjectBindingPattern; } export function isArrayBindingPattern(node: Node): node is ArrayBindingPattern { return node.kind === SyntaxKind.ArrayBindingPattern; } export function isBindingElement(node: Node): node is BindingElement { return node.kind === SyntaxKind.BindingElement; } // Expression export function isArrayLiteralExpression(node: Node): node is ArrayLiteralExpression { return node.kind === SyntaxKind.ArrayLiteralExpression; } export function isObjectLiteralExpression(node: Node): node is ObjectLiteralExpression { return node.kind === SyntaxKind.ObjectLiteralExpression; } export function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression { return node.kind === SyntaxKind.PropertyAccessExpression; } export function isElementAccessExpression(node: Node): node is ElementAccessExpression { return node.kind === SyntaxKind.ElementAccessExpression; } export function isCallExpression(node: Node): node is CallExpression { return node.kind === SyntaxKind.CallExpression; } export function isNewExpression(node: Node): node is NewExpression { return node.kind === SyntaxKind.NewExpression; } export function isTaggedTemplateExpression(node: Node): node is TaggedTemplateExpression { return node.kind === SyntaxKind.TaggedTemplateExpression; } export function isTypeAssertion(node: Node): node is TypeAssertion { return node.kind === SyntaxKind.TypeAssertionExpression; } export function isConstTypeReference(node: Node) { return isTypeReferenceNode(node) && isIdentifier(node.typeName) && node.typeName.escapedText === "const" && !node.typeArguments; } export function isParenthesizedExpression(node: Node): node is ParenthesizedExpression { return node.kind === SyntaxKind.ParenthesizedExpression; } export function skipPartiallyEmittedExpressions(node: Expression): Expression; export function skipPartiallyEmittedExpressions(node: Node): Node; export function skipPartiallyEmittedExpressions(node: Node) { while (node.kind === SyntaxKind.PartiallyEmittedExpression) { node = (node).expression; } return node; } export function isFunctionExpression(node: Node): node is FunctionExpression { return node.kind === SyntaxKind.FunctionExpression; } export function isArrowFunction(node: Node): node is ArrowFunction { return node.kind === SyntaxKind.ArrowFunction; } export function isDeleteExpression(node: Node): node is DeleteExpression { return node.kind === SyntaxKind.DeleteExpression; } export function isTypeOfExpression(node: Node): node is TypeOfExpression { return node.kind === SyntaxKind.TypeOfExpression; } export function isVoidExpression(node: Node): node is VoidExpression { return node.kind === SyntaxKind.VoidExpression; } export function isAwaitExpression(node: Node): node is AwaitExpression { return node.kind === SyntaxKind.AwaitExpression; } export function isPrefixUnaryExpression(node: Node): node is PrefixUnaryExpression { return node.kind === SyntaxKind.PrefixUnaryExpression; } export function isPostfixUnaryExpression(node: Node): node is PostfixUnaryExpression { return node.kind === SyntaxKind.PostfixUnaryExpression; } export function isBinaryExpression(node: Node): node is BinaryExpression { return node.kind === SyntaxKind.BinaryExpression; } export function isConditionalExpression(node: Node): node is ConditionalExpression { return node.kind === SyntaxKind.ConditionalExpression; } export function isTemplateExpression(node: Node): node is TemplateExpression { return node.kind === SyntaxKind.TemplateExpression; } export function isYieldExpression(node: Node): node is YieldExpression { return node.kind === SyntaxKind.YieldExpression; } export function isSpreadElement(node: Node): node is SpreadElement { return node.kind === SyntaxKind.SpreadElement; } export function isClassExpression(node: Node): node is ClassExpression { return node.kind === SyntaxKind.ClassExpression; } export function isOmittedExpression(node: Node): node is OmittedExpression { return node.kind === SyntaxKind.OmittedExpression; } export function isExpressionWithTypeArguments(node: Node): node is ExpressionWithTypeArguments { return node.kind === SyntaxKind.ExpressionWithTypeArguments; } export function isAsExpression(node: Node): node is AsExpression { return node.kind === SyntaxKind.AsExpression; } export function isNonNullExpression(node: Node): node is NonNullExpression { return node.kind === SyntaxKind.NonNullExpression; } export function isMetaProperty(node: Node): node is MetaProperty { return node.kind === SyntaxKind.MetaProperty; } // Misc export function isTemplateSpan(node: Node): node is TemplateSpan { return node.kind === SyntaxKind.TemplateSpan; } export function isSemicolonClassElement(node: Node): node is SemicolonClassElement { return node.kind === SyntaxKind.SemicolonClassElement; } // Block export function isBlock(node: Node): node is Block { return node.kind === SyntaxKind.Block; } export function isVariableStatement(node: Node): node is VariableStatement { return node.kind === SyntaxKind.VariableStatement; } export function isEmptyStatement(node: Node): node is EmptyStatement { return node.kind === SyntaxKind.EmptyStatement; } export function isExpressionStatement(node: Node): node is ExpressionStatement { return node.kind === SyntaxKind.ExpressionStatement; } export function isIfStatement(node: Node): node is IfStatement { return node.kind === SyntaxKind.IfStatement; } export function isDoStatement(node: Node): node is DoStatement { return node.kind === SyntaxKind.DoStatement; } export function isWhileStatement(node: Node): node is WhileStatement { return node.kind === SyntaxKind.WhileStatement; } export function isForStatement(node: Node): node is ForStatement { return node.kind === SyntaxKind.ForStatement; } export function isForInStatement(node: Node): node is ForInStatement { return node.kind === SyntaxKind.ForInStatement; } export function isForOfStatement(node: Node): node is ForOfStatement { return node.kind === SyntaxKind.ForOfStatement; } export function isContinueStatement(node: Node): node is ContinueStatement { return node.kind === SyntaxKind.ContinueStatement; } export function isBreakStatement(node: Node): node is BreakStatement { return node.kind === SyntaxKind.BreakStatement; } export function isBreakOrContinueStatement(node: Node): node is BreakOrContinueStatement { return node.kind === SyntaxKind.BreakStatement || node.kind === SyntaxKind.ContinueStatement; } export function isReturnStatement(node: Node): node is ReturnStatement { return node.kind === SyntaxKind.ReturnStatement; } export function isWithStatement(node: Node): node is WithStatement { return node.kind === SyntaxKind.WithStatement; } export function isSwitchStatement(node: Node): node is SwitchStatement { return node.kind === SyntaxKind.SwitchStatement; } export function isLabeledStatement(node: Node): node is LabeledStatement { return node.kind === SyntaxKind.LabeledStatement; } export function isThrowStatement(node: Node): node is ThrowStatement { return node.kind === SyntaxKind.ThrowStatement; } export function isTryStatement(node: Node): node is TryStatement { return node.kind === SyntaxKind.TryStatement; } export function isDebuggerStatement(node: Node): node is DebuggerStatement { return node.kind === SyntaxKind.DebuggerStatement; } export function isVariableDeclaration(node: Node): node is VariableDeclaration { return node.kind === SyntaxKind.VariableDeclaration; } export function isVariableDeclarationList(node: Node): node is VariableDeclarationList { return node.kind === SyntaxKind.VariableDeclarationList; } export function isFunctionDeclaration(node: Node): node is FunctionDeclaration { return node.kind === SyntaxKind.FunctionDeclaration; } export function isClassDeclaration(node: Node): node is ClassDeclaration { return node.kind === SyntaxKind.ClassDeclaration; } export function isInterfaceDeclaration(node: Node): node is InterfaceDeclaration { return node.kind === SyntaxKind.InterfaceDeclaration; } export function isTypeAliasDeclaration(node: Node): node is TypeAliasDeclaration { return node.kind === SyntaxKind.TypeAliasDeclaration; } export function isEnumDeclaration(node: Node): node is EnumDeclaration { return node.kind === SyntaxKind.EnumDeclaration; } export function isModuleDeclaration(node: Node): node is ModuleDeclaration { return node.kind === SyntaxKind.ModuleDeclaration; } export function isModuleBlock(node: Node): node is ModuleBlock { return node.kind === SyntaxKind.ModuleBlock; } export function isCaseBlock(node: Node): node is CaseBlock { return node.kind === SyntaxKind.CaseBlock; } export function isNamespaceExportDeclaration(node: Node): node is NamespaceExportDeclaration { return node.kind === SyntaxKind.NamespaceExportDeclaration; } export function isImportEqualsDeclaration(node: Node): node is ImportEqualsDeclaration { return node.kind === SyntaxKind.ImportEqualsDeclaration; } export function isImportDeclaration(node: Node): node is ImportDeclaration { return node.kind === SyntaxKind.ImportDeclaration; } export function isImportClause(node: Node): node is ImportClause { return node.kind === SyntaxKind.ImportClause; } export function isNamespaceImport(node: Node): node is NamespaceImport { return node.kind === SyntaxKind.NamespaceImport; } export function isNamedImports(node: Node): node is NamedImports { return node.kind === SyntaxKind.NamedImports; } export function isImportSpecifier(node: Node): node is ImportSpecifier { return node.kind === SyntaxKind.ImportSpecifier; } export function isExportAssignment(node: Node): node is ExportAssignment { return node.kind === SyntaxKind.ExportAssignment; } export function isExportDeclaration(node: Node): node is ExportDeclaration { return node.kind === SyntaxKind.ExportDeclaration; } export function isNamedExports(node: Node): node is NamedExports { return node.kind === SyntaxKind.NamedExports; } export function isExportSpecifier(node: Node): node is ExportSpecifier { return node.kind === SyntaxKind.ExportSpecifier; } export function isMissingDeclaration(node: Node): node is MissingDeclaration { return node.kind === SyntaxKind.MissingDeclaration; } // Module References export function isExternalModuleReference(node: Node): node is ExternalModuleReference { return node.kind === SyntaxKind.ExternalModuleReference; } // JSX export function isJsxElement(node: Node): node is JsxElement { return node.kind === SyntaxKind.JsxElement; } export function isJsxSelfClosingElement(node: Node): node is JsxSelfClosingElement { return node.kind === SyntaxKind.JsxSelfClosingElement; } export function isJsxOpeningElement(node: Node): node is JsxOpeningElement { return node.kind === SyntaxKind.JsxOpeningElement; } export function isJsxClosingElement(node: Node): node is JsxClosingElement { return node.kind === SyntaxKind.JsxClosingElement; } export function isJsxFragment(node: Node): node is JsxFragment { return node.kind === SyntaxKind.JsxFragment; } export function isJsxOpeningFragment(node: Node): node is JsxOpeningFragment { return node.kind === SyntaxKind.JsxOpeningFragment; } export function isJsxClosingFragment(node: Node): node is JsxClosingFragment { return node.kind === SyntaxKind.JsxClosingFragment; } export function isJsxAttribute(node: Node): node is JsxAttribute { return node.kind === SyntaxKind.JsxAttribute; } export function isJsxAttributes(node: Node): node is JsxAttributes { return node.kind === SyntaxKind.JsxAttributes; } export function isJsxSpreadAttribute(node: Node): node is JsxSpreadAttribute { return node.kind === SyntaxKind.JsxSpreadAttribute; } export function isJsxExpression(node: Node): node is JsxExpression { return node.kind === SyntaxKind.JsxExpression; } // Clauses export function isCaseClause(node: Node): node is CaseClause { return node.kind === SyntaxKind.CaseClause; } export function isDefaultClause(node: Node): node is DefaultClause { return node.kind === SyntaxKind.DefaultClause; } export function isHeritageClause(node: Node): node is HeritageClause { return node.kind === SyntaxKind.HeritageClause; } export function isCatchClause(node: Node): node is CatchClause { return node.kind === SyntaxKind.CatchClause; } // Property assignments export function isPropertyAssignment(node: Node): node is PropertyAssignment { return node.kind === SyntaxKind.PropertyAssignment; } export function isShorthandPropertyAssignment(node: Node): node is ShorthandPropertyAssignment { return node.kind === SyntaxKind.ShorthandPropertyAssignment; } export function isSpreadAssignment(node: Node): node is SpreadAssignment { return node.kind === SyntaxKind.SpreadAssignment; } // Enum export function isEnumMember(node: Node): node is EnumMember { return node.kind === SyntaxKind.EnumMember; } // Top-level nodes export function isSourceFile(node: Node): node is SourceFile { return node.kind === SyntaxKind.SourceFile; } export function isBundle(node: Node): node is Bundle { return node.kind === SyntaxKind.Bundle; } export function isUnparsedSource(node: Node): node is UnparsedSource { return node.kind === SyntaxKind.UnparsedSource; } export function isUnparsedPrepend(node: Node): node is UnparsedPrepend { return node.kind === SyntaxKind.UnparsedPrepend; } export function isUnparsedTextLike(node: Node): node is UnparsedTextLike { switch (node.kind) { case SyntaxKind.UnparsedText: case SyntaxKind.UnparsedInternalText: return true; default: return false; } } export function isUnparsedNode(node: Node): node is UnparsedNode { return isUnparsedTextLike(node) || node.kind === SyntaxKind.UnparsedPrologue || node.kind === SyntaxKind.UnparsedSyntheticReference; } // JSDoc export function isJSDocTypeExpression(node: Node): node is JSDocTypeExpression { return node.kind === SyntaxKind.JSDocTypeExpression; } export function isJSDocAllType(node: JSDocAllType): node is JSDocAllType { return node.kind === SyntaxKind.JSDocAllType; } export function isJSDocUnknownType(node: Node): node is JSDocUnknownType { return node.kind === SyntaxKind.JSDocUnknownType; } export function isJSDocNullableType(node: Node): node is JSDocNullableType { return node.kind === SyntaxKind.JSDocNullableType; } export function isJSDocNonNullableType(node: Node): node is JSDocNonNullableType { return node.kind === SyntaxKind.JSDocNonNullableType; } export function isJSDocOptionalType(node: Node): node is JSDocOptionalType { return node.kind === SyntaxKind.JSDocOptionalType; } export function isJSDocFunctionType(node: Node): node is JSDocFunctionType { return node.kind === SyntaxKind.JSDocFunctionType; } export function isJSDocVariadicType(node: Node): node is JSDocVariadicType { return node.kind === SyntaxKind.JSDocVariadicType; } export function isJSDoc(node: Node): node is JSDoc { return node.kind === SyntaxKind.JSDocComment; } export function isJSDocAuthorTag(node: Node): node is JSDocAuthorTag { return node.kind === SyntaxKind.JSDocAuthorTag; } export function isJSDocAugmentsTag(node: Node): node is JSDocAugmentsTag { return node.kind === SyntaxKind.JSDocAugmentsTag; } export function isJSDocClassTag(node: Node): node is JSDocClassTag { return node.kind === SyntaxKind.JSDocClassTag; } export function isJSDocEnumTag(node: Node): node is JSDocEnumTag { return node.kind === SyntaxKind.JSDocEnumTag; } export function isJSDocThisTag(node: Node): node is JSDocThisTag { return node.kind === SyntaxKind.JSDocThisTag; } export function isJSDocParameterTag(node: Node): node is JSDocParameterTag { return node.kind === SyntaxKind.JSDocParameterTag; } export function isJSDocReturnTag(node: Node): node is JSDocReturnTag { return node.kind === SyntaxKind.JSDocReturnTag; } export function isJSDocTypeTag(node: Node): node is JSDocTypeTag { return node.kind === SyntaxKind.JSDocTypeTag; } export function isJSDocTemplateTag(node: Node): node is JSDocTemplateTag { return node.kind === SyntaxKind.JSDocTemplateTag; } export function isJSDocTypedefTag(node: Node): node is JSDocTypedefTag { return node.kind === SyntaxKind.JSDocTypedefTag; } export function isJSDocPropertyTag(node: Node): node is JSDocPropertyTag { return node.kind === SyntaxKind.JSDocPropertyTag; } export function isJSDocPropertyLikeTag(node: Node): node is JSDocPropertyLikeTag { return node.kind === SyntaxKind.JSDocPropertyTag || node.kind === SyntaxKind.JSDocParameterTag; } export function isJSDocTypeLiteral(node: Node): node is JSDocTypeLiteral { return node.kind === SyntaxKind.JSDocTypeLiteral; } export function isJSDocCallbackTag(node: Node): node is JSDocCallbackTag { return node.kind === SyntaxKind.JSDocCallbackTag; } export function isJSDocSignature(node: Node): node is JSDocSignature { return node.kind === SyntaxKind.JSDocSignature; } } // Node tests // // All node tests in the following list should *not* reference parent pointers so that // they may be used with transformations. namespace ts { /* @internal */ export function isSyntaxList(n: Node): n is SyntaxList { return n.kind === SyntaxKind.SyntaxList; } /* @internal */ export function isNode(node: Node) { return isNodeKind(node.kind); } /* @internal */ export function isNodeKind(kind: SyntaxKind) { return kind >= SyntaxKind.FirstNode; } /** * True if node is of some token syntax kind. * For example, this is true for an IfKeyword but not for an IfStatement. * Literals are considered tokens, except TemplateLiteral, but does include TemplateHead/Middle/Tail. */ export function isToken(n: Node): boolean { return n.kind >= SyntaxKind.FirstToken && n.kind <= SyntaxKind.LastToken; } // Node Arrays /* @internal */ export function isNodeArray(array: ReadonlyArray): array is NodeArray { return array.hasOwnProperty("pos") && array.hasOwnProperty("end"); } // Literals /* @internal */ export function isLiteralKind(kind: SyntaxKind): boolean { return SyntaxKind.FirstLiteralToken <= kind && kind <= SyntaxKind.LastLiteralToken; } export function isLiteralExpression(node: Node): node is LiteralExpression { return isLiteralKind(node.kind); } // Pseudo-literals /* @internal */ export function isTemplateLiteralKind(kind: SyntaxKind): boolean { return SyntaxKind.FirstTemplateToken <= kind && kind <= SyntaxKind.LastTemplateToken; } export type TemplateLiteralToken = NoSubstitutionTemplateLiteral | TemplateHead | TemplateMiddle | TemplateTail; export function isTemplateLiteralToken(node: Node): node is TemplateLiteralToken { return isTemplateLiteralKind(node.kind); } export function isTemplateMiddleOrTemplateTail(node: Node): node is TemplateMiddle | TemplateTail { const kind = node.kind; return kind === SyntaxKind.TemplateMiddle || kind === SyntaxKind.TemplateTail; } export function isImportOrExportSpecifier(node: Node): node is ImportSpecifier | ExportSpecifier { return isImportSpecifier(node) || isExportSpecifier(node); } export function isStringTextContainingNode(node: Node): node is StringLiteral | TemplateLiteralToken { return node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind); } // Identifiers /* @internal */ export function isGeneratedIdentifier(node: Node): node is GeneratedIdentifier { return isIdentifier(node) && (node.autoGenerateFlags! & GeneratedIdentifierFlags.KindMask) > GeneratedIdentifierFlags.None; } // Keywords /* @internal */ export function isModifierKind(token: SyntaxKind): token is Modifier["kind"] { switch (token) { case SyntaxKind.AbstractKeyword: case SyntaxKind.AsyncKeyword: case SyntaxKind.ConstKeyword: case SyntaxKind.DeclareKeyword: case SyntaxKind.DefaultKeyword: case SyntaxKind.ExportKeyword: case SyntaxKind.PublicKeyword: case SyntaxKind.PrivateKeyword: case SyntaxKind.ProtectedKeyword: case SyntaxKind.ReadonlyKeyword: case SyntaxKind.StaticKeyword: return true; } return false; } /* @internal */ export function isParameterPropertyModifier(kind: SyntaxKind): boolean { return !!(modifierToFlag(kind) & ModifierFlags.ParameterPropertyModifier); } /* @internal */ export function isClassMemberModifier(idToken: SyntaxKind): boolean { return isParameterPropertyModifier(idToken) || idToken === SyntaxKind.StaticKeyword; } export function isModifier(node: Node): node is Modifier { return isModifierKind(node.kind); } export function isEntityName(node: Node): node is EntityName { const kind = node.kind; return kind === SyntaxKind.QualifiedName || kind === SyntaxKind.Identifier; } export function isPropertyName(node: Node): node is PropertyName { const kind = node.kind; return kind === SyntaxKind.Identifier || kind === SyntaxKind.StringLiteral || kind === SyntaxKind.NumericLiteral || kind === SyntaxKind.ComputedPropertyName; } export function isBindingName(node: Node): node is BindingName { const kind = node.kind; return kind === SyntaxKind.Identifier || kind === SyntaxKind.ObjectBindingPattern || kind === SyntaxKind.ArrayBindingPattern; } // Functions export function isFunctionLike(node: Node): node is SignatureDeclaration { return node && isFunctionLikeKind(node.kind); } /* @internal */ export function isFunctionLikeDeclaration(node: Node): node is FunctionLikeDeclaration { return node && isFunctionLikeDeclarationKind(node.kind); } function isFunctionLikeDeclarationKind(kind: SyntaxKind): boolean { switch (kind) { case SyntaxKind.FunctionDeclaration: case SyntaxKind.MethodDeclaration: case SyntaxKind.Constructor: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: return true; default: return false; } } /* @internal */ export function isFunctionLikeKind(kind: SyntaxKind): boolean { switch (kind) { case SyntaxKind.MethodSignature: case SyntaxKind.CallSignature: case SyntaxKind.JSDocSignature: case SyntaxKind.ConstructSignature: case SyntaxKind.IndexSignature: case SyntaxKind.FunctionType: case SyntaxKind.JSDocFunctionType: case SyntaxKind.ConstructorType: return true; default: return isFunctionLikeDeclarationKind(kind); } } /* @internal */ export function isFunctionOrModuleBlock(node: Node): boolean { return isSourceFile(node) || isModuleBlock(node) || isBlock(node) && isFunctionLike(node.parent); } // Classes export function isClassElement(node: Node): node is ClassElement { const kind = node.kind; return kind === SyntaxKind.Constructor || kind === SyntaxKind.PropertyDeclaration || kind === SyntaxKind.MethodDeclaration || kind === SyntaxKind.GetAccessor || kind === SyntaxKind.SetAccessor || kind === SyntaxKind.IndexSignature || kind === SyntaxKind.SemicolonClassElement; } export function isClassLike(node: Node): node is ClassLikeDeclaration { return node && (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression); } export function isAccessor(node: Node): node is AccessorDeclaration { return node && (node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor); } /* @internal */ export function isMethodOrAccessor(node: Node): node is MethodDeclaration | AccessorDeclaration { switch (node.kind) { case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: return true; default: return false; } } // Type members export function isTypeElement(node: Node): node is TypeElement { const kind = node.kind; return kind === SyntaxKind.ConstructSignature || kind === SyntaxKind.CallSignature || kind === SyntaxKind.PropertySignature || kind === SyntaxKind.MethodSignature || kind === SyntaxKind.IndexSignature; } export function isClassOrTypeElement(node: Node): node is ClassElement | TypeElement { return isTypeElement(node) || isClassElement(node); } export function isObjectLiteralElementLike(node: Node): node is ObjectLiteralElementLike { const kind = node.kind; return kind === SyntaxKind.PropertyAssignment || kind === SyntaxKind.ShorthandPropertyAssignment || kind === SyntaxKind.SpreadAssignment || kind === SyntaxKind.MethodDeclaration || kind === SyntaxKind.GetAccessor || kind === SyntaxKind.SetAccessor; } // Type /** * Node test that determines whether a node is a valid type node. * This differs from the `isPartOfTypeNode` function which determines whether a node is *part* * of a TypeNode. */ export function isTypeNode(node: Node): node is TypeNode { return isTypeNodeKind(node.kind); } export function isFunctionOrConstructorTypeNode(node: Node): node is FunctionTypeNode | ConstructorTypeNode { switch (node.kind) { case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: return true; } return false; } // Binding patterns /* @internal */ export function isBindingPattern(node: Node | undefined): node is BindingPattern { if (node) { const kind = node.kind; return kind === SyntaxKind.ArrayBindingPattern || kind === SyntaxKind.ObjectBindingPattern; } return false; } /* @internal */ export function isAssignmentPattern(node: Node): node is AssignmentPattern { const kind = node.kind; return kind === SyntaxKind.ArrayLiteralExpression || kind === SyntaxKind.ObjectLiteralExpression; } /* @internal */ export function isArrayBindingElement(node: Node): node is ArrayBindingElement { const kind = node.kind; return kind === SyntaxKind.BindingElement || kind === SyntaxKind.OmittedExpression; } /** * Determines whether the BindingOrAssignmentElement is a BindingElement-like declaration */ /* @internal */ export function isDeclarationBindingElement(bindingElement: BindingOrAssignmentElement): bindingElement is VariableDeclaration | ParameterDeclaration | BindingElement { switch (bindingElement.kind) { case SyntaxKind.VariableDeclaration: case SyntaxKind.Parameter: case SyntaxKind.BindingElement: return true; } return false; } /** * Determines whether a node is a BindingOrAssignmentPattern */ /* @internal */ export function isBindingOrAssignmentPattern(node: BindingOrAssignmentElementTarget): node is BindingOrAssignmentPattern { return isObjectBindingOrAssignmentPattern(node) || isArrayBindingOrAssignmentPattern(node); } /** * Determines whether a node is an ObjectBindingOrAssignmentPattern */ /* @internal */ export function isObjectBindingOrAssignmentPattern(node: BindingOrAssignmentElementTarget): node is ObjectBindingOrAssignmentPattern { switch (node.kind) { case SyntaxKind.ObjectBindingPattern: case SyntaxKind.ObjectLiteralExpression: return true; } return false; } /** * Determines whether a node is an ArrayBindingOrAssignmentPattern */ /* @internal */ export function isArrayBindingOrAssignmentPattern(node: BindingOrAssignmentElementTarget): node is ArrayBindingOrAssignmentPattern { switch (node.kind) { case SyntaxKind.ArrayBindingPattern: case SyntaxKind.ArrayLiteralExpression: return true; } return false; } /* @internal */ export function isPropertyAccessOrQualifiedNameOrImportTypeNode(node: Node): node is PropertyAccessExpression | QualifiedName | ImportTypeNode { const kind = node.kind; return kind === SyntaxKind.PropertyAccessExpression || kind === SyntaxKind.QualifiedName || kind === SyntaxKind.ImportType; } // Expression export function isPropertyAccessOrQualifiedName(node: Node): node is PropertyAccessExpression | QualifiedName { const kind = node.kind; return kind === SyntaxKind.PropertyAccessExpression || kind === SyntaxKind.QualifiedName; } export function isCallLikeExpression(node: Node): node is CallLikeExpression { switch (node.kind) { case SyntaxKind.JsxOpeningElement: case SyntaxKind.JsxSelfClosingElement: case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: case SyntaxKind.TaggedTemplateExpression: case SyntaxKind.Decorator: return true; default: return false; } } export function isCallOrNewExpression(node: Node): node is CallExpression | NewExpression { return node.kind === SyntaxKind.CallExpression || node.kind === SyntaxKind.NewExpression; } export function isTemplateLiteral(node: Node): node is TemplateLiteral { const kind = node.kind; return kind === SyntaxKind.TemplateExpression || kind === SyntaxKind.NoSubstitutionTemplateLiteral; } /* @internal */ export function isLeftHandSideExpression(node: Node): node is LeftHandSideExpression { return isLeftHandSideExpressionKind(skipPartiallyEmittedExpressions(node).kind); } function isLeftHandSideExpressionKind(kind: SyntaxKind): boolean { switch (kind) { case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: case SyntaxKind.NewExpression: case SyntaxKind.CallExpression: case SyntaxKind.JsxElement: case SyntaxKind.JsxSelfClosingElement: case SyntaxKind.JsxFragment: case SyntaxKind.TaggedTemplateExpression: case SyntaxKind.ArrayLiteralExpression: case SyntaxKind.ParenthesizedExpression: case SyntaxKind.ObjectLiteralExpression: case SyntaxKind.ClassExpression: case SyntaxKind.FunctionExpression: case SyntaxKind.Identifier: case SyntaxKind.RegularExpressionLiteral: case SyntaxKind.NumericLiteral: case SyntaxKind.BigIntLiteral: case SyntaxKind.StringLiteral: case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.TemplateExpression: case SyntaxKind.FalseKeyword: case SyntaxKind.NullKeyword: case SyntaxKind.ThisKeyword: case SyntaxKind.TrueKeyword: case SyntaxKind.SuperKeyword: case SyntaxKind.NonNullExpression: case SyntaxKind.MetaProperty: case SyntaxKind.ImportKeyword: // technically this is only an Expression if it's in a CallExpression return true; default: return false; } } /* @internal */ export function isUnaryExpression(node: Node): node is UnaryExpression { return isUnaryExpressionKind(skipPartiallyEmittedExpressions(node).kind); } function isUnaryExpressionKind(kind: SyntaxKind): boolean { switch (kind) { case SyntaxKind.PrefixUnaryExpression: case SyntaxKind.PostfixUnaryExpression: case SyntaxKind.DeleteExpression: case SyntaxKind.TypeOfExpression: case SyntaxKind.VoidExpression: case SyntaxKind.AwaitExpression: case SyntaxKind.TypeAssertionExpression: return true; default: return isLeftHandSideExpressionKind(kind); } } /* @internal */ export function isUnaryExpressionWithWrite(expr: Node): expr is PrefixUnaryExpression | PostfixUnaryExpression { switch (expr.kind) { case SyntaxKind.PostfixUnaryExpression: return true; case SyntaxKind.PrefixUnaryExpression: return (expr).operator === SyntaxKind.PlusPlusToken || (expr).operator === SyntaxKind.MinusMinusToken; default: return false; } } /* @internal */ /** * Determines whether a node is an expression based only on its kind. * Use `isExpressionNode` if not in transforms. */ export function isExpression(node: Node): node is Expression { return isExpressionKind(skipPartiallyEmittedExpressions(node).kind); } function isExpressionKind(kind: SyntaxKind): boolean { switch (kind) { case SyntaxKind.ConditionalExpression: case SyntaxKind.YieldExpression: case SyntaxKind.ArrowFunction: case SyntaxKind.BinaryExpression: case SyntaxKind.SpreadElement: case SyntaxKind.AsExpression: case SyntaxKind.OmittedExpression: case SyntaxKind.CommaListExpression: case SyntaxKind.PartiallyEmittedExpression: return true; default: return isUnaryExpressionKind(kind); } } export function isAssertionExpression(node: Node): node is AssertionExpression { const kind = node.kind; return kind === SyntaxKind.TypeAssertionExpression || kind === SyntaxKind.AsExpression; } /* @internal */ export function isPartiallyEmittedExpression(node: Node): node is PartiallyEmittedExpression { return node.kind === SyntaxKind.PartiallyEmittedExpression; } /* @internal */ export function isNotEmittedStatement(node: Node): node is NotEmittedStatement { return node.kind === SyntaxKind.NotEmittedStatement; } /* @internal */ export function isNotEmittedOrPartiallyEmittedNode(node: Node): node is NotEmittedStatement | PartiallyEmittedExpression { return isNotEmittedStatement(node) || isPartiallyEmittedExpression(node); } // Statement export function isIterationStatement(node: Node, lookInLabeledStatements: false): node is IterationStatement; export function isIterationStatement(node: Node, lookInLabeledStatements: boolean): node is IterationStatement | LabeledStatement; export function isIterationStatement(node: Node, lookInLabeledStatements: boolean): node is IterationStatement { switch (node.kind) { case SyntaxKind.ForStatement: case SyntaxKind.ForInStatement: case SyntaxKind.ForOfStatement: case SyntaxKind.DoStatement: case SyntaxKind.WhileStatement: return true; case SyntaxKind.LabeledStatement: return lookInLabeledStatements && isIterationStatement((node).statement, lookInLabeledStatements); } return false; } /* @internal */ export function isForInOrOfStatement(node: Node): node is ForInOrOfStatement { return node.kind === SyntaxKind.ForInStatement || node.kind === SyntaxKind.ForOfStatement; } // Element /* @internal */ export function isConciseBody(node: Node): node is ConciseBody { return isBlock(node) || isExpression(node); } /* @internal */ export function isFunctionBody(node: Node): node is FunctionBody { return isBlock(node); } /* @internal */ export function isForInitializer(node: Node): node is ForInitializer { return isVariableDeclarationList(node) || isExpression(node); } /* @internal */ export function isModuleBody(node: Node): node is ModuleBody { const kind = node.kind; return kind === SyntaxKind.ModuleBlock || kind === SyntaxKind.ModuleDeclaration || kind === SyntaxKind.Identifier; } /* @internal */ export function isNamespaceBody(node: Node): node is NamespaceBody { const kind = node.kind; return kind === SyntaxKind.ModuleBlock || kind === SyntaxKind.ModuleDeclaration; } /* @internal */ export function isJSDocNamespaceBody(node: Node): node is JSDocNamespaceBody { const kind = node.kind; return kind === SyntaxKind.Identifier || kind === SyntaxKind.ModuleDeclaration; } /* @internal */ export function isNamedImportBindings(node: Node): node is NamedImportBindings { const kind = node.kind; return kind === SyntaxKind.NamedImports || kind === SyntaxKind.NamespaceImport; } /* @internal */ export function isModuleOrEnumDeclaration(node: Node): node is ModuleDeclaration | EnumDeclaration { return node.kind === SyntaxKind.ModuleDeclaration || node.kind === SyntaxKind.EnumDeclaration; } function isDeclarationKind(kind: SyntaxKind) { return kind === SyntaxKind.ArrowFunction || kind === SyntaxKind.BindingElement || kind === SyntaxKind.ClassDeclaration || kind === SyntaxKind.ClassExpression || kind === SyntaxKind.Constructor || kind === SyntaxKind.EnumDeclaration || kind === SyntaxKind.EnumMember || kind === SyntaxKind.ExportSpecifier || kind === SyntaxKind.FunctionDeclaration || kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.GetAccessor || kind === SyntaxKind.ImportClause || kind === SyntaxKind.ImportEqualsDeclaration || kind === SyntaxKind.ImportSpecifier || kind === SyntaxKind.InterfaceDeclaration || kind === SyntaxKind.JsxAttribute || kind === SyntaxKind.MethodDeclaration || kind === SyntaxKind.MethodSignature || kind === SyntaxKind.ModuleDeclaration || kind === SyntaxKind.NamespaceExportDeclaration || kind === SyntaxKind.NamespaceImport || kind === SyntaxKind.Parameter || kind === SyntaxKind.PropertyAssignment || kind === SyntaxKind.PropertyDeclaration || kind === SyntaxKind.PropertySignature || kind === SyntaxKind.SetAccessor || kind === SyntaxKind.ShorthandPropertyAssignment || kind === SyntaxKind.TypeAliasDeclaration || kind === SyntaxKind.TypeParameter || kind === SyntaxKind.VariableDeclaration || kind === SyntaxKind.JSDocTypedefTag || kind === SyntaxKind.JSDocCallbackTag || kind === SyntaxKind.JSDocPropertyTag; } function isDeclarationStatementKind(kind: SyntaxKind) { return kind === SyntaxKind.FunctionDeclaration || kind === SyntaxKind.MissingDeclaration || kind === SyntaxKind.ClassDeclaration || kind === SyntaxKind.InterfaceDeclaration || kind === SyntaxKind.TypeAliasDeclaration || kind === SyntaxKind.EnumDeclaration || kind === SyntaxKind.ModuleDeclaration || kind === SyntaxKind.ImportDeclaration || kind === SyntaxKind.ImportEqualsDeclaration || kind === SyntaxKind.ExportDeclaration || kind === SyntaxKind.ExportAssignment || kind === SyntaxKind.NamespaceExportDeclaration; } function isStatementKindButNotDeclarationKind(kind: SyntaxKind) { return kind === SyntaxKind.BreakStatement || kind === SyntaxKind.ContinueStatement || kind === SyntaxKind.DebuggerStatement || kind === SyntaxKind.DoStatement || kind === SyntaxKind.ExpressionStatement || kind === SyntaxKind.EmptyStatement || kind === SyntaxKind.ForInStatement || kind === SyntaxKind.ForOfStatement || kind === SyntaxKind.ForStatement || kind === SyntaxKind.IfStatement || kind === SyntaxKind.LabeledStatement || kind === SyntaxKind.ReturnStatement || kind === SyntaxKind.SwitchStatement || kind === SyntaxKind.ThrowStatement || kind === SyntaxKind.TryStatement || kind === SyntaxKind.VariableStatement || kind === SyntaxKind.WhileStatement || kind === SyntaxKind.WithStatement || kind === SyntaxKind.NotEmittedStatement || kind === SyntaxKind.EndOfDeclarationMarker || kind === SyntaxKind.MergeDeclarationMarker; } /* @internal */ export function isDeclaration(node: Node): node is NamedDeclaration { if (node.kind === SyntaxKind.TypeParameter) { return (node.parent && node.parent.kind !== SyntaxKind.JSDocTemplateTag) || isInJSFile(node); } return isDeclarationKind(node.kind); } /* @internal */ export function isDeclarationStatement(node: Node): node is DeclarationStatement { return isDeclarationStatementKind(node.kind); } /** * Determines whether the node is a statement that is not also a declaration */ /* @internal */ export function isStatementButNotDeclaration(node: Node): node is Statement { return isStatementKindButNotDeclarationKind(node.kind); } /* @internal */ export function isStatement(node: Node): node is Statement { const kind = node.kind; return isStatementKindButNotDeclarationKind(kind) || isDeclarationStatementKind(kind) || isBlockStatement(node); } function isBlockStatement(node: Node): node is Block { if (node.kind !== SyntaxKind.Block) return false; if (node.parent !== undefined) { if (node.parent.kind === SyntaxKind.TryStatement || node.parent.kind === SyntaxKind.CatchClause) { return false; } } return !isFunctionBlock(node); } // Module references /* @internal */ export function isModuleReference(node: Node): node is ModuleReference { const kind = node.kind; return kind === SyntaxKind.ExternalModuleReference || kind === SyntaxKind.QualifiedName || kind === SyntaxKind.Identifier; } // JSX /* @internal */ export function isJsxTagNameExpression(node: Node): node is JsxTagNameExpression { const kind = node.kind; return kind === SyntaxKind.ThisKeyword || kind === SyntaxKind.Identifier || kind === SyntaxKind.PropertyAccessExpression; } /* @internal */ export function isJsxChild(node: Node): node is JsxChild { const kind = node.kind; return kind === SyntaxKind.JsxElement || kind === SyntaxKind.JsxExpression || kind === SyntaxKind.JsxSelfClosingElement || kind === SyntaxKind.JsxText || kind === SyntaxKind.JsxFragment; } /* @internal */ export function isJsxAttributeLike(node: Node): node is JsxAttributeLike { const kind = node.kind; return kind === SyntaxKind.JsxAttribute || kind === SyntaxKind.JsxSpreadAttribute; } /* @internal */ export function isStringLiteralOrJsxExpression(node: Node): node is StringLiteral | JsxExpression { const kind = node.kind; return kind === SyntaxKind.StringLiteral || kind === SyntaxKind.JsxExpression; } export function isJsxOpeningLikeElement(node: Node): node is JsxOpeningLikeElement { const kind = node.kind; return kind === SyntaxKind.JsxOpeningElement || kind === SyntaxKind.JsxSelfClosingElement; } // Clauses export function isCaseOrDefaultClause(node: Node): node is CaseOrDefaultClause { const kind = node.kind; return kind === SyntaxKind.CaseClause || kind === SyntaxKind.DefaultClause; } // JSDoc /** True if node is of some JSDoc syntax kind. */ /* @internal */ export function isJSDocNode(node: Node): boolean { return node.kind >= SyntaxKind.FirstJSDocNode && node.kind <= SyntaxKind.LastJSDocNode; } /** True if node is of a kind that may contain comment text. */ export function isJSDocCommentContainingNode(node: Node): boolean { return node.kind === SyntaxKind.JSDocComment || isJSDocTag(node) || isJSDocTypeLiteral(node) || isJSDocSignature(node); } // TODO: determine what this does before making it public. /* @internal */ export function isJSDocTag(node: Node): node is JSDocTag { return node.kind >= SyntaxKind.FirstJSDocTagNode && node.kind <= SyntaxKind.LastJSDocTagNode; } export function isSetAccessor(node: Node): node is SetAccessorDeclaration { return node.kind === SyntaxKind.SetAccessor; } export function isGetAccessor(node: Node): node is GetAccessorDeclaration { return node.kind === SyntaxKind.GetAccessor; } /** True if has jsdoc nodes attached to it. */ /* @internal */ // TODO: GH#19856 Would like to return `node is Node & { jsDoc: JSDoc[] }` but it causes long compile times export function hasJSDocNodes(node: Node): node is HasJSDoc { const { jsDoc } = node as JSDocContainer; return !!jsDoc && jsDoc.length > 0; } /** True if has type node attached to it. */ /* @internal */ export function hasType(node: Node): node is HasType { return !!(node as HasType).type; } /** True if has initializer node attached to it. */ /* @internal */ export function hasInitializer(node: Node): node is HasInitializer { return !!(node as HasInitializer).initializer; } /** True if has initializer node attached to it. */ /* @internal */ export function hasOnlyExpressionInitializer(node: Node): node is HasExpressionInitializer { return hasInitializer(node) && !isForStatement(node) && !isForInStatement(node) && !isForOfStatement(node) && !isJsxAttribute(node); } export function isObjectLiteralElement(node: Node): node is ObjectLiteralElement { return node.kind === SyntaxKind.JsxAttribute || node.kind === SyntaxKind.JsxSpreadAttribute || isObjectLiteralElementLike(node); } /* @internal */ export function isTypeReferenceType(node: Node): node is TypeReferenceType { return node.kind === SyntaxKind.TypeReference || node.kind === SyntaxKind.ExpressionWithTypeArguments; } const MAX_SMI_X86 = 0x3fff_ffff; /* @internal */ export function guessIndentation(lines: string[]) { let indentation = MAX_SMI_X86; for (const line of lines) { if (!line.length) { continue; } let i = 0; for (; i < line.length && i < indentation; i++) { if (!isWhiteSpaceLike(line.charCodeAt(i))) { break; } } if (i < indentation) { indentation = i; } if (indentation === 0) { return 0; } } return indentation === MAX_SMI_X86 ? undefined : indentation; } export function isStringLiteralLike(node: Node): node is StringLiteralLike { return node.kind === SyntaxKind.StringLiteral || node.kind === SyntaxKind.NoSubstitutionTemplateLiteral; } } /* @internal */ namespace ts { export function isNamedImportsOrExports(node: Node): node is NamedImportsOrExports { return node.kind === SyntaxKind.NamedImports || node.kind === SyntaxKind.NamedExports; } export interface ObjectAllocator { getNodeConstructor(): new (kind: SyntaxKind, pos?: number, end?: number) => Node; getTokenConstructor(): new (kind: TKind, pos?: number, end?: number) => Token; getIdentifierConstructor(): new (kind: SyntaxKind.Identifier, pos?: number, end?: number) => Identifier; getSourceFileConstructor(): new (kind: SyntaxKind.SourceFile, pos?: number, end?: number) => SourceFile; getSymbolConstructor(): new (flags: SymbolFlags, name: __String) => Symbol; getTypeConstructor(): new (checker: TypeChecker, flags: TypeFlags) => Type; getSignatureConstructor(): new (checker: TypeChecker) => Signature; getSourceMapSourceConstructor(): new (fileName: string, text: string, skipTrivia?: (pos: number) => number) => SourceMapSource; } function Symbol(this: Symbol, flags: SymbolFlags, name: __String) { this.flags = flags; this.escapedName = name; this.declarations = undefined!; this.valueDeclaration = undefined!; this.id = undefined; this.mergeId = undefined; this.parent = undefined; } function Type(this: Type, checker: TypeChecker, flags: TypeFlags) { this.flags = flags; if (Debug.isDebugging) { this.checker = checker; } } function Signature() {} // tslint:disable-line no-empty function Node(this: Node, kind: SyntaxKind, pos: number, end: number) { this.pos = pos; this.end = end; this.kind = kind; this.id = 0; this.flags = NodeFlags.None; this.modifierFlagsCache = ModifierFlags.None; this.transformFlags = TransformFlags.None; this.parent = undefined!; this.original = undefined; } function SourceMapSource(this: SourceMapSource, fileName: string, text: string, skipTrivia?: (pos: number) => number) { this.fileName = fileName; this.text = text; this.skipTrivia = skipTrivia || (pos => pos); } export let objectAllocator: ObjectAllocator = { getNodeConstructor: () => Node, getTokenConstructor: () => Node, getIdentifierConstructor: () => Node, getSourceFileConstructor: () => Node, getSymbolConstructor: () => Symbol, getTypeConstructor: () => Type, getSignatureConstructor: () => Signature, getSourceMapSourceConstructor: () => SourceMapSource, }; export function formatStringFromArgs(text: string, args: ArrayLike, baseIndex = 0): string { return text.replace(/{(\d+)}/g, (_match, index: string) => "" + Debug.assertDefined(args[+index + baseIndex])); } export let localizedDiagnosticMessages: MapLike | undefined; export function getLocaleSpecificMessage(message: DiagnosticMessage) { return localizedDiagnosticMessages && localizedDiagnosticMessages[message.key] || message.message; } export function createFileDiagnostic(file: SourceFile, start: number, length: number, message: DiagnosticMessage, ...args: (string | number | undefined)[]): DiagnosticWithLocation; export function createFileDiagnostic(file: SourceFile, start: number, length: number, message: DiagnosticMessage): DiagnosticWithLocation { Debug.assertGreaterThanOrEqual(start, 0); Debug.assertGreaterThanOrEqual(length, 0); if (file) { Debug.assertLessThanOrEqual(start, file.text.length); Debug.assertLessThanOrEqual(start + length, file.text.length); } let text = getLocaleSpecificMessage(message); if (arguments.length > 4) { text = formatStringFromArgs(text, arguments, 4); } return { file, start, length, messageText: text, category: message.category, code: message.code, reportsUnnecessary: message.reportsUnnecessary, }; } export function formatMessage(_dummy: any, message: DiagnosticMessage, ...args: (string | number | undefined)[]): string; export function formatMessage(_dummy: any, message: DiagnosticMessage): string { let text = getLocaleSpecificMessage(message); if (arguments.length > 2) { text = formatStringFromArgs(text, arguments, 2); } return text; } export function createCompilerDiagnostic(message: DiagnosticMessage, ...args: (string | number | undefined)[]): Diagnostic; export function createCompilerDiagnostic(message: DiagnosticMessage): Diagnostic { let text = getLocaleSpecificMessage(message); if (arguments.length > 1) { text = formatStringFromArgs(text, arguments, 1); } return { file: undefined, start: undefined, length: undefined, messageText: text, category: message.category, code: message.code, reportsUnnecessary: message.reportsUnnecessary, }; } export function createCompilerDiagnosticFromMessageChain(chain: DiagnosticMessageChain): Diagnostic { return { file: undefined, start: undefined, length: undefined, code: chain.code, category: chain.category, messageText: chain.next ? chain : chain.messageText, }; } export function chainDiagnosticMessages(details: DiagnosticMessageChain | DiagnosticMessageChain[] | undefined, message: DiagnosticMessage, ...args: (string | number | undefined)[]): DiagnosticMessageChain; export function chainDiagnosticMessages(details: DiagnosticMessageChain | DiagnosticMessageChain[] | undefined, message: DiagnosticMessage): DiagnosticMessageChain { let text = getLocaleSpecificMessage(message); if (arguments.length > 2) { text = formatStringFromArgs(text, arguments, 2); } return { messageText: text, category: message.category, code: message.code, next: details === undefined || Array.isArray(details) ? details : [details] }; } export function concatenateDiagnosticMessageChains(headChain: DiagnosticMessageChain, tailChain: DiagnosticMessageChain): void { let lastChain = headChain; while (lastChain.next) { lastChain = lastChain.next[0]; } lastChain.next = [tailChain]; } function getDiagnosticFilePath(diagnostic: Diagnostic): string | undefined { return diagnostic.file ? diagnostic.file.path : undefined; } export function compareDiagnostics(d1: Diagnostic, d2: Diagnostic): Comparison { return compareDiagnosticsSkipRelatedInformation(d1, d2) || compareRelatedInformation(d1, d2) || Comparison.EqualTo; } export function compareDiagnosticsSkipRelatedInformation(d1: Diagnostic, d2: Diagnostic): Comparison { return compareStringsCaseSensitive(getDiagnosticFilePath(d1), getDiagnosticFilePath(d2)) || compareValues(d1.start, d2.start) || compareValues(d1.length, d2.length) || compareValues(d1.code, d2.code) || compareMessageText(d1.messageText, d2.messageText) || Comparison.EqualTo; } function compareRelatedInformation(d1: Diagnostic, d2: Diagnostic): Comparison { if (!d1.relatedInformation && !d2.relatedInformation) { return Comparison.EqualTo; } if (d1.relatedInformation && d2.relatedInformation) { return compareValues(d1.relatedInformation.length, d2.relatedInformation.length) || forEach(d1.relatedInformation, (d1i, index) => { const d2i = d2.relatedInformation![index]; return compareDiagnostics(d1i, d2i); // EqualTo is 0, so falsy, and will cause the next item to be compared }) || Comparison.EqualTo; } return d1.relatedInformation ? Comparison.LessThan : Comparison.GreaterThan; } function compareMessageText(t1: string | DiagnosticMessageChain, t2: string | DiagnosticMessageChain): Comparison { if (typeof t1 === "string" && typeof t2 === "string") { return compareStringsCaseSensitive(t1, t2); } else if (typeof t1 === "string") { return Comparison.LessThan; } else if (typeof t2 === "string") { return Comparison.GreaterThan; } let res = compareStringsCaseSensitive(t1.messageText, t2.messageText); if (res) { return res; } if (!t1.next && !t2.next) { return Comparison.EqualTo; } if (!t1.next) { return Comparison.LessThan; } if (!t2.next) { return Comparison.GreaterThan; } const len = Math.min(t1.next.length, t2.next.length); for (let i = 0; i < len; i++) { res = compareMessageText(t1.next[i], t2.next[i]); if (res) { return res; } } if (t1.next.length < t2.next.length) { return Comparison.LessThan; } else if (t1.next.length > t2.next.length) { return Comparison.GreaterThan; } return Comparison.EqualTo; } export function getEmitScriptTarget(compilerOptions: CompilerOptions) { return compilerOptions.target || ScriptTarget.ES3; } export function getEmitModuleKind(compilerOptions: {module?: CompilerOptions["module"], target?: CompilerOptions["target"]}) { return typeof compilerOptions.module === "number" ? compilerOptions.module : getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2015 ? ModuleKind.ES2015 : ModuleKind.CommonJS; } export function getEmitModuleResolutionKind(compilerOptions: CompilerOptions) { let moduleResolution = compilerOptions.moduleResolution; if (moduleResolution === undefined) { moduleResolution = getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS ? ModuleResolutionKind.NodeJs : ModuleResolutionKind.Classic; } return moduleResolution; } export function hasJsonModuleEmitEnabled(options: CompilerOptions) { switch (getEmitModuleKind(options)) { case ModuleKind.CommonJS: case ModuleKind.AMD: case ModuleKind.ES2015: case ModuleKind.ESNext: return true; default: return false; } } export function unreachableCodeIsError(options: CompilerOptions): boolean { return options.allowUnreachableCode === false; } export function unusedLabelIsError(options: CompilerOptions): boolean { return options.allowUnusedLabels === false; } export function getAreDeclarationMapsEnabled(options: CompilerOptions) { return !!(getEmitDeclarations(options) && options.declarationMap); } export function getAllowSyntheticDefaultImports(compilerOptions: CompilerOptions) { const moduleKind = getEmitModuleKind(compilerOptions); return compilerOptions.allowSyntheticDefaultImports !== undefined ? compilerOptions.allowSyntheticDefaultImports : compilerOptions.esModuleInterop || moduleKind === ModuleKind.System; } export function getEmitDeclarations(compilerOptions: CompilerOptions): boolean { return !!(compilerOptions.declaration || compilerOptions.composite); } export function isIncrementalCompilation(options: CompilerOptions) { return !!(options.incremental || options.composite); } export type StrictOptionName = "noImplicitAny" | "noImplicitThis" | "strictNullChecks" | "strictFunctionTypes" | "strictBindCallApply" | "strictPropertyInitialization" | "alwaysStrict"; export function getStrictOptionValue(compilerOptions: CompilerOptions, flag: StrictOptionName): boolean { return compilerOptions[flag] === undefined ? !!compilerOptions.strict : !!compilerOptions[flag]; } export function compilerOptionsAffectSemanticDiagnostics(newOptions: CompilerOptions, oldOptions: CompilerOptions): boolean { return oldOptions !== newOptions && semanticDiagnosticsOptionDeclarations.some(option => !isJsonEqual(getCompilerOptionValue(oldOptions, option), getCompilerOptionValue(newOptions, option))); } export function compilerOptionsAffectEmit(newOptions: CompilerOptions, oldOptions: CompilerOptions): boolean { return oldOptions !== newOptions && affectsEmitOptionDeclarations.some(option => !isJsonEqual(getCompilerOptionValue(oldOptions, option), getCompilerOptionValue(newOptions, option))); } export function getCompilerOptionValue(options: CompilerOptions, option: CommandLineOption): unknown { return option.strictFlag ? getStrictOptionValue(options, option.name as StrictOptionName) : options[option.name]; } export function hasZeroOrOneAsteriskCharacter(str: string): boolean { let seenAsterisk = false; for (let i = 0; i < str.length; i++) { if (str.charCodeAt(i) === CharacterCodes.asterisk) { if (!seenAsterisk) { seenAsterisk = true; } else { // have already seen asterisk return false; } } } return true; } /** * 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; /** * Normalize path separators. */ export function normalizeSlashes(path: string): string { return path.replace(backslashRegExp, directorySeparator); } 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; } 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 ~(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; } // TODO(rbuckton): replace references with `resolvePath` export function normalizePath(path: string): string { return resolvePath(path); } 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 }; } } /** * Returns the path except for its basename. Semantics align with NodeJS's `path.dirname` * except that we support URL's as well. * * ```ts * getDirectoryPath("/path/to/file.ext") === "/path/to" * getDirectoryPath("/path/to/") === "/path" * getDirectoryPath("/") === "/" * ``` */ export function getDirectoryPath(path: Path): Path; /** * Returns the path except for its basename. Semantics align with NodeJS's `path.dirname` * except that we support URL's as well. * * ```ts * getDirectoryPath("/path/to/file.ext") === "/path/to" * getDirectoryPath("/path/to/") === "/path" * getDirectoryPath("/") === "/" * ``` */ 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))); } 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 + "\\"); } export function isUrl(path: string) { return getEncodedRootLength(path) < 0; } export function pathIsRelative(path: string): boolean { return /^\.\.?($|[\\/])/.test(path); } /** * Determines whether a path is an absolute 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; } export function convertToRelativePath(absoluteOrRelativePath: string, basePath: string, getCanonicalFileName: (path: string) => string): string { return !isRootedDiskPath(absoluteOrRelativePath) ? absoluteOrRelativePath : getRelativePathToDirectoryOrUrl(basePath, absoluteOrRelativePath, basePath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); } 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 (`/`). */ export function getPathComponents(path: string, currentDirectory = "") { path = combinePaths(currentDirectory, path); const rootLength = getRootLength(path); return pathComponents(path, rootLength); } /** * Reduce an array of path components to a more simplified path by navigating any * `"."` or `".."` entries in the path. */ export function reducePathComponents(components: ReadonlyArray) { 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; } /** * 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 (`/`). */ 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)); } /** * Formats a parsed path consisting of a root component (at index 0) and zero or more path * segments (at indices > 0). */ export function getPathFromPathComponents(pathComponents: ReadonlyArray) { if (pathComponents.length === 0) return ""; const root = pathComponents[0] && ensureTrailingDirectorySeparator(pathComponents[0]); return root + pathComponents.slice(1).join(directorySeparator); } export function getNormalizedAbsolutePathWithoutRoot(fileName: string, currentDirectory: string | undefined) { return getPathWithoutRoot(getNormalizedPathComponents(fileName, currentDirectory)); } function getPathWithoutRoot(pathComponents: ReadonlyArray) { if (pathComponents.length === 0) return ""; return pathComponents.slice(1).join(directorySeparator); } } /* @internal */ namespace ts { 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]; } export function getRelativePathFromFile(from: string, to: string, getCanonicalFileName: GetCanonicalFileName) { return ensurePathIsNonModuleName(getRelativePathFromDirectory(getDirectoryPath(from), to, getCanonicalFileName)); } /** * 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`. */ // tslint:disable-next-line:unified-signatures export function getRelativePathFromDirectory(fromDirectory: string, to: string, getCanonicalFileName: GetCanonicalFileName): string; 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 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); } /** * 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. */ export function ensurePathIsNonModuleName(path: string): string { return getRootLength(path) === 0 && !pathIsRelative(path) ? "./" + path : path; } /** * 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 * getBaseFileName("/path/to/file.ext") === "file.ext" * getBaseFileName("/path/to/") === "to" * getBaseFileName("/") === "" * ``` */ 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" * ``` */ export function getBaseFileName(path: string, extensions: string | ReadonlyArray, ignoreCase: boolean): string; export function getBaseFileName(path: string, extensions?: string | ReadonlyArray, 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; } /** * Combines paths. If a path is absolute, it replaces any previous path. */ 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; } } return path; } /** * Combines and resolves paths. If a path is absolute, it replaces any previous path. Any * `.` and `..` path components are resolved. */ export function resolvePath(path: string, ...paths: (string | undefined)[]): string { const combined = some(paths) ? combinePaths(path, ...paths) : normalizeSlashes(path); const normalized = getPathFromPathComponents(reducePathComponents(getPathComponents(combined))); return normalized && hasTrailingDirectorySeparator(combined) ? ensureTrailingDirectorySeparator(normalized) : normalized; } /** * Determines whether a path has a trailing separator (`/` or `\\`). */ export function hasTrailingDirectorySeparator(path: string) { if (path.length === 0) return false; const ch = path.charCodeAt(path.length - 1); return ch === CharacterCodes.slash || ch === CharacterCodes.backslash; } /** * Removes a trailing directory separator from a path. * @param path The path. */ 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. * @param path The path. */ export function ensureTrailingDirectorySeparator(path: Path): Path; export function ensureTrailingDirectorySeparator(path: string): string; export function ensureTrailingDirectorySeparator(path: string) { if (!hasTrailingDirectorySeparator(path)) { return path + directorySeparator; } return path; } // 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); 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. */ 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); } 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)); } 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; } } return true; } function isDirectorySeparator(charCode: number): boolean { return charCode === CharacterCodes.slash || charCode === CharacterCodes.backslash; } function stripLeadingDirectorySeparator(s: string): string | undefined { return isDirectorySeparator(s.charCodeAt(0)) ? s.slice(1) : undefined; } export function tryRemoveDirectoryPrefix(path: string, dirPath: string, getCanonicalFileName: GetCanonicalFileName): string | undefined { const withoutPrefix = tryRemovePrefix(path, dirPath, getCanonicalFileName); return withoutPrefix === undefined ? undefined : stripLeadingDirectorySeparator(withoutPrefix); } // Reserved characters, forces escaping of any non-word (or digit), non-whitespace character. // It may be inefficient (we could just match (/[-[\]{}()*+?.,\\^$|#\s]/g), but this is future // proof. const reservedCharacterPattern = /[^\w\s\/]/g; export function regExpEscape(text: string) { return text.replace(reservedCharacterPattern, escapeRegExpCharacter); } function escapeRegExpCharacter(match: string) { return "\\" + match; } const wildcardCharCodes = [CharacterCodes.asterisk, CharacterCodes.question]; export function hasExtension(fileName: string): boolean { return stringContains(getBaseFileName(fileName), "."); } export const commonPackageFolders: ReadonlyArray = ["node_modules", "bower_components", "jspm_packages"]; const implicitExcludePathRegexPattern = `(?!(${commonPackageFolders.join("|")})(/|$))`; interface WildcardMatcher { singleAsteriskRegexFragment: string; doubleAsteriskRegexFragment: string; replaceWildcardCharacter: (match: string) => string; } const filesMatcher: WildcardMatcher = { /** * Matches any single directory segment unless it is the last segment and a .min.js file * Breakdown: * [^./] # matches everything up to the first . character (excluding directory separators) * (\\.(?!min\\.js$))? # matches . characters but not if they are part of the .min.js file extension */ singleAsteriskRegexFragment: "([^./]|(\\.(?!min\\.js$))?)*", /** * Regex for the ** wildcard. Matches any number of subdirectories. When used for including * files or directories, does not match subdirectories that start with a . character */ doubleAsteriskRegexFragment: `(/${implicitExcludePathRegexPattern}[^/.][^/]*)*?`, replaceWildcardCharacter: match => replaceWildcardCharacter(match, filesMatcher.singleAsteriskRegexFragment) }; const directoriesMatcher: WildcardMatcher = { singleAsteriskRegexFragment: "[^/]*", /** * Regex for the ** wildcard. Matches any number of subdirectories. When used for including * files or directories, does not match subdirectories that start with a . character */ doubleAsteriskRegexFragment: `(/${implicitExcludePathRegexPattern}[^/.][^/]*)*?`, replaceWildcardCharacter: match => replaceWildcardCharacter(match, directoriesMatcher.singleAsteriskRegexFragment) }; const excludeMatcher: WildcardMatcher = { singleAsteriskRegexFragment: "[^/]*", doubleAsteriskRegexFragment: "(/.+?)?", replaceWildcardCharacter: match => replaceWildcardCharacter(match, excludeMatcher.singleAsteriskRegexFragment) }; const wildcardMatchers = { files: filesMatcher, directories: directoriesMatcher, exclude: excludeMatcher }; export function getRegularExpressionForWildcard(specs: ReadonlyArray | undefined, basePath: string, usage: "files" | "directories" | "exclude"): string | undefined { const patterns = getRegularExpressionsForWildcards(specs, basePath, usage); if (!patterns || !patterns.length) { return undefined; } const pattern = patterns.map(pattern => `(${pattern})`).join("|"); // If excluding, match "foo/bar/baz...", but if including, only allow "foo". const terminator = usage === "exclude" ? "($|/)" : "$"; return `^(${pattern})${terminator}`; } export function getRegularExpressionsForWildcards(specs: ReadonlyArray | undefined, basePath: string, usage: "files" | "directories" | "exclude"): ReadonlyArray | undefined { if (specs === undefined || specs.length === 0) { return undefined; } return flatMap(specs, spec => spec && getSubPatternFromSpec(spec, basePath, usage, wildcardMatchers[usage])); } /** * An "includes" path "foo" is implicitly a glob "foo/** /*" (without the space) if its last component has no extension, * and does not contain any glob characters itself. */ export function isImplicitGlob(lastPathComponent: string): boolean { return !/[.*?]/.test(lastPathComponent); } function getSubPatternFromSpec(spec: string, basePath: string, usage: "files" | "directories" | "exclude", { singleAsteriskRegexFragment, doubleAsteriskRegexFragment, replaceWildcardCharacter }: WildcardMatcher): string | undefined { let subpattern = ""; let hasWrittenComponent = false; const components = getNormalizedPathComponents(spec, basePath); const lastComponent = last(components); if (usage !== "exclude" && lastComponent === "**") { return undefined; } // getNormalizedPathComponents includes the separator for the root component. // We need to remove to create our regex correctly. components[0] = removeTrailingDirectorySeparator(components[0]); if (isImplicitGlob(lastComponent)) { components.push("**", "*"); } let optionalCount = 0; for (let component of components) { if (component === "**") { subpattern += doubleAsteriskRegexFragment; } else { if (usage === "directories") { subpattern += "("; optionalCount++; } if (hasWrittenComponent) { subpattern += directorySeparator; } if (usage !== "exclude") { let componentPattern = ""; // The * and ? wildcards should not match directories or files that start with . if they // appear first in a component. Dotted directories and files can be included explicitly // like so: **/.*/.* if (component.charCodeAt(0) === CharacterCodes.asterisk) { componentPattern += "([^./]" + singleAsteriskRegexFragment + ")?"; component = component.substr(1); } else if (component.charCodeAt(0) === CharacterCodes.question) { componentPattern += "[^./]"; component = component.substr(1); } componentPattern += component.replace(reservedCharacterPattern, replaceWildcardCharacter); // Patterns should not include subfolders like node_modules unless they are // explicitly included as part of the path. // // As an optimization, if the component pattern is the same as the component, // then there definitely were no wildcard characters and we do not need to // add the exclusion pattern. if (componentPattern !== component) { subpattern += implicitExcludePathRegexPattern; } subpattern += componentPattern; } else { subpattern += component.replace(reservedCharacterPattern, replaceWildcardCharacter); } } hasWrittenComponent = true; } while (optionalCount > 0) { subpattern += ")?"; optionalCount--; } return subpattern; } function replaceWildcardCharacter(match: string, singleAsteriskRegexFragment: string) { return match === "*" ? singleAsteriskRegexFragment : match === "?" ? "[^/]" : "\\" + match; } export interface FileSystemEntries { readonly files: ReadonlyArray; readonly directories: ReadonlyArray; } export interface FileMatcherPatterns { /** One pattern for each "include" spec. */ includeFilePatterns: ReadonlyArray | undefined; /** One pattern matching one of any of the "include" specs. */ includeFilePattern: string | undefined; includeDirectoryPattern: string | undefined; excludePattern: string | undefined; basePaths: ReadonlyArray; } /** @param path directory of the tsconfig.json */ export function getFileMatcherPatterns(path: string, excludes: ReadonlyArray | undefined, includes: ReadonlyArray | undefined, useCaseSensitiveFileNames: boolean, currentDirectory: string): FileMatcherPatterns { path = normalizePath(path); currentDirectory = normalizePath(currentDirectory); const absolutePath = combinePaths(currentDirectory, path); return { includeFilePatterns: map(getRegularExpressionsForWildcards(includes, absolutePath, "files"), pattern => `^${pattern}$`), includeFilePattern: getRegularExpressionForWildcard(includes, absolutePath, "files"), includeDirectoryPattern: getRegularExpressionForWildcard(includes, absolutePath, "directories"), excludePattern: getRegularExpressionForWildcard(excludes, absolutePath, "exclude"), basePaths: getBasePaths(path, includes, useCaseSensitiveFileNames) }; } export function getRegexFromPattern(pattern: string, useCaseSensitiveFileNames: boolean): RegExp { return new RegExp(pattern, useCaseSensitiveFileNames ? "" : "i"); } /** @param path directory of the tsconfig.json */ export function matchFiles(path: string, extensions: ReadonlyArray | undefined, excludes: ReadonlyArray | undefined, includes: ReadonlyArray | undefined, useCaseSensitiveFileNames: boolean, currentDirectory: string, depth: number | undefined, getFileSystemEntries: (path: string) => FileSystemEntries, realpath: (path: string) => string): string[] { path = normalizePath(path); currentDirectory = normalizePath(currentDirectory); const patterns = getFileMatcherPatterns(path, excludes, includes, useCaseSensitiveFileNames, currentDirectory); const includeFileRegexes = patterns.includeFilePatterns && patterns.includeFilePatterns.map(pattern => getRegexFromPattern(pattern, useCaseSensitiveFileNames)); const includeDirectoryRegex = patterns.includeDirectoryPattern && getRegexFromPattern(patterns.includeDirectoryPattern, useCaseSensitiveFileNames); const excludeRegex = patterns.excludePattern && getRegexFromPattern(patterns.excludePattern, useCaseSensitiveFileNames); // Associate an array of results with each include regex. This keeps results in order of the "include" order. // If there are no "includes", then just put everything in results[0]. const results: string[][] = includeFileRegexes ? includeFileRegexes.map(() => []) : [[]]; const visited = createMap(); const toCanonical = createGetCanonicalFileName(useCaseSensitiveFileNames); for (const basePath of patterns.basePaths) { visitDirectory(basePath, combinePaths(currentDirectory, basePath), depth); } return flatten(results); function visitDirectory(path: string, absolutePath: string, depth: number | undefined) { const canonicalPath = toCanonical(realpath(absolutePath)); if (visited.has(canonicalPath)) return; visited.set(canonicalPath, true); const { files, directories } = getFileSystemEntries(path); for (const current of sort(files, compareStringsCaseSensitive)) { const name = combinePaths(path, current); const absoluteName = combinePaths(absolutePath, current); if (extensions && !fileExtensionIsOneOf(name, extensions)) continue; if (excludeRegex && excludeRegex.test(absoluteName)) continue; if (!includeFileRegexes) { results[0].push(name); } else { const includeIndex = findIndex(includeFileRegexes, re => re.test(absoluteName)); if (includeIndex !== -1) { results[includeIndex].push(name); } } } if (depth !== undefined) { depth--; if (depth === 0) { return; } } for (const current of sort(directories, compareStringsCaseSensitive)) { const name = combinePaths(path, current); const absoluteName = combinePaths(absolutePath, current); if ((!includeDirectoryRegex || includeDirectoryRegex.test(absoluteName)) && (!excludeRegex || !excludeRegex.test(absoluteName))) { visitDirectory(name, absoluteName, depth); } } } } /** * Computes the unique non-wildcard base paths amongst the provided include patterns. */ function getBasePaths(path: string, includes: ReadonlyArray | undefined, useCaseSensitiveFileNames: boolean): string[] { // Storage for our results in the form of literal paths (e.g. the paths as written by the user). const basePaths: string[] = [path]; if (includes) { // Storage for literal base paths amongst the include patterns. const includeBasePaths: string[] = []; for (const include of includes) { // We also need to check the relative paths by converting them to absolute and normalizing // in case they escape the base path (e.g "..\somedirectory") const absolute: string = isRootedDiskPath(include) ? include : normalizePath(combinePaths(path, include)); // Append the literal and canonical candidate base paths. includeBasePaths.push(getIncludeBasePath(absolute)); } // Sort the offsets array using either the literal or canonical path representations. includeBasePaths.sort(getStringComparer(!useCaseSensitiveFileNames)); // Iterate over each include base path and include unique base paths that are not a // subpath of an existing base path for (const includeBasePath of includeBasePaths) { if (every(basePaths, basePath => !containsPath(basePath, includeBasePath, path, !useCaseSensitiveFileNames))) { basePaths.push(includeBasePath); } } } return basePaths; } function getIncludeBasePath(absolute: string): string { const wildcardOffset = indexOfAnyCharCode(absolute, wildcardCharCodes); if (wildcardOffset < 0) { // No "*" or "?" in the path return !hasExtension(absolute) ? absolute : removeTrailingDirectorySeparator(getDirectoryPath(absolute)); } return absolute.substring(0, absolute.lastIndexOf(directorySeparator, wildcardOffset)); } export function ensureScriptKind(fileName: string, scriptKind: ScriptKind | undefined): ScriptKind { // Using scriptKind as a condition handles both: // - 'scriptKind' is unspecified and thus it is `undefined` // - 'scriptKind' is set and it is `Unknown` (0) // If the 'scriptKind' is 'undefined' or 'Unknown' then we attempt // to get the ScriptKind from the file name. If it cannot be resolved // from the file name then the default 'TS' script kind is returned. return scriptKind || getScriptKindFromFileName(fileName) || ScriptKind.TS; } export function getScriptKindFromFileName(fileName: string): ScriptKind { const ext = fileName.substr(fileName.lastIndexOf(".")); switch (ext.toLowerCase()) { case Extension.Js: return ScriptKind.JS; case Extension.Jsx: return ScriptKind.JSX; case Extension.Ts: return ScriptKind.TS; case Extension.Tsx: return ScriptKind.TSX; case Extension.Json: return ScriptKind.JSON; default: return ScriptKind.Unknown; } } /** * List of supported extensions in order of file resolution precedence. */ export const supportedTSExtensions: ReadonlyArray = [Extension.Ts, Extension.Tsx, Extension.Dts]; export const supportedTSExtensionsWithJson: ReadonlyArray = [Extension.Ts, Extension.Tsx, Extension.Dts, Extension.Json]; /** Must have ".d.ts" first because if ".ts" goes first, that will be detected as the extension instead of ".d.ts". */ export const supportedTSExtensionsForExtractExtension: ReadonlyArray = [Extension.Dts, Extension.Ts, Extension.Tsx]; export const supportedJSExtensions: ReadonlyArray = [Extension.Js, Extension.Jsx]; export const supportedJSAndJsonExtensions: ReadonlyArray = [Extension.Js, Extension.Jsx, Extension.Json]; const allSupportedExtensions: ReadonlyArray = [...supportedTSExtensions, ...supportedJSExtensions]; const allSupportedExtensionsWithJson: ReadonlyArray = [...supportedTSExtensions, ...supportedJSExtensions, Extension.Json]; export function getSupportedExtensions(options?: CompilerOptions): ReadonlyArray; export function getSupportedExtensions(options?: CompilerOptions, extraFileExtensions?: ReadonlyArray): ReadonlyArray; export function getSupportedExtensions(options?: CompilerOptions, extraFileExtensions?: ReadonlyArray): ReadonlyArray { const needJsExtensions = options && options.allowJs; if (!extraFileExtensions || extraFileExtensions.length === 0) { return needJsExtensions ? allSupportedExtensions : supportedTSExtensions; } const extensions = [ ...needJsExtensions ? allSupportedExtensions : supportedTSExtensions, ...mapDefined(extraFileExtensions, x => x.scriptKind === ScriptKind.Deferred || needJsExtensions && isJSLike(x.scriptKind) ? x.extension : undefined) ]; return deduplicate(extensions, equateStringsCaseSensitive, compareStringsCaseSensitive); } export function getSuppoertedExtensionsWithJsonIfResolveJsonModule(options: CompilerOptions | undefined, supportedExtensions: ReadonlyArray): ReadonlyArray { if (!options || !options.resolveJsonModule) { return supportedExtensions; } if (supportedExtensions === allSupportedExtensions) { return allSupportedExtensionsWithJson; } if (supportedExtensions === supportedTSExtensions) { return supportedTSExtensionsWithJson; } return [...supportedExtensions, Extension.Json]; } function isJSLike(scriptKind: ScriptKind | undefined): boolean { return scriptKind === ScriptKind.JS || scriptKind === ScriptKind.JSX; } export function hasJSFileExtension(fileName: string): boolean { return some(supportedJSExtensions, extension => fileExtensionIs(fileName, extension)); } export function hasJSOrJsonFileExtension(fileName: string): boolean { return supportedJSAndJsonExtensions.some(ext => fileExtensionIs(fileName, ext)); } export function hasTSFileExtension(fileName: string): boolean { return some(supportedTSExtensions, extension => fileExtensionIs(fileName, extension)); } export function isSupportedSourceFileName(fileName: string, compilerOptions?: CompilerOptions, extraFileExtensions?: ReadonlyArray) { if (!fileName) { return false; } const supportedExtensions = getSupportedExtensions(compilerOptions, extraFileExtensions); for (const extension of getSuppoertedExtensionsWithJsonIfResolveJsonModule(compilerOptions, supportedExtensions)) { if (fileExtensionIs(fileName, extension)) { return true; } } return false; } /** * Extension boundaries by priority. Lower numbers indicate higher priorities, and are * aligned to the offset of the highest priority extension in the * allSupportedExtensions array. */ export const enum ExtensionPriority { TypeScriptFiles = 0, DeclarationAndJavaScriptFiles = 2, Highest = TypeScriptFiles, Lowest = DeclarationAndJavaScriptFiles, } export function getExtensionPriority(path: string, supportedExtensions: ReadonlyArray): ExtensionPriority { for (let i = supportedExtensions.length - 1; i >= 0; i--) { if (fileExtensionIs(path, supportedExtensions[i])) { return adjustExtensionPriority(i, supportedExtensions); } } // If its not in the list of supported extensions, this is likely a // TypeScript file with a non-ts extension return ExtensionPriority.Highest; } /** * Adjusts an extension priority to be the highest priority within the same range. */ export function adjustExtensionPriority(extensionPriority: ExtensionPriority, supportedExtensions: ReadonlyArray): ExtensionPriority { if (extensionPriority < ExtensionPriority.DeclarationAndJavaScriptFiles) { return ExtensionPriority.TypeScriptFiles; } else if (extensionPriority < supportedExtensions.length) { return ExtensionPriority.DeclarationAndJavaScriptFiles; } else { return supportedExtensions.length; } } /** * Gets the next lowest extension priority for a given priority. */ export function getNextLowestExtensionPriority(extensionPriority: ExtensionPriority, supportedExtensions: ReadonlyArray): ExtensionPriority { if (extensionPriority < ExtensionPriority.DeclarationAndJavaScriptFiles) { return ExtensionPriority.DeclarationAndJavaScriptFiles; } else { return supportedExtensions.length; } } const extensionsToRemove = [Extension.Dts, Extension.Ts, Extension.Js, Extension.Tsx, Extension.Jsx, Extension.Json]; export function removeFileExtension(path: string): string { for (const ext of extensionsToRemove) { const extensionless = tryRemoveExtension(path, ext); if (extensionless !== undefined) { return extensionless; } } return path; } export function tryRemoveExtension(path: string, extension: string): string | undefined { return fileExtensionIs(path, extension) ? removeExtension(path, extension) : undefined; } export function removeExtension(path: string, extension: string): string { return path.substring(0, path.length - extension.length); } export function changeExtension(path: T, newExtension: string): T { return changeAnyExtension(path, newExtension, extensionsToRemove, /*ignoreCase*/ false); } export function changeAnyExtension(path: string, ext: string): string; export function changeAnyExtension(path: string, ext: string, extensions: string | ReadonlyArray, ignoreCase: boolean): string; export function changeAnyExtension(path: string, ext: string, extensions?: string | ReadonlyArray, 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; } export function tryParsePattern(pattern: string): Pattern | undefined { // This should be verified outside of here and a proper error thrown. Debug.assert(hasZeroOrOneAsteriskCharacter(pattern)); const indexOfStar = pattern.indexOf("*"); return indexOfStar === -1 ? undefined : { prefix: pattern.substr(0, indexOfStar), suffix: pattern.substr(indexOfStar + 1) }; } export function positionIsSynthesized(pos: number): boolean { // This is a fast way of testing the following conditions: // pos === undefined || pos === null || isNaN(pos) || pos < 0; return !(pos >= 0); } /** True if an extension is one of the supported TypeScript extensions. */ export function extensionIsTS(ext: Extension): boolean { return ext === Extension.Ts || ext === Extension.Tsx || ext === Extension.Dts; } export function resolutionExtensionIsTSOrJson(ext: Extension) { return extensionIsTS(ext) || ext === Extension.Json; } /** * Gets the extension from a path. * Path must have a valid extension. */ export function extensionFromPath(path: string): Extension { const ext = tryGetExtensionFromPath(path); return ext !== undefined ? ext : Debug.fail(`File ${path} has unknown extension.`); } export function isAnySupportedFileExtension(path: string): boolean { return tryGetExtensionFromPath(path) !== undefined; } export function tryGetExtensionFromPath(path: string): Extension | undefined { return find(extensionsToRemove, e => fileExtensionIs(path, e)); } function getAnyExtensionFromPathWorker(path: string, extensions: string | ReadonlyArray, stringEqualityComparer: (a: string, b: string) => boolean) { if (typeof extensions === "string") extensions = [extensions]; for (let extension of extensions) { if (!startsWith(extension, ".")) extension = "." + extension; if (path.length >= extension.length && path.charAt(path.length - extension.length) === ".") { const pathExtension = path.slice(path.length - extension.length); if (stringEqualityComparer(pathExtension, extension)) { return pathExtension; } } } return ""; } /** * Gets the file extension for a path. */ export function getAnyExtensionFromPath(path: string): string; /** * Gets the file extension for a path, provided it is one of the provided extensions. */ export function getAnyExtensionFromPath(path: string, extensions: string | ReadonlyArray, ignoreCase: boolean): string; export function getAnyExtensionFromPath(path: string, extensions?: string | ReadonlyArray, 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(path, extensions, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive); } const baseFileName = getBaseFileName(path); const extensionIndex = baseFileName.lastIndexOf("."); if (extensionIndex >= 0) { return baseFileName.substring(extensionIndex); } return ""; } export function isCheckJsEnabledForFile(sourceFile: SourceFile, compilerOptions: CompilerOptions) { return sourceFile.checkJsDirective ? sourceFile.checkJsDirective.enabled : compilerOptions.checkJs; } export const emptyFileSystemEntries: FileSystemEntries = { files: emptyArray, directories: emptyArray }; /** * patternStrings contains both pattern strings (containing "*") and regular strings. * Return an exact match if possible, or a pattern match, or undefined. * (These are verified by verifyCompilerOptions to have 0 or 1 "*" characters.) */ export function matchPatternOrExact(patternStrings: ReadonlyArray, candidate: string): string | Pattern | undefined { const patterns: Pattern[] = []; for (const patternString of patternStrings) { const pattern = tryParsePattern(patternString); if (pattern) { patterns.push(pattern); } else if (patternString === candidate) { // pattern was matched as is - no need to search further return patternString; } } return findBestPatternMatch(patterns, _ => _, candidate); } export type Mutable = { -readonly [K in keyof T]: T[K] }; export function sliceAfter(arr: ReadonlyArray, value: T): ReadonlyArray { const index = arr.indexOf(value); Debug.assert(index !== -1); return arr.slice(index); } export function addRelatedInfo(diagnostic: T, ...relatedInformation: DiagnosticRelatedInformation[]): T { if (!diagnostic.relatedInformation) { diagnostic.relatedInformation = []; } diagnostic.relatedInformation.push(...relatedInformation); return diagnostic; } export function minAndMax(arr: ReadonlyArray, getValue: (value: T) => number): { readonly min: number, readonly max: number } { Debug.assert(arr.length !== 0); let min = getValue(arr[0]); let max = min; for (let i = 1; i < arr.length; i++) { const value = getValue(arr[i]); if (value < min) { min = value; } else if (value > max) { max = value; } } return { min, max }; } export interface ReadonlyNodeSet { has(node: TNode): boolean; forEach(cb: (node: TNode) => void): void; some(pred: (node: TNode) => boolean): boolean; } export class NodeSet implements ReadonlyNodeSet { private map = createMap(); add(node: TNode): void { this.map.set(String(getNodeId(node)), node); } tryAdd(node: TNode): boolean { if (this.has(node)) return false; this.add(node); return true; } has(node: TNode): boolean { return this.map.has(String(getNodeId(node))); } forEach(cb: (node: TNode) => void): void { this.map.forEach(cb); } some(pred: (node: TNode) => boolean): boolean { return forEachEntry(this.map, pred) || false; } } export interface ReadonlyNodeMap { get(node: TNode): TValue | undefined; has(node: TNode): boolean; } export class NodeMap implements ReadonlyNodeMap { private map = createMap<{ node: TNode, value: TValue }>(); get(node: TNode): TValue | undefined { const res = this.map.get(String(getNodeId(node))); return res && res.value; } getOrUpdate(node: TNode, setValue: () => TValue): TValue { const res = this.get(node); if (res) return res; const value = setValue(); this.set(node, value); return value; } set(node: TNode, value: TValue): void { this.map.set(String(getNodeId(node)), { node, value }); } has(node: TNode): boolean { return this.map.has(String(getNodeId(node))); } forEach(cb: (value: TValue, node: TNode) => void): void { this.map.forEach(({ node, value }) => cb(value, node)); } } export function rangeOfNode(node: Node): TextRange { return { pos: getTokenPosOfNode(node), end: node.end }; } export function rangeOfTypeParameters(typeParameters: NodeArray): TextRange { // Include the `<>` return { pos: typeParameters.pos - 1, end: typeParameters.end + 1 }; } export function skipTypeChecking(sourceFile: SourceFile, options: CompilerOptions) { // If skipLibCheck is enabled, skip reporting errors if file is a declaration file. // If skipDefaultLibCheck is enabled, skip reporting errors if file contains a // '/// ' directive. return options.skipLibCheck && sourceFile.isDeclarationFile || options.skipDefaultLibCheck && sourceFile.hasNoDefaultLib; } export function isJsonEqual(a: unknown, b: unknown): boolean { return a === b || typeof a === "object" && a !== null && typeof b === "object" && b !== null && equalOwnProperties(a as MapLike, b as MapLike, isJsonEqual); } export function getOrUpdate(map: Map, key: string, getDefault: () => T): T { const got = map.get(key); if (got === undefined) { const value = getDefault(); map.set(key, value); return value; } else { return got; } } /** * Converts a bigint literal string, e.g. `0x1234n`, * to its decimal string representation, e.g. `4660`. */ export function parsePseudoBigInt(stringValue: string): string { let log2Base: number; switch (stringValue.charCodeAt(1)) { // "x" in "0x123" case CharacterCodes.b: case CharacterCodes.B: // 0b or 0B log2Base = 1; break; case CharacterCodes.o: case CharacterCodes.O: // 0o or 0O log2Base = 3; break; case CharacterCodes.x: case CharacterCodes.X: // 0x or 0X log2Base = 4; break; default: // already in decimal; omit trailing "n" const nIndex = stringValue.length - 1; // Skip leading 0s let nonZeroStart = 0; while (stringValue.charCodeAt(nonZeroStart) === CharacterCodes._0) { nonZeroStart++; } return stringValue.slice(nonZeroStart, nIndex) || "0"; } // Omit leading "0b", "0o", or "0x", and trailing "n" const startIndex = 2, endIndex = stringValue.length - 1; const bitsNeeded = (endIndex - startIndex) * log2Base; // Stores the value specified by the string as a LE array of 16-bit integers // using Uint16 instead of Uint32 so combining steps can use bitwise operators const segments = new Uint16Array((bitsNeeded >>> 4) + (bitsNeeded & 15 ? 1 : 0)); // Add the digits, one at a time for (let i = endIndex - 1, bitOffset = 0; i >= startIndex; i--, bitOffset += log2Base) { const segment = bitOffset >>> 4; const digitChar = stringValue.charCodeAt(i); // Find character range: 0-9 < A-F < a-f const digit = digitChar <= CharacterCodes._9 ? digitChar - CharacterCodes._0 : 10 + digitChar - (digitChar <= CharacterCodes.F ? CharacterCodes.A : CharacterCodes.a); const shiftedDigit = digit << (bitOffset & 15); segments[segment] |= shiftedDigit; const residual = shiftedDigit >>> 16; if (residual) segments[segment + 1] |= residual; // overflows segment } // Repeatedly divide segments by 10 and add remainder to base10Value let base10Value = ""; let firstNonzeroSegment = segments.length - 1; let segmentsRemaining = true; while (segmentsRemaining) { let mod10 = 0; segmentsRemaining = false; for (let segment = firstNonzeroSegment; segment >= 0; segment--) { const newSegment = mod10 << 16 | segments[segment]; const segmentValue = (newSegment / 10) | 0; segments[segment] = segmentValue; mod10 = newSegment - segmentValue * 10; if (segmentValue && !segmentsRemaining) { firstNonzeroSegment = segment; segmentsRemaining = true; } } base10Value = mod10 + base10Value; } return base10Value; } export function pseudoBigIntToString({negative, base10Value}: PseudoBigInt): string { return (negative && base10Value !== "0" ? "-" : "") + base10Value; } }