From c447ebc59c49a1b071c099ce6055e94517973eab Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 27 Nov 2019 13:44:31 -0800 Subject: [PATCH] Refactor: No more than 1 namespace declaration per file (#35373) * Refactor: No more than 1 namespace declaration per file * Simplify refs where possible --- .eslintrc.json | 1 + Gulpfile.js | 6 +- .../eslint/rules/one-namespace-per-file.ts | 44 + src/compiler/builder.ts | 163 +- src/compiler/builderPublic.ts | 160 + src/compiler/builderState.ts | 1013 +++-- src/compiler/builderStatePublic.ts | 13 + src/compiler/core.ts | 87 +- src/compiler/corePublic.ts | 71 + src/compiler/factory.ts | 3672 +---------------- src/compiler/factoryPublic.ts | 3669 ++++++++++++++++ src/compiler/performance.ts | 7 - src/compiler/performanceTimestamp.ts | 6 + src/compiler/tsbuild.ts | 2072 +--------- src/compiler/tsbuildPublic.ts | 2069 ++++++++++ src/compiler/tsconfig.json | 9 + src/compiler/utilities.ts | 2480 ----------- src/compiler/utilitiesPublic.ts | 2473 +++++++++++ src/compiler/visitor.ts | 956 +---- src/compiler/visitorPublic.ts | 953 +++++ src/compiler/watch.ts | 717 ---- src/compiler/watchPublic.ts | 716 ++++ src/harness/fourslash.ts | 1606 ------- src/harness/fourslashInterface.ts | 1605 +++++++ src/harness/harness.ts | 449 +- src/harness/harnessGlobals.ts | 48 + src/harness/harnessLanguageService.ts | 2 +- src/harness/harnessUtils.ts | 391 ++ src/harness/tsconfig.json | 3 + src/server/tsconfig.json | 2 + src/server/utilities.ts | 137 - src/server/utilitiesPublic.ts | 122 + src/server/watchType.ts | 13 + src/services/exportAsModule.ts | 7 + src/services/findAllReferences.ts | 2785 +++++++------ src/services/globalThisShim.ts | 50 + src/services/shims.ts | 21 +- src/services/tsconfig.json | 2 + src/services/utilities.ts | 14 +- src/testRunner/unittests/tscWatch/helpers.ts | 4 +- .../unittests/tsserver/compileOnSave.ts | 10 +- .../unittests/tsserver/configuredProjects.ts | 32 +- .../unittests/tsserver/documentRegistry.ts | 6 +- .../tsserver/events/largeFileReferenced.ts | 8 +- .../tsserver/events/projectLoading.ts | 14 +- .../unittests/tsserver/externalProjects.ts | 8 +- .../unittests/tsserver/inferredProjects.ts | 26 +- .../unittests/tsserver/projectErrors.ts | 18 +- .../tsserver/projectReferenceCompileOnSave.ts | 6 +- .../tsserver/projectReferenceErrors.ts | 4 +- .../unittests/tsserver/projectReferences.ts | 38 +- src/testRunner/unittests/tsserver/projects.ts | 20 +- .../unittests/tsserver/resolutionCache.ts | 86 +- .../unittests/tsserver/syntaxOperations.ts | 6 +- .../tsserver/typeReferenceDirectives.ts | 8 +- .../unittests/tsserver/typingsInstaller.ts | 18 +- .../reference/api/tsserverlibrary.d.ts | 10 +- tests/baselines/reference/api/typescript.d.ts | 10 +- 58 files changed, 14502 insertions(+), 14444 deletions(-) create mode 100644 scripts/eslint/rules/one-namespace-per-file.ts create mode 100644 src/compiler/builderPublic.ts create mode 100644 src/compiler/builderStatePublic.ts create mode 100644 src/compiler/corePublic.ts create mode 100644 src/compiler/factoryPublic.ts create mode 100644 src/compiler/performanceTimestamp.ts create mode 100644 src/compiler/tsbuildPublic.ts create mode 100644 src/compiler/utilitiesPublic.ts create mode 100644 src/compiler/visitorPublic.ts create mode 100644 src/compiler/watchPublic.ts create mode 100644 src/harness/fourslashInterface.ts create mode 100644 src/harness/harnessGlobals.ts create mode 100644 src/harness/harnessUtils.ts create mode 100644 src/server/utilitiesPublic.ts create mode 100644 src/server/watchType.ts create mode 100644 src/services/exportAsModule.ts create mode 100644 src/services/globalThisShim.ts diff --git a/.eslintrc.json b/.eslintrc.json index 1f39c29101f..6eb16b973f2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -54,6 +54,7 @@ "simple-indent": "error", "debug-assert": "error", "no-keywords": "error", + "one-namespace-per-file": "error", // eslint-plugin-import "import/no-extraneous-dependencies": ["error", { "optionalDependencies": false }], diff --git a/Gulpfile.js b/Gulpfile.js index 44ce8c84b23..d033ee33c96 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -587,15 +587,15 @@ task("generate-spec").description = "Generates a Markdown version of the Languag task("clean", series(parallel(cleanTasks), cleanBuilt)); task("clean").description = "Cleans build outputs"; -const configureNightly = () => exec(process.execPath, ["scripts/configurePrerelease.js", "dev", "package.json", "src/compiler/core.ts"]); +const configureNightly = () => exec(process.execPath, ["scripts/configurePrerelease.js", "dev", "package.json", "src/compiler/corePublic.ts"]); task("configure-nightly", series(buildScripts, configureNightly)); task("configure-nightly").description = "Runs scripts/configurePrerelease.ts to prepare a build for nightly publishing"; -const configureInsiders = () => exec(process.execPath, ["scripts/configurePrerelease.js", "insiders", "package.json", "src/compiler/core.ts"]); +const configureInsiders = () => exec(process.execPath, ["scripts/configurePrerelease.js", "insiders", "package.json", "src/compiler/corePublic.ts"]); task("configure-insiders", series(buildScripts, configureInsiders)); task("configure-insiders").description = "Runs scripts/configurePrerelease.ts to prepare a build for insiders publishing"; -const configureExperimental = () => exec(process.execPath, ["scripts/configurePrerelease.js", "experimental", "package.json", "src/compiler/core.ts"]); +const configureExperimental = () => exec(process.execPath, ["scripts/configurePrerelease.js", "experimental", "package.json", "src/compiler/corePublic.ts"]); task("configure-experimental", series(buildScripts, configureExperimental)); task("configure-experimental").description = "Runs scripts/configurePrerelease.ts to prepare a build for experimental publishing"; diff --git a/scripts/eslint/rules/one-namespace-per-file.ts b/scripts/eslint/rules/one-namespace-per-file.ts new file mode 100644 index 00000000000..36eeee930f7 --- /dev/null +++ b/scripts/eslint/rules/one-namespace-per-file.ts @@ -0,0 +1,44 @@ +import { AST_NODE_TYPES, TSESTree } from "@typescript-eslint/experimental-utils"; +import { createRule } from "./utils"; + +export = createRule({ + name: "one-namespace-per-file", + meta: { + docs: { + description: `Limits each file to having at most one top-level namespace declaration`, + category: "Possible Errors", + recommended: "error", + }, + messages: { + excessNamespaceError: `All but one of these namespaces should be moved into seperate files.`, + }, + schema: [], + type: "problem", + }, + defaultOptions: [], + + create(context) { + const isNamespaceDeclaration = (node: TSESTree.Node): node is TSESTree.TSModuleDeclaration => node.type === AST_NODE_TYPES.TSModuleDeclaration; + + const checkSourceFile = (node: TSESTree.Program) => { + if (context.getFilename().endsWith(".d.ts")) { + return; + } + const members = node.body; + const namespaces = members.filter(isNamespaceDeclaration); + if (namespaces.length <= 1) { + return; + } + + namespaces.forEach(n => { + context.report({ + messageId: "excessNamespaceError", node: n + }); + }); + }; + + return { + Program: checkSourceFile, + }; + }, +}); diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index dc3b108b983..819765013a5 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -1183,165 +1183,4 @@ namespace ts { return Debug.assertDefined(state.program); } } -} - -namespace ts { - export type AffectedFileResult = { result: T; affected: SourceFile | Program; } | undefined; - - export interface BuilderProgramHost { - /** - * return true if file names are treated with case sensitivity - */ - useCaseSensitiveFileNames(): boolean; - /** - * If provided this would be used this hash instead of actual file shape text for detecting changes - */ - createHash?: (data: string) => string; - /** - * When emit or emitNextAffectedFile are called without writeFile, - * this callback if present would be used to write files - */ - writeFile?: WriteFileCallback; - } - - /** - * Builder to manage the program state changes - */ - export interface BuilderProgram { - /*@internal*/ - getState(): ReusableBuilderProgramState; - /*@internal*/ - backupState(): void; - /*@internal*/ - restoreState(): void; - /** - * Returns current program - */ - getProgram(): Program; - /** - * Returns current program that could be undefined if the program was released - */ - /*@internal*/ - getProgramOrUndefined(): Program | undefined; - /** - * Releases reference to the program, making all the other operations that need program to fail. - */ - /*@internal*/ - releaseProgram(): void; - /** - * Get compiler options of the program - */ - getCompilerOptions(): CompilerOptions; - /** - * Get the source file in the program with file name - */ - getSourceFile(fileName: string): SourceFile | undefined; - /** - * Get a list of files in the program - */ - getSourceFiles(): readonly SourceFile[]; - /** - * Get the diagnostics for compiler options - */ - getOptionsDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; - /** - * Get the diagnostics that dont belong to any file - */ - getGlobalDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; - /** - * Get the diagnostics from config file parsing - */ - getConfigFileParsingDiagnostics(): readonly Diagnostic[]; - /** - * Get the syntax diagnostics, for all source files if source file is not supplied - */ - getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; - /** - * Get the declaration diagnostics, for all source files if source file is not supplied - */ - getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[]; - /** - * Get all the dependencies of the file - */ - getAllDependencies(sourceFile: SourceFile): readonly string[]; - - /** - * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program - * The semantic diagnostics are cached and managed here - * Note that it is assumed that when asked about semantic diagnostics through this API, - * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics - * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, - * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics - */ - getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; - /** - * Emits the JavaScript and declaration files. - * When targetSource file is specified, emits the files corresponding to that source file, - * otherwise for the whole program. - * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, - * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, - * it will only emit all the affected files instead of whole program - * - * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host - * in that order would be used to write the files - */ - emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; - /** - * Get the current directory of the program - */ - getCurrentDirectory(): string; - } - - /** - * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files - */ - export interface SemanticDiagnosticsBuilderProgram extends BuilderProgram { - /** - * Gets the semantic diagnostics from the program for the next affected file and caches it - * Returns undefined if the iteration is complete - */ - getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult; - } - - /** - * The builder that can handle the changes in program and iterate through changed file to emit the files - * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files - */ - export interface EmitAndSemanticDiagnosticsBuilderProgram extends SemanticDiagnosticsBuilderProgram { - /** - * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete - * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host - * in that order would be used to write the files - */ - emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult; - } - - /** - * Create the builder to manage semantic diagnostics and cache them - */ - export function createSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): SemanticDiagnosticsBuilderProgram; - export function createSemanticDiagnosticsBuilderProgram(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): SemanticDiagnosticsBuilderProgram; - export function createSemanticDiagnosticsBuilderProgram(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | SemanticDiagnosticsBuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]) { - return createBuilderProgram(BuilderProgramKind.SemanticDiagnosticsBuilderProgram, getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences)); - } - - /** - * Create the builder that can handle the changes in program and iterate through changed files - * to emit the those files and manage semantic diagnostics cache as well - */ - export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): EmitAndSemanticDiagnosticsBuilderProgram; - export function createEmitAndSemanticDiagnosticsBuilderProgram(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): EmitAndSemanticDiagnosticsBuilderProgram; - export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]) { - return createBuilderProgram(BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences)); - } - - /** - * Creates a builder thats just abstraction over program and can be used with watch - */ - export function createAbstractBuilder(newProgram: Program, host: BuilderProgramHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): BuilderProgram; - export function createAbstractBuilder(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderProgram; - export function createAbstractBuilder(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | BuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderProgram { - const { newProgram, configFileParsingDiagnostics: newConfigFileParsingDiagnostics } = getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences); - return createRedirectedBuilderProgram({ program: newProgram, compilerOptions: newProgram.getCompilerOptions() }, newConfigFileParsingDiagnostics); - } -} +} \ No newline at end of file diff --git a/src/compiler/builderPublic.ts b/src/compiler/builderPublic.ts new file mode 100644 index 00000000000..72e02941731 --- /dev/null +++ b/src/compiler/builderPublic.ts @@ -0,0 +1,160 @@ +namespace ts { + export type AffectedFileResult = { result: T; affected: SourceFile | Program; } | undefined; + + export interface BuilderProgramHost { + /** + * return true if file names are treated with case sensitivity + */ + useCaseSensitiveFileNames(): boolean; + /** + * If provided this would be used this hash instead of actual file shape text for detecting changes + */ + createHash?: (data: string) => string; + /** + * When emit or emitNextAffectedFile are called without writeFile, + * this callback if present would be used to write files + */ + writeFile?: WriteFileCallback; + } + + /** + * Builder to manage the program state changes + */ + export interface BuilderProgram { + /*@internal*/ + getState(): ReusableBuilderProgramState; + /*@internal*/ + backupState(): void; + /*@internal*/ + restoreState(): void; + /** + * Returns current program + */ + getProgram(): Program; + /** + * Returns current program that could be undefined if the program was released + */ + /*@internal*/ + getProgramOrUndefined(): Program | undefined; + /** + * Releases reference to the program, making all the other operations that need program to fail. + */ + /*@internal*/ + releaseProgram(): void; + /** + * Get compiler options of the program + */ + getCompilerOptions(): CompilerOptions; + /** + * Get the source file in the program with file name + */ + getSourceFile(fileName: string): SourceFile | undefined; + /** + * Get a list of files in the program + */ + getSourceFiles(): readonly SourceFile[]; + /** + * Get the diagnostics for compiler options + */ + getOptionsDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; + /** + * Get the diagnostics that dont belong to any file + */ + getGlobalDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; + /** + * Get the diagnostics from config file parsing + */ + getConfigFileParsingDiagnostics(): readonly Diagnostic[]; + /** + * Get the syntax diagnostics, for all source files if source file is not supplied + */ + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; + /** + * Get the declaration diagnostics, for all source files if source file is not supplied + */ + getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[]; + /** + * Get all the dependencies of the file + */ + getAllDependencies(sourceFile: SourceFile): readonly string[]; + + /** + * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program + * The semantic diagnostics are cached and managed here + * Note that it is assumed that when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics + * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, + * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics + */ + getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; + /** + * Emits the JavaScript and declaration files. + * When targetSource file is specified, emits the files corresponding to that source file, + * otherwise for the whole program. + * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, + * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, + * it will only emit all the affected files instead of whole program + * + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files + */ + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; + /** + * Get the current directory of the program + */ + getCurrentDirectory(): string; + } + + /** + * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files + */ + export interface SemanticDiagnosticsBuilderProgram extends BuilderProgram { + /** + * Gets the semantic diagnostics from the program for the next affected file and caches it + * Returns undefined if the iteration is complete + */ + getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult; + } + + /** + * The builder that can handle the changes in program and iterate through changed file to emit the files + * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files + */ + export interface EmitAndSemanticDiagnosticsBuilderProgram extends SemanticDiagnosticsBuilderProgram { + /** + * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files + */ + emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult; + } + + /** + * Create the builder to manage semantic diagnostics and cache them + */ + export function createSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): SemanticDiagnosticsBuilderProgram; + export function createSemanticDiagnosticsBuilderProgram(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): SemanticDiagnosticsBuilderProgram; + export function createSemanticDiagnosticsBuilderProgram(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | SemanticDiagnosticsBuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]) { + return createBuilderProgram(BuilderProgramKind.SemanticDiagnosticsBuilderProgram, getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences)); + } + + /** + * Create the builder that can handle the changes in program and iterate through changed files + * to emit the those files and manage semantic diagnostics cache as well + */ + export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): EmitAndSemanticDiagnosticsBuilderProgram; + export function createEmitAndSemanticDiagnosticsBuilderProgram(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): EmitAndSemanticDiagnosticsBuilderProgram; + export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]) { + return createBuilderProgram(BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences)); + } + + /** + * Creates a builder thats just abstraction over program and can be used with watch + */ + export function createAbstractBuilder(newProgram: Program, host: BuilderProgramHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): BuilderProgram; + export function createAbstractBuilder(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderProgram; + export function createAbstractBuilder(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | BuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderProgram { + const { newProgram, configFileParsingDiagnostics: newConfigFileParsingDiagnostics } = getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences); + return createRedirectedBuilderProgram({ program: newProgram, compilerOptions: newProgram.getCompilerOptions() }, newConfigFileParsingDiagnostics); + } +} diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index b34a0bf8370..bb2272cff8e 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -1,17 +1,3 @@ -namespace ts { - export interface EmitOutput { - outputFiles: OutputFile[]; - emitSkipped: boolean; - /* @internal */ exportedModulesFromDeclarationEmit?: ExportedModulesFromDeclarationEmit; - } - - export interface OutputFile { - name: string; - writeByteOrderMark: boolean; - text: string; - } -} - /*@internal*/ namespace ts { export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, @@ -75,507 +61,506 @@ namespace ts { allFileNames?: readonly string[]; } + export namespace BuilderState { + /** + * Information about the source file: Its version and optional signature from last emit + */ + export interface FileInfo { + readonly version: string; + signature: string | undefined; + } + /** + * Referenced files with values for the keys as referenced file's path to be true + */ + export type ReferencedSet = ReadonlyMap; + /** + * Compute the hash to store the shape of the file + */ + export type ComputeHash = (data: string) => string; + + /** + * Exported modules to from declaration emit being computed. + * This can contain false in the affected file path to specify that there are no exported module(types from other modules) for this file + */ + export type ComputingExportedModulesMap = Map; + + /** + * Get the referencedFile from the imported module symbol + */ + function getReferencedFileFromImportedModuleSymbol(symbol: Symbol) { + if (symbol.declarations && symbol.declarations[0]) { + const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]); + return declarationSourceFile && declarationSourceFile.resolvedPath; + } + } + + /** + * Get the referencedFile from the import name node from file + */ + function getReferencedFileFromImportLiteral(checker: TypeChecker, importName: StringLiteralLike) { + const symbol = checker.getSymbolAtLocation(importName); + return symbol && getReferencedFileFromImportedModuleSymbol(symbol); + } + + /** + * Gets the path to reference file from file name, it could be resolvedPath if present otherwise path + */ + function getReferencedFileFromFileName(program: Program, fileName: string, sourceFileDirectory: Path, getCanonicalFileName: GetCanonicalFileName): Path { + return toPath(program.getProjectReferenceRedirect(fileName) || fileName, sourceFileDirectory, getCanonicalFileName); + } + + /** + * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true + */ + function getReferencedFiles(program: Program, sourceFile: SourceFile, getCanonicalFileName: GetCanonicalFileName): Map | undefined { + let referencedFiles: Map | undefined; + + // We need to use a set here since the code can contain the same import twice, + // but that will only be one dependency. + // To avoid invernal conversion, the key of the referencedFiles map must be of type Path + if (sourceFile.imports && sourceFile.imports.length > 0) { + const checker: TypeChecker = program.getTypeChecker(); + for (const importName of sourceFile.imports) { + const declarationSourceFilePath = getReferencedFileFromImportLiteral(checker, importName); + if (declarationSourceFilePath) { + addReferencedFile(declarationSourceFilePath); + } + } + } + + const sourceFileDirectory = getDirectoryPath(sourceFile.path); + // Handle triple slash references + if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { + for (const referencedFile of sourceFile.referencedFiles) { + const referencedPath = getReferencedFileFromFileName(program, referencedFile.fileName, sourceFileDirectory, getCanonicalFileName); + addReferencedFile(referencedPath); + } + } + + // Handle type reference directives + if (sourceFile.resolvedTypeReferenceDirectiveNames) { + sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => { + if (!resolvedTypeReferenceDirective) { + return; + } + + const fileName = resolvedTypeReferenceDirective.resolvedFileName!; // TODO: GH#18217 + const typeFilePath = getReferencedFileFromFileName(program, fileName, sourceFileDirectory, getCanonicalFileName); + addReferencedFile(typeFilePath); + }); + } + + // Add module augmentation as references + if (sourceFile.moduleAugmentations.length) { + const checker = program.getTypeChecker(); + for (const moduleName of sourceFile.moduleAugmentations) { + if (!isStringLiteral(moduleName)) { continue; } + const symbol = checker.getSymbolAtLocation(moduleName); + if (!symbol) { continue; } + + // Add any file other than our own as reference + addReferenceFromAmbientModule(symbol); + } + } + + // From ambient modules + for (const ambientModule of program.getTypeChecker().getAmbientModules()) { + if (ambientModule.declarations.length > 1) { + addReferenceFromAmbientModule(ambientModule); + } + } + + return referencedFiles; + + function addReferenceFromAmbientModule(symbol: Symbol) { + // Add any file other than our own as reference + for (const declaration of symbol.declarations) { + const declarationSourceFile = getSourceFileOfNode(declaration); + if (declarationSourceFile && + declarationSourceFile !== sourceFile) { + addReferencedFile(declarationSourceFile.resolvedPath); + } + } + } + + function addReferencedFile(referencedPath: Path) { + if (!referencedFiles) { + referencedFiles = createMap(); + } + referencedFiles.set(referencedPath, true); + } + } + + /** + * Returns true if oldState is reusable, that is the emitKind = module/non module has not changed + */ + export function canReuseOldState(newReferencedMap: ReadonlyMap | undefined, oldState: Readonly | undefined) { + return oldState && !oldState.referencedMap === !newReferencedMap; + } + + /** + * Creates the state of file references and signature for the new program from oldState if it is safe + */ + export function create(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly): BuilderState { + const fileInfos = createMap(); + const referencedMap = newProgram.getCompilerOptions().module !== ModuleKind.None ? createMap() : undefined; + const exportedModulesMap = referencedMap ? createMap() : undefined; + const hasCalledUpdateShapeSignature = createMap(); + const useOldState = canReuseOldState(referencedMap, oldState); + + // Create the reference map, and set the file infos + for (const sourceFile of newProgram.getSourceFiles()) { + const version = Debug.assertDefined(sourceFile.version, "Program intended to be used with Builder should have source files with versions set"); + const oldInfo = useOldState ? oldState!.fileInfos.get(sourceFile.path) : undefined; + if (referencedMap) { + const newReferences = getReferencedFiles(newProgram, sourceFile, getCanonicalFileName); + if (newReferences) { + referencedMap.set(sourceFile.path, newReferences); + } + // Copy old visible to outside files map + if (useOldState) { + const exportedModules = oldState!.exportedModulesMap!.get(sourceFile.path); + if (exportedModules) { + exportedModulesMap!.set(sourceFile.path, exportedModules); + } + } + } + fileInfos.set(sourceFile.path, { version, signature: oldInfo && oldInfo.signature }); + } + + return { + fileInfos, + referencedMap, + exportedModulesMap, + hasCalledUpdateShapeSignature + }; + } + + /** + * Releases needed properties + */ + export function releaseCache(state: BuilderState) { + state.allFilesExcludingDefaultLibraryFile = undefined; + state.allFileNames = undefined; + } + + /** + * Creates a clone of the state + */ + export function clone(state: Readonly): BuilderState { + const fileInfos = createMap(); + state.fileInfos.forEach((value, key) => { + fileInfos.set(key, { ...value }); + }); + // Dont need to backup allFiles info since its cache anyway + return { + fileInfos, + referencedMap: cloneMapOrUndefined(state.referencedMap), + exportedModulesMap: cloneMapOrUndefined(state.exportedModulesMap), + hasCalledUpdateShapeSignature: cloneMap(state.hasCalledUpdateShapeSignature), + }; + } + + /** + * Gets the files affected by the path from the program + */ + export function getFilesAffectedBy(state: BuilderState, programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, cacheToUpdateSignature?: Map, exportedModulesMapCache?: ComputingExportedModulesMap): readonly SourceFile[] { + // Since the operation could be cancelled, the signatures are always stored in the cache + // They will be committed once it is safe to use them + // eg when calling this api from tsserver, if there is no cancellation of the operation + // In the other cases the affected files signatures are committed only after the iteration through the result is complete + const signatureCache = cacheToUpdateSignature || createMap(); + const sourceFile = programOfThisState.getSourceFileByPath(path); + if (!sourceFile) { + return emptyArray; + } + + if (!updateShapeSignature(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash, exportedModulesMapCache)) { + return [sourceFile]; + } + + const result = (state.referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit)(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash, exportedModulesMapCache); + if (!cacheToUpdateSignature) { + // Commit all the signatures in the signature cache + updateSignaturesFromCache(state, signatureCache); + } + return result; + } + + /** + * Updates the signatures from the cache into state's fileinfo signatures + * This should be called whenever it is safe to commit the state of the builder + */ + export function updateSignaturesFromCache(state: BuilderState, signatureCache: Map) { + signatureCache.forEach((signature, path) => { + state.fileInfos.get(path)!.signature = signature; + state.hasCalledUpdateShapeSignature.set(path, true); + }); + } + + /** + * Returns if the shape of the signature has changed since last emit + */ + export function updateShapeSignature(state: Readonly, programOfThisState: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, exportedModulesMapCache?: ComputingExportedModulesMap) { + Debug.assert(!!sourceFile); + Debug.assert(!exportedModulesMapCache || !!state.exportedModulesMap, "Compute visible to outside map only if visibleToOutsideReferencedMap present in the state"); + + // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate + if (state.hasCalledUpdateShapeSignature.has(sourceFile.path) || cacheToUpdateSignature.has(sourceFile.path)) { + return false; + } + + const info = state.fileInfos.get(sourceFile.path); + if (!info) return Debug.fail(); + + const prevSignature = info.signature; + let latestSignature: string; + if (sourceFile.isDeclarationFile) { + latestSignature = sourceFile.version; + if (exportedModulesMapCache && latestSignature !== prevSignature) { + // All the references in this file are exported + const references = state.referencedMap ? state.referencedMap.get(sourceFile.path) : undefined; + exportedModulesMapCache.set(sourceFile.path, references || false); + } + } + else { + const emitOutput = getFileEmitOutput( + programOfThisState, + sourceFile, + /*emitOnlyDtsFiles*/ true, + cancellationToken, + /*customTransformers*/ undefined, + /*forceDtsEmit*/ true + ); + const firstDts = emitOutput.outputFiles && + programOfThisState.getCompilerOptions().declarationMap ? + emitOutput.outputFiles.length > 1 ? emitOutput.outputFiles[1] : undefined : + emitOutput.outputFiles.length > 0 ? emitOutput.outputFiles[0] : undefined; + if (firstDts) { + Debug.assert(fileExtensionIs(firstDts.name, Extension.Dts), "File extension for signature expected to be dts", () => `Found: ${getAnyExtensionFromPath(firstDts.name)} for ${firstDts.name}:: All output files: ${JSON.stringify(emitOutput.outputFiles.map(f => f.name))}`); + latestSignature = computeHash(firstDts.text); + if (exportedModulesMapCache && latestSignature !== prevSignature) { + updateExportedModules(sourceFile, emitOutput.exportedModulesFromDeclarationEmit, exportedModulesMapCache); + } + } + else { + latestSignature = prevSignature!; // TODO: GH#18217 + } + + } + cacheToUpdateSignature.set(sourceFile.path, latestSignature); + + return !prevSignature || latestSignature !== prevSignature; + } + + /** + * Coverts the declaration emit result into exported modules map + */ + function updateExportedModules(sourceFile: SourceFile, exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined, exportedModulesMapCache: ComputingExportedModulesMap) { + if (!exportedModulesFromDeclarationEmit) { + exportedModulesMapCache.set(sourceFile.path, false); + return; + } + + let exportedModules: Map | undefined; + exportedModulesFromDeclarationEmit.forEach(symbol => addExportedModule(getReferencedFileFromImportedModuleSymbol(symbol))); + exportedModulesMapCache.set(sourceFile.path, exportedModules || false); + + function addExportedModule(exportedModulePath: Path | undefined) { + if (exportedModulePath) { + if (!exportedModules) { + exportedModules = createMap(); + } + exportedModules.set(exportedModulePath, true); + } + } + } + + /** + * Updates the exported modules from cache into state's exported modules map + * This should be called whenever it is safe to commit the state of the builder + */ + export function updateExportedFilesMapFromCache(state: BuilderState, exportedModulesMapCache: ComputingExportedModulesMap | undefined) { + if (exportedModulesMapCache) { + Debug.assert(!!state.exportedModulesMap); + exportedModulesMapCache.forEach((exportedModules, path) => { + if (exportedModules) { + state.exportedModulesMap!.set(path, exportedModules); + } + else { + state.exportedModulesMap!.delete(path); + } + }); + } + } + + /** + * Get all the dependencies of the sourceFile + */ + export function getAllDependencies(state: BuilderState, programOfThisState: Program, sourceFile: SourceFile): readonly string[] { + const compilerOptions = programOfThisState.getCompilerOptions(); + // With --out or --outFile all outputs go into single file, all files depend on each other + if (compilerOptions.outFile || compilerOptions.out) { + return getAllFileNames(state, programOfThisState); + } + + // If this is non module emit, or its a global file, it depends on all the source files + if (!state.referencedMap || isFileAffectingGlobalScope(sourceFile)) { + return getAllFileNames(state, programOfThisState); + } + + // Get the references, traversing deep from the referenceMap + const seenMap = createMap(); + const queue = [sourceFile.path]; + while (queue.length) { + const path = queue.pop()!; + if (!seenMap.has(path)) { + seenMap.set(path, true); + const references = state.referencedMap.get(path); + if (references) { + const iterator = references.keys(); + for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) { + queue.push(iterResult.value as Path); + } + } + } + } + + return arrayFrom(mapDefinedIterator(seenMap.keys(), path => { + const file = programOfThisState.getSourceFileByPath(path as Path); + return file ? file.fileName : path; + })); + } + + /** + * Gets the names of all files from the program + */ + function getAllFileNames(state: BuilderState, programOfThisState: Program): readonly string[] { + if (!state.allFileNames) { + const sourceFiles = programOfThisState.getSourceFiles(); + state.allFileNames = sourceFiles === emptyArray ? emptyArray : sourceFiles.map(file => file.fileName); + } + return state.allFileNames; + } + + /** + * Gets the files referenced by the the file path + */ + export function getReferencedByPaths(state: Readonly, referencedFilePath: Path) { + return arrayFrom(mapDefinedIterator(state.referencedMap!.entries(), ([filePath, referencesInFile]) => + referencesInFile.has(referencedFilePath) ? filePath as Path : undefined + )); + } + + /** + * For script files that contains only ambient external modules, although they are not actually external module files, + * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, + * there are no point to rebuild all script files if these special files have changed. However, if any statement + * in the file is not ambient external module, we treat it as a regular script file. + */ + function containsOnlyAmbientModules(sourceFile: SourceFile) { + for (const statement of sourceFile.statements) { + if (!isModuleWithStringLiteralName(statement)) { + return false; + } + } + return true; + } + + /** + * Return true if file contains anything that augments to global scope we need to build them as if + * they are global files as well as module + */ + function containsGlobalScopeAugmentation(sourceFile: SourceFile) { + return some(sourceFile.moduleAugmentations, augmentation => isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)); + } + + /** + * Return true if the file will invalidate all files because it affectes global scope + */ + function isFileAffectingGlobalScope(sourceFile: SourceFile) { + return containsGlobalScopeAugmentation(sourceFile) || + !isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile); + } + + /** + * Gets all files of the program excluding the default library file + */ + function getAllFilesExcludingDefaultLibraryFile(state: BuilderState, programOfThisState: Program, firstSourceFile: SourceFile): readonly SourceFile[] { + // Use cached result + if (state.allFilesExcludingDefaultLibraryFile) { + return state.allFilesExcludingDefaultLibraryFile; + } + + let result: SourceFile[] | undefined; + addSourceFile(firstSourceFile); + for (const sourceFile of programOfThisState.getSourceFiles()) { + if (sourceFile !== firstSourceFile) { + addSourceFile(sourceFile); + } + } + state.allFilesExcludingDefaultLibraryFile = result || emptyArray; + return state.allFilesExcludingDefaultLibraryFile; + + function addSourceFile(sourceFile: SourceFile) { + if (!programOfThisState.isSourceFileDefaultLibrary(sourceFile)) { + (result || (result = [])).push(sourceFile); + } + } + } + + /** + * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed + */ + function getFilesAffectedByUpdatedShapeWhenNonModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { + const compilerOptions = programOfThisState.getCompilerOptions(); + // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, + // so returning the file itself is good enough. + if (compilerOptions && (compilerOptions.out || compilerOptions.outFile)) { + return [sourceFileWithUpdatedShape]; + } + return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); + } + + /** + * When program emits modular code, gets the files affected by the sourceFile whose shape has changed + */ + function getFilesAffectedByUpdatedShapeWhenModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash | undefined, exportedModulesMapCache: ComputingExportedModulesMap | undefined) { + if (isFileAffectingGlobalScope(sourceFileWithUpdatedShape)) { + return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); + } + + const compilerOptions = programOfThisState.getCompilerOptions(); + if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) { + return [sourceFileWithUpdatedShape]; + } + + // Now we need to if each file in the referencedBy list has a shape change as well. + // Because if so, its own referencedBy files need to be saved as well to make the + // emitting result consistent with files on disk. + const seenFileNamesMap = createMap(); + + // Start with the paths this file was referenced by + seenFileNamesMap.set(sourceFileWithUpdatedShape.path, sourceFileWithUpdatedShape); + const queue = getReferencedByPaths(state, sourceFileWithUpdatedShape.resolvedPath); + while (queue.length > 0) { + const currentPath = queue.pop()!; + if (!seenFileNamesMap.has(currentPath)) { + const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath)!; + seenFileNamesMap.set(currentPath, currentSourceFile); + if (currentSourceFile && updateShapeSignature(state, programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken, computeHash!, exportedModulesMapCache)) { // TODO: GH#18217 + queue.push(...getReferencedByPaths(state, currentSourceFile.resolvedPath)); + } + } + } + + // Return array of values that needs emit + // Return array of values that needs emit + return arrayFrom(mapDefinedIterator(seenFileNamesMap.values(), value => value)); + } + } + export function cloneMapOrUndefined(map: ReadonlyMap | undefined) { return map ? cloneMap(map) : undefined; } } - -/*@internal*/ -namespace ts.BuilderState { - /** - * Information about the source file: Its version and optional signature from last emit - */ - export interface FileInfo { - readonly version: string; - signature: string | undefined; - } - /** - * Referenced files with values for the keys as referenced file's path to be true - */ - export type ReferencedSet = ReadonlyMap; - /** - * Compute the hash to store the shape of the file - */ - export type ComputeHash = (data: string) => string; - - /** - * Exported modules to from declaration emit being computed. - * This can contain false in the affected file path to specify that there are no exported module(types from other modules) for this file - */ - export type ComputingExportedModulesMap = Map; - - /** - * Get the referencedFile from the imported module symbol - */ - function getReferencedFileFromImportedModuleSymbol(symbol: Symbol) { - if (symbol.declarations && symbol.declarations[0]) { - const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]); - return declarationSourceFile && declarationSourceFile.resolvedPath; - } - } - - /** - * Get the referencedFile from the import name node from file - */ - function getReferencedFileFromImportLiteral(checker: TypeChecker, importName: StringLiteralLike) { - const symbol = checker.getSymbolAtLocation(importName); - return symbol && getReferencedFileFromImportedModuleSymbol(symbol); - } - - /** - * Gets the path to reference file from file name, it could be resolvedPath if present otherwise path - */ - function getReferencedFileFromFileName(program: Program, fileName: string, sourceFileDirectory: Path, getCanonicalFileName: GetCanonicalFileName): Path { - return toPath(program.getProjectReferenceRedirect(fileName) || fileName, sourceFileDirectory, getCanonicalFileName); - } - - /** - * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true - */ - function getReferencedFiles(program: Program, sourceFile: SourceFile, getCanonicalFileName: GetCanonicalFileName): Map | undefined { - let referencedFiles: Map | undefined; - - // We need to use a set here since the code can contain the same import twice, - // but that will only be one dependency. - // To avoid invernal conversion, the key of the referencedFiles map must be of type Path - if (sourceFile.imports && sourceFile.imports.length > 0) { - const checker: TypeChecker = program.getTypeChecker(); - for (const importName of sourceFile.imports) { - const declarationSourceFilePath = getReferencedFileFromImportLiteral(checker, importName); - if (declarationSourceFilePath) { - addReferencedFile(declarationSourceFilePath); - } - } - } - - const sourceFileDirectory = getDirectoryPath(sourceFile.path); - // Handle triple slash references - if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { - for (const referencedFile of sourceFile.referencedFiles) { - const referencedPath = getReferencedFileFromFileName(program, referencedFile.fileName, sourceFileDirectory, getCanonicalFileName); - addReferencedFile(referencedPath); - } - } - - // Handle type reference directives - if (sourceFile.resolvedTypeReferenceDirectiveNames) { - sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => { - if (!resolvedTypeReferenceDirective) { - return; - } - - const fileName = resolvedTypeReferenceDirective.resolvedFileName!; // TODO: GH#18217 - const typeFilePath = getReferencedFileFromFileName(program, fileName, sourceFileDirectory, getCanonicalFileName); - addReferencedFile(typeFilePath); - }); - } - - // Add module augmentation as references - if (sourceFile.moduleAugmentations.length) { - const checker = program.getTypeChecker(); - for (const moduleName of sourceFile.moduleAugmentations) { - if (!isStringLiteral(moduleName)) { continue; } - const symbol = checker.getSymbolAtLocation(moduleName); - if (!symbol) { continue; } - - // Add any file other than our own as reference - addReferenceFromAmbientModule(symbol); - } - } - - // From ambient modules - for (const ambientModule of program.getTypeChecker().getAmbientModules()) { - if (ambientModule.declarations.length > 1) { - addReferenceFromAmbientModule(ambientModule); - } - } - - return referencedFiles; - - function addReferenceFromAmbientModule(symbol: Symbol) { - // Add any file other than our own as reference - for (const declaration of symbol.declarations) { - const declarationSourceFile = getSourceFileOfNode(declaration); - if (declarationSourceFile && - declarationSourceFile !== sourceFile) { - addReferencedFile(declarationSourceFile.resolvedPath); - } - } - } - - function addReferencedFile(referencedPath: Path) { - if (!referencedFiles) { - referencedFiles = createMap(); - } - referencedFiles.set(referencedPath, true); - } - } - - /** - * Returns true if oldState is reusable, that is the emitKind = module/non module has not changed - */ - export function canReuseOldState(newReferencedMap: ReadonlyMap | undefined, oldState: Readonly | undefined) { - return oldState && !oldState.referencedMap === !newReferencedMap; - } - - /** - * Creates the state of file references and signature for the new program from oldState if it is safe - */ - export function create(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly): BuilderState { - const fileInfos = createMap(); - const referencedMap = newProgram.getCompilerOptions().module !== ModuleKind.None ? createMap() : undefined; - const exportedModulesMap = referencedMap ? createMap() : undefined; - const hasCalledUpdateShapeSignature = createMap(); - const useOldState = canReuseOldState(referencedMap, oldState); - - // Create the reference map, and set the file infos - for (const sourceFile of newProgram.getSourceFiles()) { - const version = Debug.assertDefined(sourceFile.version, "Program intended to be used with Builder should have source files with versions set"); - const oldInfo = useOldState ? oldState!.fileInfos.get(sourceFile.path) : undefined; - if (referencedMap) { - const newReferences = getReferencedFiles(newProgram, sourceFile, getCanonicalFileName); - if (newReferences) { - referencedMap.set(sourceFile.path, newReferences); - } - // Copy old visible to outside files map - if (useOldState) { - const exportedModules = oldState!.exportedModulesMap!.get(sourceFile.path); - if (exportedModules) { - exportedModulesMap!.set(sourceFile.path, exportedModules); - } - } - } - fileInfos.set(sourceFile.path, { version, signature: oldInfo && oldInfo.signature }); - } - - return { - fileInfos, - referencedMap, - exportedModulesMap, - hasCalledUpdateShapeSignature - }; - } - - /** - * Releases needed properties - */ - export function releaseCache(state: BuilderState) { - state.allFilesExcludingDefaultLibraryFile = undefined; - state.allFileNames = undefined; - } - - /** - * Creates a clone of the state - */ - export function clone(state: Readonly): BuilderState { - const fileInfos = createMap(); - state.fileInfos.forEach((value, key) => { - fileInfos.set(key, { ...value }); - }); - // Dont need to backup allFiles info since its cache anyway - return { - fileInfos, - referencedMap: cloneMapOrUndefined(state.referencedMap), - exportedModulesMap: cloneMapOrUndefined(state.exportedModulesMap), - hasCalledUpdateShapeSignature: cloneMap(state.hasCalledUpdateShapeSignature), - }; - } - - /** - * Gets the files affected by the path from the program - */ - export function getFilesAffectedBy(state: BuilderState, programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, cacheToUpdateSignature?: Map, exportedModulesMapCache?: ComputingExportedModulesMap): readonly SourceFile[] { - // Since the operation could be cancelled, the signatures are always stored in the cache - // They will be committed once it is safe to use them - // eg when calling this api from tsserver, if there is no cancellation of the operation - // In the other cases the affected files signatures are committed only after the iteration through the result is complete - const signatureCache = cacheToUpdateSignature || createMap(); - const sourceFile = programOfThisState.getSourceFileByPath(path); - if (!sourceFile) { - return emptyArray; - } - - if (!updateShapeSignature(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash, exportedModulesMapCache)) { - return [sourceFile]; - } - - const result = (state.referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit)(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash, exportedModulesMapCache); - if (!cacheToUpdateSignature) { - // Commit all the signatures in the signature cache - updateSignaturesFromCache(state, signatureCache); - } - return result; - } - - /** - * Updates the signatures from the cache into state's fileinfo signatures - * This should be called whenever it is safe to commit the state of the builder - */ - export function updateSignaturesFromCache(state: BuilderState, signatureCache: Map) { - signatureCache.forEach((signature, path) => { - state.fileInfos.get(path)!.signature = signature; - state.hasCalledUpdateShapeSignature.set(path, true); - }); - } - - /** - * Returns if the shape of the signature has changed since last emit - */ - export function updateShapeSignature(state: Readonly, programOfThisState: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, exportedModulesMapCache?: ComputingExportedModulesMap) { - Debug.assert(!!sourceFile); - Debug.assert(!exportedModulesMapCache || !!state.exportedModulesMap, "Compute visible to outside map only if visibleToOutsideReferencedMap present in the state"); - - // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate - if (state.hasCalledUpdateShapeSignature.has(sourceFile.path) || cacheToUpdateSignature.has(sourceFile.path)) { - return false; - } - - const info = state.fileInfos.get(sourceFile.path); - if (!info) return Debug.fail(); - - const prevSignature = info.signature; - let latestSignature: string; - if (sourceFile.isDeclarationFile) { - latestSignature = sourceFile.version; - if (exportedModulesMapCache && latestSignature !== prevSignature) { - // All the references in this file are exported - const references = state.referencedMap ? state.referencedMap.get(sourceFile.path) : undefined; - exportedModulesMapCache.set(sourceFile.path, references || false); - } - } - else { - const emitOutput = getFileEmitOutput( - programOfThisState, - sourceFile, - /*emitOnlyDtsFiles*/ true, - cancellationToken, - /*customTransformers*/ undefined, - /*forceDtsEmit*/ true - ); - const firstDts = emitOutput.outputFiles && - programOfThisState.getCompilerOptions().declarationMap ? - emitOutput.outputFiles.length > 1 ? emitOutput.outputFiles[1] : undefined : - emitOutput.outputFiles.length > 0 ? emitOutput.outputFiles[0] : undefined; - if (firstDts) { - Debug.assert(fileExtensionIs(firstDts.name, Extension.Dts), "File extension for signature expected to be dts", () => `Found: ${getAnyExtensionFromPath(firstDts.name)} for ${firstDts.name}:: All output files: ${JSON.stringify(emitOutput.outputFiles.map(f => f.name))}`); - latestSignature = computeHash(firstDts.text); - if (exportedModulesMapCache && latestSignature !== prevSignature) { - updateExportedModules(sourceFile, emitOutput.exportedModulesFromDeclarationEmit, exportedModulesMapCache); - } - } - else { - latestSignature = prevSignature!; // TODO: GH#18217 - } - - } - cacheToUpdateSignature.set(sourceFile.path, latestSignature); - - return !prevSignature || latestSignature !== prevSignature; - } - - /** - * Coverts the declaration emit result into exported modules map - */ - function updateExportedModules(sourceFile: SourceFile, exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined, exportedModulesMapCache: ComputingExportedModulesMap) { - if (!exportedModulesFromDeclarationEmit) { - exportedModulesMapCache.set(sourceFile.path, false); - return; - } - - let exportedModules: Map | undefined; - exportedModulesFromDeclarationEmit.forEach(symbol => addExportedModule(getReferencedFileFromImportedModuleSymbol(symbol))); - exportedModulesMapCache.set(sourceFile.path, exportedModules || false); - - function addExportedModule(exportedModulePath: Path | undefined) { - if (exportedModulePath) { - if (!exportedModules) { - exportedModules = createMap(); - } - exportedModules.set(exportedModulePath, true); - } - } - } - - /** - * Updates the exported modules from cache into state's exported modules map - * This should be called whenever it is safe to commit the state of the builder - */ - export function updateExportedFilesMapFromCache(state: BuilderState, exportedModulesMapCache: ComputingExportedModulesMap | undefined) { - if (exportedModulesMapCache) { - Debug.assert(!!state.exportedModulesMap); - exportedModulesMapCache.forEach((exportedModules, path) => { - if (exportedModules) { - state.exportedModulesMap!.set(path, exportedModules); - } - else { - state.exportedModulesMap!.delete(path); - } - }); - } - } - - /** - * Get all the dependencies of the sourceFile - */ - export function getAllDependencies(state: BuilderState, programOfThisState: Program, sourceFile: SourceFile): readonly string[] { - const compilerOptions = programOfThisState.getCompilerOptions(); - // With --out or --outFile all outputs go into single file, all files depend on each other - if (compilerOptions.outFile || compilerOptions.out) { - return getAllFileNames(state, programOfThisState); - } - - // If this is non module emit, or its a global file, it depends on all the source files - if (!state.referencedMap || isFileAffectingGlobalScope(sourceFile)) { - return getAllFileNames(state, programOfThisState); - } - - // Get the references, traversing deep from the referenceMap - const seenMap = createMap(); - const queue = [sourceFile.path]; - while (queue.length) { - const path = queue.pop()!; - if (!seenMap.has(path)) { - seenMap.set(path, true); - const references = state.referencedMap.get(path); - if (references) { - const iterator = references.keys(); - for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) { - queue.push(iterResult.value as Path); - } - } - } - } - - return arrayFrom(mapDefinedIterator(seenMap.keys(), path => { - const file = programOfThisState.getSourceFileByPath(path as Path); - return file ? file.fileName : path; - })); - } - - /** - * Gets the names of all files from the program - */ - function getAllFileNames(state: BuilderState, programOfThisState: Program): readonly string[] { - if (!state.allFileNames) { - const sourceFiles = programOfThisState.getSourceFiles(); - state.allFileNames = sourceFiles === emptyArray ? emptyArray : sourceFiles.map(file => file.fileName); - } - return state.allFileNames; - } - - /** - * Gets the files referenced by the the file path - */ - export function getReferencedByPaths(state: Readonly, referencedFilePath: Path) { - return arrayFrom(mapDefinedIterator(state.referencedMap!.entries(), ([filePath, referencesInFile]) => - referencesInFile.has(referencedFilePath) ? filePath as Path : undefined - )); - } - - /** - * For script files that contains only ambient external modules, although they are not actually external module files, - * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, - * there are no point to rebuild all script files if these special files have changed. However, if any statement - * in the file is not ambient external module, we treat it as a regular script file. - */ - function containsOnlyAmbientModules(sourceFile: SourceFile) { - for (const statement of sourceFile.statements) { - if (!isModuleWithStringLiteralName(statement)) { - return false; - } - } - return true; - } - - /** - * Return true if file contains anything that augments to global scope we need to build them as if - * they are global files as well as module - */ - function containsGlobalScopeAugmentation(sourceFile: SourceFile) { - return some(sourceFile.moduleAugmentations, augmentation => isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)); - } - - /** - * Return true if the file will invalidate all files because it affectes global scope - */ - function isFileAffectingGlobalScope(sourceFile: SourceFile) { - return containsGlobalScopeAugmentation(sourceFile) || - !isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile); - } - - /** - * Gets all files of the program excluding the default library file - */ - function getAllFilesExcludingDefaultLibraryFile(state: BuilderState, programOfThisState: Program, firstSourceFile: SourceFile): readonly SourceFile[] { - // Use cached result - if (state.allFilesExcludingDefaultLibraryFile) { - return state.allFilesExcludingDefaultLibraryFile; - } - - let result: SourceFile[] | undefined; - addSourceFile(firstSourceFile); - for (const sourceFile of programOfThisState.getSourceFiles()) { - if (sourceFile !== firstSourceFile) { - addSourceFile(sourceFile); - } - } - state.allFilesExcludingDefaultLibraryFile = result || emptyArray; - return state.allFilesExcludingDefaultLibraryFile; - - function addSourceFile(sourceFile: SourceFile) { - if (!programOfThisState.isSourceFileDefaultLibrary(sourceFile)) { - (result || (result = [])).push(sourceFile); - } - } - } - - /** - * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed - */ - function getFilesAffectedByUpdatedShapeWhenNonModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { - const compilerOptions = programOfThisState.getCompilerOptions(); - // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, - // so returning the file itself is good enough. - if (compilerOptions && (compilerOptions.out || compilerOptions.outFile)) { - return [sourceFileWithUpdatedShape]; - } - return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); - } - - /** - * When program emits modular code, gets the files affected by the sourceFile whose shape has changed - */ - function getFilesAffectedByUpdatedShapeWhenModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash | undefined, exportedModulesMapCache: ComputingExportedModulesMap | undefined) { - if (isFileAffectingGlobalScope(sourceFileWithUpdatedShape)) { - return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); - } - - const compilerOptions = programOfThisState.getCompilerOptions(); - if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) { - return [sourceFileWithUpdatedShape]; - } - - // Now we need to if each file in the referencedBy list has a shape change as well. - // Because if so, its own referencedBy files need to be saved as well to make the - // emitting result consistent with files on disk. - const seenFileNamesMap = createMap(); - - // Start with the paths this file was referenced by - seenFileNamesMap.set(sourceFileWithUpdatedShape.path, sourceFileWithUpdatedShape); - const queue = getReferencedByPaths(state, sourceFileWithUpdatedShape.resolvedPath); - while (queue.length > 0) { - const currentPath = queue.pop()!; - if (!seenFileNamesMap.has(currentPath)) { - const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath)!; - seenFileNamesMap.set(currentPath, currentSourceFile); - if (currentSourceFile && updateShapeSignature(state, programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken, computeHash!, exportedModulesMapCache)) { // TODO: GH#18217 - queue.push(...getReferencedByPaths(state, currentSourceFile.resolvedPath)); - } - } - } - - // Return array of values that needs emit - // Return array of values that needs emit - return arrayFrom(mapDefinedIterator(seenFileNamesMap.values(), value => value)); - } -} diff --git a/src/compiler/builderStatePublic.ts b/src/compiler/builderStatePublic.ts new file mode 100644 index 00000000000..2e70063f90a --- /dev/null +++ b/src/compiler/builderStatePublic.ts @@ -0,0 +1,13 @@ +namespace ts { + export interface EmitOutput { + outputFiles: OutputFile[]; + emitSkipped: boolean; + /* @internal */ exportedModulesFromDeclarationEmit?: ExportedModulesFromDeclarationEmit; + } + + export interface OutputFile { + name: string; + writeByteOrderMark: boolean; + text: string; + } +} \ No newline at end of file diff --git a/src/compiler/core.ts b/src/compiler/core.ts index d03b7e1c85c..63c58b8d6f9 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1,95 +1,22 @@ -namespace ts { - // WARNING: The script `configureNightly.ts` uses a regexp to parse out these values. - // If changing the text in this section, be sure to test `configureNightly` too. - export const versionMajorMinor = "3.8"; - /** The version of the TypeScript compiler release */ - export const version = `${versionMajorMinor}.0-dev`; -} - -namespace ts { - /** - * Type of objects whose values are all of the same type. - * The `in` and `for-in` operators can *not* be safely used, - * since `Object.prototype` may be modified by outside code. - */ - export interface MapLike { - [index: string]: T; - } - - export interface SortedReadonlyArray extends ReadonlyArray { - " __sortedArrayBrand": any; - } - - export interface SortedArray extends Array { - " __sortedArrayBrand": any; - } - - /** ES6 Map interface, only read methods included. */ - export interface ReadonlyMap { - get(key: string): T | undefined; - has(key: string): boolean; - forEach(action: (value: T, key: string) => void): void; - readonly size: number; - keys(): Iterator; - values(): Iterator; - entries(): Iterator<[string, T]>; - } - - /** ES6 Map interface. */ - export interface Map extends ReadonlyMap { - set(key: string, value: T): this; - delete(key: string): boolean; - clear(): void; - } - - /* @internal */ - export interface MapConstructor { - // eslint-disable-next-line @typescript-eslint/prefer-function-type - new (): Map; - } - - /** ES6 Iterator type. */ - export interface Iterator { - next(): { value: T, done?: false } | { value: never, done: true }; - } - - /** Array that is only intended to be pushed to, never read. */ - export interface Push { - push(...values: T[]): void; - } - - /* @internal */ - export type EqualityComparer = (a: T, b: T) => boolean; - - /* @internal */ - export type Comparer = (a: T, b: T) => Comparison; - - /* @internal */ - export const enum Comparison { - LessThan = -1, - EqualTo = 0, - GreaterThan = 1 - } -} /* @internal */ namespace ts { - // Natives - // NOTE: This must be declared in a separate block from the one below so that we don't collide with the exported definition of `Map`. - declare const Map: (new () => Map) | undefined; /** * Returns the native Map implementation if it is available and compatible (i.e. supports iteration). */ export function tryGetNativeMap(): MapConstructor | undefined { // Internet Explorer's Map doesn't support iteration, so don't use it. + // Natives + // NOTE: TS doesn't strictly allow in-line declares, but if we suppress the error, the declaration + // is still used for typechecking _and_ correctly elided, which is out goal, as this prevents us from + // needing to pollute an outer scope with a declaration of `Map` just to satisfy the checks in this function + //@ts-ignore + declare const Map: (new () => Map) | undefined; // eslint-disable-next-line no-in-operator return typeof Map !== "undefined" && "entries" in Map.prototype ? Map : undefined; } -} -/* @internal */ -namespace ts { export const emptyArray: never[] = [] as never[]; export const Map: MapConstructor = tryGetNativeMap() || (() => { @@ -2074,4 +2001,4 @@ namespace ts { } } } -} +} \ No newline at end of file diff --git a/src/compiler/corePublic.ts b/src/compiler/corePublic.ts new file mode 100644 index 00000000000..7ea17fef24a --- /dev/null +++ b/src/compiler/corePublic.ts @@ -0,0 +1,71 @@ +namespace ts { + // WARNING: The script `configureNightly.ts` uses a regexp to parse out these values. + // If changing the text in this section, be sure to test `configureNightly` too. + export const versionMajorMinor = "3.8"; + /** The version of the TypeScript compiler release */ + export const version = `${versionMajorMinor}.0-dev`; + + /** + * Type of objects whose values are all of the same type. + * The `in` and `for-in` operators can *not* be safely used, + * since `Object.prototype` may be modified by outside code. + */ + export interface MapLike { + [index: string]: T; + } + + export interface SortedReadonlyArray extends ReadonlyArray { + " __sortedArrayBrand": any; + } + + export interface SortedArray extends Array { + " __sortedArrayBrand": any; + } + + /** ES6 Map interface, only read methods included. */ + export interface ReadonlyMap { + get(key: string): T | undefined; + has(key: string): boolean; + forEach(action: (value: T, key: string) => void): void; + readonly size: number; + keys(): Iterator; + values(): Iterator; + entries(): Iterator<[string, T]>; + } + + /** ES6 Map interface. */ + export interface Map extends ReadonlyMap { + set(key: string, value: T): this; + delete(key: string): boolean; + clear(): void; + } + + /* @internal */ + export interface MapConstructor { + // eslint-disable-next-line @typescript-eslint/prefer-function-type + new (): Map; + } + + /** ES6 Iterator type. */ + export interface Iterator { + next(): { value: T, done?: false } | { value: never, done: true }; + } + + /** Array that is only intended to be pushed to, never read. */ + export interface Push { + push(...values: T[]): void; + } + + /* @internal */ + export type EqualityComparer = (a: T, b: T) => boolean; + + /* @internal */ + export type Comparer = (a: T, b: T) => Comparison; + + /* @internal */ + export const enum Comparison { + LessThan = -1, + EqualTo = 0, + GreaterThan = 1 + } +} \ No newline at end of file diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 12437d088ff..6d98ee3eca4 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1,3673 +1,3 @@ -namespace ts { - function createSynthesizedNode(kind: SyntaxKind): Node { - const node = createNode(kind, -1, -1); - node.flags |= NodeFlags.Synthesized; - return node; - } - - /* @internal */ - export function updateNode(updated: T, original: T): T { - if (updated !== original) { - setOriginalNode(updated, original); - setTextRange(updated, original); - aggregateTransformFlags(updated); - } - return updated; - } - - /* @internal */ export function createNodeArray(elements?: T[], hasTrailingComma?: boolean): MutableNodeArray; - export function createNodeArray(elements?: readonly T[], hasTrailingComma?: boolean): NodeArray; - /** - * Make `elements` into a `NodeArray`. If `elements` is `undefined`, returns an empty `NodeArray`. - */ - export function createNodeArray(elements?: readonly T[], hasTrailingComma?: boolean): NodeArray { - if (!elements || elements === emptyArray) { - elements = []; - } - else if (isNodeArray(elements)) { - return elements; - } - - const array = >elements; - array.pos = -1; - array.end = -1; - array.hasTrailingComma = hasTrailingComma; - return array; - } - - /** - * Creates a shallow, memberwise clone of a node with no source map location. - */ - /* @internal */ - export function getSynthesizedClone(node: T): T { - // We don't use "clone" from core.ts here, as we need to preserve the prototype chain of - // the original node. We also need to exclude specific properties and only include own- - // properties (to skip members already defined on the shared prototype). - - if (node === undefined) { - return node; - } - - const clone = createSynthesizedNode(node.kind); - clone.flags |= node.flags; - setOriginalNode(clone, node); - - for (const key in node) { - if (clone.hasOwnProperty(key) || !node.hasOwnProperty(key)) { - continue; - } - - (clone)[key] = (node)[key]; - } - - return clone; - } - - // Literals - - /* @internal */ export function createLiteral(value: string | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier, isSingleQuote: boolean): StringLiteral; // eslint-disable-line @typescript-eslint/unified-signatures - /* @internal */ export function createLiteral(value: string | number, isSingleQuote: boolean): StringLiteral | NumericLiteral; - /** If a node is passed, creates a string literal whose source text is read from a source node during emit. */ - export function createLiteral(value: string | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier): StringLiteral; - export function createLiteral(value: number | PseudoBigInt): NumericLiteral; - export function createLiteral(value: boolean): BooleanLiteral; - export function createLiteral(value: string | number | PseudoBigInt | boolean): PrimaryExpression; - export function createLiteral(value: string | number | PseudoBigInt | boolean | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier, isSingleQuote?: boolean): PrimaryExpression { - if (typeof value === "number") { - return createNumericLiteral(value + ""); - } - // eslint-disable-next-line no-in-operator - if (typeof value === "object" && "base10Value" in value) { // PseudoBigInt - return createBigIntLiteral(pseudoBigIntToString(value) + "n"); - } - if (typeof value === "boolean") { - return value ? createTrue() : createFalse(); - } - if (isString(value)) { - const res = createStringLiteral(value); - if (isSingleQuote) res.singleQuote = true; - return res; - } - return createLiteralFromNode(value); - } - - export function createNumericLiteral(value: string, numericLiteralFlags: TokenFlags = TokenFlags.None): NumericLiteral { - const node = createSynthesizedNode(SyntaxKind.NumericLiteral); - node.text = value; - node.numericLiteralFlags = numericLiteralFlags; - return node; - } - - export function createBigIntLiteral(value: string): BigIntLiteral { - const node = createSynthesizedNode(SyntaxKind.BigIntLiteral); - node.text = value; - return node; - } - - export function createStringLiteral(text: string): StringLiteral { - const node = createSynthesizedNode(SyntaxKind.StringLiteral); - node.text = text; - return node; - } - - export function createRegularExpressionLiteral(text: string): RegularExpressionLiteral { - const node = createSynthesizedNode(SyntaxKind.RegularExpressionLiteral); - node.text = text; - return node; - } - - function createLiteralFromNode(sourceNode: PropertyNameLiteral): StringLiteral { - const node = createStringLiteral(getTextOfIdentifierOrLiteral(sourceNode)); - node.textSourceNode = sourceNode; - return node; - } - - - // Identifiers - - export function createIdentifier(text: string): Identifier; - /* @internal */ - export function createIdentifier(text: string, typeArguments: readonly (TypeNode | TypeParameterDeclaration)[] | undefined): Identifier; // eslint-disable-line @typescript-eslint/unified-signatures - export function createIdentifier(text: string, typeArguments?: readonly (TypeNode | TypeParameterDeclaration)[]): Identifier { - const node = createSynthesizedNode(SyntaxKind.Identifier); - node.escapedText = escapeLeadingUnderscores(text); - node.originalKeywordKind = text ? stringToToken(text) : SyntaxKind.Unknown; - node.autoGenerateFlags = GeneratedIdentifierFlags.None; - node.autoGenerateId = 0; - if (typeArguments) { - node.typeArguments = createNodeArray(typeArguments as readonly TypeNode[]); - } - return node; - } - - export function updateIdentifier(node: Identifier): Identifier; - /* @internal */ - export function updateIdentifier(node: Identifier, typeArguments: NodeArray | undefined): Identifier; // eslint-disable-line @typescript-eslint/unified-signatures - export function updateIdentifier(node: Identifier, typeArguments?: NodeArray | undefined): Identifier { - return node.typeArguments !== typeArguments - ? updateNode(createIdentifier(idText(node), typeArguments), node) - : node; - } - - let nextAutoGenerateId = 0; - - /** Create a unique temporary variable. */ - export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined): Identifier; - /* @internal */ export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined, reservedInNestedScopes: boolean): GeneratedIdentifier; - export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined, reservedInNestedScopes?: boolean): GeneratedIdentifier { - const name = createIdentifier("") as GeneratedIdentifier; - name.autoGenerateFlags = GeneratedIdentifierFlags.Auto; - name.autoGenerateId = nextAutoGenerateId; - nextAutoGenerateId++; - if (recordTempVariable) { - recordTempVariable(name); - } - if (reservedInNestedScopes) { - name.autoGenerateFlags |= GeneratedIdentifierFlags.ReservedInNestedScopes; - } - return name; - } - - /** Create a unique temporary variable for use in a loop. */ - export function createLoopVariable(): Identifier { - const name = createIdentifier(""); - name.autoGenerateFlags = GeneratedIdentifierFlags.Loop; - name.autoGenerateId = nextAutoGenerateId; - nextAutoGenerateId++; - return name; - } - - /** Create a unique name based on the supplied text. */ - export function createUniqueName(text: string): Identifier { - const name = createIdentifier(text); - name.autoGenerateFlags = GeneratedIdentifierFlags.Unique; - name.autoGenerateId = nextAutoGenerateId; - nextAutoGenerateId++; - return name; - } - - /* @internal */ export function createOptimisticUniqueName(text: string): GeneratedIdentifier; - /** Create a unique name based on the supplied text. */ - export function createOptimisticUniqueName(text: string): Identifier; - export function createOptimisticUniqueName(text: string): GeneratedIdentifier { - const name = createIdentifier(text) as GeneratedIdentifier; - name.autoGenerateFlags = GeneratedIdentifierFlags.Unique | GeneratedIdentifierFlags.Optimistic; - name.autoGenerateId = nextAutoGenerateId; - nextAutoGenerateId++; - return name; - } - - /** Create a unique name based on the supplied text. This does not consider names injected by the transformer. */ - export function createFileLevelUniqueName(text: string): Identifier { - const name = createOptimisticUniqueName(text); - name.autoGenerateFlags |= GeneratedIdentifierFlags.FileLevel; - return name; - } - - /** Create a unique name generated for a node. */ - export function getGeneratedNameForNode(node: Node | undefined): Identifier; - /* @internal */ export function getGeneratedNameForNode(node: Node | undefined, flags: GeneratedIdentifierFlags): Identifier; // eslint-disable-line @typescript-eslint/unified-signatures - export function getGeneratedNameForNode(node: Node | undefined, flags?: GeneratedIdentifierFlags): Identifier { - const name = createIdentifier(node && isIdentifier(node) ? idText(node) : ""); - name.autoGenerateFlags = GeneratedIdentifierFlags.Node | flags!; - name.autoGenerateId = nextAutoGenerateId; - name.original = node; - nextAutoGenerateId++; - return name; - } - - // Punctuation - - export function createToken(token: TKind) { - return >createSynthesizedNode(token); - } - - // Reserved words - - export function createSuper() { - return createSynthesizedNode(SyntaxKind.SuperKeyword); - } - - export function createThis() { - return >createSynthesizedNode(SyntaxKind.ThisKeyword); - } - - export function createNull() { - return >createSynthesizedNode(SyntaxKind.NullKeyword); - } - - export function createTrue() { - return >createSynthesizedNode(SyntaxKind.TrueKeyword); - } - - export function createFalse() { - return >createSynthesizedNode(SyntaxKind.FalseKeyword); - } - - // Modifiers - - export function createModifier(kind: T): Token { - return createToken(kind); - } - - export function createModifiersFromModifierFlags(flags: ModifierFlags) { - const result: Modifier[] = []; - if (flags & ModifierFlags.Export) { result.push(createModifier(SyntaxKind.ExportKeyword)); } - if (flags & ModifierFlags.Ambient) { result.push(createModifier(SyntaxKind.DeclareKeyword)); } - if (flags & ModifierFlags.Default) { result.push(createModifier(SyntaxKind.DefaultKeyword)); } - if (flags & ModifierFlags.Const) { result.push(createModifier(SyntaxKind.ConstKeyword)); } - if (flags & ModifierFlags.Public) { result.push(createModifier(SyntaxKind.PublicKeyword)); } - if (flags & ModifierFlags.Private) { result.push(createModifier(SyntaxKind.PrivateKeyword)); } - if (flags & ModifierFlags.Protected) { result.push(createModifier(SyntaxKind.ProtectedKeyword)); } - if (flags & ModifierFlags.Abstract) { result.push(createModifier(SyntaxKind.AbstractKeyword)); } - if (flags & ModifierFlags.Static) { result.push(createModifier(SyntaxKind.StaticKeyword)); } - if (flags & ModifierFlags.Readonly) { result.push(createModifier(SyntaxKind.ReadonlyKeyword)); } - if (flags & ModifierFlags.Async) { result.push(createModifier(SyntaxKind.AsyncKeyword)); } - return result; - } - - // Names - - export function createQualifiedName(left: EntityName, right: string | Identifier) { - const node = createSynthesizedNode(SyntaxKind.QualifiedName); - node.left = left; - node.right = asName(right); - return node; - } - - export function updateQualifiedName(node: QualifiedName, left: EntityName, right: Identifier) { - return node.left !== left - || node.right !== right - ? updateNode(createQualifiedName(left, right), node) - : node; - } - - function parenthesizeForComputedName(expression: Expression): Expression { - return isCommaSequence(expression) - ? createParen(expression) - : expression; - } - - export function createComputedPropertyName(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.ComputedPropertyName); - node.expression = parenthesizeForComputedName(expression); - return node; - } - - export function updateComputedPropertyName(node: ComputedPropertyName, expression: Expression) { - return node.expression !== expression - ? updateNode(createComputedPropertyName(expression), node) - : node; - } - - // Signature elements - - export function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode) { - const node = createSynthesizedNode(SyntaxKind.TypeParameter) as TypeParameterDeclaration; - node.name = asName(name); - node.constraint = constraint; - node.default = defaultType; - return node; - } - - export function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined) { - return node.name !== name - || node.constraint !== constraint - || node.default !== defaultType - ? updateNode(createTypeParameterDeclaration(name, constraint, defaultType), node) - : node; - } - - export function createParameter( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - dotDotDotToken: DotDotDotToken | undefined, - name: string | BindingName, - questionToken?: QuestionToken, - type?: TypeNode, - initializer?: Expression) { - const node = createSynthesizedNode(SyntaxKind.Parameter); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.dotDotDotToken = dotDotDotToken; - node.name = asName(name); - node.questionToken = questionToken; - node.type = type; - node.initializer = initializer ? parenthesizeExpressionForList(initializer) : undefined; - return node; - } - - export function updateParameter( - node: ParameterDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - dotDotDotToken: DotDotDotToken | undefined, - name: string | BindingName, - questionToken: QuestionToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.dotDotDotToken !== dotDotDotToken - || node.name !== name - || node.questionToken !== questionToken - || node.type !== type - || node.initializer !== initializer - ? updateNode(createParameter(decorators, modifiers, dotDotDotToken, name, questionToken, type, initializer), node) - : node; - } - - export function createDecorator(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.Decorator); - node.expression = parenthesizeForAccess(expression); - return node; - } - - export function updateDecorator(node: Decorator, expression: Expression) { - return node.expression !== expression - ? updateNode(createDecorator(expression), node) - : node; - } - - - // Type Elements - - export function createPropertySignature( - modifiers: readonly Modifier[] | undefined, - name: PropertyName | string, - questionToken: QuestionToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined): PropertySignature { - const node = createSynthesizedNode(SyntaxKind.PropertySignature) as PropertySignature; - node.modifiers = asNodeArray(modifiers); - node.name = asName(name); - node.questionToken = questionToken; - node.type = type; - node.initializer = initializer; - return node; - } - - export function updatePropertySignature( - node: PropertySignature, - modifiers: readonly Modifier[] | undefined, - name: PropertyName, - questionToken: QuestionToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined) { - return node.modifiers !== modifiers - || node.name !== name - || node.questionToken !== questionToken - || node.type !== type - || node.initializer !== initializer - ? updateNode(createPropertySignature(modifiers, name, questionToken, type, initializer), node) - : node; - } - - export function createProperty( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - questionOrExclamationToken: QuestionToken | ExclamationToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined) { - const node = createSynthesizedNode(SyntaxKind.PropertyDeclaration); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.name = asName(name); - node.questionToken = questionOrExclamationToken !== undefined && questionOrExclamationToken.kind === SyntaxKind.QuestionToken ? questionOrExclamationToken : undefined; - node.exclamationToken = questionOrExclamationToken !== undefined && questionOrExclamationToken.kind === SyntaxKind.ExclamationToken ? questionOrExclamationToken : undefined; - node.type = type; - node.initializer = initializer; - return node; - } - - export function updateProperty( - node: PropertyDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - questionOrExclamationToken: QuestionToken | ExclamationToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.questionToken !== (questionOrExclamationToken !== undefined && questionOrExclamationToken.kind === SyntaxKind.QuestionToken ? questionOrExclamationToken : undefined) - || node.exclamationToken !== (questionOrExclamationToken !== undefined && questionOrExclamationToken.kind === SyntaxKind.ExclamationToken ? questionOrExclamationToken : undefined) - || node.type !== type - || node.initializer !== initializer - ? updateNode(createProperty(decorators, modifiers, name, questionOrExclamationToken, type, initializer), node) - : node; - } - - export function createMethodSignature( - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - name: string | PropertyName, - questionToken: QuestionToken | undefined) { - const node = createSignatureDeclaration(SyntaxKind.MethodSignature, typeParameters, parameters, type) as MethodSignature; - node.name = asName(name); - node.questionToken = questionToken; - return node; - } - - export function updateMethodSignature(node: MethodSignature, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined, name: PropertyName, questionToken: QuestionToken | undefined) { - return node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - || node.name !== name - || node.questionToken !== questionToken - ? updateNode(createMethodSignature(typeParameters, parameters, type, name, questionToken), node) - : node; - } - - export function createMethod( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: string | PropertyName, - questionToken: QuestionToken | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined) { - const node = createSynthesizedNode(SyntaxKind.MethodDeclaration); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.asteriskToken = asteriskToken; - node.name = asName(name); - node.questionToken = questionToken; - node.typeParameters = asNodeArray(typeParameters); - node.parameters = createNodeArray(parameters); - node.type = type; - node.body = body; - return node; - } - - function createMethodCall(object: Expression, methodName: string | Identifier, argumentsList: readonly Expression[]) { - return createCall( - createPropertyAccess(object, asName(methodName)), - /*typeArguments*/ undefined, - argumentsList - ); - } - - function createGlobalMethodCall(globalObjectName: string, methodName: string, argumentsList: readonly Expression[]) { - return createMethodCall(createIdentifier(globalObjectName), methodName, argumentsList); - } - - /* @internal */ - export function createObjectDefinePropertyCall(target: Expression, propertyName: string | Expression, attributes: Expression) { - return createGlobalMethodCall("Object", "defineProperty", [target, asExpression(propertyName), attributes]); - } - - function tryAddPropertyAssignment(properties: Push, propertyName: string, expression: Expression | undefined) { - if (expression) { - properties.push(createPropertyAssignment(propertyName, expression)); - return true; - } - return false; - } - - /* @internal */ - export function createPropertyDescriptor(attributes: PropertyDescriptorAttributes, singleLine?: boolean) { - const properties: PropertyAssignment[] = []; - tryAddPropertyAssignment(properties, "enumerable", asExpression(attributes.enumerable)); - tryAddPropertyAssignment(properties, "configurable", asExpression(attributes.configurable)); - - let isData = tryAddPropertyAssignment(properties, "writable", asExpression(attributes.writable)); - isData = tryAddPropertyAssignment(properties, "value", attributes.value) || isData; - - let isAccessor = tryAddPropertyAssignment(properties, "get", attributes.get); - isAccessor = tryAddPropertyAssignment(properties, "set", attributes.set) || isAccessor; - - Debug.assert(!(isData && isAccessor), "A PropertyDescriptor may not be both an accessor descriptor and a data descriptor."); - return createObjectLiteral(properties, !singleLine); - } - - export function updateMethod( - node: MethodDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: PropertyName, - questionToken: QuestionToken | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.asteriskToken !== asteriskToken - || node.name !== name - || node.questionToken !== questionToken - || node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - || node.body !== body - ? updateNode(createMethod(decorators, modifiers, asteriskToken, name, questionToken, typeParameters, parameters, type, body), node) - : node; - } - - export function createConstructor(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, parameters: readonly ParameterDeclaration[], body: Block | undefined) { - const node = createSynthesizedNode(SyntaxKind.Constructor); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.typeParameters = undefined; - node.parameters = createNodeArray(parameters); - node.type = undefined; - node.body = body; - return node; - } - - export function updateConstructor( - node: ConstructorDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - parameters: readonly ParameterDeclaration[], - body: Block | undefined) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.parameters !== parameters - || node.body !== body - ? updateNode(createConstructor(decorators, modifiers, parameters, body), node) - : node; - } - - export function createGetAccessor( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined) { - const node = createSynthesizedNode(SyntaxKind.GetAccessor); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.name = asName(name); - node.typeParameters = undefined; - node.parameters = createNodeArray(parameters); - node.type = type; - node.body = body; - return node; - } - - export function updateGetAccessor( - node: GetAccessorDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: PropertyName, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.parameters !== parameters - || node.type !== type - || node.body !== body - ? updateNode(createGetAccessor(decorators, modifiers, name, parameters, type, body), node) - : node; - } - - export function createSetAccessor( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - parameters: readonly ParameterDeclaration[], - body: Block | undefined) { - const node = createSynthesizedNode(SyntaxKind.SetAccessor); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.name = asName(name); - node.typeParameters = undefined; - node.parameters = createNodeArray(parameters); - node.body = body; - return node; - } - - export function updateSetAccessor( - node: SetAccessorDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: PropertyName, - parameters: readonly ParameterDeclaration[], - body: Block | undefined) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.parameters !== parameters - || node.body !== body - ? updateNode(createSetAccessor(decorators, modifiers, name, parameters, body), node) - : node; - } - - export function createCallSignature(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined) { - return createSignatureDeclaration(SyntaxKind.CallSignature, typeParameters, parameters, type) as CallSignatureDeclaration; - } - - export function updateCallSignature(node: CallSignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { - return updateSignatureDeclaration(node, typeParameters, parameters, type); - } - - export function createConstructSignature(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined) { - return createSignatureDeclaration(SyntaxKind.ConstructSignature, typeParameters, parameters, type) as ConstructSignatureDeclaration; - } - - export function updateConstructSignature(node: ConstructSignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { - return updateSignatureDeclaration(node, typeParameters, parameters, type); - } - - export function createIndexSignature( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode): IndexSignatureDeclaration { - const node = createSynthesizedNode(SyntaxKind.IndexSignature) as IndexSignatureDeclaration; - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.parameters = createNodeArray(parameters); - node.type = type; - return node; - } - - export function updateIndexSignature( - node: IndexSignatureDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode) { - return node.parameters !== parameters - || node.type !== type - || node.decorators !== decorators - || node.modifiers !== modifiers - ? updateNode(createIndexSignature(decorators, modifiers, parameters, type), node) - : node; - } - - /* @internal */ - export function createSignatureDeclaration(kind: SyntaxKind, typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined, typeArguments?: readonly TypeNode[] | undefined) { - const node = createSynthesizedNode(kind) as SignatureDeclaration; - node.typeParameters = asNodeArray(typeParameters); - node.parameters = asNodeArray(parameters); - node.type = type; - node.typeArguments = asNodeArray(typeArguments); - return node; - } - - function updateSignatureDeclaration(node: T, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): T { - return node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - ? updateNode(createSignatureDeclaration(node.kind, typeParameters, parameters, type), node) - : node; - } - - // Types - - export function createKeywordTypeNode(kind: KeywordTypeNode["kind"]) { - return createSynthesizedNode(kind); - } - - export function createTypePredicateNode(parameterName: Identifier | ThisTypeNode | string, type: TypeNode) { - return createTypePredicateNodeWithModifier(/*assertsModifier*/ undefined, parameterName, type); - } - - export function createTypePredicateNodeWithModifier(assertsModifier: AssertsToken | undefined, parameterName: Identifier | ThisTypeNode | string, type: TypeNode | undefined) { - const node = createSynthesizedNode(SyntaxKind.TypePredicate) as TypePredicateNode; - node.assertsModifier = assertsModifier; - node.parameterName = asName(parameterName); - node.type = type; - return node; - } - - export function updateTypePredicateNode(node: TypePredicateNode, parameterName: Identifier | ThisTypeNode, type: TypeNode) { - return updateTypePredicateNodeWithModifier(node, node.assertsModifier, parameterName, type); - } - - export function updateTypePredicateNodeWithModifier(node: TypePredicateNode, assertsModifier: AssertsToken | undefined, parameterName: Identifier | ThisTypeNode, type: TypeNode | undefined) { - return node.assertsModifier !== assertsModifier - || node.parameterName !== parameterName - || node.type !== type - ? updateNode(createTypePredicateNodeWithModifier(assertsModifier, parameterName, type), node) - : node; - } - - export function createTypeReferenceNode(typeName: string | EntityName, typeArguments: readonly TypeNode[] | undefined) { - const node = createSynthesizedNode(SyntaxKind.TypeReference) as TypeReferenceNode; - node.typeName = asName(typeName); - node.typeArguments = typeArguments && parenthesizeTypeParameters(typeArguments); - return node; - } - - export function updateTypeReferenceNode(node: TypeReferenceNode, typeName: EntityName, typeArguments: NodeArray | undefined) { - return node.typeName !== typeName - || node.typeArguments !== typeArguments - ? updateNode(createTypeReferenceNode(typeName, typeArguments), node) - : node; - } - - export function createFunctionTypeNode(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined) { - return createSignatureDeclaration(SyntaxKind.FunctionType, typeParameters, parameters, type) as FunctionTypeNode; - } - - export function updateFunctionTypeNode(node: FunctionTypeNode, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { - return updateSignatureDeclaration(node, typeParameters, parameters, type); - } - - export function createConstructorTypeNode(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined) { - return createSignatureDeclaration(SyntaxKind.ConstructorType, typeParameters, parameters, type) as ConstructorTypeNode; - } - - export function updateConstructorTypeNode(node: ConstructorTypeNode, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { - return updateSignatureDeclaration(node, typeParameters, parameters, type); - } - - export function createTypeQueryNode(exprName: EntityName) { - const node = createSynthesizedNode(SyntaxKind.TypeQuery) as TypeQueryNode; - node.exprName = exprName; - return node; - } - - export function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName) { - return node.exprName !== exprName - ? updateNode(createTypeQueryNode(exprName), node) - : node; - } - - export function createTypeLiteralNode(members: readonly TypeElement[] | undefined) { - const node = createSynthesizedNode(SyntaxKind.TypeLiteral) as TypeLiteralNode; - node.members = createNodeArray(members); - return node; - } - - export function updateTypeLiteralNode(node: TypeLiteralNode, members: NodeArray) { - return node.members !== members - ? updateNode(createTypeLiteralNode(members), node) - : node; - } - - export function createArrayTypeNode(elementType: TypeNode) { - const node = createSynthesizedNode(SyntaxKind.ArrayType) as ArrayTypeNode; - node.elementType = parenthesizeArrayTypeMember(elementType); - return node; - } - - export function updateArrayTypeNode(node: ArrayTypeNode, elementType: TypeNode): ArrayTypeNode { - return node.elementType !== elementType - ? updateNode(createArrayTypeNode(elementType), node) - : node; - } - - export function createTupleTypeNode(elementTypes: readonly TypeNode[]) { - const node = createSynthesizedNode(SyntaxKind.TupleType) as TupleTypeNode; - node.elementTypes = createNodeArray(elementTypes); - return node; - } - - export function updateTupleTypeNode(node: TupleTypeNode, elementTypes: readonly TypeNode[]) { - return node.elementTypes !== elementTypes - ? updateNode(createTupleTypeNode(elementTypes), node) - : node; - } - - export function createOptionalTypeNode(type: TypeNode) { - const node = createSynthesizedNode(SyntaxKind.OptionalType) as OptionalTypeNode; - node.type = parenthesizeArrayTypeMember(type); - return node; - } - - export function updateOptionalTypeNode(node: OptionalTypeNode, type: TypeNode): OptionalTypeNode { - return node.type !== type - ? updateNode(createOptionalTypeNode(type), node) - : node; - } - - export function createRestTypeNode(type: TypeNode) { - const node = createSynthesizedNode(SyntaxKind.RestType) as RestTypeNode; - node.type = type; - return node; - } - - export function updateRestTypeNode(node: RestTypeNode, type: TypeNode): RestTypeNode { - return node.type !== type - ? updateNode(createRestTypeNode(type), node) - : node; - } - - export function createUnionTypeNode(types: readonly TypeNode[]): UnionTypeNode { - return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, types); - } - - export function updateUnionTypeNode(node: UnionTypeNode, types: NodeArray) { - return updateUnionOrIntersectionTypeNode(node, types); - } - - export function createIntersectionTypeNode(types: readonly TypeNode[]): IntersectionTypeNode { - return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, types); - } - - export function updateIntersectionTypeNode(node: IntersectionTypeNode, types: NodeArray) { - return updateUnionOrIntersectionTypeNode(node, types); - } - - export function createUnionOrIntersectionTypeNode(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, types: readonly TypeNode[]) { - const node = createSynthesizedNode(kind) as UnionTypeNode | IntersectionTypeNode; - node.types = parenthesizeElementTypeMembers(types); - return node; - } - - function updateUnionOrIntersectionTypeNode(node: T, types: NodeArray): T { - return node.types !== types - ? updateNode(createUnionOrIntersectionTypeNode(node.kind, types), node) - : node; - } - - export function createConditionalTypeNode(checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) { - const node = createSynthesizedNode(SyntaxKind.ConditionalType) as ConditionalTypeNode; - node.checkType = parenthesizeConditionalTypeMember(checkType); - node.extendsType = parenthesizeConditionalTypeMember(extendsType); - node.trueType = trueType; - node.falseType = falseType; - return node; - } - - export function updateConditionalTypeNode(node: ConditionalTypeNode, checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) { - return node.checkType !== checkType - || node.extendsType !== extendsType - || node.trueType !== trueType - || node.falseType !== falseType - ? updateNode(createConditionalTypeNode(checkType, extendsType, trueType, falseType), node) - : node; - } - - export function createInferTypeNode(typeParameter: TypeParameterDeclaration) { - const node = createSynthesizedNode(SyntaxKind.InferType); - node.typeParameter = typeParameter; - return node; - } - - export function updateInferTypeNode(node: InferTypeNode, typeParameter: TypeParameterDeclaration) { - return node.typeParameter !== typeParameter - ? updateNode(createInferTypeNode(typeParameter), node) - : node; - } - - export function createImportTypeNode(argument: TypeNode, qualifier?: EntityName, typeArguments?: readonly TypeNode[], isTypeOf?: boolean) { - const node = createSynthesizedNode(SyntaxKind.ImportType); - node.argument = argument; - node.qualifier = qualifier; - node.typeArguments = parenthesizeTypeParameters(typeArguments); - node.isTypeOf = isTypeOf; - return node; - } - - export function updateImportTypeNode(node: ImportTypeNode, argument: TypeNode, qualifier?: EntityName, typeArguments?: readonly TypeNode[], isTypeOf?: boolean) { - return node.argument !== argument - || node.qualifier !== qualifier - || node.typeArguments !== typeArguments - || node.isTypeOf !== isTypeOf - ? updateNode(createImportTypeNode(argument, qualifier, typeArguments, isTypeOf), node) - : node; - } - - export function createParenthesizedType(type: TypeNode) { - const node = createSynthesizedNode(SyntaxKind.ParenthesizedType); - node.type = type; - return node; - } - - export function updateParenthesizedType(node: ParenthesizedTypeNode, type: TypeNode) { - return node.type !== type - ? updateNode(createParenthesizedType(type), node) - : node; - } - - export function createThisTypeNode() { - return createSynthesizedNode(SyntaxKind.ThisType); - } - - export function createTypeOperatorNode(type: TypeNode): TypeOperatorNode; - export function createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword, type: TypeNode): TypeOperatorNode; - export function createTypeOperatorNode(operatorOrType: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword | TypeNode, type?: TypeNode) { - const node = createSynthesizedNode(SyntaxKind.TypeOperator) as TypeOperatorNode; - node.operator = typeof operatorOrType === "number" ? operatorOrType : SyntaxKind.KeyOfKeyword; - node.type = parenthesizeElementTypeMember(typeof operatorOrType === "number" ? type! : operatorOrType); - return node; - } - - export function updateTypeOperatorNode(node: TypeOperatorNode, type: TypeNode) { - return node.type !== type ? updateNode(createTypeOperatorNode(node.operator, type), node) : node; - } - - export function createIndexedAccessTypeNode(objectType: TypeNode, indexType: TypeNode) { - const node = createSynthesizedNode(SyntaxKind.IndexedAccessType) as IndexedAccessTypeNode; - node.objectType = parenthesizeElementTypeMember(objectType); - node.indexType = indexType; - return node; - } - - export function updateIndexedAccessTypeNode(node: IndexedAccessTypeNode, objectType: TypeNode, indexType: TypeNode) { - return node.objectType !== objectType - || node.indexType !== indexType - ? updateNode(createIndexedAccessTypeNode(objectType, indexType), node) - : node; - } - - export function createMappedTypeNode(readonlyToken: ReadonlyToken | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode { - const node = createSynthesizedNode(SyntaxKind.MappedType) as MappedTypeNode; - node.readonlyToken = readonlyToken; - node.typeParameter = typeParameter; - node.questionToken = questionToken; - node.type = type; - return node; - } - - export function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyToken | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode { - return node.readonlyToken !== readonlyToken - || node.typeParameter !== typeParameter - || node.questionToken !== questionToken - || node.type !== type - ? updateNode(createMappedTypeNode(readonlyToken, typeParameter, questionToken, type), node) - : node; - } - - export function createLiteralTypeNode(literal: LiteralTypeNode["literal"]) { - const node = createSynthesizedNode(SyntaxKind.LiteralType) as LiteralTypeNode; - node.literal = literal; - return node; - } - - export function updateLiteralTypeNode(node: LiteralTypeNode, literal: LiteralTypeNode["literal"]) { - return node.literal !== literal - ? updateNode(createLiteralTypeNode(literal), node) - : node; - } - - // Binding Patterns - - export function createObjectBindingPattern(elements: readonly BindingElement[]) { - const node = createSynthesizedNode(SyntaxKind.ObjectBindingPattern); - node.elements = createNodeArray(elements); - return node; - } - - export function updateObjectBindingPattern(node: ObjectBindingPattern, elements: readonly BindingElement[]) { - return node.elements !== elements - ? updateNode(createObjectBindingPattern(elements), node) - : node; - } - - export function createArrayBindingPattern(elements: readonly ArrayBindingElement[]) { - const node = createSynthesizedNode(SyntaxKind.ArrayBindingPattern); - node.elements = createNodeArray(elements); - return node; - } - - export function updateArrayBindingPattern(node: ArrayBindingPattern, elements: readonly ArrayBindingElement[]) { - return node.elements !== elements - ? updateNode(createArrayBindingPattern(elements), node) - : node; - } - - export function createBindingElement(dotDotDotToken: DotDotDotToken | undefined, propertyName: string | PropertyName | undefined, name: string | BindingName, initializer?: Expression) { - const node = createSynthesizedNode(SyntaxKind.BindingElement); - node.dotDotDotToken = dotDotDotToken; - node.propertyName = asName(propertyName); - node.name = asName(name); - node.initializer = initializer; - return node; - } - - export function updateBindingElement(node: BindingElement, dotDotDotToken: DotDotDotToken | undefined, propertyName: PropertyName | undefined, name: BindingName, initializer: Expression | undefined) { - return node.propertyName !== propertyName - || node.dotDotDotToken !== dotDotDotToken - || node.name !== name - || node.initializer !== initializer - ? updateNode(createBindingElement(dotDotDotToken, propertyName, name, initializer), node) - : node; - } - - // Expression - - export function createArrayLiteral(elements?: readonly Expression[], multiLine?: boolean) { - const node = createSynthesizedNode(SyntaxKind.ArrayLiteralExpression); - node.elements = parenthesizeListElements(createNodeArray(elements)); - if (multiLine) node.multiLine = true; - return node; - } - - export function updateArrayLiteral(node: ArrayLiteralExpression, elements: readonly Expression[]) { - return node.elements !== elements - ? updateNode(createArrayLiteral(elements, node.multiLine), node) - : node; - } - - export function createObjectLiteral(properties?: readonly ObjectLiteralElementLike[], multiLine?: boolean) { - const node = createSynthesizedNode(SyntaxKind.ObjectLiteralExpression); - node.properties = createNodeArray(properties); - if (multiLine) node.multiLine = true; - return node; - } - - export function updateObjectLiteral(node: ObjectLiteralExpression, properties: readonly ObjectLiteralElementLike[]) { - return node.properties !== properties - ? updateNode(createObjectLiteral(properties, node.multiLine), node) - : node; - } - - export function createPropertyAccess(expression: Expression, name: string | Identifier) { - const node = createSynthesizedNode(SyntaxKind.PropertyAccessExpression); - node.expression = parenthesizeForAccess(expression); - node.name = asName(name); - setEmitFlags(node, EmitFlags.NoIndentation); - return node; - } - - export function updatePropertyAccess(node: PropertyAccessExpression, expression: Expression, name: Identifier) { - if (isOptionalChain(node)) { - return updatePropertyAccessChain(node, expression, node.questionDotToken, name); - } - // Because we are updating existed propertyAccess we want to inherit its emitFlags - // instead of using the default from createPropertyAccess - return node.expression !== expression - || node.name !== name - ? updateNode(setEmitFlags(createPropertyAccess(expression, name), getEmitFlags(node)), node) - : node; - } - - export function createPropertyAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, name: string | Identifier) { - const node = createSynthesizedNode(SyntaxKind.PropertyAccessExpression); - node.flags |= NodeFlags.OptionalChain; - node.expression = parenthesizeForAccess(expression); - node.questionDotToken = questionDotToken; - node.name = asName(name); - setEmitFlags(node, EmitFlags.NoIndentation); - return node; - } - - export function updatePropertyAccessChain(node: PropertyAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, name: Identifier) { - Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a PropertyAccessExpression using updatePropertyAccessChain. Use updatePropertyAccess instead."); - // Because we are updating an existing PropertyAccessChain we want to inherit its emitFlags - // instead of using the default from createPropertyAccess - return node.expression !== expression - || node.questionDotToken !== questionDotToken - || node.name !== name - ? updateNode(setEmitFlags(createPropertyAccessChain(expression, questionDotToken, name), getEmitFlags(node)), node) - : node; - } - - export function createElementAccess(expression: Expression, index: number | Expression) { - const node = createSynthesizedNode(SyntaxKind.ElementAccessExpression); - node.expression = parenthesizeForAccess(expression); - node.argumentExpression = asExpression(index); - return node; - } - - export function updateElementAccess(node: ElementAccessExpression, expression: Expression, argumentExpression: Expression) { - if (isOptionalChain(node)) { - return updateElementAccessChain(node, expression, node.questionDotToken, argumentExpression); - } - return node.expression !== expression - || node.argumentExpression !== argumentExpression - ? updateNode(createElementAccess(expression, argumentExpression), node) - : node; - } - - export function createElementAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, index: number | Expression) { - const node = createSynthesizedNode(SyntaxKind.ElementAccessExpression); - node.flags |= NodeFlags.OptionalChain; - node.expression = parenthesizeForAccess(expression); - node.questionDotToken = questionDotToken; - node.argumentExpression = asExpression(index); - return node; - } - - export function updateElementAccessChain(node: ElementAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, argumentExpression: Expression) { - Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update an ElementAccessExpression using updateElementAccessChain. Use updateElementAccess instead."); - return node.expression !== expression - || node.questionDotToken !== questionDotToken - || node.argumentExpression !== argumentExpression - ? updateNode(createElementAccessChain(expression, questionDotToken, argumentExpression), node) - : node; - } - - export function createCall(expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { - const node = createSynthesizedNode(SyntaxKind.CallExpression); - node.expression = parenthesizeForAccess(expression); - node.typeArguments = asNodeArray(typeArguments); - node.arguments = parenthesizeListElements(createNodeArray(argumentsArray)); - return node; - } - - export function updateCall(node: CallExpression, expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[]) { - if (isOptionalChain(node)) { - return updateCallChain(node, expression, node.questionDotToken, typeArguments, argumentsArray); - } - return node.expression !== expression - || node.typeArguments !== typeArguments - || node.arguments !== argumentsArray - ? updateNode(createCall(expression, typeArguments, argumentsArray), node) - : node; - } - - export function createCallChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { - const node = createSynthesizedNode(SyntaxKind.CallExpression); - node.flags |= NodeFlags.OptionalChain; - node.expression = parenthesizeForAccess(expression); - node.questionDotToken = questionDotToken; - node.typeArguments = asNodeArray(typeArguments); - node.arguments = parenthesizeListElements(createNodeArray(argumentsArray)); - return node; - } - - export function updateCallChain(node: CallChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[]) { - Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a CallExpression using updateCallChain. Use updateCall instead."); - return node.expression !== expression - || node.questionDotToken !== questionDotToken - || node.typeArguments !== typeArguments - || node.arguments !== argumentsArray - ? updateNode(createCallChain(expression, questionDotToken, typeArguments, argumentsArray), node) - : node; - } - - export function createNew(expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { - const node = createSynthesizedNode(SyntaxKind.NewExpression); - node.expression = parenthesizeForNew(expression); - node.typeArguments = asNodeArray(typeArguments); - node.arguments = argumentsArray ? parenthesizeListElements(createNodeArray(argumentsArray)) : undefined; - return node; - } - - export function updateNew(node: NewExpression, expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { - return node.expression !== expression - || node.typeArguments !== typeArguments - || node.arguments !== argumentsArray - ? updateNode(createNew(expression, typeArguments, argumentsArray), node) - : node; - } - - /** @deprecated */ export function createTaggedTemplate(tag: Expression, template: TemplateLiteral): TaggedTemplateExpression; - export function createTaggedTemplate(tag: Expression, typeArguments: readonly TypeNode[] | undefined, template: TemplateLiteral): TaggedTemplateExpression; - /** @internal */ - export function createTaggedTemplate(tag: Expression, typeArgumentsOrTemplate: readonly TypeNode[] | TemplateLiteral | undefined, template?: TemplateLiteral): TaggedTemplateExpression; - export function createTaggedTemplate(tag: Expression, typeArgumentsOrTemplate: readonly TypeNode[] | TemplateLiteral | undefined, template?: TemplateLiteral) { - const node = createSynthesizedNode(SyntaxKind.TaggedTemplateExpression); - node.tag = parenthesizeForAccess(tag); - if (template) { - node.typeArguments = asNodeArray(typeArgumentsOrTemplate as readonly TypeNode[]); - node.template = template; - } - else { - node.typeArguments = undefined; - node.template = typeArgumentsOrTemplate as TemplateLiteral; - } - return node; - } - - /** @deprecated */ export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, template: TemplateLiteral): TaggedTemplateExpression; - export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, typeArguments: readonly TypeNode[] | undefined, template: TemplateLiteral): TaggedTemplateExpression; - export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, typeArgumentsOrTemplate: readonly TypeNode[] | TemplateLiteral | undefined, template?: TemplateLiteral) { - return node.tag !== tag - || (template - ? node.typeArguments !== typeArgumentsOrTemplate || node.template !== template - : node.typeArguments !== undefined || node.template !== typeArgumentsOrTemplate) - ? updateNode(createTaggedTemplate(tag, typeArgumentsOrTemplate, template), node) - : node; - } - - export function createTypeAssertion(type: TypeNode, expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.TypeAssertionExpression); - node.type = type; - node.expression = parenthesizePrefixOperand(expression); - return node; - } - - export function updateTypeAssertion(node: TypeAssertion, type: TypeNode, expression: Expression) { - return node.type !== type - || node.expression !== expression - ? updateNode(createTypeAssertion(type, expression), node) - : node; - } - - export function createParen(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.ParenthesizedExpression); - node.expression = expression; - return node; - } - - export function updateParen(node: ParenthesizedExpression, expression: Expression) { - return node.expression !== expression - ? updateNode(createParen(expression), node) - : node; - } - - export function createFunctionExpression( - modifiers: readonly Modifier[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: string | Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[] | undefined, - type: TypeNode | undefined, - body: Block) { - const node = createSynthesizedNode(SyntaxKind.FunctionExpression); - node.modifiers = asNodeArray(modifiers); - node.asteriskToken = asteriskToken; - node.name = asName(name); - node.typeParameters = asNodeArray(typeParameters); - node.parameters = createNodeArray(parameters); - node.type = type; - node.body = body; - return node; - } - - export function updateFunctionExpression( - node: FunctionExpression, - modifiers: readonly Modifier[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block) { - return node.name !== name - || node.modifiers !== modifiers - || node.asteriskToken !== asteriskToken - || node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - || node.body !== body - ? updateNode(createFunctionExpression(modifiers, asteriskToken, name, typeParameters, parameters, type, body), node) - : node; - } - - export function createArrowFunction( - modifiers: readonly Modifier[] | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - equalsGreaterThanToken: EqualsGreaterThanToken | undefined, - body: ConciseBody) { - const node = createSynthesizedNode(SyntaxKind.ArrowFunction); - node.modifiers = asNodeArray(modifiers); - node.typeParameters = asNodeArray(typeParameters); - node.parameters = createNodeArray(parameters); - node.type = type; - node.equalsGreaterThanToken = equalsGreaterThanToken || createToken(SyntaxKind.EqualsGreaterThanToken); - node.body = parenthesizeConciseBody(body); - return node; - } - export function updateArrowFunction( - node: ArrowFunction, - modifiers: readonly Modifier[] | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - equalsGreaterThanToken: Token, - body: ConciseBody - ): ArrowFunction { - return node.modifiers !== modifiers - || node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - || node.equalsGreaterThanToken !== equalsGreaterThanToken - || node.body !== body - ? updateNode(createArrowFunction(modifiers, typeParameters, parameters, type, equalsGreaterThanToken, body), node) - : node; - } - - export function createDelete(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.DeleteExpression); - node.expression = parenthesizePrefixOperand(expression); - return node; - } - - export function updateDelete(node: DeleteExpression, expression: Expression) { - return node.expression !== expression - ? updateNode(createDelete(expression), node) - : node; - } - - export function createTypeOf(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.TypeOfExpression); - node.expression = parenthesizePrefixOperand(expression); - return node; - } - - export function updateTypeOf(node: TypeOfExpression, expression: Expression) { - return node.expression !== expression - ? updateNode(createTypeOf(expression), node) - : node; - } - - export function createVoid(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.VoidExpression); - node.expression = parenthesizePrefixOperand(expression); - return node; - } - - export function updateVoid(node: VoidExpression, expression: Expression) { - return node.expression !== expression - ? updateNode(createVoid(expression), node) - : node; - } - - export function createAwait(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.AwaitExpression); - node.expression = parenthesizePrefixOperand(expression); - return node; - } - - export function updateAwait(node: AwaitExpression, expression: Expression) { - return node.expression !== expression - ? updateNode(createAwait(expression), node) - : node; - } - - export function createPrefix(operator: PrefixUnaryOperator, operand: Expression) { - const node = createSynthesizedNode(SyntaxKind.PrefixUnaryExpression); - node.operator = operator; - node.operand = parenthesizePrefixOperand(operand); - return node; - } - - export function updatePrefix(node: PrefixUnaryExpression, operand: Expression) { - return node.operand !== operand - ? updateNode(createPrefix(node.operator, operand), node) - : node; - } - - export function createPostfix(operand: Expression, operator: PostfixUnaryOperator) { - const node = createSynthesizedNode(SyntaxKind.PostfixUnaryExpression); - node.operand = parenthesizePostfixOperand(operand); - node.operator = operator; - return node; - } - - export function updatePostfix(node: PostfixUnaryExpression, operand: Expression) { - return node.operand !== operand - ? updateNode(createPostfix(operand, node.operator), node) - : node; - } - - export function createBinary(left: Expression, operator: BinaryOperator | BinaryOperatorToken, right: Expression) { - const node = createSynthesizedNode(SyntaxKind.BinaryExpression); - const operatorToken = asToken(operator); - const operatorKind = operatorToken.kind; - node.left = parenthesizeBinaryOperand(operatorKind, left, /*isLeftSideOfBinary*/ true, /*leftOperand*/ undefined); - node.operatorToken = operatorToken; - node.right = parenthesizeBinaryOperand(operatorKind, right, /*isLeftSideOfBinary*/ false, node.left); - return node; - } - - export function updateBinary(node: BinaryExpression, left: Expression, right: Expression, operator?: BinaryOperator | BinaryOperatorToken) { - return node.left !== left - || node.right !== right - ? updateNode(createBinary(left, operator || node.operatorToken, right), node) - : node; - } - - /** @deprecated */ export function createConditional(condition: Expression, whenTrue: Expression, whenFalse: Expression): ConditionalExpression; - export function createConditional(condition: Expression, questionToken: QuestionToken, whenTrue: Expression, colonToken: ColonToken, whenFalse: Expression): ConditionalExpression; - export function createConditional(condition: Expression, questionTokenOrWhenTrue: QuestionToken | Expression, whenTrueOrWhenFalse: Expression, colonToken?: ColonToken, whenFalse?: Expression) { - const node = createSynthesizedNode(SyntaxKind.ConditionalExpression); - node.condition = parenthesizeForConditionalHead(condition); - node.questionToken = whenFalse ? questionTokenOrWhenTrue : createToken(SyntaxKind.QuestionToken); - node.whenTrue = parenthesizeSubexpressionOfConditionalExpression(whenFalse ? whenTrueOrWhenFalse : questionTokenOrWhenTrue); - node.colonToken = whenFalse ? colonToken! : createToken(SyntaxKind.ColonToken); - node.whenFalse = parenthesizeSubexpressionOfConditionalExpression(whenFalse ? whenFalse : whenTrueOrWhenFalse); - return node; - } - export function updateConditional( - node: ConditionalExpression, - condition: Expression, - questionToken: Token, - whenTrue: Expression, - colonToken: Token, - whenFalse: Expression - ): ConditionalExpression { - return node.condition !== condition - || node.questionToken !== questionToken - || node.whenTrue !== whenTrue - || node.colonToken !== colonToken - || node.whenFalse !== whenFalse - ? updateNode(createConditional(condition, questionToken, whenTrue, colonToken, whenFalse), node) - : node; - } - - export function createTemplateExpression(head: TemplateHead, templateSpans: readonly TemplateSpan[]) { - const node = createSynthesizedNode(SyntaxKind.TemplateExpression); - node.head = head; - node.templateSpans = createNodeArray(templateSpans); - return node; - } - - export function updateTemplateExpression(node: TemplateExpression, head: TemplateHead, templateSpans: readonly TemplateSpan[]) { - return node.head !== head - || node.templateSpans !== templateSpans - ? updateNode(createTemplateExpression(head, templateSpans), node) - : node; - } - - let rawTextScanner: Scanner | undefined; - const invalidValueSentinel: object = {}; - - function getCookedText(kind: TemplateLiteralToken["kind"], rawText: string) { - if (!rawTextScanner) { - rawTextScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, LanguageVariant.Standard); - } - switch (kind) { - case SyntaxKind.NoSubstitutionTemplateLiteral: - rawTextScanner.setText("`" + rawText + "`"); - break; - case SyntaxKind.TemplateHead: - rawTextScanner.setText("`" + rawText + "${"); - break; - case SyntaxKind.TemplateMiddle: - rawTextScanner.setText("}" + rawText + "${"); - break; - case SyntaxKind.TemplateTail: - rawTextScanner.setText("}" + rawText + "`"); - break; - } - - let token = rawTextScanner.scan(); - if (token === SyntaxKind.CloseBracketToken) { - token = rawTextScanner.reScanTemplateToken(); - } - - if (rawTextScanner.isUnterminated()) { - rawTextScanner.setText(undefined); - return invalidValueSentinel; - } - - let tokenValue: string | undefined; - switch (token) { - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.TemplateHead: - case SyntaxKind.TemplateMiddle: - case SyntaxKind.TemplateTail: - tokenValue = rawTextScanner.getTokenValue(); - break; - } - - if (rawTextScanner.scan() !== SyntaxKind.EndOfFileToken) { - rawTextScanner.setText(undefined); - return invalidValueSentinel; - } - - rawTextScanner.setText(undefined); - return tokenValue; - } - - function createTemplateLiteralLikeNode(kind: TemplateLiteralToken["kind"], text: string, rawText: string | undefined) { - const node = createSynthesizedNode(kind); - node.text = text; - if (rawText === undefined || text === rawText) { - node.rawText = rawText; - } - else { - const cooked = getCookedText(kind, rawText); - if (typeof cooked === "object") { - return Debug.fail("Invalid raw text"); - } - - Debug.assert(text === cooked, "Expected argument 'text' to be the normalized (i.e. 'cooked') version of argument 'rawText'."); - node.rawText = rawText; - } - return node; - } - - export function createTemplateHead(text: string, rawText?: string) { - const node = createTemplateLiteralLikeNode(SyntaxKind.TemplateHead, text, rawText); - node.text = text; - return node; - } - - export function createTemplateMiddle(text: string, rawText?: string) { - const node = createTemplateLiteralLikeNode(SyntaxKind.TemplateMiddle, text, rawText); - node.text = text; - return node; - } - - export function createTemplateTail(text: string, rawText?: string) { - const node = createTemplateLiteralLikeNode(SyntaxKind.TemplateTail, text, rawText); - node.text = text; - return node; - } - - export function createNoSubstitutionTemplateLiteral(text: string, rawText?: string) { - const node = createTemplateLiteralLikeNode(SyntaxKind.NoSubstitutionTemplateLiteral, text, rawText); - return node; - } - - export function createYield(expression?: Expression): YieldExpression; - export function createYield(asteriskToken: AsteriskToken | undefined, expression: Expression): YieldExpression; - export function createYield(asteriskTokenOrExpression?: AsteriskToken | undefined | Expression, expression?: Expression) { - const node = createSynthesizedNode(SyntaxKind.YieldExpression); - node.asteriskToken = asteriskTokenOrExpression && asteriskTokenOrExpression.kind === SyntaxKind.AsteriskToken ? asteriskTokenOrExpression : undefined; - node.expression = asteriskTokenOrExpression && asteriskTokenOrExpression.kind !== SyntaxKind.AsteriskToken ? asteriskTokenOrExpression : expression; - return node; - } - - export function updateYield(node: YieldExpression, asteriskToken: AsteriskToken | undefined, expression: Expression) { - return node.expression !== expression - || node.asteriskToken !== asteriskToken - ? updateNode(createYield(asteriskToken, expression), node) - : node; - } - - export function createSpread(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.SpreadElement); - node.expression = parenthesizeExpressionForList(expression); - return node; - } - - export function updateSpread(node: SpreadElement, expression: Expression) { - return node.expression !== expression - ? updateNode(createSpread(expression), node) - : node; - } - - export function createClassExpression( - modifiers: readonly Modifier[] | undefined, - name: string | Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly ClassElement[]) { - const node = createSynthesizedNode(SyntaxKind.ClassExpression); - node.decorators = undefined; - node.modifiers = asNodeArray(modifiers); - node.name = asName(name); - node.typeParameters = asNodeArray(typeParameters); - node.heritageClauses = asNodeArray(heritageClauses); - node.members = createNodeArray(members); - return node; - } - - export function updateClassExpression( - node: ClassExpression, - modifiers: readonly Modifier[] | undefined, - name: Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly ClassElement[]) { - return node.modifiers !== modifiers - || node.name !== name - || node.typeParameters !== typeParameters - || node.heritageClauses !== heritageClauses - || node.members !== members - ? updateNode(createClassExpression(modifiers, name, typeParameters, heritageClauses, members), node) - : node; - } - - export function createOmittedExpression() { - return createSynthesizedNode(SyntaxKind.OmittedExpression); - } - - export function createExpressionWithTypeArguments(typeArguments: readonly TypeNode[] | undefined, expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.ExpressionWithTypeArguments); - node.expression = parenthesizeForAccess(expression); - node.typeArguments = asNodeArray(typeArguments); - return node; - } - - export function updateExpressionWithTypeArguments(node: ExpressionWithTypeArguments, typeArguments: readonly TypeNode[] | undefined, expression: Expression) { - return node.typeArguments !== typeArguments - || node.expression !== expression - ? updateNode(createExpressionWithTypeArguments(typeArguments, expression), node) - : node; - } - - export function createAsExpression(expression: Expression, type: TypeNode) { - const node = createSynthesizedNode(SyntaxKind.AsExpression); - node.expression = expression; - node.type = type; - return node; - } - - export function updateAsExpression(node: AsExpression, expression: Expression, type: TypeNode) { - return node.expression !== expression - || node.type !== type - ? updateNode(createAsExpression(expression, type), node) - : node; - } - - export function createNonNullExpression(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.NonNullExpression); - node.expression = parenthesizeForAccess(expression); - return node; - } - - export function updateNonNullExpression(node: NonNullExpression, expression: Expression) { - return node.expression !== expression - ? updateNode(createNonNullExpression(expression), node) - : node; - } - - export function createMetaProperty(keywordToken: MetaProperty["keywordToken"], name: Identifier) { - const node = createSynthesizedNode(SyntaxKind.MetaProperty); - node.keywordToken = keywordToken; - node.name = name; - return node; - } - - export function updateMetaProperty(node: MetaProperty, name: Identifier) { - return node.name !== name - ? updateNode(createMetaProperty(node.keywordToken, name), node) - : node; - } - - // Misc - - export function createTemplateSpan(expression: Expression, literal: TemplateMiddle | TemplateTail) { - const node = createSynthesizedNode(SyntaxKind.TemplateSpan); - node.expression = expression; - node.literal = literal; - return node; - } - - export function updateTemplateSpan(node: TemplateSpan, expression: Expression, literal: TemplateMiddle | TemplateTail) { - return node.expression !== expression - || node.literal !== literal - ? updateNode(createTemplateSpan(expression, literal), node) - : node; - } - - export function createSemicolonClassElement() { - return createSynthesizedNode(SyntaxKind.SemicolonClassElement); - } - - // Element - - export function createBlock(statements: readonly Statement[], multiLine?: boolean): Block { - const block = createSynthesizedNode(SyntaxKind.Block); - block.statements = createNodeArray(statements); - if (multiLine) block.multiLine = multiLine; - return block; - } - - export function updateBlock(node: Block, statements: readonly Statement[]) { - return node.statements !== statements - ? updateNode(createBlock(statements, node.multiLine), node) - : node; - } - - export function createVariableStatement(modifiers: readonly Modifier[] | undefined, declarationList: VariableDeclarationList | readonly VariableDeclaration[]) { - const node = createSynthesizedNode(SyntaxKind.VariableStatement); - node.decorators = undefined; - node.modifiers = asNodeArray(modifiers); - node.declarationList = isArray(declarationList) ? createVariableDeclarationList(declarationList) : declarationList; - return node; - } - - export function updateVariableStatement(node: VariableStatement, modifiers: readonly Modifier[] | undefined, declarationList: VariableDeclarationList) { - return node.modifiers !== modifiers - || node.declarationList !== declarationList - ? updateNode(createVariableStatement(modifiers, declarationList), node) - : node; - } - - export function createEmptyStatement() { - return createSynthesizedNode(SyntaxKind.EmptyStatement); - } - - export function createExpressionStatement(expression: Expression): ExpressionStatement { - const node = createSynthesizedNode(SyntaxKind.ExpressionStatement); - node.expression = parenthesizeExpressionForExpressionStatement(expression); - return node; - } - - export function updateExpressionStatement(node: ExpressionStatement, expression: Expression) { - return node.expression !== expression - ? updateNode(createExpressionStatement(expression), node) - : node; - } - - /** @deprecated Use `createExpressionStatement` instead. */ - export const createStatement = createExpressionStatement; - /** @deprecated Use `updateExpressionStatement` instead. */ - export const updateStatement = updateExpressionStatement; - - export function createIf(expression: Expression, thenStatement: Statement, elseStatement?: Statement) { - const node = createSynthesizedNode(SyntaxKind.IfStatement); - node.expression = expression; - node.thenStatement = asEmbeddedStatement(thenStatement); - node.elseStatement = asEmbeddedStatement(elseStatement); - return node; - } - - export function updateIf(node: IfStatement, expression: Expression, thenStatement: Statement, elseStatement: Statement | undefined) { - return node.expression !== expression - || node.thenStatement !== thenStatement - || node.elseStatement !== elseStatement - ? updateNode(createIf(expression, thenStatement, elseStatement), node) - : node; - } - - export function createDo(statement: Statement, expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.DoStatement); - node.statement = asEmbeddedStatement(statement); - node.expression = expression; - return node; - } - - export function updateDo(node: DoStatement, statement: Statement, expression: Expression) { - return node.statement !== statement - || node.expression !== expression - ? updateNode(createDo(statement, expression), node) - : node; - } - - export function createWhile(expression: Expression, statement: Statement) { - const node = createSynthesizedNode(SyntaxKind.WhileStatement); - node.expression = expression; - node.statement = asEmbeddedStatement(statement); - return node; - } - - export function updateWhile(node: WhileStatement, expression: Expression, statement: Statement) { - return node.expression !== expression - || node.statement !== statement - ? updateNode(createWhile(expression, statement), node) - : node; - } - - export function createFor(initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement) { - const node = createSynthesizedNode(SyntaxKind.ForStatement); - node.initializer = initializer; - node.condition = condition; - node.incrementor = incrementor; - node.statement = asEmbeddedStatement(statement); - return node; - } - - export function updateFor(node: ForStatement, initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement) { - return node.initializer !== initializer - || node.condition !== condition - || node.incrementor !== incrementor - || node.statement !== statement - ? updateNode(createFor(initializer, condition, incrementor, statement), node) - : node; - } - - export function createForIn(initializer: ForInitializer, expression: Expression, statement: Statement) { - const node = createSynthesizedNode(SyntaxKind.ForInStatement); - node.initializer = initializer; - node.expression = expression; - node.statement = asEmbeddedStatement(statement); - return node; - } - - export function updateForIn(node: ForInStatement, initializer: ForInitializer, expression: Expression, statement: Statement) { - return node.initializer !== initializer - || node.expression !== expression - || node.statement !== statement - ? updateNode(createForIn(initializer, expression, statement), node) - : node; - } - - export function createForOf(awaitModifier: AwaitKeywordToken | undefined, initializer: ForInitializer, expression: Expression, statement: Statement) { - const node = createSynthesizedNode(SyntaxKind.ForOfStatement); - node.awaitModifier = awaitModifier; - node.initializer = initializer; - node.expression = isCommaSequence(expression) ? createParen(expression) : expression; - node.statement = asEmbeddedStatement(statement); - return node; - } - - export function updateForOf(node: ForOfStatement, awaitModifier: AwaitKeywordToken | undefined, initializer: ForInitializer, expression: Expression, statement: Statement) { - return node.awaitModifier !== awaitModifier - || node.initializer !== initializer - || node.expression !== expression - || node.statement !== statement - ? updateNode(createForOf(awaitModifier, initializer, expression, statement), node) - : node; - } - - export function createContinue(label?: string | Identifier): ContinueStatement { - const node = createSynthesizedNode(SyntaxKind.ContinueStatement); - node.label = asName(label); - return node; - } - - export function updateContinue(node: ContinueStatement, label: Identifier | undefined) { - return node.label !== label - ? updateNode(createContinue(label), node) - : node; - } - - export function createBreak(label?: string | Identifier): BreakStatement { - const node = createSynthesizedNode(SyntaxKind.BreakStatement); - node.label = asName(label); - return node; - } - - export function updateBreak(node: BreakStatement, label: Identifier | undefined) { - return node.label !== label - ? updateNode(createBreak(label), node) - : node; - } - - export function createReturn(expression?: Expression): ReturnStatement { - const node = createSynthesizedNode(SyntaxKind.ReturnStatement); - node.expression = expression; - return node; - } - - export function updateReturn(node: ReturnStatement, expression: Expression | undefined) { - return node.expression !== expression - ? updateNode(createReturn(expression), node) - : node; - } - - export function createWith(expression: Expression, statement: Statement) { - const node = createSynthesizedNode(SyntaxKind.WithStatement); - node.expression = expression; - node.statement = asEmbeddedStatement(statement); - return node; - } - - export function updateWith(node: WithStatement, expression: Expression, statement: Statement) { - return node.expression !== expression - || node.statement !== statement - ? updateNode(createWith(expression, statement), node) - : node; - } - - export function createSwitch(expression: Expression, caseBlock: CaseBlock): SwitchStatement { - const node = createSynthesizedNode(SyntaxKind.SwitchStatement); - node.expression = parenthesizeExpressionForList(expression); - node.caseBlock = caseBlock; - return node; - } - - export function updateSwitch(node: SwitchStatement, expression: Expression, caseBlock: CaseBlock) { - return node.expression !== expression - || node.caseBlock !== caseBlock - ? updateNode(createSwitch(expression, caseBlock), node) - : node; - } - - export function createLabel(label: string | Identifier, statement: Statement) { - const node = createSynthesizedNode(SyntaxKind.LabeledStatement); - node.label = asName(label); - node.statement = asEmbeddedStatement(statement); - return node; - } - - export function updateLabel(node: LabeledStatement, label: Identifier, statement: Statement) { - return node.label !== label - || node.statement !== statement - ? updateNode(createLabel(label, statement), node) - : node; - } - - export function createThrow(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.ThrowStatement); - node.expression = expression; - return node; - } - - export function updateThrow(node: ThrowStatement, expression: Expression) { - return node.expression !== expression - ? updateNode(createThrow(expression), node) - : node; - } - - export function createTry(tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined) { - const node = createSynthesizedNode(SyntaxKind.TryStatement); - node.tryBlock = tryBlock; - node.catchClause = catchClause; - node.finallyBlock = finallyBlock; - return node; - } - - export function updateTry(node: TryStatement, tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined) { - return node.tryBlock !== tryBlock - || node.catchClause !== catchClause - || node.finallyBlock !== finallyBlock - ? updateNode(createTry(tryBlock, catchClause, finallyBlock), node) - : node; - } - - export function createDebuggerStatement() { - return createSynthesizedNode(SyntaxKind.DebuggerStatement); - } - - export function createVariableDeclaration(name: string | BindingName, type?: TypeNode, initializer?: Expression) { - /* Internally, one should probably use createTypeScriptVariableDeclaration instead and handle definite assignment assertions */ - const node = createSynthesizedNode(SyntaxKind.VariableDeclaration); - node.name = asName(name); - node.type = type; - node.initializer = initializer !== undefined ? parenthesizeExpressionForList(initializer) : undefined; - return node; - } - - export function updateVariableDeclaration(node: VariableDeclaration, name: BindingName, type: TypeNode | undefined, initializer: Expression | undefined) { - /* Internally, one should probably use updateTypeScriptVariableDeclaration instead and handle definite assignment assertions */ - return node.name !== name - || node.type !== type - || node.initializer !== initializer - ? updateNode(createVariableDeclaration(name, type, initializer), node) - : node; - } - - /* @internal */ - export function createTypeScriptVariableDeclaration(name: string | BindingName, exclaimationToken?: Token, type?: TypeNode, initializer?: Expression) { - const node = createSynthesizedNode(SyntaxKind.VariableDeclaration); - node.name = asName(name); - node.type = type; - node.initializer = initializer !== undefined ? parenthesizeExpressionForList(initializer) : undefined; - node.exclamationToken = exclaimationToken; - return node; - } - - /* @internal */ - export function updateTypeScriptVariableDeclaration(node: VariableDeclaration, name: BindingName, exclaimationToken: Token | undefined, type: TypeNode | undefined, initializer: Expression | undefined) { - return node.name !== name - || node.type !== type - || node.initializer !== initializer - || node.exclamationToken !== exclaimationToken - ? updateNode(createTypeScriptVariableDeclaration(name, exclaimationToken, type, initializer), node) - : node; - } - - export function createVariableDeclarationList(declarations: readonly VariableDeclaration[], flags = NodeFlags.None) { - const node = createSynthesizedNode(SyntaxKind.VariableDeclarationList); - node.flags |= flags & NodeFlags.BlockScoped; - node.declarations = createNodeArray(declarations); - return node; - } - - export function updateVariableDeclarationList(node: VariableDeclarationList, declarations: readonly VariableDeclaration[]) { - return node.declarations !== declarations - ? updateNode(createVariableDeclarationList(declarations, node.flags), node) - : node; - } - - export function createFunctionDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: string | Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined) { - const node = createSynthesizedNode(SyntaxKind.FunctionDeclaration); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.asteriskToken = asteriskToken; - node.name = asName(name); - node.typeParameters = asNodeArray(typeParameters); - node.parameters = createNodeArray(parameters); - node.type = type; - node.body = body; - return node; - } - - export function updateFunctionDeclaration( - node: FunctionDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.asteriskToken !== asteriskToken - || node.name !== name - || node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - || node.body !== body - ? updateNode(createFunctionDeclaration(decorators, modifiers, asteriskToken, name, typeParameters, parameters, type, body), node) - : node; - } - - export function createClassDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly ClassElement[]) { - const node = createSynthesizedNode(SyntaxKind.ClassDeclaration); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.name = asName(name); - node.typeParameters = asNodeArray(typeParameters); - node.heritageClauses = asNodeArray(heritageClauses); - node.members = createNodeArray(members); - return node; - } - - export function updateClassDeclaration( - node: ClassDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly ClassElement[]) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.typeParameters !== typeParameters - || node.heritageClauses !== heritageClauses - || node.members !== members - ? updateNode(createClassDeclaration(decorators, modifiers, name, typeParameters, heritageClauses, members), node) - : node; - } - - export function createInterfaceDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | Identifier, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly TypeElement[]) { - const node = createSynthesizedNode(SyntaxKind.InterfaceDeclaration); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.name = asName(name); - node.typeParameters = asNodeArray(typeParameters); - node.heritageClauses = asNodeArray(heritageClauses); - node.members = createNodeArray(members); - return node; - } - - export function updateInterfaceDeclaration( - node: InterfaceDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: Identifier, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly TypeElement[]) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.typeParameters !== typeParameters - || node.heritageClauses !== heritageClauses - || node.members !== members - ? updateNode(createInterfaceDeclaration(decorators, modifiers, name, typeParameters, heritageClauses, members), node) - : node; - } - - export function createTypeAliasDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | Identifier, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - type: TypeNode) { - const node = createSynthesizedNode(SyntaxKind.TypeAliasDeclaration); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.name = asName(name); - node.typeParameters = asNodeArray(typeParameters); - node.type = type; - return node; - } - - export function updateTypeAliasDeclaration( - node: TypeAliasDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: Identifier, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - type: TypeNode) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.typeParameters !== typeParameters - || node.type !== type - ? updateNode(createTypeAliasDeclaration(decorators, modifiers, name, typeParameters, type), node) - : node; - } - - export function createEnumDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | Identifier, - members: readonly EnumMember[]) { - const node = createSynthesizedNode(SyntaxKind.EnumDeclaration); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.name = asName(name); - node.members = createNodeArray(members); - return node; - } - - export function updateEnumDeclaration( - node: EnumDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: Identifier, - members: readonly EnumMember[]) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.members !== members - ? updateNode(createEnumDeclaration(decorators, modifiers, name, members), node) - : node; - } - - export function createModuleDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: ModuleName, body: ModuleBody | undefined, flags = NodeFlags.None) { - const node = createSynthesizedNode(SyntaxKind.ModuleDeclaration); - node.flags |= flags & (NodeFlags.Namespace | NodeFlags.NestedNamespace | NodeFlags.GlobalAugmentation); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.name = name; - node.body = body; - return node; - } - - export function updateModuleDeclaration(node: ModuleDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: ModuleName, body: ModuleBody | undefined) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.body !== body - ? updateNode(createModuleDeclaration(decorators, modifiers, name, body, node.flags), node) - : node; - } - - export function createModuleBlock(statements: readonly Statement[]) { - const node = createSynthesizedNode(SyntaxKind.ModuleBlock); - node.statements = createNodeArray(statements); - return node; - } - - export function updateModuleBlock(node: ModuleBlock, statements: readonly Statement[]) { - return node.statements !== statements - ? updateNode(createModuleBlock(statements), node) - : node; - } - - export function createCaseBlock(clauses: readonly CaseOrDefaultClause[]): CaseBlock { - const node = createSynthesizedNode(SyntaxKind.CaseBlock); - node.clauses = createNodeArray(clauses); - return node; - } - - export function updateCaseBlock(node: CaseBlock, clauses: readonly CaseOrDefaultClause[]) { - return node.clauses !== clauses - ? updateNode(createCaseBlock(clauses), node) - : node; - } - - export function createNamespaceExportDeclaration(name: string | Identifier) { - const node = createSynthesizedNode(SyntaxKind.NamespaceExportDeclaration); - node.name = asName(name); - return node; - } - - export function updateNamespaceExportDeclaration(node: NamespaceExportDeclaration, name: Identifier) { - return node.name !== name - ? updateNode(createNamespaceExportDeclaration(name), node) - : node; - } - - export function createImportEqualsDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | Identifier, moduleReference: ModuleReference) { - const node = createSynthesizedNode(SyntaxKind.ImportEqualsDeclaration); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.name = asName(name); - node.moduleReference = moduleReference; - return node; - } - - export function updateImportEqualsDeclaration(node: ImportEqualsDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: Identifier, moduleReference: ModuleReference) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.moduleReference !== moduleReference - ? updateNode(createImportEqualsDeclaration(decorators, modifiers, name, moduleReference), node) - : node; - } - - export function createImportDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - importClause: ImportClause | undefined, - moduleSpecifier: Expression): ImportDeclaration { - const node = createSynthesizedNode(SyntaxKind.ImportDeclaration); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.importClause = importClause; - node.moduleSpecifier = moduleSpecifier; - return node; - } - - export function updateImportDeclaration( - node: ImportDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - importClause: ImportClause | undefined, - moduleSpecifier: Expression) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.importClause !== importClause - || node.moduleSpecifier !== moduleSpecifier - ? updateNode(createImportDeclaration(decorators, modifiers, importClause, moduleSpecifier), node) - : node; - } - - export function createImportClause(name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause { - const node = createSynthesizedNode(SyntaxKind.ImportClause); - node.name = name; - node.namedBindings = namedBindings; - return node; - } - - export function updateImportClause(node: ImportClause, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined) { - return node.name !== name - || node.namedBindings !== namedBindings - ? updateNode(createImportClause(name, namedBindings), node) - : node; - } - - export function createNamespaceImport(name: Identifier): NamespaceImport { - const node = createSynthesizedNode(SyntaxKind.NamespaceImport); - node.name = name; - return node; - } - - export function updateNamespaceImport(node: NamespaceImport, name: Identifier) { - return node.name !== name - ? updateNode(createNamespaceImport(name), node) - : node; - } - - export function createNamedImports(elements: readonly ImportSpecifier[]): NamedImports { - const node = createSynthesizedNode(SyntaxKind.NamedImports); - node.elements = createNodeArray(elements); - return node; - } - - export function updateNamedImports(node: NamedImports, elements: readonly ImportSpecifier[]) { - return node.elements !== elements - ? updateNode(createNamedImports(elements), node) - : node; - } - - export function createImportSpecifier(propertyName: Identifier | undefined, name: Identifier) { - const node = createSynthesizedNode(SyntaxKind.ImportSpecifier); - node.propertyName = propertyName; - node.name = name; - return node; - } - - export function updateImportSpecifier(node: ImportSpecifier, propertyName: Identifier | undefined, name: Identifier) { - return node.propertyName !== propertyName - || node.name !== name - ? updateNode(createImportSpecifier(propertyName, name), node) - : node; - } - - export function createExportAssignment(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isExportEquals: boolean | undefined, expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.ExportAssignment); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.isExportEquals = isExportEquals; - node.expression = isExportEquals ? parenthesizeBinaryOperand(SyntaxKind.EqualsToken, expression, /*isLeftSideOfBinary*/ false, /*leftOperand*/ undefined) : parenthesizeDefaultExpression(expression); - return node; - } - - export function updateExportAssignment(node: ExportAssignment, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, expression: Expression) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.expression !== expression - ? updateNode(createExportAssignment(decorators, modifiers, node.isExportEquals, expression), node) - : node; - } - - export function createExportDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, exportClause: NamedExports | undefined, moduleSpecifier?: Expression) { - const node = createSynthesizedNode(SyntaxKind.ExportDeclaration); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.exportClause = exportClause; - node.moduleSpecifier = moduleSpecifier; - return node; - } - - export function updateExportDeclaration( - node: ExportDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - exportClause: NamedExports | undefined, - moduleSpecifier: Expression | undefined) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.exportClause !== exportClause - || node.moduleSpecifier !== moduleSpecifier - ? updateNode(createExportDeclaration(decorators, modifiers, exportClause, moduleSpecifier), node) - : node; - } - - /* @internal */ - export function createEmptyExports() { - return createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createNamedExports([]), /*moduleSpecifier*/ undefined); - } - - export function createNamedExports(elements: readonly ExportSpecifier[]) { - const node = createSynthesizedNode(SyntaxKind.NamedExports); - node.elements = createNodeArray(elements); - return node; - } - - export function updateNamedExports(node: NamedExports, elements: readonly ExportSpecifier[]) { - return node.elements !== elements - ? updateNode(createNamedExports(elements), node) - : node; - } - - export function createExportSpecifier(propertyName: string | Identifier | undefined, name: string | Identifier) { - const node = createSynthesizedNode(SyntaxKind.ExportSpecifier); - node.propertyName = asName(propertyName); - node.name = asName(name); - return node; - } - - export function updateExportSpecifier(node: ExportSpecifier, propertyName: Identifier | undefined, name: Identifier) { - return node.propertyName !== propertyName - || node.name !== name - ? updateNode(createExportSpecifier(propertyName, name), node) - : node; - } - - // Module references - - export function createExternalModuleReference(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.ExternalModuleReference); - node.expression = expression; - return node; - } - - export function updateExternalModuleReference(node: ExternalModuleReference, expression: Expression) { - return node.expression !== expression - ? updateNode(createExternalModuleReference(expression), node) - : node; - } - - // JSDoc - - /* @internal */ - export function createJSDocTypeExpression(type: TypeNode): JSDocTypeExpression { - const node = createSynthesizedNode(SyntaxKind.JSDocTypeExpression) as JSDocTypeExpression; - node.type = type; - return node; - } - - /* @internal */ - export function createJSDocTypeTag(typeExpression: JSDocTypeExpression, comment?: string): JSDocTypeTag { - const tag = createJSDocTag(SyntaxKind.JSDocTypeTag, "type"); - tag.typeExpression = typeExpression; - tag.comment = comment; - return tag; - } - - /* @internal */ - export function createJSDocReturnTag(typeExpression?: JSDocTypeExpression, comment?: string): JSDocReturnTag { - const tag = createJSDocTag(SyntaxKind.JSDocReturnTag, "returns"); - tag.typeExpression = typeExpression; - tag.comment = comment; - return tag; - } - - /** @internal */ - export function createJSDocThisTag(typeExpression?: JSDocTypeExpression): JSDocThisTag { - const tag = createJSDocTag(SyntaxKind.JSDocThisTag, "this"); - tag.typeExpression = typeExpression; - return tag; - } - - /* @internal */ - export function createJSDocParamTag(name: EntityName, isBracketed: boolean, typeExpression?: JSDocTypeExpression, comment?: string): JSDocParameterTag { - const tag = createJSDocTag(SyntaxKind.JSDocParameterTag, "param"); - tag.typeExpression = typeExpression; - tag.name = name; - tag.isBracketed = isBracketed; - tag.comment = comment; - return tag; - } - - /* @internal */ - export function createJSDocComment(comment?: string | undefined, tags?: NodeArray | undefined) { - const node = createSynthesizedNode(SyntaxKind.JSDocComment) as JSDoc; - node.comment = comment; - node.tags = tags; - return node; - } - - /* @internal */ - function createJSDocTag(kind: T["kind"], tagName: string): T { - const node = createSynthesizedNode(kind) as T; - node.tagName = createIdentifier(tagName); - return node; - } - - // JSX - - export function createJsxElement(openingElement: JsxOpeningElement, children: readonly JsxChild[], closingElement: JsxClosingElement) { - const node = createSynthesizedNode(SyntaxKind.JsxElement); - node.openingElement = openingElement; - node.children = createNodeArray(children); - node.closingElement = closingElement; - return node; - } - - export function updateJsxElement(node: JsxElement, openingElement: JsxOpeningElement, children: readonly JsxChild[], closingElement: JsxClosingElement) { - return node.openingElement !== openingElement - || node.children !== children - || node.closingElement !== closingElement - ? updateNode(createJsxElement(openingElement, children, closingElement), node) - : node; - } - - export function createJsxSelfClosingElement(tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { - const node = createSynthesizedNode(SyntaxKind.JsxSelfClosingElement); - node.tagName = tagName; - node.typeArguments = asNodeArray(typeArguments); - node.attributes = attributes; - return node; - } - - export function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { - return node.tagName !== tagName - || node.typeArguments !== typeArguments - || node.attributes !== attributes - ? updateNode(createJsxSelfClosingElement(tagName, typeArguments, attributes), node) - : node; - } - - export function createJsxOpeningElement(tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { - const node = createSynthesizedNode(SyntaxKind.JsxOpeningElement); - node.tagName = tagName; - node.typeArguments = asNodeArray(typeArguments); - node.attributes = attributes; - return node; - } - - export function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { - return node.tagName !== tagName - || node.typeArguments !== typeArguments - || node.attributes !== attributes - ? updateNode(createJsxOpeningElement(tagName, typeArguments, attributes), node) - : node; - } - - export function createJsxClosingElement(tagName: JsxTagNameExpression) { - const node = createSynthesizedNode(SyntaxKind.JsxClosingElement); - node.tagName = tagName; - return node; - } - - export function updateJsxClosingElement(node: JsxClosingElement, tagName: JsxTagNameExpression) { - return node.tagName !== tagName - ? updateNode(createJsxClosingElement(tagName), node) - : node; - } - - export function createJsxFragment(openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) { - const node = createSynthesizedNode(SyntaxKind.JsxFragment); - node.openingFragment = openingFragment; - node.children = createNodeArray(children); - node.closingFragment = closingFragment; - return node; - } - - export function createJsxText(text: string, containsOnlyTriviaWhiteSpaces?: boolean) { - const node = createSynthesizedNode(SyntaxKind.JsxText); - node.text = text; - node.containsOnlyTriviaWhiteSpaces = !!containsOnlyTriviaWhiteSpaces; - return node; - } - - export function updateJsxText(node: JsxText, text: string, containsOnlyTriviaWhiteSpaces?: boolean) { - return node.text !== text - || node.containsOnlyTriviaWhiteSpaces !== containsOnlyTriviaWhiteSpaces - ? updateNode(createJsxText(text, containsOnlyTriviaWhiteSpaces), node) - : node; - } - - export function createJsxOpeningFragment() { - return createSynthesizedNode(SyntaxKind.JsxOpeningFragment); - } - - export function createJsxJsxClosingFragment() { - return createSynthesizedNode(SyntaxKind.JsxClosingFragment); - } - - export function updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) { - return node.openingFragment !== openingFragment - || node.children !== children - || node.closingFragment !== closingFragment - ? updateNode(createJsxFragment(openingFragment, children, closingFragment), node) - : node; - } - - export function createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression) { - const node = createSynthesizedNode(SyntaxKind.JsxAttribute); - node.name = name; - node.initializer = initializer; - return node; - } - - export function updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression) { - return node.name !== name - || node.initializer !== initializer - ? updateNode(createJsxAttribute(name, initializer), node) - : node; - } - - export function createJsxAttributes(properties: readonly JsxAttributeLike[]) { - const node = createSynthesizedNode(SyntaxKind.JsxAttributes); - node.properties = createNodeArray(properties); - return node; - } - - export function updateJsxAttributes(node: JsxAttributes, properties: readonly JsxAttributeLike[]) { - return node.properties !== properties - ? updateNode(createJsxAttributes(properties), node) - : node; - } - - export function createJsxSpreadAttribute(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.JsxSpreadAttribute); - node.expression = expression; - return node; - } - - export function updateJsxSpreadAttribute(node: JsxSpreadAttribute, expression: Expression) { - return node.expression !== expression - ? updateNode(createJsxSpreadAttribute(expression), node) - : node; - } - - export function createJsxExpression(dotDotDotToken: DotDotDotToken | undefined, expression: Expression | undefined) { - const node = createSynthesizedNode(SyntaxKind.JsxExpression); - node.dotDotDotToken = dotDotDotToken; - node.expression = expression; - return node; - } - - export function updateJsxExpression(node: JsxExpression, expression: Expression | undefined) { - return node.expression !== expression - ? updateNode(createJsxExpression(node.dotDotDotToken, expression), node) - : node; - } - - // Clauses - - export function createCaseClause(expression: Expression, statements: readonly Statement[]) { - const node = createSynthesizedNode(SyntaxKind.CaseClause); - node.expression = parenthesizeExpressionForList(expression); - node.statements = createNodeArray(statements); - return node; - } - - export function updateCaseClause(node: CaseClause, expression: Expression, statements: readonly Statement[]) { - return node.expression !== expression - || node.statements !== statements - ? updateNode(createCaseClause(expression, statements), node) - : node; - } - - export function createDefaultClause(statements: readonly Statement[]) { - const node = createSynthesizedNode(SyntaxKind.DefaultClause); - node.statements = createNodeArray(statements); - return node; - } - - export function updateDefaultClause(node: DefaultClause, statements: readonly Statement[]) { - return node.statements !== statements - ? updateNode(createDefaultClause(statements), node) - : node; - } - - export function createHeritageClause(token: HeritageClause["token"], types: readonly ExpressionWithTypeArguments[]) { - const node = createSynthesizedNode(SyntaxKind.HeritageClause); - node.token = token; - node.types = createNodeArray(types); - return node; - } - - export function updateHeritageClause(node: HeritageClause, types: readonly ExpressionWithTypeArguments[]) { - return node.types !== types - ? updateNode(createHeritageClause(node.token, types), node) - : node; - } - - export function createCatchClause(variableDeclaration: string | VariableDeclaration | undefined, block: Block) { - const node = createSynthesizedNode(SyntaxKind.CatchClause); - node.variableDeclaration = isString(variableDeclaration) ? createVariableDeclaration(variableDeclaration) : variableDeclaration; - node.block = block; - return node; - } - - export function updateCatchClause(node: CatchClause, variableDeclaration: VariableDeclaration | undefined, block: Block) { - return node.variableDeclaration !== variableDeclaration - || node.block !== block - ? updateNode(createCatchClause(variableDeclaration, block), node) - : node; - } - - // Property assignments - - export function createPropertyAssignment(name: string | PropertyName, initializer: Expression) { - const node = createSynthesizedNode(SyntaxKind.PropertyAssignment); - node.name = asName(name); - node.questionToken = undefined; - node.initializer = parenthesizeExpressionForList(initializer); - return node; - } - - export function updatePropertyAssignment(node: PropertyAssignment, name: PropertyName, initializer: Expression) { - return node.name !== name - || node.initializer !== initializer - ? updateNode(createPropertyAssignment(name, initializer), node) - : node; - } - - export function createShorthandPropertyAssignment(name: string | Identifier, objectAssignmentInitializer?: Expression) { - const node = createSynthesizedNode(SyntaxKind.ShorthandPropertyAssignment); - node.name = asName(name); - node.objectAssignmentInitializer = objectAssignmentInitializer !== undefined ? parenthesizeExpressionForList(objectAssignmentInitializer) : undefined; - return node; - } - - export function updateShorthandPropertyAssignment(node: ShorthandPropertyAssignment, name: Identifier, objectAssignmentInitializer: Expression | undefined) { - return node.name !== name - || node.objectAssignmentInitializer !== objectAssignmentInitializer - ? updateNode(createShorthandPropertyAssignment(name, objectAssignmentInitializer), node) - : node; - } - - export function createSpreadAssignment(expression: Expression) { - const node = createSynthesizedNode(SyntaxKind.SpreadAssignment); - node.expression = parenthesizeExpressionForList(expression); - return node; - } - - export function updateSpreadAssignment(node: SpreadAssignment, expression: Expression) { - return node.expression !== expression - ? updateNode(createSpreadAssignment(expression), node) - : node; - } - - // Enum - - export function createEnumMember(name: string | PropertyName, initializer?: Expression) { - const node = createSynthesizedNode(SyntaxKind.EnumMember); - node.name = asName(name); - node.initializer = initializer && parenthesizeExpressionForList(initializer); - return node; - } - - export function updateEnumMember(node: EnumMember, name: PropertyName, initializer: Expression | undefined) { - return node.name !== name - || node.initializer !== initializer - ? updateNode(createEnumMember(name, initializer), node) - : node; - } - - // Top-level nodes - - export function updateSourceFileNode(node: SourceFile, statements: readonly Statement[], isDeclarationFile?: boolean, referencedFiles?: SourceFile["referencedFiles"], typeReferences?: SourceFile["typeReferenceDirectives"], hasNoDefaultLib?: boolean, libReferences?: SourceFile["libReferenceDirectives"]) { - if ( - node.statements !== statements || - (isDeclarationFile !== undefined && node.isDeclarationFile !== isDeclarationFile) || - (referencedFiles !== undefined && node.referencedFiles !== referencedFiles) || - (typeReferences !== undefined && node.typeReferenceDirectives !== typeReferences) || - (libReferences !== undefined && node.libReferenceDirectives !== libReferences) || - (hasNoDefaultLib !== undefined && node.hasNoDefaultLib !== hasNoDefaultLib) - ) { - const updated = createSynthesizedNode(SyntaxKind.SourceFile); - updated.flags |= node.flags; - updated.statements = createNodeArray(statements); - updated.endOfFileToken = node.endOfFileToken; - updated.fileName = node.fileName; - updated.path = node.path; - updated.text = node.text; - updated.isDeclarationFile = isDeclarationFile === undefined ? node.isDeclarationFile : isDeclarationFile; - updated.referencedFiles = referencedFiles === undefined ? node.referencedFiles : referencedFiles; - updated.typeReferenceDirectives = typeReferences === undefined ? node.typeReferenceDirectives : typeReferences; - updated.hasNoDefaultLib = hasNoDefaultLib === undefined ? node.hasNoDefaultLib : hasNoDefaultLib; - updated.libReferenceDirectives = libReferences === undefined ? node.libReferenceDirectives : libReferences; - if (node.amdDependencies !== undefined) updated.amdDependencies = node.amdDependencies; - if (node.moduleName !== undefined) updated.moduleName = node.moduleName; - if (node.languageVariant !== undefined) updated.languageVariant = node.languageVariant; - if (node.renamedDependencies !== undefined) updated.renamedDependencies = node.renamedDependencies; - if (node.languageVersion !== undefined) updated.languageVersion = node.languageVersion; - if (node.scriptKind !== undefined) updated.scriptKind = node.scriptKind; - if (node.externalModuleIndicator !== undefined) updated.externalModuleIndicator = node.externalModuleIndicator; - if (node.commonJsModuleIndicator !== undefined) updated.commonJsModuleIndicator = node.commonJsModuleIndicator; - if (node.identifiers !== undefined) updated.identifiers = node.identifiers; - if (node.nodeCount !== undefined) updated.nodeCount = node.nodeCount; - if (node.identifierCount !== undefined) updated.identifierCount = node.identifierCount; - if (node.symbolCount !== undefined) updated.symbolCount = node.symbolCount; - if (node.parseDiagnostics !== undefined) updated.parseDiagnostics = node.parseDiagnostics; - if (node.bindDiagnostics !== undefined) updated.bindDiagnostics = node.bindDiagnostics; - if (node.bindSuggestionDiagnostics !== undefined) updated.bindSuggestionDiagnostics = node.bindSuggestionDiagnostics; - if (node.lineMap !== undefined) updated.lineMap = node.lineMap; - if (node.classifiableNames !== undefined) updated.classifiableNames = node.classifiableNames; - if (node.resolvedModules !== undefined) updated.resolvedModules = node.resolvedModules; - if (node.resolvedTypeReferenceDirectiveNames !== undefined) updated.resolvedTypeReferenceDirectiveNames = node.resolvedTypeReferenceDirectiveNames; - if (node.imports !== undefined) updated.imports = node.imports; - if (node.moduleAugmentations !== undefined) updated.moduleAugmentations = node.moduleAugmentations; - if (node.pragmas !== undefined) updated.pragmas = node.pragmas; - if (node.localJsxFactory !== undefined) updated.localJsxFactory = node.localJsxFactory; - if (node.localJsxNamespace !== undefined) updated.localJsxNamespace = node.localJsxNamespace; - return updateNode(updated, node); - } - - return node; - } - - /** - * Creates a shallow, memberwise clone of a node for mutation. - */ - export function getMutableClone(node: T): T { - const clone = getSynthesizedClone(node); - clone.pos = node.pos; - clone.end = node.end; - clone.parent = node.parent; - return clone; - } - - // Transformation nodes - - /** - * Creates a synthetic statement to act as a placeholder for a not-emitted statement in - * order to preserve comments. - * - * @param original The original statement. - */ - export function createNotEmittedStatement(original: Node) { - const node = createSynthesizedNode(SyntaxKind.NotEmittedStatement); - node.original = original; - setTextRange(node, original); - return node; - } - - /** - * Creates a synthetic element to act as a placeholder for the end of an emitted declaration in - * order to properly emit exports. - */ - /* @internal */ - export function createEndOfDeclarationMarker(original: Node) { - const node = createSynthesizedNode(SyntaxKind.EndOfDeclarationMarker); - node.emitNode = {} as EmitNode; - node.original = original; - return node; - } - - /** - * Creates a synthetic element to act as a placeholder for the beginning of a merged declaration in - * order to properly emit exports. - */ - /* @internal */ - export function createMergeDeclarationMarker(original: Node) { - const node = createSynthesizedNode(SyntaxKind.MergeDeclarationMarker); - node.emitNode = {} as EmitNode; - node.original = original; - return node; - } - - /** - * Creates a synthetic expression to act as a placeholder for a not-emitted expression in - * order to preserve comments or sourcemap positions. - * - * @param expression The inner expression to emit. - * @param original The original outer expression. - * @param location The location for the expression. Defaults to the positions from "original" if provided. - */ - export function createPartiallyEmittedExpression(expression: Expression, original?: Node) { - const node = createSynthesizedNode(SyntaxKind.PartiallyEmittedExpression); - node.expression = expression; - node.original = original; - setTextRange(node, original); - return node; - } - - export function updatePartiallyEmittedExpression(node: PartiallyEmittedExpression, expression: Expression) { - if (node.expression !== expression) { - return updateNode(createPartiallyEmittedExpression(expression, node.original), node); - } - return node; - } - - function flattenCommaElements(node: Expression): Expression | readonly Expression[] { - if (nodeIsSynthesized(node) && !isParseTreeNode(node) && !node.original && !node.emitNode && !node.id) { - if (node.kind === SyntaxKind.CommaListExpression) { - return (node).elements; - } - if (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.CommaToken) { - return [node.left, node.right]; - } - } - return node; - } - - export function createCommaList(elements: readonly Expression[]) { - const node = createSynthesizedNode(SyntaxKind.CommaListExpression); - node.elements = createNodeArray(sameFlatMap(elements, flattenCommaElements)); - return node; - } - - export function updateCommaList(node: CommaListExpression, elements: readonly Expression[]) { - return node.elements !== elements - ? updateNode(createCommaList(elements), node) - : node; - } - - /* @internal */ - export function createSyntheticReferenceExpression(expression: Expression, thisArg: Expression) { - const node = createSynthesizedNode(SyntaxKind.SyntheticReferenceExpression); - node.expression = expression; - node.thisArg = thisArg; - return node; - } - - /* @internal */ - export function updateSyntheticReferenceExpression(node: SyntheticReferenceExpression, expression: Expression, thisArg: Expression) { - return node.expression !== expression - || node.thisArg !== thisArg - ? updateNode(createSyntheticReferenceExpression(expression, thisArg), node) - : node; - } - - export function createBundle(sourceFiles: readonly SourceFile[], prepends: readonly (UnparsedSource | InputFiles)[] = emptyArray) { - const node = createNode(SyntaxKind.Bundle); - node.prepends = prepends; - node.sourceFiles = sourceFiles; - return node; - } - - let allUnscopedEmitHelpers: ReadonlyMap | undefined; - function getAllUnscopedEmitHelpers() { - return allUnscopedEmitHelpers || (allUnscopedEmitHelpers = arrayToMap([ - valuesHelper, - readHelper, - spreadHelper, - spreadArraysHelper, - restHelper, - decorateHelper, - metadataHelper, - paramHelper, - awaiterHelper, - assignHelper, - awaitHelper, - asyncGeneratorHelper, - asyncDelegator, - asyncValues, - extendsHelper, - templateObjectHelper, - generatorHelper, - importStarHelper, - importDefaultHelper - ], helper => helper.name)); - } - - function createUnparsedSource() { - const node = createNode(SyntaxKind.UnparsedSource); - node.prologues = emptyArray; - node.referencedFiles = emptyArray; - node.libReferenceDirectives = emptyArray; - node.getLineAndCharacterOfPosition = pos => getLineAndCharacterOfPosition(node, pos); - return node; - } - - export function createUnparsedSourceFile(text: string): UnparsedSource; - export function createUnparsedSourceFile(inputFile: InputFiles, type: "js" | "dts", stripInternal?: boolean): UnparsedSource; - export function createUnparsedSourceFile(text: string, mapPath: string | undefined, map: string | undefined): UnparsedSource; - export function createUnparsedSourceFile(textOrInputFiles: string | InputFiles, mapPathOrType?: string, mapTextOrStripInternal?: string | boolean): UnparsedSource { - const node = createUnparsedSource(); - let stripInternal: boolean | undefined; - let bundleFileInfo: BundleFileInfo | undefined; - if (!isString(textOrInputFiles)) { - Debug.assert(mapPathOrType === "js" || mapPathOrType === "dts"); - node.fileName = (mapPathOrType === "js" ? textOrInputFiles.javascriptPath : textOrInputFiles.declarationPath) || ""; - node.sourceMapPath = mapPathOrType === "js" ? textOrInputFiles.javascriptMapPath : textOrInputFiles.declarationMapPath; - Object.defineProperties(node, { - text: { get() { return mapPathOrType === "js" ? textOrInputFiles.javascriptText : textOrInputFiles.declarationText; } }, - sourceMapText: { get() { return mapPathOrType === "js" ? textOrInputFiles.javascriptMapText : textOrInputFiles.declarationMapText; } }, - }); - - - if (textOrInputFiles.buildInfo && textOrInputFiles.buildInfo.bundle) { - node.oldFileOfCurrentEmit = textOrInputFiles.oldFileOfCurrentEmit; - Debug.assert(mapTextOrStripInternal === undefined || typeof mapTextOrStripInternal === "boolean"); - stripInternal = mapTextOrStripInternal as boolean | undefined; - bundleFileInfo = mapPathOrType === "js" ? textOrInputFiles.buildInfo.bundle.js : textOrInputFiles.buildInfo.bundle.dts; - if (node.oldFileOfCurrentEmit) { - parseOldFileOfCurrentEmit(node, Debug.assertDefined(bundleFileInfo)); - return node; - } - } - } - else { - node.fileName = ""; - node.text = textOrInputFiles; - node.sourceMapPath = mapPathOrType; - node.sourceMapText = mapTextOrStripInternal as string; - } - Debug.assert(!node.oldFileOfCurrentEmit); - parseUnparsedSourceFile(node, bundleFileInfo, stripInternal); - return node; - } - - function parseUnparsedSourceFile(node: UnparsedSource, bundleFileInfo: BundleFileInfo | undefined, stripInternal: boolean | undefined) { - let prologues: UnparsedPrologue[] | undefined; - let helpers: UnscopedEmitHelper[] | undefined; - let referencedFiles: FileReference[] | undefined; - let typeReferenceDirectives: string[] | undefined; - let libReferenceDirectives: FileReference[] | undefined; - let texts: UnparsedSourceText[] | undefined; - - for (const section of bundleFileInfo ? bundleFileInfo.sections : emptyArray) { - switch (section.kind) { - case BundleFileSectionKind.Prologue: - (prologues || (prologues = [])).push(createUnparsedNode(section, node) as UnparsedPrologue); - break; - case BundleFileSectionKind.EmitHelpers: - (helpers || (helpers = [])).push(getAllUnscopedEmitHelpers().get(section.data)!); - break; - case BundleFileSectionKind.NoDefaultLib: - node.hasNoDefaultLib = true; - break; - case BundleFileSectionKind.Reference: - (referencedFiles || (referencedFiles = [])).push({ pos: -1, end: -1, fileName: section.data }); - break; - case BundleFileSectionKind.Type: - (typeReferenceDirectives || (typeReferenceDirectives = [])).push(section.data); - break; - case BundleFileSectionKind.Lib: - (libReferenceDirectives || (libReferenceDirectives = [])).push({ pos: -1, end: -1, fileName: section.data }); - break; - case BundleFileSectionKind.Prepend: - const prependNode = createUnparsedNode(section, node) as UnparsedPrepend; - let prependTexts: UnparsedTextLike[] | undefined; - for (const text of section.texts) { - if (!stripInternal || text.kind !== BundleFileSectionKind.Internal) { - (prependTexts || (prependTexts = [])).push(createUnparsedNode(text, node) as UnparsedTextLike); - } - } - prependNode.texts = prependTexts || emptyArray; - (texts || (texts = [])).push(prependNode); - break; - case BundleFileSectionKind.Internal: - if (stripInternal) { - if (!texts) texts = []; - break; - } - // falls through - - case BundleFileSectionKind.Text: - (texts || (texts = [])).push(createUnparsedNode(section, node) as UnparsedTextLike); - break; - default: - Debug.assertNever(section); - } - } - - node.prologues = prologues || emptyArray; - node.helpers = helpers; - node.referencedFiles = referencedFiles || emptyArray; - node.typeReferenceDirectives = typeReferenceDirectives; - node.libReferenceDirectives = libReferenceDirectives || emptyArray; - node.texts = texts || [createUnparsedNode({ kind: BundleFileSectionKind.Text, pos: 0, end: node.text.length }, node)]; - } - - function parseOldFileOfCurrentEmit(node: UnparsedSource, bundleFileInfo: BundleFileInfo) { - Debug.assert(!!node.oldFileOfCurrentEmit); - let texts: UnparsedTextLike[] | undefined; - let syntheticReferences: UnparsedSyntheticReference[] | undefined; - for (const section of bundleFileInfo.sections) { - switch (section.kind) { - case BundleFileSectionKind.Internal: - case BundleFileSectionKind.Text: - (texts || (texts = [])).push(createUnparsedNode(section, node) as UnparsedTextLike); - break; - - case BundleFileSectionKind.NoDefaultLib: - case BundleFileSectionKind.Reference: - case BundleFileSectionKind.Type: - case BundleFileSectionKind.Lib: - (syntheticReferences || (syntheticReferences = [])).push(createUnparsedSyntheticReference(section, node)); - break; - - // Ignore - case BundleFileSectionKind.Prologue: - case BundleFileSectionKind.EmitHelpers: - case BundleFileSectionKind.Prepend: - break; - - default: - Debug.assertNever(section); - } - } - node.texts = texts || emptyArray; - node.helpers = map(bundleFileInfo.sources && bundleFileInfo.sources.helpers, name => getAllUnscopedEmitHelpers().get(name)!); - node.syntheticReferences = syntheticReferences; - return node; - } - - function mapBundleFileSectionKindToSyntaxKind(kind: BundleFileSectionKind): SyntaxKind { - switch (kind) { - case BundleFileSectionKind.Prologue: return SyntaxKind.UnparsedPrologue; - case BundleFileSectionKind.Prepend: return SyntaxKind.UnparsedPrepend; - case BundleFileSectionKind.Internal: return SyntaxKind.UnparsedInternalText; - case BundleFileSectionKind.Text: return SyntaxKind.UnparsedText; - - case BundleFileSectionKind.EmitHelpers: - case BundleFileSectionKind.NoDefaultLib: - case BundleFileSectionKind.Reference: - case BundleFileSectionKind.Type: - case BundleFileSectionKind.Lib: - return Debug.fail(`BundleFileSectionKind: ${kind} not yet mapped to SyntaxKind`); - - default: - return Debug.assertNever(kind); - } - } - - function createUnparsedNode(section: BundleFileSection, parent: UnparsedSource): UnparsedNode { - const node = createNode(mapBundleFileSectionKindToSyntaxKind(section.kind), section.pos, section.end) as UnparsedNode; - node.parent = parent; - node.data = section.data; - return node; - } - - function createUnparsedSyntheticReference(section: BundleFileHasNoDefaultLib | BundleFileReference, parent: UnparsedSource) { - const node = createNode(SyntaxKind.UnparsedSyntheticReference, section.pos, section.end) as UnparsedSyntheticReference; - node.parent = parent; - node.data = section.data; - node.section = section; - return node; - } - - export function createInputFiles( - javascriptText: string, - declarationText: string - ): InputFiles; - export function createInputFiles( - readFileText: (path: string) => string | undefined, - javascriptPath: string, - javascriptMapPath: string | undefined, - declarationPath: string, - declarationMapPath: string | undefined, - buildInfoPath: string | undefined - ): InputFiles; - export function createInputFiles( - javascriptText: string, - declarationText: string, - javascriptMapPath: string | undefined, - javascriptMapText: string | undefined, - declarationMapPath: string | undefined, - declarationMapText: string | undefined - ): InputFiles; - /*@internal*/ - export function createInputFiles( - javascriptText: string, - declarationText: string, - javascriptMapPath: string | undefined, - javascriptMapText: string | undefined, - declarationMapPath: string | undefined, - declarationMapText: string | undefined, - javascriptPath: string | undefined, - declarationPath: string | undefined, - buildInfoPath?: string | undefined, - buildInfo?: BuildInfo, - oldFileOfCurrentEmit?: boolean - ): InputFiles; - export function createInputFiles( - javascriptTextOrReadFileText: string | ((path: string) => string | undefined), - declarationTextOrJavascriptPath: string, - javascriptMapPath?: string, - javascriptMapTextOrDeclarationPath?: string, - declarationMapPath?: string, - declarationMapTextOrBuildInfoPath?: string, - javascriptPath?: string | undefined, - declarationPath?: string | undefined, - buildInfoPath?: string | undefined, - buildInfo?: BuildInfo, - oldFileOfCurrentEmit?: boolean - ): InputFiles { - const node = createNode(SyntaxKind.InputFiles); - if (!isString(javascriptTextOrReadFileText)) { - const cache = createMap(); - const textGetter = (path: string | undefined) => { - if (path === undefined) return undefined; - let value = cache.get(path); - if (value === undefined) { - value = javascriptTextOrReadFileText(path); - cache.set(path, value !== undefined ? value : false); - } - return value !== false ? value as string : undefined; - }; - const definedTextGetter = (path: string) => { - const result = textGetter(path); - return result !== undefined ? result : `/* Input file ${path} was missing */\r\n`; - }; - let buildInfo: BuildInfo | false; - const getAndCacheBuildInfo = (getText: () => string | undefined) => { - if (buildInfo === undefined) { - const result = getText(); - buildInfo = result !== undefined ? getBuildInfo(result) : false; - } - return buildInfo || undefined; - }; - node.javascriptPath = declarationTextOrJavascriptPath; - node.javascriptMapPath = javascriptMapPath; - node.declarationPath = Debug.assertDefined(javascriptMapTextOrDeclarationPath); - node.declarationMapPath = declarationMapPath; - node.buildInfoPath = declarationMapTextOrBuildInfoPath; - Object.defineProperties(node, { - javascriptText: { get() { return definedTextGetter(declarationTextOrJavascriptPath); } }, - javascriptMapText: { get() { return textGetter(javascriptMapPath); } }, // TODO:: if there is inline sourceMap in jsFile, use that - declarationText: { get() { return definedTextGetter(Debug.assertDefined(javascriptMapTextOrDeclarationPath)); } }, - declarationMapText: { get() { return textGetter(declarationMapPath); } }, // TODO:: if there is inline sourceMap in dtsFile, use that - buildInfo: { get() { return getAndCacheBuildInfo(() => textGetter(declarationMapTextOrBuildInfoPath)); } } - }); - } - else { - node.javascriptText = javascriptTextOrReadFileText; - node.javascriptMapPath = javascriptMapPath; - node.javascriptMapText = javascriptMapTextOrDeclarationPath; - node.declarationText = declarationTextOrJavascriptPath; - node.declarationMapPath = declarationMapPath; - node.declarationMapText = declarationMapTextOrBuildInfoPath; - node.javascriptPath = javascriptPath; - node.declarationPath = declarationPath; - node.buildInfoPath = buildInfoPath; - node.buildInfo = buildInfo; - node.oldFileOfCurrentEmit = oldFileOfCurrentEmit; - } - return node; - } - - export function updateBundle(node: Bundle, sourceFiles: readonly SourceFile[], prepends: readonly (UnparsedSource | InputFiles)[] = emptyArray) { - if (node.sourceFiles !== sourceFiles || node.prepends !== prepends) { - return createBundle(sourceFiles, prepends); - } - return node; - } - - // Compound nodes - - export function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[]): CallExpression; - export function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; - export function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[], param?: ParameterDeclaration, paramValue?: Expression) { - return createCall( - createFunctionExpression( - /*modifiers*/ undefined, - /*asteriskToken*/ undefined, - /*name*/ undefined, - /*typeParameters*/ undefined, - /*parameters*/ param ? [param] : [], - /*type*/ undefined, - createBlock(statements, /*multiLine*/ true) - ), - /*typeArguments*/ undefined, - /*argumentsArray*/ paramValue ? [paramValue] : [] - ); - } - - export function createImmediatelyInvokedArrowFunction(statements: readonly Statement[]): CallExpression; - export function createImmediatelyInvokedArrowFunction(statements: readonly Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; - export function createImmediatelyInvokedArrowFunction(statements: readonly Statement[], param?: ParameterDeclaration, paramValue?: Expression) { - return createCall( - createArrowFunction( - /*modifiers*/ undefined, - /*typeParameters*/ undefined, - /*parameters*/ param ? [param] : [], - /*type*/ undefined, - /*equalsGreaterThanToken*/ undefined, - createBlock(statements, /*multiLine*/ true) - ), - /*typeArguments*/ undefined, - /*argumentsArray*/ paramValue ? [paramValue] : [] - ); - } - - - export function createComma(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.CommaToken, right); - } - - export function createLessThan(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.LessThanToken, right); - } - - export function createAssignment(left: ObjectLiteralExpression | ArrayLiteralExpression, right: Expression): DestructuringAssignment; - export function createAssignment(left: Expression, right: Expression): BinaryExpression; - export function createAssignment(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.EqualsToken, right); - } - - export function createStrictEquality(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.EqualsEqualsEqualsToken, right); - } - - export function createStrictInequality(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.ExclamationEqualsEqualsToken, right); - } - - export function createAdd(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.PlusToken, right); - } - - export function createSubtract(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.MinusToken, right); - } - - export function createPostfixIncrement(operand: Expression) { - return createPostfix(operand, SyntaxKind.PlusPlusToken); - } - - export function createLogicalAnd(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.AmpersandAmpersandToken, right); - } - - export function createLogicalOr(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.BarBarToken, right); - } - - export function createNullishCoalesce(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.QuestionQuestionToken, right); - } - - export function createLogicalNot(operand: Expression) { - return createPrefix(SyntaxKind.ExclamationToken, operand); - } - - export function createVoidZero() { - return createVoid(createLiteral(0)); - } - - export function createExportDefault(expression: Expression) { - return createExportAssignment(/*decorators*/ undefined, /*modifiers*/ undefined, /*isExportEquals*/ false, expression); - } - - export function createExternalModuleExport(exportName: Identifier) { - return createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createNamedExports([createExportSpecifier(/*propertyName*/ undefined, exportName)])); - } - - // Utilities - - function asName(name: string | T): T | Identifier { - return isString(name) ? createIdentifier(name) : name; - } - - function asExpression(value: string | number | boolean | T): T | StringLiteral | NumericLiteral | BooleanLiteral { - return typeof value === "string" ? createStringLiteral(value) : - typeof value === "number" ? createNumericLiteral(""+value) : - typeof value === "boolean" ? value ? createTrue() : createFalse() : - value; - } - - function asNodeArray(array: readonly T[]): NodeArray; - function asNodeArray(array: readonly T[] | undefined): NodeArray | undefined; - function asNodeArray(array: readonly T[] | undefined): NodeArray | undefined { - return array ? createNodeArray(array) : undefined; - } - - function asToken(value: TKind | Token): Token { - return typeof value === "number" ? createToken(value) : value; - } - - function asEmbeddedStatement(statement: T): T | EmptyStatement; - function asEmbeddedStatement(statement: T | undefined): T | EmptyStatement | undefined; - function asEmbeddedStatement(statement: T | undefined): T | EmptyStatement | undefined { - return statement && isNotEmittedStatement(statement) ? setTextRange(setOriginalNode(createEmptyStatement(), statement), statement) : statement; - } - - /** - * Clears any EmitNode entries from parse-tree nodes. - * @param sourceFile A source file. - */ - export function disposeEmitNodes(sourceFile: SourceFile) { - // During transformation we may need to annotate a parse tree node with transient - // transformation properties. As parse tree nodes live longer than transformation - // nodes, we need to make sure we reclaim any memory allocated for custom ranges - // from these nodes to ensure we do not hold onto entire subtrees just for position - // information. We also need to reset these nodes to a pre-transformation state - // for incremental parsing scenarios so that we do not impact later emit. - sourceFile = getSourceFileOfNode(getParseTreeNode(sourceFile)); - const emitNode = sourceFile && sourceFile.emitNode; - const annotatedNodes = emitNode && emitNode.annotatedNodes; - if (annotatedNodes) { - for (const node of annotatedNodes) { - node.emitNode = undefined; - } - } - } - - /** - * Associates a node with the current transformation, initializing - * various transient transformation properties. - */ - /* @internal */ - export function getOrCreateEmitNode(node: Node): EmitNode { - if (!node.emitNode) { - if (isParseTreeNode(node)) { - // To avoid holding onto transformation artifacts, we keep track of any - // parse tree node we are annotating. This allows us to clean them up after - // all transformations have completed. - if (node.kind === SyntaxKind.SourceFile) { - return node.emitNode = { annotatedNodes: [node] } as EmitNode; - } - - const sourceFile = getSourceFileOfNode(getParseTreeNode(getSourceFileOfNode(node))); - getOrCreateEmitNode(sourceFile).annotatedNodes!.push(node); - } - - node.emitNode = {} as EmitNode; - } - - return node.emitNode; - } - - /** - * Sets `EmitFlags.NoComments` on a node and removes any leading and trailing synthetic comments. - * @internal - */ - export function removeAllComments(node: T): T { - const emitNode = getOrCreateEmitNode(node); - emitNode.flags |= EmitFlags.NoComments; - emitNode.leadingComments = undefined; - emitNode.trailingComments = undefined; - return node; - } - - export function setTextRange(range: T, location: TextRange | undefined): T { - if (location) { - range.pos = location.pos; - range.end = location.end; - } - return range; - } - - /** - * Sets flags that control emit behavior of a node. - */ - export function setEmitFlags(node: T, emitFlags: EmitFlags) { - getOrCreateEmitNode(node).flags = emitFlags; - return node; - } - - /** - * Sets flags that control emit behavior of a node. - */ - /* @internal */ - export function addEmitFlags(node: T, emitFlags: EmitFlags) { - const emitNode = getOrCreateEmitNode(node); - emitNode.flags = emitNode.flags | emitFlags; - return node; - } - - /** - * Gets a custom text range to use when emitting source maps. - */ - export function getSourceMapRange(node: Node): SourceMapRange { - const emitNode = node.emitNode; - return (emitNode && emitNode.sourceMapRange) || node; - } - - /** - * Sets a custom text range to use when emitting source maps. - */ - export function setSourceMapRange(node: T, range: SourceMapRange | undefined) { - getOrCreateEmitNode(node).sourceMapRange = range; - return node; - } - - let SourceMapSource: new (fileName: string, text: string, skipTrivia?: (pos: number) => number) => SourceMapSource; - - /** - * Create an external source map source file reference - */ - export function createSourceMapSource(fileName: string, text: string, skipTrivia?: (pos: number) => number): SourceMapSource { - return new (SourceMapSource || (SourceMapSource = objectAllocator.getSourceMapSourceConstructor()))(fileName, text, skipTrivia); - } - - /** - * Gets the TextRange to use for source maps for a token of a node. - */ - export function getTokenSourceMapRange(node: Node, token: SyntaxKind): SourceMapRange | undefined { - const emitNode = node.emitNode; - const tokenSourceMapRanges = emitNode && emitNode.tokenSourceMapRanges; - return tokenSourceMapRanges && tokenSourceMapRanges[token]; - } - - /** - * Sets the TextRange to use for source maps for a token of a node. - */ - export function setTokenSourceMapRange(node: T, token: SyntaxKind, range: SourceMapRange | undefined) { - const emitNode = getOrCreateEmitNode(node); - const tokenSourceMapRanges = emitNode.tokenSourceMapRanges || (emitNode.tokenSourceMapRanges = []); - tokenSourceMapRanges[token] = range; - return node; - } - - /** - * Gets a custom text range to use when emitting comments. - */ - /*@internal*/ - export function getStartsOnNewLine(node: Node) { - const emitNode = node.emitNode; - return emitNode && emitNode.startsOnNewLine; - } - - /** - * Sets a custom text range to use when emitting comments. - */ - /*@internal*/ - export function setStartsOnNewLine(node: T, newLine: boolean) { - getOrCreateEmitNode(node).startsOnNewLine = newLine; - return node; - } - - /** - * Gets a custom text range to use when emitting comments. - */ - export function getCommentRange(node: Node) { - const emitNode = node.emitNode; - return (emitNode && emitNode.commentRange) || node; - } - - /** - * Sets a custom text range to use when emitting comments. - */ - export function setCommentRange(node: T, range: TextRange) { - getOrCreateEmitNode(node).commentRange = range; - return node; - } - - export function getSyntheticLeadingComments(node: Node): SynthesizedComment[] | undefined { - const emitNode = node.emitNode; - return emitNode && emitNode.leadingComments; - } - - export function setSyntheticLeadingComments(node: T, comments: SynthesizedComment[] | undefined) { - getOrCreateEmitNode(node).leadingComments = comments; - return node; - } - - export function addSyntheticLeadingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) { - return setSyntheticLeadingComments(node, append(getSyntheticLeadingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text })); - } - - export function getSyntheticTrailingComments(node: Node): SynthesizedComment[] | undefined { - const emitNode = node.emitNode; - return emitNode && emitNode.trailingComments; - } - - export function setSyntheticTrailingComments(node: T, comments: SynthesizedComment[] | undefined) { - getOrCreateEmitNode(node).trailingComments = comments; - return node; - } - - export function addSyntheticTrailingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) { - return setSyntheticTrailingComments(node, append(getSyntheticTrailingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text })); - } - - export function moveSyntheticComments(node: T, original: Node): T { - setSyntheticLeadingComments(node, getSyntheticLeadingComments(original)); - setSyntheticTrailingComments(node, getSyntheticTrailingComments(original)); - const emit = getOrCreateEmitNode(original); - emit.leadingComments = undefined; - emit.trailingComments = undefined; - return node; - } - - /** - * Gets the constant value to emit for an expression. - */ - export function getConstantValue(node: PropertyAccessExpression | ElementAccessExpression): string | number | undefined { - const emitNode = node.emitNode; - return emitNode && emitNode.constantValue; - } - - /** - * Sets the constant value to emit for an expression. - */ - export function setConstantValue(node: PropertyAccessExpression | ElementAccessExpression, value: string | number) { - const emitNode = getOrCreateEmitNode(node); - emitNode.constantValue = value; - return node; - } - - /** - * Adds an EmitHelper to a node. - */ - export function addEmitHelper(node: T, helper: EmitHelper): T { - const emitNode = getOrCreateEmitNode(node); - emitNode.helpers = append(emitNode.helpers, helper); - return node; - } - - /** - * Add EmitHelpers to a node. - */ - export function addEmitHelpers(node: T, helpers: EmitHelper[] | undefined): T { - if (some(helpers)) { - const emitNode = getOrCreateEmitNode(node); - for (const helper of helpers) { - emitNode.helpers = appendIfUnique(emitNode.helpers, helper); - } - } - return node; - } - - /** - * Removes an EmitHelper from a node. - */ - export function removeEmitHelper(node: Node, helper: EmitHelper): boolean { - const emitNode = node.emitNode; - if (emitNode) { - const helpers = emitNode.helpers; - if (helpers) { - return orderedRemoveItem(helpers, helper); - } - } - return false; - } - - /** - * Gets the EmitHelpers of a node. - */ - export function getEmitHelpers(node: Node): EmitHelper[] | undefined { - const emitNode = node.emitNode; - return emitNode && emitNode.helpers; - } - - /** - * Moves matching emit helpers from a source node to a target node. - */ - export function moveEmitHelpers(source: Node, target: Node, predicate: (helper: EmitHelper) => boolean) { - const sourceEmitNode = source.emitNode; - const sourceEmitHelpers = sourceEmitNode && sourceEmitNode.helpers; - if (!some(sourceEmitHelpers)) return; - - const targetEmitNode = getOrCreateEmitNode(target); - let helpersRemoved = 0; - for (let i = 0; i < sourceEmitHelpers.length; i++) { - const helper = sourceEmitHelpers[i]; - if (predicate(helper)) { - helpersRemoved++; - targetEmitNode.helpers = appendIfUnique(targetEmitNode.helpers, helper); - } - else if (helpersRemoved > 0) { - sourceEmitHelpers[i - helpersRemoved] = helper; - } - } - - if (helpersRemoved > 0) { - sourceEmitHelpers.length -= helpersRemoved; - } - } - - /* @internal */ - export function compareEmitHelpers(x: EmitHelper, y: EmitHelper) { - if (x === y) return Comparison.EqualTo; - if (x.priority === y.priority) return Comparison.EqualTo; - if (x.priority === undefined) return Comparison.GreaterThan; - if (y.priority === undefined) return Comparison.LessThan; - return compareValues(x.priority, y.priority); - } - - export function setOriginalNode(node: T, original: Node | undefined): T { - node.original = original; - if (original) { - const emitNode = original.emitNode; - if (emitNode) node.emitNode = mergeEmitNode(emitNode, node.emitNode); - } - return node; - } - - function mergeEmitNode(sourceEmitNode: EmitNode, destEmitNode: EmitNode | undefined) { - const { - flags, - leadingComments, - trailingComments, - commentRange, - sourceMapRange, - tokenSourceMapRanges, - constantValue, - helpers, - startsOnNewLine, - } = sourceEmitNode; - if (!destEmitNode) destEmitNode = {} as EmitNode; - // We are using `.slice()` here in case `destEmitNode.leadingComments` is pushed to later. - if (leadingComments) destEmitNode.leadingComments = addRange(leadingComments.slice(), destEmitNode.leadingComments); - if (trailingComments) destEmitNode.trailingComments = addRange(trailingComments.slice(), destEmitNode.trailingComments); - if (flags) destEmitNode.flags = flags; - if (commentRange) destEmitNode.commentRange = commentRange; - if (sourceMapRange) destEmitNode.sourceMapRange = sourceMapRange; - if (tokenSourceMapRanges) destEmitNode.tokenSourceMapRanges = mergeTokenSourceMapRanges(tokenSourceMapRanges, destEmitNode.tokenSourceMapRanges!); - if (constantValue !== undefined) destEmitNode.constantValue = constantValue; - if (helpers) destEmitNode.helpers = addRange(destEmitNode.helpers, helpers); - if (startsOnNewLine !== undefined) destEmitNode.startsOnNewLine = startsOnNewLine; - return destEmitNode; - } - - function mergeTokenSourceMapRanges(sourceRanges: (TextRange | undefined)[], destRanges: (TextRange | undefined)[]) { - if (!destRanges) destRanges = []; - for (const key in sourceRanges) { - destRanges[key] = sourceRanges[key]; - } - return destRanges; - } -} - /* @internal */ namespace ts { export const nullTransformationContext: TransformationContext = { @@ -5570,4 +1900,4 @@ namespace ts { Debug.assertNode(node, isExpression); return node; } -} +} \ No newline at end of file diff --git a/src/compiler/factoryPublic.ts b/src/compiler/factoryPublic.ts new file mode 100644 index 00000000000..3c17ce2bdab --- /dev/null +++ b/src/compiler/factoryPublic.ts @@ -0,0 +1,3669 @@ +namespace ts { + function createSynthesizedNode(kind: SyntaxKind): Node { + const node = createNode(kind, -1, -1); + node.flags |= NodeFlags.Synthesized; + return node; + } + + /* @internal */ + export function updateNode(updated: T, original: T): T { + if (updated !== original) { + setOriginalNode(updated, original); + setTextRange(updated, original); + aggregateTransformFlags(updated); + } + return updated; + } + + /* @internal */ export function createNodeArray(elements?: T[], hasTrailingComma?: boolean): MutableNodeArray; + export function createNodeArray(elements?: readonly T[], hasTrailingComma?: boolean): NodeArray; + /** + * Make `elements` into a `NodeArray`. If `elements` is `undefined`, returns an empty `NodeArray`. + */ + export function createNodeArray(elements?: readonly T[], hasTrailingComma?: boolean): NodeArray { + if (!elements || elements === emptyArray) { + elements = []; + } + else if (isNodeArray(elements)) { + return elements; + } + + const array = >elements; + array.pos = -1; + array.end = -1; + array.hasTrailingComma = hasTrailingComma; + return array; + } + + /** + * Creates a shallow, memberwise clone of a node with no source map location. + */ + /* @internal */ + export function getSynthesizedClone(node: T): T { + // We don't use "clone" from core.ts here, as we need to preserve the prototype chain of + // the original node. We also need to exclude specific properties and only include own- + // properties (to skip members already defined on the shared prototype). + + if (node === undefined) { + return node; + } + + const clone = createSynthesizedNode(node.kind); + clone.flags |= node.flags; + setOriginalNode(clone, node); + + for (const key in node) { + if (clone.hasOwnProperty(key) || !node.hasOwnProperty(key)) { + continue; + } + + (clone)[key] = (node)[key]; + } + + return clone; + } + + // Literals + + /* @internal */ export function createLiteral(value: string | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier, isSingleQuote: boolean): StringLiteral; // eslint-disable-line @typescript-eslint/unified-signatures + /* @internal */ export function createLiteral(value: string | number, isSingleQuote: boolean): StringLiteral | NumericLiteral; + /** If a node is passed, creates a string literal whose source text is read from a source node during emit. */ + export function createLiteral(value: string | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier): StringLiteral; + export function createLiteral(value: number | PseudoBigInt): NumericLiteral; + export function createLiteral(value: boolean): BooleanLiteral; + export function createLiteral(value: string | number | PseudoBigInt | boolean): PrimaryExpression; + export function createLiteral(value: string | number | PseudoBigInt | boolean | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier, isSingleQuote?: boolean): PrimaryExpression { + if (typeof value === "number") { + return createNumericLiteral(value + ""); + } + // eslint-disable-next-line no-in-operator + if (typeof value === "object" && "base10Value" in value) { // PseudoBigInt + return createBigIntLiteral(pseudoBigIntToString(value) + "n"); + } + if (typeof value === "boolean") { + return value ? createTrue() : createFalse(); + } + if (isString(value)) { + const res = createStringLiteral(value); + if (isSingleQuote) res.singleQuote = true; + return res; + } + return createLiteralFromNode(value); + } + + export function createNumericLiteral(value: string, numericLiteralFlags: TokenFlags = TokenFlags.None): NumericLiteral { + const node = createSynthesizedNode(SyntaxKind.NumericLiteral); + node.text = value; + node.numericLiteralFlags = numericLiteralFlags; + return node; + } + + export function createBigIntLiteral(value: string): BigIntLiteral { + const node = createSynthesizedNode(SyntaxKind.BigIntLiteral); + node.text = value; + return node; + } + + export function createStringLiteral(text: string): StringLiteral { + const node = createSynthesizedNode(SyntaxKind.StringLiteral); + node.text = text; + return node; + } + + export function createRegularExpressionLiteral(text: string): RegularExpressionLiteral { + const node = createSynthesizedNode(SyntaxKind.RegularExpressionLiteral); + node.text = text; + return node; + } + + function createLiteralFromNode(sourceNode: PropertyNameLiteral): StringLiteral { + const node = createStringLiteral(getTextOfIdentifierOrLiteral(sourceNode)); + node.textSourceNode = sourceNode; + return node; + } + + + // Identifiers + + export function createIdentifier(text: string): Identifier; + /* @internal */ + export function createIdentifier(text: string, typeArguments: readonly (TypeNode | TypeParameterDeclaration)[] | undefined): Identifier; // eslint-disable-line @typescript-eslint/unified-signatures + export function createIdentifier(text: string, typeArguments?: readonly (TypeNode | TypeParameterDeclaration)[]): Identifier { + const node = createSynthesizedNode(SyntaxKind.Identifier); + node.escapedText = escapeLeadingUnderscores(text); + node.originalKeywordKind = text ? stringToToken(text) : SyntaxKind.Unknown; + node.autoGenerateFlags = GeneratedIdentifierFlags.None; + node.autoGenerateId = 0; + if (typeArguments) { + node.typeArguments = createNodeArray(typeArguments as readonly TypeNode[]); + } + return node; + } + + export function updateIdentifier(node: Identifier): Identifier; + /* @internal */ + export function updateIdentifier(node: Identifier, typeArguments: NodeArray | undefined): Identifier; // eslint-disable-line @typescript-eslint/unified-signatures + export function updateIdentifier(node: Identifier, typeArguments?: NodeArray | undefined): Identifier { + return node.typeArguments !== typeArguments + ? updateNode(createIdentifier(idText(node), typeArguments), node) + : node; + } + + let nextAutoGenerateId = 0; + + /** Create a unique temporary variable. */ + export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined): Identifier; + /* @internal */ export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined, reservedInNestedScopes: boolean): GeneratedIdentifier; + export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined, reservedInNestedScopes?: boolean): GeneratedIdentifier { + const name = createIdentifier("") as GeneratedIdentifier; + name.autoGenerateFlags = GeneratedIdentifierFlags.Auto; + name.autoGenerateId = nextAutoGenerateId; + nextAutoGenerateId++; + if (recordTempVariable) { + recordTempVariable(name); + } + if (reservedInNestedScopes) { + name.autoGenerateFlags |= GeneratedIdentifierFlags.ReservedInNestedScopes; + } + return name; + } + + /** Create a unique temporary variable for use in a loop. */ + export function createLoopVariable(): Identifier { + const name = createIdentifier(""); + name.autoGenerateFlags = GeneratedIdentifierFlags.Loop; + name.autoGenerateId = nextAutoGenerateId; + nextAutoGenerateId++; + return name; + } + + /** Create a unique name based on the supplied text. */ + export function createUniqueName(text: string): Identifier { + const name = createIdentifier(text); + name.autoGenerateFlags = GeneratedIdentifierFlags.Unique; + name.autoGenerateId = nextAutoGenerateId; + nextAutoGenerateId++; + return name; + } + + /* @internal */ export function createOptimisticUniqueName(text: string): GeneratedIdentifier; + /** Create a unique name based on the supplied text. */ + export function createOptimisticUniqueName(text: string): Identifier; + export function createOptimisticUniqueName(text: string): GeneratedIdentifier { + const name = createIdentifier(text) as GeneratedIdentifier; + name.autoGenerateFlags = GeneratedIdentifierFlags.Unique | GeneratedIdentifierFlags.Optimistic; + name.autoGenerateId = nextAutoGenerateId; + nextAutoGenerateId++; + return name; + } + + /** Create a unique name based on the supplied text. This does not consider names injected by the transformer. */ + export function createFileLevelUniqueName(text: string): Identifier { + const name = createOptimisticUniqueName(text); + name.autoGenerateFlags |= GeneratedIdentifierFlags.FileLevel; + return name; + } + + /** Create a unique name generated for a node. */ + export function getGeneratedNameForNode(node: Node | undefined): Identifier; + /* @internal */ export function getGeneratedNameForNode(node: Node | undefined, flags: GeneratedIdentifierFlags): Identifier; // eslint-disable-line @typescript-eslint/unified-signatures + export function getGeneratedNameForNode(node: Node | undefined, flags?: GeneratedIdentifierFlags): Identifier { + const name = createIdentifier(node && isIdentifier(node) ? idText(node) : ""); + name.autoGenerateFlags = GeneratedIdentifierFlags.Node | flags!; + name.autoGenerateId = nextAutoGenerateId; + name.original = node; + nextAutoGenerateId++; + return name; + } + + // Punctuation + + export function createToken(token: TKind) { + return >createSynthesizedNode(token); + } + + // Reserved words + + export function createSuper() { + return createSynthesizedNode(SyntaxKind.SuperKeyword); + } + + export function createThis() { + return >createSynthesizedNode(SyntaxKind.ThisKeyword); + } + + export function createNull() { + return >createSynthesizedNode(SyntaxKind.NullKeyword); + } + + export function createTrue() { + return >createSynthesizedNode(SyntaxKind.TrueKeyword); + } + + export function createFalse() { + return >createSynthesizedNode(SyntaxKind.FalseKeyword); + } + + // Modifiers + + export function createModifier(kind: T): Token { + return createToken(kind); + } + + export function createModifiersFromModifierFlags(flags: ModifierFlags) { + const result: Modifier[] = []; + if (flags & ModifierFlags.Export) { result.push(createModifier(SyntaxKind.ExportKeyword)); } + if (flags & ModifierFlags.Ambient) { result.push(createModifier(SyntaxKind.DeclareKeyword)); } + if (flags & ModifierFlags.Default) { result.push(createModifier(SyntaxKind.DefaultKeyword)); } + if (flags & ModifierFlags.Const) { result.push(createModifier(SyntaxKind.ConstKeyword)); } + if (flags & ModifierFlags.Public) { result.push(createModifier(SyntaxKind.PublicKeyword)); } + if (flags & ModifierFlags.Private) { result.push(createModifier(SyntaxKind.PrivateKeyword)); } + if (flags & ModifierFlags.Protected) { result.push(createModifier(SyntaxKind.ProtectedKeyword)); } + if (flags & ModifierFlags.Abstract) { result.push(createModifier(SyntaxKind.AbstractKeyword)); } + if (flags & ModifierFlags.Static) { result.push(createModifier(SyntaxKind.StaticKeyword)); } + if (flags & ModifierFlags.Readonly) { result.push(createModifier(SyntaxKind.ReadonlyKeyword)); } + if (flags & ModifierFlags.Async) { result.push(createModifier(SyntaxKind.AsyncKeyword)); } + return result; + } + + // Names + + export function createQualifiedName(left: EntityName, right: string | Identifier) { + const node = createSynthesizedNode(SyntaxKind.QualifiedName); + node.left = left; + node.right = asName(right); + return node; + } + + export function updateQualifiedName(node: QualifiedName, left: EntityName, right: Identifier) { + return node.left !== left + || node.right !== right + ? updateNode(createQualifiedName(left, right), node) + : node; + } + + function parenthesizeForComputedName(expression: Expression): Expression { + return isCommaSequence(expression) + ? createParen(expression) + : expression; + } + + export function createComputedPropertyName(expression: Expression) { + const node = createSynthesizedNode(SyntaxKind.ComputedPropertyName); + node.expression = parenthesizeForComputedName(expression); + return node; + } + + export function updateComputedPropertyName(node: ComputedPropertyName, expression: Expression) { + return node.expression !== expression + ? updateNode(createComputedPropertyName(expression), node) + : node; + } + + // Signature elements + + export function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode) { + const node = createSynthesizedNode(SyntaxKind.TypeParameter) as TypeParameterDeclaration; + node.name = asName(name); + node.constraint = constraint; + node.default = defaultType; + return node; + } + + export function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined) { + return node.name !== name + || node.constraint !== constraint + || node.default !== defaultType + ? updateNode(createTypeParameterDeclaration(name, constraint, defaultType), node) + : node; + } + + export function createParameter( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + dotDotDotToken: DotDotDotToken | undefined, + name: string | BindingName, + questionToken?: QuestionToken, + type?: TypeNode, + initializer?: Expression) { + const node = createSynthesizedNode(SyntaxKind.Parameter); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.dotDotDotToken = dotDotDotToken; + node.name = asName(name); + node.questionToken = questionToken; + node.type = type; + node.initializer = initializer ? parenthesizeExpressionForList(initializer) : undefined; + return node; + } + + export function updateParameter( + node: ParameterDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + dotDotDotToken: DotDotDotToken | undefined, + name: string | BindingName, + questionToken: QuestionToken | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.dotDotDotToken !== dotDotDotToken + || node.name !== name + || node.questionToken !== questionToken + || node.type !== type + || node.initializer !== initializer + ? updateNode(createParameter(decorators, modifiers, dotDotDotToken, name, questionToken, type, initializer), node) + : node; + } + + export function createDecorator(expression: Expression) { + const node = createSynthesizedNode(SyntaxKind.Decorator); + node.expression = parenthesizeForAccess(expression); + return node; + } + + export function updateDecorator(node: Decorator, expression: Expression) { + return node.expression !== expression + ? updateNode(createDecorator(expression), node) + : node; + } + + + // Type Elements + + export function createPropertySignature( + modifiers: readonly Modifier[] | undefined, + name: PropertyName | string, + questionToken: QuestionToken | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined): PropertySignature { + const node = createSynthesizedNode(SyntaxKind.PropertySignature) as PropertySignature; + node.modifiers = asNodeArray(modifiers); + node.name = asName(name); + node.questionToken = questionToken; + node.type = type; + node.initializer = initializer; + return node; + } + + export function updatePropertySignature( + node: PropertySignature, + modifiers: readonly Modifier[] | undefined, + name: PropertyName, + questionToken: QuestionToken | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined) { + return node.modifiers !== modifiers + || node.name !== name + || node.questionToken !== questionToken + || node.type !== type + || node.initializer !== initializer + ? updateNode(createPropertySignature(modifiers, name, questionToken, type, initializer), node) + : node; + } + + export function createProperty( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | PropertyName, + questionOrExclamationToken: QuestionToken | ExclamationToken | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined) { + const node = createSynthesizedNode(SyntaxKind.PropertyDeclaration); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.name = asName(name); + node.questionToken = questionOrExclamationToken !== undefined && questionOrExclamationToken.kind === SyntaxKind.QuestionToken ? questionOrExclamationToken : undefined; + node.exclamationToken = questionOrExclamationToken !== undefined && questionOrExclamationToken.kind === SyntaxKind.ExclamationToken ? questionOrExclamationToken : undefined; + node.type = type; + node.initializer = initializer; + return node; + } + + export function updateProperty( + node: PropertyDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | PropertyName, + questionOrExclamationToken: QuestionToken | ExclamationToken | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.questionToken !== (questionOrExclamationToken !== undefined && questionOrExclamationToken.kind === SyntaxKind.QuestionToken ? questionOrExclamationToken : undefined) + || node.exclamationToken !== (questionOrExclamationToken !== undefined && questionOrExclamationToken.kind === SyntaxKind.ExclamationToken ? questionOrExclamationToken : undefined) + || node.type !== type + || node.initializer !== initializer + ? updateNode(createProperty(decorators, modifiers, name, questionOrExclamationToken, type, initializer), node) + : node; + } + + export function createMethodSignature( + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + name: string | PropertyName, + questionToken: QuestionToken | undefined) { + const node = createSignatureDeclaration(SyntaxKind.MethodSignature, typeParameters, parameters, type) as MethodSignature; + node.name = asName(name); + node.questionToken = questionToken; + return node; + } + + export function updateMethodSignature(node: MethodSignature, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined, name: PropertyName, questionToken: QuestionToken | undefined) { + return node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + || node.name !== name + || node.questionToken !== questionToken + ? updateNode(createMethodSignature(typeParameters, parameters, type, name, questionToken), node) + : node; + } + + export function createMethod( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + asteriskToken: AsteriskToken | undefined, + name: string | PropertyName, + questionToken: QuestionToken | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block | undefined) { + const node = createSynthesizedNode(SyntaxKind.MethodDeclaration); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.asteriskToken = asteriskToken; + node.name = asName(name); + node.questionToken = questionToken; + node.typeParameters = asNodeArray(typeParameters); + node.parameters = createNodeArray(parameters); + node.type = type; + node.body = body; + return node; + } + + function createMethodCall(object: Expression, methodName: string | Identifier, argumentsList: readonly Expression[]) { + return createCall( + createPropertyAccess(object, asName(methodName)), + /*typeArguments*/ undefined, + argumentsList + ); + } + + function createGlobalMethodCall(globalObjectName: string, methodName: string, argumentsList: readonly Expression[]) { + return createMethodCall(createIdentifier(globalObjectName), methodName, argumentsList); + } + + /* @internal */ + export function createObjectDefinePropertyCall(target: Expression, propertyName: string | Expression, attributes: Expression) { + return createGlobalMethodCall("Object", "defineProperty", [target, asExpression(propertyName), attributes]); + } + + function tryAddPropertyAssignment(properties: Push, propertyName: string, expression: Expression | undefined) { + if (expression) { + properties.push(createPropertyAssignment(propertyName, expression)); + return true; + } + return false; + } + + /* @internal */ + export function createPropertyDescriptor(attributes: PropertyDescriptorAttributes, singleLine?: boolean) { + const properties: PropertyAssignment[] = []; + tryAddPropertyAssignment(properties, "enumerable", asExpression(attributes.enumerable)); + tryAddPropertyAssignment(properties, "configurable", asExpression(attributes.configurable)); + + let isData = tryAddPropertyAssignment(properties, "writable", asExpression(attributes.writable)); + isData = tryAddPropertyAssignment(properties, "value", attributes.value) || isData; + + let isAccessor = tryAddPropertyAssignment(properties, "get", attributes.get); + isAccessor = tryAddPropertyAssignment(properties, "set", attributes.set) || isAccessor; + + Debug.assert(!(isData && isAccessor), "A PropertyDescriptor may not be both an accessor descriptor and a data descriptor."); + return createObjectLiteral(properties, !singleLine); + } + + export function updateMethod( + node: MethodDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + asteriskToken: AsteriskToken | undefined, + name: PropertyName, + questionToken: QuestionToken | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block | undefined) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.asteriskToken !== asteriskToken + || node.name !== name + || node.questionToken !== questionToken + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + || node.body !== body + ? updateNode(createMethod(decorators, modifiers, asteriskToken, name, questionToken, typeParameters, parameters, type, body), node) + : node; + } + + export function createConstructor(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, parameters: readonly ParameterDeclaration[], body: Block | undefined) { + const node = createSynthesizedNode(SyntaxKind.Constructor); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.typeParameters = undefined; + node.parameters = createNodeArray(parameters); + node.type = undefined; + node.body = body; + return node; + } + + export function updateConstructor( + node: ConstructorDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + parameters: readonly ParameterDeclaration[], + body: Block | undefined) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.parameters !== parameters + || node.body !== body + ? updateNode(createConstructor(decorators, modifiers, parameters, body), node) + : node; + } + + export function createGetAccessor( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | PropertyName, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block | undefined) { + const node = createSynthesizedNode(SyntaxKind.GetAccessor); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.name = asName(name); + node.typeParameters = undefined; + node.parameters = createNodeArray(parameters); + node.type = type; + node.body = body; + return node; + } + + export function updateGetAccessor( + node: GetAccessorDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: PropertyName, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block | undefined) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.parameters !== parameters + || node.type !== type + || node.body !== body + ? updateNode(createGetAccessor(decorators, modifiers, name, parameters, type, body), node) + : node; + } + + export function createSetAccessor( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | PropertyName, + parameters: readonly ParameterDeclaration[], + body: Block | undefined) { + const node = createSynthesizedNode(SyntaxKind.SetAccessor); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.name = asName(name); + node.typeParameters = undefined; + node.parameters = createNodeArray(parameters); + node.body = body; + return node; + } + + export function updateSetAccessor( + node: SetAccessorDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: PropertyName, + parameters: readonly ParameterDeclaration[], + body: Block | undefined) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.parameters !== parameters + || node.body !== body + ? updateNode(createSetAccessor(decorators, modifiers, name, parameters, body), node) + : node; + } + + export function createCallSignature(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined) { + return createSignatureDeclaration(SyntaxKind.CallSignature, typeParameters, parameters, type) as CallSignatureDeclaration; + } + + export function updateCallSignature(node: CallSignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { + return updateSignatureDeclaration(node, typeParameters, parameters, type); + } + + export function createConstructSignature(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined) { + return createSignatureDeclaration(SyntaxKind.ConstructSignature, typeParameters, parameters, type) as ConstructSignatureDeclaration; + } + + export function updateConstructSignature(node: ConstructSignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { + return updateSignatureDeclaration(node, typeParameters, parameters, type); + } + + export function createIndexSignature( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode): IndexSignatureDeclaration { + const node = createSynthesizedNode(SyntaxKind.IndexSignature) as IndexSignatureDeclaration; + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.parameters = createNodeArray(parameters); + node.type = type; + return node; + } + + export function updateIndexSignature( + node: IndexSignatureDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode) { + return node.parameters !== parameters + || node.type !== type + || node.decorators !== decorators + || node.modifiers !== modifiers + ? updateNode(createIndexSignature(decorators, modifiers, parameters, type), node) + : node; + } + + /* @internal */ + export function createSignatureDeclaration(kind: SyntaxKind, typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined, typeArguments?: readonly TypeNode[] | undefined) { + const node = createSynthesizedNode(kind) as SignatureDeclaration; + node.typeParameters = asNodeArray(typeParameters); + node.parameters = asNodeArray(parameters); + node.type = type; + node.typeArguments = asNodeArray(typeArguments); + return node; + } + + function updateSignatureDeclaration(node: T, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): T { + return node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + ? updateNode(createSignatureDeclaration(node.kind, typeParameters, parameters, type), node) + : node; + } + + // Types + + export function createKeywordTypeNode(kind: KeywordTypeNode["kind"]) { + return createSynthesizedNode(kind); + } + + export function createTypePredicateNode(parameterName: Identifier | ThisTypeNode | string, type: TypeNode) { + return createTypePredicateNodeWithModifier(/*assertsModifier*/ undefined, parameterName, type); + } + + export function createTypePredicateNodeWithModifier(assertsModifier: AssertsToken | undefined, parameterName: Identifier | ThisTypeNode | string, type: TypeNode | undefined) { + const node = createSynthesizedNode(SyntaxKind.TypePredicate) as TypePredicateNode; + node.assertsModifier = assertsModifier; + node.parameterName = asName(parameterName); + node.type = type; + return node; + } + + export function updateTypePredicateNode(node: TypePredicateNode, parameterName: Identifier | ThisTypeNode, type: TypeNode) { + return updateTypePredicateNodeWithModifier(node, node.assertsModifier, parameterName, type); + } + + export function updateTypePredicateNodeWithModifier(node: TypePredicateNode, assertsModifier: AssertsToken | undefined, parameterName: Identifier | ThisTypeNode, type: TypeNode | undefined) { + return node.assertsModifier !== assertsModifier + || node.parameterName !== parameterName + || node.type !== type + ? updateNode(createTypePredicateNodeWithModifier(assertsModifier, parameterName, type), node) + : node; + } + + export function createTypeReferenceNode(typeName: string | EntityName, typeArguments: readonly TypeNode[] | undefined) { + const node = createSynthesizedNode(SyntaxKind.TypeReference) as TypeReferenceNode; + node.typeName = asName(typeName); + node.typeArguments = typeArguments && parenthesizeTypeParameters(typeArguments); + return node; + } + + export function updateTypeReferenceNode(node: TypeReferenceNode, typeName: EntityName, typeArguments: NodeArray | undefined) { + return node.typeName !== typeName + || node.typeArguments !== typeArguments + ? updateNode(createTypeReferenceNode(typeName, typeArguments), node) + : node; + } + + export function createFunctionTypeNode(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined) { + return createSignatureDeclaration(SyntaxKind.FunctionType, typeParameters, parameters, type) as FunctionTypeNode; + } + + export function updateFunctionTypeNode(node: FunctionTypeNode, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { + return updateSignatureDeclaration(node, typeParameters, parameters, type); + } + + export function createConstructorTypeNode(typeParameters: readonly TypeParameterDeclaration[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined) { + return createSignatureDeclaration(SyntaxKind.ConstructorType, typeParameters, parameters, type) as ConstructorTypeNode; + } + + export function updateConstructorTypeNode(node: ConstructorTypeNode, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { + return updateSignatureDeclaration(node, typeParameters, parameters, type); + } + + export function createTypeQueryNode(exprName: EntityName) { + const node = createSynthesizedNode(SyntaxKind.TypeQuery) as TypeQueryNode; + node.exprName = exprName; + return node; + } + + export function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName) { + return node.exprName !== exprName + ? updateNode(createTypeQueryNode(exprName), node) + : node; + } + + export function createTypeLiteralNode(members: readonly TypeElement[] | undefined) { + const node = createSynthesizedNode(SyntaxKind.TypeLiteral) as TypeLiteralNode; + node.members = createNodeArray(members); + return node; + } + + export function updateTypeLiteralNode(node: TypeLiteralNode, members: NodeArray) { + return node.members !== members + ? updateNode(createTypeLiteralNode(members), node) + : node; + } + + export function createArrayTypeNode(elementType: TypeNode) { + const node = createSynthesizedNode(SyntaxKind.ArrayType) as ArrayTypeNode; + node.elementType = parenthesizeArrayTypeMember(elementType); + return node; + } + + export function updateArrayTypeNode(node: ArrayTypeNode, elementType: TypeNode): ArrayTypeNode { + return node.elementType !== elementType + ? updateNode(createArrayTypeNode(elementType), node) + : node; + } + + export function createTupleTypeNode(elementTypes: readonly TypeNode[]) { + const node = createSynthesizedNode(SyntaxKind.TupleType) as TupleTypeNode; + node.elementTypes = createNodeArray(elementTypes); + return node; + } + + export function updateTupleTypeNode(node: TupleTypeNode, elementTypes: readonly TypeNode[]) { + return node.elementTypes !== elementTypes + ? updateNode(createTupleTypeNode(elementTypes), node) + : node; + } + + export function createOptionalTypeNode(type: TypeNode) { + const node = createSynthesizedNode(SyntaxKind.OptionalType) as OptionalTypeNode; + node.type = parenthesizeArrayTypeMember(type); + return node; + } + + export function updateOptionalTypeNode(node: OptionalTypeNode, type: TypeNode): OptionalTypeNode { + return node.type !== type + ? updateNode(createOptionalTypeNode(type), node) + : node; + } + + export function createRestTypeNode(type: TypeNode) { + const node = createSynthesizedNode(SyntaxKind.RestType) as RestTypeNode; + node.type = type; + return node; + } + + export function updateRestTypeNode(node: RestTypeNode, type: TypeNode): RestTypeNode { + return node.type !== type + ? updateNode(createRestTypeNode(type), node) + : node; + } + + export function createUnionTypeNode(types: readonly TypeNode[]): UnionTypeNode { + return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, types); + } + + export function updateUnionTypeNode(node: UnionTypeNode, types: NodeArray) { + return updateUnionOrIntersectionTypeNode(node, types); + } + + export function createIntersectionTypeNode(types: readonly TypeNode[]): IntersectionTypeNode { + return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, types); + } + + export function updateIntersectionTypeNode(node: IntersectionTypeNode, types: NodeArray) { + return updateUnionOrIntersectionTypeNode(node, types); + } + + export function createUnionOrIntersectionTypeNode(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, types: readonly TypeNode[]) { + const node = createSynthesizedNode(kind) as UnionTypeNode | IntersectionTypeNode; + node.types = parenthesizeElementTypeMembers(types); + return node; + } + + function updateUnionOrIntersectionTypeNode(node: T, types: NodeArray): T { + return node.types !== types + ? updateNode(createUnionOrIntersectionTypeNode(node.kind, types), node) + : node; + } + + export function createConditionalTypeNode(checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) { + const node = createSynthesizedNode(SyntaxKind.ConditionalType) as ConditionalTypeNode; + node.checkType = parenthesizeConditionalTypeMember(checkType); + node.extendsType = parenthesizeConditionalTypeMember(extendsType); + node.trueType = trueType; + node.falseType = falseType; + return node; + } + + export function updateConditionalTypeNode(node: ConditionalTypeNode, checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) { + return node.checkType !== checkType + || node.extendsType !== extendsType + || node.trueType !== trueType + || node.falseType !== falseType + ? updateNode(createConditionalTypeNode(checkType, extendsType, trueType, falseType), node) + : node; + } + + export function createInferTypeNode(typeParameter: TypeParameterDeclaration) { + const node = createSynthesizedNode(SyntaxKind.InferType); + node.typeParameter = typeParameter; + return node; + } + + export function updateInferTypeNode(node: InferTypeNode, typeParameter: TypeParameterDeclaration) { + return node.typeParameter !== typeParameter + ? updateNode(createInferTypeNode(typeParameter), node) + : node; + } + + export function createImportTypeNode(argument: TypeNode, qualifier?: EntityName, typeArguments?: readonly TypeNode[], isTypeOf?: boolean) { + const node = createSynthesizedNode(SyntaxKind.ImportType); + node.argument = argument; + node.qualifier = qualifier; + node.typeArguments = parenthesizeTypeParameters(typeArguments); + node.isTypeOf = isTypeOf; + return node; + } + + export function updateImportTypeNode(node: ImportTypeNode, argument: TypeNode, qualifier?: EntityName, typeArguments?: readonly TypeNode[], isTypeOf?: boolean) { + return node.argument !== argument + || node.qualifier !== qualifier + || node.typeArguments !== typeArguments + || node.isTypeOf !== isTypeOf + ? updateNode(createImportTypeNode(argument, qualifier, typeArguments, isTypeOf), node) + : node; + } + + export function createParenthesizedType(type: TypeNode) { + const node = createSynthesizedNode(SyntaxKind.ParenthesizedType); + node.type = type; + return node; + } + + export function updateParenthesizedType(node: ParenthesizedTypeNode, type: TypeNode) { + return node.type !== type + ? updateNode(createParenthesizedType(type), node) + : node; + } + + export function createThisTypeNode() { + return createSynthesizedNode(SyntaxKind.ThisType); + } + + export function createTypeOperatorNode(type: TypeNode): TypeOperatorNode; + export function createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword, type: TypeNode): TypeOperatorNode; + export function createTypeOperatorNode(operatorOrType: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword | TypeNode, type?: TypeNode) { + const node = createSynthesizedNode(SyntaxKind.TypeOperator) as TypeOperatorNode; + node.operator = typeof operatorOrType === "number" ? operatorOrType : SyntaxKind.KeyOfKeyword; + node.type = parenthesizeElementTypeMember(typeof operatorOrType === "number" ? type! : operatorOrType); + return node; + } + + export function updateTypeOperatorNode(node: TypeOperatorNode, type: TypeNode) { + return node.type !== type ? updateNode(createTypeOperatorNode(node.operator, type), node) : node; + } + + export function createIndexedAccessTypeNode(objectType: TypeNode, indexType: TypeNode) { + const node = createSynthesizedNode(SyntaxKind.IndexedAccessType) as IndexedAccessTypeNode; + node.objectType = parenthesizeElementTypeMember(objectType); + node.indexType = indexType; + return node; + } + + export function updateIndexedAccessTypeNode(node: IndexedAccessTypeNode, objectType: TypeNode, indexType: TypeNode) { + return node.objectType !== objectType + || node.indexType !== indexType + ? updateNode(createIndexedAccessTypeNode(objectType, indexType), node) + : node; + } + + export function createMappedTypeNode(readonlyToken: ReadonlyToken | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode { + const node = createSynthesizedNode(SyntaxKind.MappedType) as MappedTypeNode; + node.readonlyToken = readonlyToken; + node.typeParameter = typeParameter; + node.questionToken = questionToken; + node.type = type; + return node; + } + + export function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyToken | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode { + return node.readonlyToken !== readonlyToken + || node.typeParameter !== typeParameter + || node.questionToken !== questionToken + || node.type !== type + ? updateNode(createMappedTypeNode(readonlyToken, typeParameter, questionToken, type), node) + : node; + } + + export function createLiteralTypeNode(literal: LiteralTypeNode["literal"]) { + const node = createSynthesizedNode(SyntaxKind.LiteralType) as LiteralTypeNode; + node.literal = literal; + return node; + } + + export function updateLiteralTypeNode(node: LiteralTypeNode, literal: LiteralTypeNode["literal"]) { + return node.literal !== literal + ? updateNode(createLiteralTypeNode(literal), node) + : node; + } + + // Binding Patterns + + export function createObjectBindingPattern(elements: readonly BindingElement[]) { + const node = createSynthesizedNode(SyntaxKind.ObjectBindingPattern); + node.elements = createNodeArray(elements); + return node; + } + + export function updateObjectBindingPattern(node: ObjectBindingPattern, elements: readonly BindingElement[]) { + return node.elements !== elements + ? updateNode(createObjectBindingPattern(elements), node) + : node; + } + + export function createArrayBindingPattern(elements: readonly ArrayBindingElement[]) { + const node = createSynthesizedNode(SyntaxKind.ArrayBindingPattern); + node.elements = createNodeArray(elements); + return node; + } + + export function updateArrayBindingPattern(node: ArrayBindingPattern, elements: readonly ArrayBindingElement[]) { + return node.elements !== elements + ? updateNode(createArrayBindingPattern(elements), node) + : node; + } + + export function createBindingElement(dotDotDotToken: DotDotDotToken | undefined, propertyName: string | PropertyName | undefined, name: string | BindingName, initializer?: Expression) { + const node = createSynthesizedNode(SyntaxKind.BindingElement); + node.dotDotDotToken = dotDotDotToken; + node.propertyName = asName(propertyName); + node.name = asName(name); + node.initializer = initializer; + return node; + } + + export function updateBindingElement(node: BindingElement, dotDotDotToken: DotDotDotToken | undefined, propertyName: PropertyName | undefined, name: BindingName, initializer: Expression | undefined) { + return node.propertyName !== propertyName + || node.dotDotDotToken !== dotDotDotToken + || node.name !== name + || node.initializer !== initializer + ? updateNode(createBindingElement(dotDotDotToken, propertyName, name, initializer), node) + : node; + } + + // Expression + + export function createArrayLiteral(elements?: readonly Expression[], multiLine?: boolean) { + const node = createSynthesizedNode(SyntaxKind.ArrayLiteralExpression); + node.elements = parenthesizeListElements(createNodeArray(elements)); + if (multiLine) node.multiLine = true; + return node; + } + + export function updateArrayLiteral(node: ArrayLiteralExpression, elements: readonly Expression[]) { + return node.elements !== elements + ? updateNode(createArrayLiteral(elements, node.multiLine), node) + : node; + } + + export function createObjectLiteral(properties?: readonly ObjectLiteralElementLike[], multiLine?: boolean) { + const node = createSynthesizedNode(SyntaxKind.ObjectLiteralExpression); + node.properties = createNodeArray(properties); + if (multiLine) node.multiLine = true; + return node; + } + + export function updateObjectLiteral(node: ObjectLiteralExpression, properties: readonly ObjectLiteralElementLike[]) { + return node.properties !== properties + ? updateNode(createObjectLiteral(properties, node.multiLine), node) + : node; + } + + export function createPropertyAccess(expression: Expression, name: string | Identifier) { + const node = createSynthesizedNode(SyntaxKind.PropertyAccessExpression); + node.expression = parenthesizeForAccess(expression); + node.name = asName(name); + setEmitFlags(node, EmitFlags.NoIndentation); + return node; + } + + export function updatePropertyAccess(node: PropertyAccessExpression, expression: Expression, name: Identifier) { + if (isOptionalChain(node)) { + return updatePropertyAccessChain(node, expression, node.questionDotToken, name); + } + // Because we are updating existed propertyAccess we want to inherit its emitFlags + // instead of using the default from createPropertyAccess + return node.expression !== expression + || node.name !== name + ? updateNode(setEmitFlags(createPropertyAccess(expression, name), getEmitFlags(node)), node) + : node; + } + + export function createPropertyAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, name: string | Identifier) { + const node = createSynthesizedNode(SyntaxKind.PropertyAccessExpression); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizeForAccess(expression); + node.questionDotToken = questionDotToken; + node.name = asName(name); + setEmitFlags(node, EmitFlags.NoIndentation); + return node; + } + + export function updatePropertyAccessChain(node: PropertyAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, name: Identifier) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a PropertyAccessExpression using updatePropertyAccessChain. Use updatePropertyAccess instead."); + // Because we are updating an existing PropertyAccessChain we want to inherit its emitFlags + // instead of using the default from createPropertyAccess + return node.expression !== expression + || node.questionDotToken !== questionDotToken + || node.name !== name + ? updateNode(setEmitFlags(createPropertyAccessChain(expression, questionDotToken, name), getEmitFlags(node)), node) + : node; + } + + export function createElementAccess(expression: Expression, index: number | Expression) { + const node = createSynthesizedNode(SyntaxKind.ElementAccessExpression); + node.expression = parenthesizeForAccess(expression); + node.argumentExpression = asExpression(index); + return node; + } + + export function updateElementAccess(node: ElementAccessExpression, expression: Expression, argumentExpression: Expression) { + if (isOptionalChain(node)) { + return updateElementAccessChain(node, expression, node.questionDotToken, argumentExpression); + } + return node.expression !== expression + || node.argumentExpression !== argumentExpression + ? updateNode(createElementAccess(expression, argumentExpression), node) + : node; + } + + export function createElementAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, index: number | Expression) { + const node = createSynthesizedNode(SyntaxKind.ElementAccessExpression); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizeForAccess(expression); + node.questionDotToken = questionDotToken; + node.argumentExpression = asExpression(index); + return node; + } + + export function updateElementAccessChain(node: ElementAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, argumentExpression: Expression) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update an ElementAccessExpression using updateElementAccessChain. Use updateElementAccess instead."); + return node.expression !== expression + || node.questionDotToken !== questionDotToken + || node.argumentExpression !== argumentExpression + ? updateNode(createElementAccessChain(expression, questionDotToken, argumentExpression), node) + : node; + } + + export function createCall(expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { + const node = createSynthesizedNode(SyntaxKind.CallExpression); + node.expression = parenthesizeForAccess(expression); + node.typeArguments = asNodeArray(typeArguments); + node.arguments = parenthesizeListElements(createNodeArray(argumentsArray)); + return node; + } + + export function updateCall(node: CallExpression, expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[]) { + if (isOptionalChain(node)) { + return updateCallChain(node, expression, node.questionDotToken, typeArguments, argumentsArray); + } + return node.expression !== expression + || node.typeArguments !== typeArguments + || node.arguments !== argumentsArray + ? updateNode(createCall(expression, typeArguments, argumentsArray), node) + : node; + } + + export function createCallChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { + const node = createSynthesizedNode(SyntaxKind.CallExpression); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizeForAccess(expression); + node.questionDotToken = questionDotToken; + node.typeArguments = asNodeArray(typeArguments); + node.arguments = parenthesizeListElements(createNodeArray(argumentsArray)); + return node; + } + + export function updateCallChain(node: CallChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[]) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a CallExpression using updateCallChain. Use updateCall instead."); + return node.expression !== expression + || node.questionDotToken !== questionDotToken + || node.typeArguments !== typeArguments + || node.arguments !== argumentsArray + ? updateNode(createCallChain(expression, questionDotToken, typeArguments, argumentsArray), node) + : node; + } + + export function createNew(expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { + const node = createSynthesizedNode(SyntaxKind.NewExpression); + node.expression = parenthesizeForNew(expression); + node.typeArguments = asNodeArray(typeArguments); + node.arguments = argumentsArray ? parenthesizeListElements(createNodeArray(argumentsArray)) : undefined; + return node; + } + + export function updateNew(node: NewExpression, expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { + return node.expression !== expression + || node.typeArguments !== typeArguments + || node.arguments !== argumentsArray + ? updateNode(createNew(expression, typeArguments, argumentsArray), node) + : node; + } + + /** @deprecated */ export function createTaggedTemplate(tag: Expression, template: TemplateLiteral): TaggedTemplateExpression; + export function createTaggedTemplate(tag: Expression, typeArguments: readonly TypeNode[] | undefined, template: TemplateLiteral): TaggedTemplateExpression; + /** @internal */ + export function createTaggedTemplate(tag: Expression, typeArgumentsOrTemplate: readonly TypeNode[] | TemplateLiteral | undefined, template?: TemplateLiteral): TaggedTemplateExpression; + export function createTaggedTemplate(tag: Expression, typeArgumentsOrTemplate: readonly TypeNode[] | TemplateLiteral | undefined, template?: TemplateLiteral) { + const node = createSynthesizedNode(SyntaxKind.TaggedTemplateExpression); + node.tag = parenthesizeForAccess(tag); + if (template) { + node.typeArguments = asNodeArray(typeArgumentsOrTemplate as readonly TypeNode[]); + node.template = template; + } + else { + node.typeArguments = undefined; + node.template = typeArgumentsOrTemplate as TemplateLiteral; + } + return node; + } + + /** @deprecated */ export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, template: TemplateLiteral): TaggedTemplateExpression; + export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, typeArguments: readonly TypeNode[] | undefined, template: TemplateLiteral): TaggedTemplateExpression; + export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, typeArgumentsOrTemplate: readonly TypeNode[] | TemplateLiteral | undefined, template?: TemplateLiteral) { + return node.tag !== tag + || (template + ? node.typeArguments !== typeArgumentsOrTemplate || node.template !== template + : node.typeArguments !== undefined || node.template !== typeArgumentsOrTemplate) + ? updateNode(createTaggedTemplate(tag, typeArgumentsOrTemplate, template), node) + : node; + } + + export function createTypeAssertion(type: TypeNode, expression: Expression) { + const node = createSynthesizedNode(SyntaxKind.TypeAssertionExpression); + node.type = type; + node.expression = parenthesizePrefixOperand(expression); + return node; + } + + export function updateTypeAssertion(node: TypeAssertion, type: TypeNode, expression: Expression) { + return node.type !== type + || node.expression !== expression + ? updateNode(createTypeAssertion(type, expression), node) + : node; + } + + export function createParen(expression: Expression) { + const node = createSynthesizedNode(SyntaxKind.ParenthesizedExpression); + node.expression = expression; + return node; + } + + export function updateParen(node: ParenthesizedExpression, expression: Expression) { + return node.expression !== expression + ? updateNode(createParen(expression), node) + : node; + } + + export function createFunctionExpression( + modifiers: readonly Modifier[] | undefined, + asteriskToken: AsteriskToken | undefined, + name: string | Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[] | undefined, + type: TypeNode | undefined, + body: Block) { + const node = createSynthesizedNode(SyntaxKind.FunctionExpression); + node.modifiers = asNodeArray(modifiers); + node.asteriskToken = asteriskToken; + node.name = asName(name); + node.typeParameters = asNodeArray(typeParameters); + node.parameters = createNodeArray(parameters); + node.type = type; + node.body = body; + return node; + } + + export function updateFunctionExpression( + node: FunctionExpression, + modifiers: readonly Modifier[] | undefined, + asteriskToken: AsteriskToken | undefined, + name: Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block) { + return node.name !== name + || node.modifiers !== modifiers + || node.asteriskToken !== asteriskToken + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + || node.body !== body + ? updateNode(createFunctionExpression(modifiers, asteriskToken, name, typeParameters, parameters, type, body), node) + : node; + } + + export function createArrowFunction( + modifiers: readonly Modifier[] | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + equalsGreaterThanToken: EqualsGreaterThanToken | undefined, + body: ConciseBody) { + const node = createSynthesizedNode(SyntaxKind.ArrowFunction); + node.modifiers = asNodeArray(modifiers); + node.typeParameters = asNodeArray(typeParameters); + node.parameters = createNodeArray(parameters); + node.type = type; + node.equalsGreaterThanToken = equalsGreaterThanToken || createToken(SyntaxKind.EqualsGreaterThanToken); + node.body = parenthesizeConciseBody(body); + return node; + } + export function updateArrowFunction( + node: ArrowFunction, + modifiers: readonly Modifier[] | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + equalsGreaterThanToken: Token, + body: ConciseBody + ): ArrowFunction { + return node.modifiers !== modifiers + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + || node.equalsGreaterThanToken !== equalsGreaterThanToken + || node.body !== body + ? updateNode(createArrowFunction(modifiers, typeParameters, parameters, type, equalsGreaterThanToken, body), node) + : node; + } + + export function createDelete(expression: Expression) { + const node = createSynthesizedNode(SyntaxKind.DeleteExpression); + node.expression = parenthesizePrefixOperand(expression); + return node; + } + + export function updateDelete(node: DeleteExpression, expression: Expression) { + return node.expression !== expression + ? updateNode(createDelete(expression), node) + : node; + } + + export function createTypeOf(expression: Expression) { + const node = createSynthesizedNode(SyntaxKind.TypeOfExpression); + node.expression = parenthesizePrefixOperand(expression); + return node; + } + + export function updateTypeOf(node: TypeOfExpression, expression: Expression) { + return node.expression !== expression + ? updateNode(createTypeOf(expression), node) + : node; + } + + export function createVoid(expression: Expression) { + const node = createSynthesizedNode(SyntaxKind.VoidExpression); + node.expression = parenthesizePrefixOperand(expression); + return node; + } + + export function updateVoid(node: VoidExpression, expression: Expression) { + return node.expression !== expression + ? updateNode(createVoid(expression), node) + : node; + } + + export function createAwait(expression: Expression) { + const node = createSynthesizedNode(SyntaxKind.AwaitExpression); + node.expression = parenthesizePrefixOperand(expression); + return node; + } + + export function updateAwait(node: AwaitExpression, expression: Expression) { + return node.expression !== expression + ? updateNode(createAwait(expression), node) + : node; + } + + export function createPrefix(operator: PrefixUnaryOperator, operand: Expression) { + const node = createSynthesizedNode(SyntaxKind.PrefixUnaryExpression); + node.operator = operator; + node.operand = parenthesizePrefixOperand(operand); + return node; + } + + export function updatePrefix(node: PrefixUnaryExpression, operand: Expression) { + return node.operand !== operand + ? updateNode(createPrefix(node.operator, operand), node) + : node; + } + + export function createPostfix(operand: Expression, operator: PostfixUnaryOperator) { + const node = createSynthesizedNode(SyntaxKind.PostfixUnaryExpression); + node.operand = parenthesizePostfixOperand(operand); + node.operator = operator; + return node; + } + + export function updatePostfix(node: PostfixUnaryExpression, operand: Expression) { + return node.operand !== operand + ? updateNode(createPostfix(operand, node.operator), node) + : node; + } + + export function createBinary(left: Expression, operator: BinaryOperator | BinaryOperatorToken, right: Expression) { + const node = createSynthesizedNode(SyntaxKind.BinaryExpression); + const operatorToken = asToken(operator); + const operatorKind = operatorToken.kind; + node.left = parenthesizeBinaryOperand(operatorKind, left, /*isLeftSideOfBinary*/ true, /*leftOperand*/ undefined); + node.operatorToken = operatorToken; + node.right = parenthesizeBinaryOperand(operatorKind, right, /*isLeftSideOfBinary*/ false, node.left); + return node; + } + + export function updateBinary(node: BinaryExpression, left: Expression, right: Expression, operator?: BinaryOperator | BinaryOperatorToken) { + return node.left !== left + || node.right !== right + ? updateNode(createBinary(left, operator || node.operatorToken, right), node) + : node; + } + + /** @deprecated */ export function createConditional(condition: Expression, whenTrue: Expression, whenFalse: Expression): ConditionalExpression; + export function createConditional(condition: Expression, questionToken: QuestionToken, whenTrue: Expression, colonToken: ColonToken, whenFalse: Expression): ConditionalExpression; + export function createConditional(condition: Expression, questionTokenOrWhenTrue: QuestionToken | Expression, whenTrueOrWhenFalse: Expression, colonToken?: ColonToken, whenFalse?: Expression) { + const node = createSynthesizedNode(SyntaxKind.ConditionalExpression); + node.condition = parenthesizeForConditionalHead(condition); + node.questionToken = whenFalse ? questionTokenOrWhenTrue : createToken(SyntaxKind.QuestionToken); + node.whenTrue = parenthesizeSubexpressionOfConditionalExpression(whenFalse ? whenTrueOrWhenFalse : questionTokenOrWhenTrue); + node.colonToken = whenFalse ? colonToken! : createToken(SyntaxKind.ColonToken); + node.whenFalse = parenthesizeSubexpressionOfConditionalExpression(whenFalse ? whenFalse : whenTrueOrWhenFalse); + return node; + } + export function updateConditional( + node: ConditionalExpression, + condition: Expression, + questionToken: Token, + whenTrue: Expression, + colonToken: Token, + whenFalse: Expression + ): ConditionalExpression { + return node.condition !== condition + || node.questionToken !== questionToken + || node.whenTrue !== whenTrue + || node.colonToken !== colonToken + || node.whenFalse !== whenFalse + ? updateNode(createConditional(condition, questionToken, whenTrue, colonToken, whenFalse), node) + : node; + } + + export function createTemplateExpression(head: TemplateHead, templateSpans: readonly TemplateSpan[]) { + const node = createSynthesizedNode(SyntaxKind.TemplateExpression); + node.head = head; + node.templateSpans = createNodeArray(templateSpans); + return node; + } + + export function updateTemplateExpression(node: TemplateExpression, head: TemplateHead, templateSpans: readonly TemplateSpan[]) { + return node.head !== head + || node.templateSpans !== templateSpans + ? updateNode(createTemplateExpression(head, templateSpans), node) + : node; + } + + let rawTextScanner: Scanner | undefined; + const invalidValueSentinel: object = {}; + + function getCookedText(kind: TemplateLiteralToken["kind"], rawText: string) { + if (!rawTextScanner) { + rawTextScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, LanguageVariant.Standard); + } + switch (kind) { + case SyntaxKind.NoSubstitutionTemplateLiteral: + rawTextScanner.setText("`" + rawText + "`"); + break; + case SyntaxKind.TemplateHead: + rawTextScanner.setText("`" + rawText + "${"); + break; + case SyntaxKind.TemplateMiddle: + rawTextScanner.setText("}" + rawText + "${"); + break; + case SyntaxKind.TemplateTail: + rawTextScanner.setText("}" + rawText + "`"); + break; + } + + let token = rawTextScanner.scan(); + if (token === SyntaxKind.CloseBracketToken) { + token = rawTextScanner.reScanTemplateToken(); + } + + if (rawTextScanner.isUnterminated()) { + rawTextScanner.setText(undefined); + return invalidValueSentinel; + } + + let tokenValue: string | undefined; + switch (token) { + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TemplateHead: + case SyntaxKind.TemplateMiddle: + case SyntaxKind.TemplateTail: + tokenValue = rawTextScanner.getTokenValue(); + break; + } + + if (rawTextScanner.scan() !== SyntaxKind.EndOfFileToken) { + rawTextScanner.setText(undefined); + return invalidValueSentinel; + } + + rawTextScanner.setText(undefined); + return tokenValue; + } + + function createTemplateLiteralLikeNode(kind: TemplateLiteralToken["kind"], text: string, rawText: string | undefined) { + const node = createSynthesizedNode(kind); + node.text = text; + if (rawText === undefined || text === rawText) { + node.rawText = rawText; + } + else { + const cooked = getCookedText(kind, rawText); + if (typeof cooked === "object") { + return Debug.fail("Invalid raw text"); + } + + Debug.assert(text === cooked, "Expected argument 'text' to be the normalized (i.e. 'cooked') version of argument 'rawText'."); + node.rawText = rawText; + } + return node; + } + + export function createTemplateHead(text: string, rawText?: string) { + const node = createTemplateLiteralLikeNode(SyntaxKind.TemplateHead, text, rawText); + node.text = text; + return node; + } + + export function createTemplateMiddle(text: string, rawText?: string) { + const node = createTemplateLiteralLikeNode(SyntaxKind.TemplateMiddle, text, rawText); + node.text = text; + return node; + } + + export function createTemplateTail(text: string, rawText?: string) { + const node = createTemplateLiteralLikeNode(SyntaxKind.TemplateTail, text, rawText); + node.text = text; + return node; + } + + export function createNoSubstitutionTemplateLiteral(text: string, rawText?: string) { + const node = createTemplateLiteralLikeNode(SyntaxKind.NoSubstitutionTemplateLiteral, text, rawText); + return node; + } + + export function createYield(expression?: Expression): YieldExpression; + export function createYield(asteriskToken: AsteriskToken | undefined, expression: Expression): YieldExpression; + export function createYield(asteriskTokenOrExpression?: AsteriskToken | undefined | Expression, expression?: Expression) { + const node = createSynthesizedNode(SyntaxKind.YieldExpression); + node.asteriskToken = asteriskTokenOrExpression && asteriskTokenOrExpression.kind === SyntaxKind.AsteriskToken ? asteriskTokenOrExpression : undefined; + node.expression = asteriskTokenOrExpression && asteriskTokenOrExpression.kind !== SyntaxKind.AsteriskToken ? asteriskTokenOrExpression : expression; + return node; + } + + export function updateYield(node: YieldExpression, asteriskToken: AsteriskToken | undefined, expression: Expression) { + return node.expression !== expression + || node.asteriskToken !== asteriskToken + ? updateNode(createYield(asteriskToken, expression), node) + : node; + } + + export function createSpread(expression: Expression) { + const node = createSynthesizedNode(SyntaxKind.SpreadElement); + node.expression = parenthesizeExpressionForList(expression); + return node; + } + + export function updateSpread(node: SpreadElement, expression: Expression) { + return node.expression !== expression + ? updateNode(createSpread(expression), node) + : node; + } + + export function createClassExpression( + modifiers: readonly Modifier[] | undefined, + name: string | Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined, + members: readonly ClassElement[]) { + const node = createSynthesizedNode(SyntaxKind.ClassExpression); + node.decorators = undefined; + node.modifiers = asNodeArray(modifiers); + node.name = asName(name); + node.typeParameters = asNodeArray(typeParameters); + node.heritageClauses = asNodeArray(heritageClauses); + node.members = createNodeArray(members); + return node; + } + + export function updateClassExpression( + node: ClassExpression, + modifiers: readonly Modifier[] | undefined, + name: Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined, + members: readonly ClassElement[]) { + return node.modifiers !== modifiers + || node.name !== name + || node.typeParameters !== typeParameters + || node.heritageClauses !== heritageClauses + || node.members !== members + ? updateNode(createClassExpression(modifiers, name, typeParameters, heritageClauses, members), node) + : node; + } + + export function createOmittedExpression() { + return createSynthesizedNode(SyntaxKind.OmittedExpression); + } + + export function createExpressionWithTypeArguments(typeArguments: readonly TypeNode[] | undefined, expression: Expression) { + const node = createSynthesizedNode(SyntaxKind.ExpressionWithTypeArguments); + node.expression = parenthesizeForAccess(expression); + node.typeArguments = asNodeArray(typeArguments); + return node; + } + + export function updateExpressionWithTypeArguments(node: ExpressionWithTypeArguments, typeArguments: readonly TypeNode[] | undefined, expression: Expression) { + return node.typeArguments !== typeArguments + || node.expression !== expression + ? updateNode(createExpressionWithTypeArguments(typeArguments, expression), node) + : node; + } + + export function createAsExpression(expression: Expression, type: TypeNode) { + const node = createSynthesizedNode(SyntaxKind.AsExpression); + node.expression = expression; + node.type = type; + return node; + } + + export function updateAsExpression(node: AsExpression, expression: Expression, type: TypeNode) { + return node.expression !== expression + || node.type !== type + ? updateNode(createAsExpression(expression, type), node) + : node; + } + + export function createNonNullExpression(expression: Expression) { + const node = createSynthesizedNode(SyntaxKind.NonNullExpression); + node.expression = parenthesizeForAccess(expression); + return node; + } + + export function updateNonNullExpression(node: NonNullExpression, expression: Expression) { + return node.expression !== expression + ? updateNode(createNonNullExpression(expression), node) + : node; + } + + export function createMetaProperty(keywordToken: MetaProperty["keywordToken"], name: Identifier) { + const node = createSynthesizedNode(SyntaxKind.MetaProperty); + node.keywordToken = keywordToken; + node.name = name; + return node; + } + + export function updateMetaProperty(node: MetaProperty, name: Identifier) { + return node.name !== name + ? updateNode(createMetaProperty(node.keywordToken, name), node) + : node; + } + + // Misc + + export function createTemplateSpan(expression: Expression, literal: TemplateMiddle | TemplateTail) { + const node = createSynthesizedNode(SyntaxKind.TemplateSpan); + node.expression = expression; + node.literal = literal; + return node; + } + + export function updateTemplateSpan(node: TemplateSpan, expression: Expression, literal: TemplateMiddle | TemplateTail) { + return node.expression !== expression + || node.literal !== literal + ? updateNode(createTemplateSpan(expression, literal), node) + : node; + } + + export function createSemicolonClassElement() { + return createSynthesizedNode(SyntaxKind.SemicolonClassElement); + } + + // Element + + export function createBlock(statements: readonly Statement[], multiLine?: boolean): Block { + const block = createSynthesizedNode(SyntaxKind.Block); + block.statements = createNodeArray(statements); + if (multiLine) block.multiLine = multiLine; + return block; + } + + export function updateBlock(node: Block, statements: readonly Statement[]) { + return node.statements !== statements + ? updateNode(createBlock(statements, node.multiLine), node) + : node; + } + + export function createVariableStatement(modifiers: readonly Modifier[] | undefined, declarationList: VariableDeclarationList | readonly VariableDeclaration[]) { + const node = createSynthesizedNode(SyntaxKind.VariableStatement); + node.decorators = undefined; + node.modifiers = asNodeArray(modifiers); + node.declarationList = isArray(declarationList) ? createVariableDeclarationList(declarationList) : declarationList; + return node; + } + + export function updateVariableStatement(node: VariableStatement, modifiers: readonly Modifier[] | undefined, declarationList: VariableDeclarationList) { + return node.modifiers !== modifiers + || node.declarationList !== declarationList + ? updateNode(createVariableStatement(modifiers, declarationList), node) + : node; + } + + export function createEmptyStatement() { + return createSynthesizedNode(SyntaxKind.EmptyStatement); + } + + export function createExpressionStatement(expression: Expression): ExpressionStatement { + const node = createSynthesizedNode(SyntaxKind.ExpressionStatement); + node.expression = parenthesizeExpressionForExpressionStatement(expression); + return node; + } + + export function updateExpressionStatement(node: ExpressionStatement, expression: Expression) { + return node.expression !== expression + ? updateNode(createExpressionStatement(expression), node) + : node; + } + + /** @deprecated Use `createExpressionStatement` instead. */ + export const createStatement = createExpressionStatement; + /** @deprecated Use `updateExpressionStatement` instead. */ + export const updateStatement = updateExpressionStatement; + + export function createIf(expression: Expression, thenStatement: Statement, elseStatement?: Statement) { + const node = createSynthesizedNode(SyntaxKind.IfStatement); + node.expression = expression; + node.thenStatement = asEmbeddedStatement(thenStatement); + node.elseStatement = asEmbeddedStatement(elseStatement); + return node; + } + + export function updateIf(node: IfStatement, expression: Expression, thenStatement: Statement, elseStatement: Statement | undefined) { + return node.expression !== expression + || node.thenStatement !== thenStatement + || node.elseStatement !== elseStatement + ? updateNode(createIf(expression, thenStatement, elseStatement), node) + : node; + } + + export function createDo(statement: Statement, expression: Expression) { + const node = createSynthesizedNode(SyntaxKind.DoStatement); + node.statement = asEmbeddedStatement(statement); + node.expression = expression; + return node; + } + + export function updateDo(node: DoStatement, statement: Statement, expression: Expression) { + return node.statement !== statement + || node.expression !== expression + ? updateNode(createDo(statement, expression), node) + : node; + } + + export function createWhile(expression: Expression, statement: Statement) { + const node = createSynthesizedNode(SyntaxKind.WhileStatement); + node.expression = expression; + node.statement = asEmbeddedStatement(statement); + return node; + } + + export function updateWhile(node: WhileStatement, expression: Expression, statement: Statement) { + return node.expression !== expression + || node.statement !== statement + ? updateNode(createWhile(expression, statement), node) + : node; + } + + export function createFor(initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement) { + const node = createSynthesizedNode(SyntaxKind.ForStatement); + node.initializer = initializer; + node.condition = condition; + node.incrementor = incrementor; + node.statement = asEmbeddedStatement(statement); + return node; + } + + export function updateFor(node: ForStatement, initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement) { + return node.initializer !== initializer + || node.condition !== condition + || node.incrementor !== incrementor + || node.statement !== statement + ? updateNode(createFor(initializer, condition, incrementor, statement), node) + : node; + } + + export function createForIn(initializer: ForInitializer, expression: Expression, statement: Statement) { + const node = createSynthesizedNode(SyntaxKind.ForInStatement); + node.initializer = initializer; + node.expression = expression; + node.statement = asEmbeddedStatement(statement); + return node; + } + + export function updateForIn(node: ForInStatement, initializer: ForInitializer, expression: Expression, statement: Statement) { + return node.initializer !== initializer + || node.expression !== expression + || node.statement !== statement + ? updateNode(createForIn(initializer, expression, statement), node) + : node; + } + + export function createForOf(awaitModifier: AwaitKeywordToken | undefined, initializer: ForInitializer, expression: Expression, statement: Statement) { + const node = createSynthesizedNode(SyntaxKind.ForOfStatement); + node.awaitModifier = awaitModifier; + node.initializer = initializer; + node.expression = isCommaSequence(expression) ? createParen(expression) : expression; + node.statement = asEmbeddedStatement(statement); + return node; + } + + export function updateForOf(node: ForOfStatement, awaitModifier: AwaitKeywordToken | undefined, initializer: ForInitializer, expression: Expression, statement: Statement) { + return node.awaitModifier !== awaitModifier + || node.initializer !== initializer + || node.expression !== expression + || node.statement !== statement + ? updateNode(createForOf(awaitModifier, initializer, expression, statement), node) + : node; + } + + export function createContinue(label?: string | Identifier): ContinueStatement { + const node = createSynthesizedNode(SyntaxKind.ContinueStatement); + node.label = asName(label); + return node; + } + + export function updateContinue(node: ContinueStatement, label: Identifier | undefined) { + return node.label !== label + ? updateNode(createContinue(label), node) + : node; + } + + export function createBreak(label?: string | Identifier): BreakStatement { + const node = createSynthesizedNode(SyntaxKind.BreakStatement); + node.label = asName(label); + return node; + } + + export function updateBreak(node: BreakStatement, label: Identifier | undefined) { + return node.label !== label + ? updateNode(createBreak(label), node) + : node; + } + + export function createReturn(expression?: Expression): ReturnStatement { + const node = createSynthesizedNode(SyntaxKind.ReturnStatement); + node.expression = expression; + return node; + } + + export function updateReturn(node: ReturnStatement, expression: Expression | undefined) { + return node.expression !== expression + ? updateNode(createReturn(expression), node) + : node; + } + + export function createWith(expression: Expression, statement: Statement) { + const node = createSynthesizedNode(SyntaxKind.WithStatement); + node.expression = expression; + node.statement = asEmbeddedStatement(statement); + return node; + } + + export function updateWith(node: WithStatement, expression: Expression, statement: Statement) { + return node.expression !== expression + || node.statement !== statement + ? updateNode(createWith(expression, statement), node) + : node; + } + + export function createSwitch(expression: Expression, caseBlock: CaseBlock): SwitchStatement { + const node = createSynthesizedNode(SyntaxKind.SwitchStatement); + node.expression = parenthesizeExpressionForList(expression); + node.caseBlock = caseBlock; + return node; + } + + export function updateSwitch(node: SwitchStatement, expression: Expression, caseBlock: CaseBlock) { + return node.expression !== expression + || node.caseBlock !== caseBlock + ? updateNode(createSwitch(expression, caseBlock), node) + : node; + } + + export function createLabel(label: string | Identifier, statement: Statement) { + const node = createSynthesizedNode(SyntaxKind.LabeledStatement); + node.label = asName(label); + node.statement = asEmbeddedStatement(statement); + return node; + } + + export function updateLabel(node: LabeledStatement, label: Identifier, statement: Statement) { + return node.label !== label + || node.statement !== statement + ? updateNode(createLabel(label, statement), node) + : node; + } + + export function createThrow(expression: Expression) { + const node = createSynthesizedNode(SyntaxKind.ThrowStatement); + node.expression = expression; + return node; + } + + export function updateThrow(node: ThrowStatement, expression: Expression) { + return node.expression !== expression + ? updateNode(createThrow(expression), node) + : node; + } + + export function createTry(tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined) { + const node = createSynthesizedNode(SyntaxKind.TryStatement); + node.tryBlock = tryBlock; + node.catchClause = catchClause; + node.finallyBlock = finallyBlock; + return node; + } + + export function updateTry(node: TryStatement, tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined) { + return node.tryBlock !== tryBlock + || node.catchClause !== catchClause + || node.finallyBlock !== finallyBlock + ? updateNode(createTry(tryBlock, catchClause, finallyBlock), node) + : node; + } + + export function createDebuggerStatement() { + return createSynthesizedNode(SyntaxKind.DebuggerStatement); + } + + export function createVariableDeclaration(name: string | BindingName, type?: TypeNode, initializer?: Expression) { + /* Internally, one should probably use createTypeScriptVariableDeclaration instead and handle definite assignment assertions */ + const node = createSynthesizedNode(SyntaxKind.VariableDeclaration); + node.name = asName(name); + node.type = type; + node.initializer = initializer !== undefined ? parenthesizeExpressionForList(initializer) : undefined; + return node; + } + + export function updateVariableDeclaration(node: VariableDeclaration, name: BindingName, type: TypeNode | undefined, initializer: Expression | undefined) { + /* Internally, one should probably use updateTypeScriptVariableDeclaration instead and handle definite assignment assertions */ + return node.name !== name + || node.type !== type + || node.initializer !== initializer + ? updateNode(createVariableDeclaration(name, type, initializer), node) + : node; + } + + /* @internal */ + export function createTypeScriptVariableDeclaration(name: string | BindingName, exclaimationToken?: Token, type?: TypeNode, initializer?: Expression) { + const node = createSynthesizedNode(SyntaxKind.VariableDeclaration); + node.name = asName(name); + node.type = type; + node.initializer = initializer !== undefined ? parenthesizeExpressionForList(initializer) : undefined; + node.exclamationToken = exclaimationToken; + return node; + } + + /* @internal */ + export function updateTypeScriptVariableDeclaration(node: VariableDeclaration, name: BindingName, exclaimationToken: Token | undefined, type: TypeNode | undefined, initializer: Expression | undefined) { + return node.name !== name + || node.type !== type + || node.initializer !== initializer + || node.exclamationToken !== exclaimationToken + ? updateNode(createTypeScriptVariableDeclaration(name, exclaimationToken, type, initializer), node) + : node; + } + + export function createVariableDeclarationList(declarations: readonly VariableDeclaration[], flags = NodeFlags.None) { + const node = createSynthesizedNode(SyntaxKind.VariableDeclarationList); + node.flags |= flags & NodeFlags.BlockScoped; + node.declarations = createNodeArray(declarations); + return node; + } + + export function updateVariableDeclarationList(node: VariableDeclarationList, declarations: readonly VariableDeclaration[]) { + return node.declarations !== declarations + ? updateNode(createVariableDeclarationList(declarations, node.flags), node) + : node; + } + + export function createFunctionDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + asteriskToken: AsteriskToken | undefined, + name: string | Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block | undefined) { + const node = createSynthesizedNode(SyntaxKind.FunctionDeclaration); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.asteriskToken = asteriskToken; + node.name = asName(name); + node.typeParameters = asNodeArray(typeParameters); + node.parameters = createNodeArray(parameters); + node.type = type; + node.body = body; + return node; + } + + export function updateFunctionDeclaration( + node: FunctionDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + asteriskToken: AsteriskToken | undefined, + name: Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block | undefined) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.asteriskToken !== asteriskToken + || node.name !== name + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + || node.body !== body + ? updateNode(createFunctionDeclaration(decorators, modifiers, asteriskToken, name, typeParameters, parameters, type, body), node) + : node; + } + + export function createClassDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined, + members: readonly ClassElement[]) { + const node = createSynthesizedNode(SyntaxKind.ClassDeclaration); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.name = asName(name); + node.typeParameters = asNodeArray(typeParameters); + node.heritageClauses = asNodeArray(heritageClauses); + node.members = createNodeArray(members); + return node; + } + + export function updateClassDeclaration( + node: ClassDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined, + members: readonly ClassElement[]) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.typeParameters !== typeParameters + || node.heritageClauses !== heritageClauses + || node.members !== members + ? updateNode(createClassDeclaration(decorators, modifiers, name, typeParameters, heritageClauses, members), node) + : node; + } + + export function createInterfaceDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | Identifier, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined, + members: readonly TypeElement[]) { + const node = createSynthesizedNode(SyntaxKind.InterfaceDeclaration); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.name = asName(name); + node.typeParameters = asNodeArray(typeParameters); + node.heritageClauses = asNodeArray(heritageClauses); + node.members = createNodeArray(members); + return node; + } + + export function updateInterfaceDeclaration( + node: InterfaceDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: Identifier, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined, + members: readonly TypeElement[]) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.typeParameters !== typeParameters + || node.heritageClauses !== heritageClauses + || node.members !== members + ? updateNode(createInterfaceDeclaration(decorators, modifiers, name, typeParameters, heritageClauses, members), node) + : node; + } + + export function createTypeAliasDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | Identifier, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + type: TypeNode) { + const node = createSynthesizedNode(SyntaxKind.TypeAliasDeclaration); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.name = asName(name); + node.typeParameters = asNodeArray(typeParameters); + node.type = type; + return node; + } + + export function updateTypeAliasDeclaration( + node: TypeAliasDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: Identifier, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + type: TypeNode) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.typeParameters !== typeParameters + || node.type !== type + ? updateNode(createTypeAliasDeclaration(decorators, modifiers, name, typeParameters, type), node) + : node; + } + + export function createEnumDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | Identifier, + members: readonly EnumMember[]) { + const node = createSynthesizedNode(SyntaxKind.EnumDeclaration); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.name = asName(name); + node.members = createNodeArray(members); + return node; + } + + export function updateEnumDeclaration( + node: EnumDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: Identifier, + members: readonly EnumMember[]) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.members !== members + ? updateNode(createEnumDeclaration(decorators, modifiers, name, members), node) + : node; + } + + export function createModuleDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: ModuleName, body: ModuleBody | undefined, flags = NodeFlags.None) { + const node = createSynthesizedNode(SyntaxKind.ModuleDeclaration); + node.flags |= flags & (NodeFlags.Namespace | NodeFlags.NestedNamespace | NodeFlags.GlobalAugmentation); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.name = name; + node.body = body; + return node; + } + + export function updateModuleDeclaration(node: ModuleDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: ModuleName, body: ModuleBody | undefined) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.body !== body + ? updateNode(createModuleDeclaration(decorators, modifiers, name, body, node.flags), node) + : node; + } + + export function createModuleBlock(statements: readonly Statement[]) { + const node = createSynthesizedNode(SyntaxKind.ModuleBlock); + node.statements = createNodeArray(statements); + return node; + } + + export function updateModuleBlock(node: ModuleBlock, statements: readonly Statement[]) { + return node.statements !== statements + ? updateNode(createModuleBlock(statements), node) + : node; + } + + export function createCaseBlock(clauses: readonly CaseOrDefaultClause[]): CaseBlock { + const node = createSynthesizedNode(SyntaxKind.CaseBlock); + node.clauses = createNodeArray(clauses); + return node; + } + + export function updateCaseBlock(node: CaseBlock, clauses: readonly CaseOrDefaultClause[]) { + return node.clauses !== clauses + ? updateNode(createCaseBlock(clauses), node) + : node; + } + + export function createNamespaceExportDeclaration(name: string | Identifier) { + const node = createSynthesizedNode(SyntaxKind.NamespaceExportDeclaration); + node.name = asName(name); + return node; + } + + export function updateNamespaceExportDeclaration(node: NamespaceExportDeclaration, name: Identifier) { + return node.name !== name + ? updateNode(createNamespaceExportDeclaration(name), node) + : node; + } + + export function createImportEqualsDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | Identifier, moduleReference: ModuleReference) { + const node = createSynthesizedNode(SyntaxKind.ImportEqualsDeclaration); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.name = asName(name); + node.moduleReference = moduleReference; + return node; + } + + export function updateImportEqualsDeclaration(node: ImportEqualsDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: Identifier, moduleReference: ModuleReference) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.moduleReference !== moduleReference + ? updateNode(createImportEqualsDeclaration(decorators, modifiers, name, moduleReference), node) + : node; + } + + export function createImportDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + importClause: ImportClause | undefined, + moduleSpecifier: Expression): ImportDeclaration { + const node = createSynthesizedNode(SyntaxKind.ImportDeclaration); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.importClause = importClause; + node.moduleSpecifier = moduleSpecifier; + return node; + } + + export function updateImportDeclaration( + node: ImportDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + importClause: ImportClause | undefined, + moduleSpecifier: Expression) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.importClause !== importClause + || node.moduleSpecifier !== moduleSpecifier + ? updateNode(createImportDeclaration(decorators, modifiers, importClause, moduleSpecifier), node) + : node; + } + + export function createImportClause(name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause { + const node = createSynthesizedNode(SyntaxKind.ImportClause); + node.name = name; + node.namedBindings = namedBindings; + return node; + } + + export function updateImportClause(node: ImportClause, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined) { + return node.name !== name + || node.namedBindings !== namedBindings + ? updateNode(createImportClause(name, namedBindings), node) + : node; + } + + export function createNamespaceImport(name: Identifier): NamespaceImport { + const node = createSynthesizedNode(SyntaxKind.NamespaceImport); + node.name = name; + return node; + } + + export function updateNamespaceImport(node: NamespaceImport, name: Identifier) { + return node.name !== name + ? updateNode(createNamespaceImport(name), node) + : node; + } + + export function createNamedImports(elements: readonly ImportSpecifier[]): NamedImports { + const node = createSynthesizedNode(SyntaxKind.NamedImports); + node.elements = createNodeArray(elements); + return node; + } + + export function updateNamedImports(node: NamedImports, elements: readonly ImportSpecifier[]) { + return node.elements !== elements + ? updateNode(createNamedImports(elements), node) + : node; + } + + export function createImportSpecifier(propertyName: Identifier | undefined, name: Identifier) { + const node = createSynthesizedNode(SyntaxKind.ImportSpecifier); + node.propertyName = propertyName; + node.name = name; + return node; + } + + export function updateImportSpecifier(node: ImportSpecifier, propertyName: Identifier | undefined, name: Identifier) { + return node.propertyName !== propertyName + || node.name !== name + ? updateNode(createImportSpecifier(propertyName, name), node) + : node; + } + + export function createExportAssignment(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isExportEquals: boolean | undefined, expression: Expression) { + const node = createSynthesizedNode(SyntaxKind.ExportAssignment); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.isExportEquals = isExportEquals; + node.expression = isExportEquals ? parenthesizeBinaryOperand(SyntaxKind.EqualsToken, expression, /*isLeftSideOfBinary*/ false, /*leftOperand*/ undefined) : parenthesizeDefaultExpression(expression); + return node; + } + + export function updateExportAssignment(node: ExportAssignment, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, expression: Expression) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.expression !== expression + ? updateNode(createExportAssignment(decorators, modifiers, node.isExportEquals, expression), node) + : node; + } + + export function createExportDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, exportClause: NamedExports | undefined, moduleSpecifier?: Expression) { + const node = createSynthesizedNode(SyntaxKind.ExportDeclaration); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.exportClause = exportClause; + node.moduleSpecifier = moduleSpecifier; + return node; + } + + export function updateExportDeclaration( + node: ExportDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + exportClause: NamedExports | undefined, + moduleSpecifier: Expression | undefined) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.exportClause !== exportClause + || node.moduleSpecifier !== moduleSpecifier + ? updateNode(createExportDeclaration(decorators, modifiers, exportClause, moduleSpecifier), node) + : node; + } + + /* @internal */ + export function createEmptyExports() { + return createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createNamedExports([]), /*moduleSpecifier*/ undefined); + } + + export function createNamedExports(elements: readonly ExportSpecifier[]) { + const node = createSynthesizedNode(SyntaxKind.NamedExports); + node.elements = createNodeArray(elements); + return node; + } + + export function updateNamedExports(node: NamedExports, elements: readonly ExportSpecifier[]) { + return node.elements !== elements + ? updateNode(createNamedExports(elements), node) + : node; + } + + export function createExportSpecifier(propertyName: string | Identifier | undefined, name: string | Identifier) { + const node = createSynthesizedNode(SyntaxKind.ExportSpecifier); + node.propertyName = asName(propertyName); + node.name = asName(name); + return node; + } + + export function updateExportSpecifier(node: ExportSpecifier, propertyName: Identifier | undefined, name: Identifier) { + return node.propertyName !== propertyName + || node.name !== name + ? updateNode(createExportSpecifier(propertyName, name), node) + : node; + } + + // Module references + + export function createExternalModuleReference(expression: Expression) { + const node = createSynthesizedNode(SyntaxKind.ExternalModuleReference); + node.expression = expression; + return node; + } + + export function updateExternalModuleReference(node: ExternalModuleReference, expression: Expression) { + return node.expression !== expression + ? updateNode(createExternalModuleReference(expression), node) + : node; + } + + // JSDoc + + /* @internal */ + export function createJSDocTypeExpression(type: TypeNode): JSDocTypeExpression { + const node = createSynthesizedNode(SyntaxKind.JSDocTypeExpression) as JSDocTypeExpression; + node.type = type; + return node; + } + + /* @internal */ + export function createJSDocTypeTag(typeExpression: JSDocTypeExpression, comment?: string): JSDocTypeTag { + const tag = createJSDocTag(SyntaxKind.JSDocTypeTag, "type"); + tag.typeExpression = typeExpression; + tag.comment = comment; + return tag; + } + + /* @internal */ + export function createJSDocReturnTag(typeExpression?: JSDocTypeExpression, comment?: string): JSDocReturnTag { + const tag = createJSDocTag(SyntaxKind.JSDocReturnTag, "returns"); + tag.typeExpression = typeExpression; + tag.comment = comment; + return tag; + } + + /** @internal */ + export function createJSDocThisTag(typeExpression?: JSDocTypeExpression): JSDocThisTag { + const tag = createJSDocTag(SyntaxKind.JSDocThisTag, "this"); + tag.typeExpression = typeExpression; + return tag; + } + + /* @internal */ + export function createJSDocParamTag(name: EntityName, isBracketed: boolean, typeExpression?: JSDocTypeExpression, comment?: string): JSDocParameterTag { + const tag = createJSDocTag(SyntaxKind.JSDocParameterTag, "param"); + tag.typeExpression = typeExpression; + tag.name = name; + tag.isBracketed = isBracketed; + tag.comment = comment; + return tag; + } + + /* @internal */ + export function createJSDocComment(comment?: string | undefined, tags?: NodeArray | undefined) { + const node = createSynthesizedNode(SyntaxKind.JSDocComment) as JSDoc; + node.comment = comment; + node.tags = tags; + return node; + } + + /* @internal */ + function createJSDocTag(kind: T["kind"], tagName: string): T { + const node = createSynthesizedNode(kind) as T; + node.tagName = createIdentifier(tagName); + return node; + } + + // JSX + + export function createJsxElement(openingElement: JsxOpeningElement, children: readonly JsxChild[], closingElement: JsxClosingElement) { + const node = createSynthesizedNode(SyntaxKind.JsxElement); + node.openingElement = openingElement; + node.children = createNodeArray(children); + node.closingElement = closingElement; + return node; + } + + export function updateJsxElement(node: JsxElement, openingElement: JsxOpeningElement, children: readonly JsxChild[], closingElement: JsxClosingElement) { + return node.openingElement !== openingElement + || node.children !== children + || node.closingElement !== closingElement + ? updateNode(createJsxElement(openingElement, children, closingElement), node) + : node; + } + + export function createJsxSelfClosingElement(tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { + const node = createSynthesizedNode(SyntaxKind.JsxSelfClosingElement); + node.tagName = tagName; + node.typeArguments = asNodeArray(typeArguments); + node.attributes = attributes; + return node; + } + + export function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { + return node.tagName !== tagName + || node.typeArguments !== typeArguments + || node.attributes !== attributes + ? updateNode(createJsxSelfClosingElement(tagName, typeArguments, attributes), node) + : node; + } + + export function createJsxOpeningElement(tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { + const node = createSynthesizedNode(SyntaxKind.JsxOpeningElement); + node.tagName = tagName; + node.typeArguments = asNodeArray(typeArguments); + node.attributes = attributes; + return node; + } + + export function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { + return node.tagName !== tagName + || node.typeArguments !== typeArguments + || node.attributes !== attributes + ? updateNode(createJsxOpeningElement(tagName, typeArguments, attributes), node) + : node; + } + + export function createJsxClosingElement(tagName: JsxTagNameExpression) { + const node = createSynthesizedNode(SyntaxKind.JsxClosingElement); + node.tagName = tagName; + return node; + } + + export function updateJsxClosingElement(node: JsxClosingElement, tagName: JsxTagNameExpression) { + return node.tagName !== tagName + ? updateNode(createJsxClosingElement(tagName), node) + : node; + } + + export function createJsxFragment(openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) { + const node = createSynthesizedNode(SyntaxKind.JsxFragment); + node.openingFragment = openingFragment; + node.children = createNodeArray(children); + node.closingFragment = closingFragment; + return node; + } + + export function createJsxText(text: string, containsOnlyTriviaWhiteSpaces?: boolean) { + const node = createSynthesizedNode(SyntaxKind.JsxText); + node.text = text; + node.containsOnlyTriviaWhiteSpaces = !!containsOnlyTriviaWhiteSpaces; + return node; + } + + export function updateJsxText(node: JsxText, text: string, containsOnlyTriviaWhiteSpaces?: boolean) { + return node.text !== text + || node.containsOnlyTriviaWhiteSpaces !== containsOnlyTriviaWhiteSpaces + ? updateNode(createJsxText(text, containsOnlyTriviaWhiteSpaces), node) + : node; + } + + export function createJsxOpeningFragment() { + return createSynthesizedNode(SyntaxKind.JsxOpeningFragment); + } + + export function createJsxJsxClosingFragment() { + return createSynthesizedNode(SyntaxKind.JsxClosingFragment); + } + + export function updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) { + return node.openingFragment !== openingFragment + || node.children !== children + || node.closingFragment !== closingFragment + ? updateNode(createJsxFragment(openingFragment, children, closingFragment), node) + : node; + } + + export function createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression) { + const node = createSynthesizedNode(SyntaxKind.JsxAttribute); + node.name = name; + node.initializer = initializer; + return node; + } + + export function updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression) { + return node.name !== name + || node.initializer !== initializer + ? updateNode(createJsxAttribute(name, initializer), node) + : node; + } + + export function createJsxAttributes(properties: readonly JsxAttributeLike[]) { + const node = createSynthesizedNode(SyntaxKind.JsxAttributes); + node.properties = createNodeArray(properties); + return node; + } + + export function updateJsxAttributes(node: JsxAttributes, properties: readonly JsxAttributeLike[]) { + return node.properties !== properties + ? updateNode(createJsxAttributes(properties), node) + : node; + } + + export function createJsxSpreadAttribute(expression: Expression) { + const node = createSynthesizedNode(SyntaxKind.JsxSpreadAttribute); + node.expression = expression; + return node; + } + + export function updateJsxSpreadAttribute(node: JsxSpreadAttribute, expression: Expression) { + return node.expression !== expression + ? updateNode(createJsxSpreadAttribute(expression), node) + : node; + } + + export function createJsxExpression(dotDotDotToken: DotDotDotToken | undefined, expression: Expression | undefined) { + const node = createSynthesizedNode(SyntaxKind.JsxExpression); + node.dotDotDotToken = dotDotDotToken; + node.expression = expression; + return node; + } + + export function updateJsxExpression(node: JsxExpression, expression: Expression | undefined) { + return node.expression !== expression + ? updateNode(createJsxExpression(node.dotDotDotToken, expression), node) + : node; + } + + // Clauses + + export function createCaseClause(expression: Expression, statements: readonly Statement[]) { + const node = createSynthesizedNode(SyntaxKind.CaseClause); + node.expression = parenthesizeExpressionForList(expression); + node.statements = createNodeArray(statements); + return node; + } + + export function updateCaseClause(node: CaseClause, expression: Expression, statements: readonly Statement[]) { + return node.expression !== expression + || node.statements !== statements + ? updateNode(createCaseClause(expression, statements), node) + : node; + } + + export function createDefaultClause(statements: readonly Statement[]) { + const node = createSynthesizedNode(SyntaxKind.DefaultClause); + node.statements = createNodeArray(statements); + return node; + } + + export function updateDefaultClause(node: DefaultClause, statements: readonly Statement[]) { + return node.statements !== statements + ? updateNode(createDefaultClause(statements), node) + : node; + } + + export function createHeritageClause(token: HeritageClause["token"], types: readonly ExpressionWithTypeArguments[]) { + const node = createSynthesizedNode(SyntaxKind.HeritageClause); + node.token = token; + node.types = createNodeArray(types); + return node; + } + + export function updateHeritageClause(node: HeritageClause, types: readonly ExpressionWithTypeArguments[]) { + return node.types !== types + ? updateNode(createHeritageClause(node.token, types), node) + : node; + } + + export function createCatchClause(variableDeclaration: string | VariableDeclaration | undefined, block: Block) { + const node = createSynthesizedNode(SyntaxKind.CatchClause); + node.variableDeclaration = isString(variableDeclaration) ? createVariableDeclaration(variableDeclaration) : variableDeclaration; + node.block = block; + return node; + } + + export function updateCatchClause(node: CatchClause, variableDeclaration: VariableDeclaration | undefined, block: Block) { + return node.variableDeclaration !== variableDeclaration + || node.block !== block + ? updateNode(createCatchClause(variableDeclaration, block), node) + : node; + } + + // Property assignments + + export function createPropertyAssignment(name: string | PropertyName, initializer: Expression) { + const node = createSynthesizedNode(SyntaxKind.PropertyAssignment); + node.name = asName(name); + node.questionToken = undefined; + node.initializer = parenthesizeExpressionForList(initializer); + return node; + } + + export function updatePropertyAssignment(node: PropertyAssignment, name: PropertyName, initializer: Expression) { + return node.name !== name + || node.initializer !== initializer + ? updateNode(createPropertyAssignment(name, initializer), node) + : node; + } + + export function createShorthandPropertyAssignment(name: string | Identifier, objectAssignmentInitializer?: Expression) { + const node = createSynthesizedNode(SyntaxKind.ShorthandPropertyAssignment); + node.name = asName(name); + node.objectAssignmentInitializer = objectAssignmentInitializer !== undefined ? parenthesizeExpressionForList(objectAssignmentInitializer) : undefined; + return node; + } + + export function updateShorthandPropertyAssignment(node: ShorthandPropertyAssignment, name: Identifier, objectAssignmentInitializer: Expression | undefined) { + return node.name !== name + || node.objectAssignmentInitializer !== objectAssignmentInitializer + ? updateNode(createShorthandPropertyAssignment(name, objectAssignmentInitializer), node) + : node; + } + + export function createSpreadAssignment(expression: Expression) { + const node = createSynthesizedNode(SyntaxKind.SpreadAssignment); + node.expression = parenthesizeExpressionForList(expression); + return node; + } + + export function updateSpreadAssignment(node: SpreadAssignment, expression: Expression) { + return node.expression !== expression + ? updateNode(createSpreadAssignment(expression), node) + : node; + } + + // Enum + + export function createEnumMember(name: string | PropertyName, initializer?: Expression) { + const node = createSynthesizedNode(SyntaxKind.EnumMember); + node.name = asName(name); + node.initializer = initializer && parenthesizeExpressionForList(initializer); + return node; + } + + export function updateEnumMember(node: EnumMember, name: PropertyName, initializer: Expression | undefined) { + return node.name !== name + || node.initializer !== initializer + ? updateNode(createEnumMember(name, initializer), node) + : node; + } + + // Top-level nodes + + export function updateSourceFileNode(node: SourceFile, statements: readonly Statement[], isDeclarationFile?: boolean, referencedFiles?: SourceFile["referencedFiles"], typeReferences?: SourceFile["typeReferenceDirectives"], hasNoDefaultLib?: boolean, libReferences?: SourceFile["libReferenceDirectives"]) { + if ( + node.statements !== statements || + (isDeclarationFile !== undefined && node.isDeclarationFile !== isDeclarationFile) || + (referencedFiles !== undefined && node.referencedFiles !== referencedFiles) || + (typeReferences !== undefined && node.typeReferenceDirectives !== typeReferences) || + (libReferences !== undefined && node.libReferenceDirectives !== libReferences) || + (hasNoDefaultLib !== undefined && node.hasNoDefaultLib !== hasNoDefaultLib) + ) { + const updated = createSynthesizedNode(SyntaxKind.SourceFile); + updated.flags |= node.flags; + updated.statements = createNodeArray(statements); + updated.endOfFileToken = node.endOfFileToken; + updated.fileName = node.fileName; + updated.path = node.path; + updated.text = node.text; + updated.isDeclarationFile = isDeclarationFile === undefined ? node.isDeclarationFile : isDeclarationFile; + updated.referencedFiles = referencedFiles === undefined ? node.referencedFiles : referencedFiles; + updated.typeReferenceDirectives = typeReferences === undefined ? node.typeReferenceDirectives : typeReferences; + updated.hasNoDefaultLib = hasNoDefaultLib === undefined ? node.hasNoDefaultLib : hasNoDefaultLib; + updated.libReferenceDirectives = libReferences === undefined ? node.libReferenceDirectives : libReferences; + if (node.amdDependencies !== undefined) updated.amdDependencies = node.amdDependencies; + if (node.moduleName !== undefined) updated.moduleName = node.moduleName; + if (node.languageVariant !== undefined) updated.languageVariant = node.languageVariant; + if (node.renamedDependencies !== undefined) updated.renamedDependencies = node.renamedDependencies; + if (node.languageVersion !== undefined) updated.languageVersion = node.languageVersion; + if (node.scriptKind !== undefined) updated.scriptKind = node.scriptKind; + if (node.externalModuleIndicator !== undefined) updated.externalModuleIndicator = node.externalModuleIndicator; + if (node.commonJsModuleIndicator !== undefined) updated.commonJsModuleIndicator = node.commonJsModuleIndicator; + if (node.identifiers !== undefined) updated.identifiers = node.identifiers; + if (node.nodeCount !== undefined) updated.nodeCount = node.nodeCount; + if (node.identifierCount !== undefined) updated.identifierCount = node.identifierCount; + if (node.symbolCount !== undefined) updated.symbolCount = node.symbolCount; + if (node.parseDiagnostics !== undefined) updated.parseDiagnostics = node.parseDiagnostics; + if (node.bindDiagnostics !== undefined) updated.bindDiagnostics = node.bindDiagnostics; + if (node.bindSuggestionDiagnostics !== undefined) updated.bindSuggestionDiagnostics = node.bindSuggestionDiagnostics; + if (node.lineMap !== undefined) updated.lineMap = node.lineMap; + if (node.classifiableNames !== undefined) updated.classifiableNames = node.classifiableNames; + if (node.resolvedModules !== undefined) updated.resolvedModules = node.resolvedModules; + if (node.resolvedTypeReferenceDirectiveNames !== undefined) updated.resolvedTypeReferenceDirectiveNames = node.resolvedTypeReferenceDirectiveNames; + if (node.imports !== undefined) updated.imports = node.imports; + if (node.moduleAugmentations !== undefined) updated.moduleAugmentations = node.moduleAugmentations; + if (node.pragmas !== undefined) updated.pragmas = node.pragmas; + if (node.localJsxFactory !== undefined) updated.localJsxFactory = node.localJsxFactory; + if (node.localJsxNamespace !== undefined) updated.localJsxNamespace = node.localJsxNamespace; + return updateNode(updated, node); + } + + return node; + } + + /** + * Creates a shallow, memberwise clone of a node for mutation. + */ + export function getMutableClone(node: T): T { + const clone = getSynthesizedClone(node); + clone.pos = node.pos; + clone.end = node.end; + clone.parent = node.parent; + return clone; + } + + // Transformation nodes + + /** + * Creates a synthetic statement to act as a placeholder for a not-emitted statement in + * order to preserve comments. + * + * @param original The original statement. + */ + export function createNotEmittedStatement(original: Node) { + const node = createSynthesizedNode(SyntaxKind.NotEmittedStatement); + node.original = original; + setTextRange(node, original); + return node; + } + + /** + * Creates a synthetic element to act as a placeholder for the end of an emitted declaration in + * order to properly emit exports. + */ + /* @internal */ + export function createEndOfDeclarationMarker(original: Node) { + const node = createSynthesizedNode(SyntaxKind.EndOfDeclarationMarker); + node.emitNode = {} as EmitNode; + node.original = original; + return node; + } + + /** + * Creates a synthetic element to act as a placeholder for the beginning of a merged declaration in + * order to properly emit exports. + */ + /* @internal */ + export function createMergeDeclarationMarker(original: Node) { + const node = createSynthesizedNode(SyntaxKind.MergeDeclarationMarker); + node.emitNode = {} as EmitNode; + node.original = original; + return node; + } + + /** + * Creates a synthetic expression to act as a placeholder for a not-emitted expression in + * order to preserve comments or sourcemap positions. + * + * @param expression The inner expression to emit. + * @param original The original outer expression. + * @param location The location for the expression. Defaults to the positions from "original" if provided. + */ + export function createPartiallyEmittedExpression(expression: Expression, original?: Node) { + const node = createSynthesizedNode(SyntaxKind.PartiallyEmittedExpression); + node.expression = expression; + node.original = original; + setTextRange(node, original); + return node; + } + + export function updatePartiallyEmittedExpression(node: PartiallyEmittedExpression, expression: Expression) { + if (node.expression !== expression) { + return updateNode(createPartiallyEmittedExpression(expression, node.original), node); + } + return node; + } + + function flattenCommaElements(node: Expression): Expression | readonly Expression[] { + if (nodeIsSynthesized(node) && !isParseTreeNode(node) && !node.original && !node.emitNode && !node.id) { + if (node.kind === SyntaxKind.CommaListExpression) { + return (node).elements; + } + if (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.CommaToken) { + return [node.left, node.right]; + } + } + return node; + } + + export function createCommaList(elements: readonly Expression[]) { + const node = createSynthesizedNode(SyntaxKind.CommaListExpression); + node.elements = createNodeArray(sameFlatMap(elements, flattenCommaElements)); + return node; + } + + export function updateCommaList(node: CommaListExpression, elements: readonly Expression[]) { + return node.elements !== elements + ? updateNode(createCommaList(elements), node) + : node; + } + + /* @internal */ + export function createSyntheticReferenceExpression(expression: Expression, thisArg: Expression) { + const node = createSynthesizedNode(SyntaxKind.SyntheticReferenceExpression); + node.expression = expression; + node.thisArg = thisArg; + return node; + } + + /* @internal */ + export function updateSyntheticReferenceExpression(node: SyntheticReferenceExpression, expression: Expression, thisArg: Expression) { + return node.expression !== expression + || node.thisArg !== thisArg + ? updateNode(createSyntheticReferenceExpression(expression, thisArg), node) + : node; + } + + export function createBundle(sourceFiles: readonly SourceFile[], prepends: readonly (UnparsedSource | InputFiles)[] = emptyArray) { + const node = createNode(SyntaxKind.Bundle); + node.prepends = prepends; + node.sourceFiles = sourceFiles; + return node; + } + + let allUnscopedEmitHelpers: ReadonlyMap | undefined; + function getAllUnscopedEmitHelpers() { + return allUnscopedEmitHelpers || (allUnscopedEmitHelpers = arrayToMap([ + valuesHelper, + readHelper, + spreadHelper, + spreadArraysHelper, + restHelper, + decorateHelper, + metadataHelper, + paramHelper, + awaiterHelper, + assignHelper, + awaitHelper, + asyncGeneratorHelper, + asyncDelegator, + asyncValues, + extendsHelper, + templateObjectHelper, + generatorHelper, + importStarHelper, + importDefaultHelper + ], helper => helper.name)); + } + + function createUnparsedSource() { + const node = createNode(SyntaxKind.UnparsedSource); + node.prologues = emptyArray; + node.referencedFiles = emptyArray; + node.libReferenceDirectives = emptyArray; + node.getLineAndCharacterOfPosition = pos => getLineAndCharacterOfPosition(node, pos); + return node; + } + + export function createUnparsedSourceFile(text: string): UnparsedSource; + export function createUnparsedSourceFile(inputFile: InputFiles, type: "js" | "dts", stripInternal?: boolean): UnparsedSource; + export function createUnparsedSourceFile(text: string, mapPath: string | undefined, map: string | undefined): UnparsedSource; + export function createUnparsedSourceFile(textOrInputFiles: string | InputFiles, mapPathOrType?: string, mapTextOrStripInternal?: string | boolean): UnparsedSource { + const node = createUnparsedSource(); + let stripInternal: boolean | undefined; + let bundleFileInfo: BundleFileInfo | undefined; + if (!isString(textOrInputFiles)) { + Debug.assert(mapPathOrType === "js" || mapPathOrType === "dts"); + node.fileName = (mapPathOrType === "js" ? textOrInputFiles.javascriptPath : textOrInputFiles.declarationPath) || ""; + node.sourceMapPath = mapPathOrType === "js" ? textOrInputFiles.javascriptMapPath : textOrInputFiles.declarationMapPath; + Object.defineProperties(node, { + text: { get() { return mapPathOrType === "js" ? textOrInputFiles.javascriptText : textOrInputFiles.declarationText; } }, + sourceMapText: { get() { return mapPathOrType === "js" ? textOrInputFiles.javascriptMapText : textOrInputFiles.declarationMapText; } }, + }); + + + if (textOrInputFiles.buildInfo && textOrInputFiles.buildInfo.bundle) { + node.oldFileOfCurrentEmit = textOrInputFiles.oldFileOfCurrentEmit; + Debug.assert(mapTextOrStripInternal === undefined || typeof mapTextOrStripInternal === "boolean"); + stripInternal = mapTextOrStripInternal as boolean | undefined; + bundleFileInfo = mapPathOrType === "js" ? textOrInputFiles.buildInfo.bundle.js : textOrInputFiles.buildInfo.bundle.dts; + if (node.oldFileOfCurrentEmit) { + parseOldFileOfCurrentEmit(node, Debug.assertDefined(bundleFileInfo)); + return node; + } + } + } + else { + node.fileName = ""; + node.text = textOrInputFiles; + node.sourceMapPath = mapPathOrType; + node.sourceMapText = mapTextOrStripInternal as string; + } + Debug.assert(!node.oldFileOfCurrentEmit); + parseUnparsedSourceFile(node, bundleFileInfo, stripInternal); + return node; + } + + function parseUnparsedSourceFile(node: UnparsedSource, bundleFileInfo: BundleFileInfo | undefined, stripInternal: boolean | undefined) { + let prologues: UnparsedPrologue[] | undefined; + let helpers: UnscopedEmitHelper[] | undefined; + let referencedFiles: FileReference[] | undefined; + let typeReferenceDirectives: string[] | undefined; + let libReferenceDirectives: FileReference[] | undefined; + let texts: UnparsedSourceText[] | undefined; + + for (const section of bundleFileInfo ? bundleFileInfo.sections : emptyArray) { + switch (section.kind) { + case BundleFileSectionKind.Prologue: + (prologues || (prologues = [])).push(createUnparsedNode(section, node) as UnparsedPrologue); + break; + case BundleFileSectionKind.EmitHelpers: + (helpers || (helpers = [])).push(getAllUnscopedEmitHelpers().get(section.data)!); + break; + case BundleFileSectionKind.NoDefaultLib: + node.hasNoDefaultLib = true; + break; + case BundleFileSectionKind.Reference: + (referencedFiles || (referencedFiles = [])).push({ pos: -1, end: -1, fileName: section.data }); + break; + case BundleFileSectionKind.Type: + (typeReferenceDirectives || (typeReferenceDirectives = [])).push(section.data); + break; + case BundleFileSectionKind.Lib: + (libReferenceDirectives || (libReferenceDirectives = [])).push({ pos: -1, end: -1, fileName: section.data }); + break; + case BundleFileSectionKind.Prepend: + const prependNode = createUnparsedNode(section, node) as UnparsedPrepend; + let prependTexts: UnparsedTextLike[] | undefined; + for (const text of section.texts) { + if (!stripInternal || text.kind !== BundleFileSectionKind.Internal) { + (prependTexts || (prependTexts = [])).push(createUnparsedNode(text, node) as UnparsedTextLike); + } + } + prependNode.texts = prependTexts || emptyArray; + (texts || (texts = [])).push(prependNode); + break; + case BundleFileSectionKind.Internal: + if (stripInternal) { + if (!texts) texts = []; + break; + } + // falls through + + case BundleFileSectionKind.Text: + (texts || (texts = [])).push(createUnparsedNode(section, node) as UnparsedTextLike); + break; + default: + Debug.assertNever(section); + } + } + + node.prologues = prologues || emptyArray; + node.helpers = helpers; + node.referencedFiles = referencedFiles || emptyArray; + node.typeReferenceDirectives = typeReferenceDirectives; + node.libReferenceDirectives = libReferenceDirectives || emptyArray; + node.texts = texts || [createUnparsedNode({ kind: BundleFileSectionKind.Text, pos: 0, end: node.text.length }, node)]; + } + + function parseOldFileOfCurrentEmit(node: UnparsedSource, bundleFileInfo: BundleFileInfo) { + Debug.assert(!!node.oldFileOfCurrentEmit); + let texts: UnparsedTextLike[] | undefined; + let syntheticReferences: UnparsedSyntheticReference[] | undefined; + for (const section of bundleFileInfo.sections) { + switch (section.kind) { + case BundleFileSectionKind.Internal: + case BundleFileSectionKind.Text: + (texts || (texts = [])).push(createUnparsedNode(section, node) as UnparsedTextLike); + break; + + case BundleFileSectionKind.NoDefaultLib: + case BundleFileSectionKind.Reference: + case BundleFileSectionKind.Type: + case BundleFileSectionKind.Lib: + (syntheticReferences || (syntheticReferences = [])).push(createUnparsedSyntheticReference(section, node)); + break; + + // Ignore + case BundleFileSectionKind.Prologue: + case BundleFileSectionKind.EmitHelpers: + case BundleFileSectionKind.Prepend: + break; + + default: + Debug.assertNever(section); + } + } + node.texts = texts || emptyArray; + node.helpers = map(bundleFileInfo.sources && bundleFileInfo.sources.helpers, name => getAllUnscopedEmitHelpers().get(name)!); + node.syntheticReferences = syntheticReferences; + return node; + } + + function mapBundleFileSectionKindToSyntaxKind(kind: BundleFileSectionKind): SyntaxKind { + switch (kind) { + case BundleFileSectionKind.Prologue: return SyntaxKind.UnparsedPrologue; + case BundleFileSectionKind.Prepend: return SyntaxKind.UnparsedPrepend; + case BundleFileSectionKind.Internal: return SyntaxKind.UnparsedInternalText; + case BundleFileSectionKind.Text: return SyntaxKind.UnparsedText; + + case BundleFileSectionKind.EmitHelpers: + case BundleFileSectionKind.NoDefaultLib: + case BundleFileSectionKind.Reference: + case BundleFileSectionKind.Type: + case BundleFileSectionKind.Lib: + return Debug.fail(`BundleFileSectionKind: ${kind} not yet mapped to SyntaxKind`); + + default: + return Debug.assertNever(kind); + } + } + + function createUnparsedNode(section: BundleFileSection, parent: UnparsedSource): UnparsedNode { + const node = createNode(mapBundleFileSectionKindToSyntaxKind(section.kind), section.pos, section.end) as UnparsedNode; + node.parent = parent; + node.data = section.data; + return node; + } + + function createUnparsedSyntheticReference(section: BundleFileHasNoDefaultLib | BundleFileReference, parent: UnparsedSource) { + const node = createNode(SyntaxKind.UnparsedSyntheticReference, section.pos, section.end) as UnparsedSyntheticReference; + node.parent = parent; + node.data = section.data; + node.section = section; + return node; + } + + export function createInputFiles( + javascriptText: string, + declarationText: string + ): InputFiles; + export function createInputFiles( + readFileText: (path: string) => string | undefined, + javascriptPath: string, + javascriptMapPath: string | undefined, + declarationPath: string, + declarationMapPath: string | undefined, + buildInfoPath: string | undefined + ): InputFiles; + export function createInputFiles( + javascriptText: string, + declarationText: string, + javascriptMapPath: string | undefined, + javascriptMapText: string | undefined, + declarationMapPath: string | undefined, + declarationMapText: string | undefined + ): InputFiles; + /*@internal*/ + export function createInputFiles( + javascriptText: string, + declarationText: string, + javascriptMapPath: string | undefined, + javascriptMapText: string | undefined, + declarationMapPath: string | undefined, + declarationMapText: string | undefined, + javascriptPath: string | undefined, + declarationPath: string | undefined, + buildInfoPath?: string | undefined, + buildInfo?: BuildInfo, + oldFileOfCurrentEmit?: boolean + ): InputFiles; + export function createInputFiles( + javascriptTextOrReadFileText: string | ((path: string) => string | undefined), + declarationTextOrJavascriptPath: string, + javascriptMapPath?: string, + javascriptMapTextOrDeclarationPath?: string, + declarationMapPath?: string, + declarationMapTextOrBuildInfoPath?: string, + javascriptPath?: string | undefined, + declarationPath?: string | undefined, + buildInfoPath?: string | undefined, + buildInfo?: BuildInfo, + oldFileOfCurrentEmit?: boolean + ): InputFiles { + const node = createNode(SyntaxKind.InputFiles); + if (!isString(javascriptTextOrReadFileText)) { + const cache = createMap(); + const textGetter = (path: string | undefined) => { + if (path === undefined) return undefined; + let value = cache.get(path); + if (value === undefined) { + value = javascriptTextOrReadFileText(path); + cache.set(path, value !== undefined ? value : false); + } + return value !== false ? value as string : undefined; + }; + const definedTextGetter = (path: string) => { + const result = textGetter(path); + return result !== undefined ? result : `/* Input file ${path} was missing */\r\n`; + }; + let buildInfo: BuildInfo | false; + const getAndCacheBuildInfo = (getText: () => string | undefined) => { + if (buildInfo === undefined) { + const result = getText(); + buildInfo = result !== undefined ? getBuildInfo(result) : false; + } + return buildInfo || undefined; + }; + node.javascriptPath = declarationTextOrJavascriptPath; + node.javascriptMapPath = javascriptMapPath; + node.declarationPath = Debug.assertDefined(javascriptMapTextOrDeclarationPath); + node.declarationMapPath = declarationMapPath; + node.buildInfoPath = declarationMapTextOrBuildInfoPath; + Object.defineProperties(node, { + javascriptText: { get() { return definedTextGetter(declarationTextOrJavascriptPath); } }, + javascriptMapText: { get() { return textGetter(javascriptMapPath); } }, // TODO:: if there is inline sourceMap in jsFile, use that + declarationText: { get() { return definedTextGetter(Debug.assertDefined(javascriptMapTextOrDeclarationPath)); } }, + declarationMapText: { get() { return textGetter(declarationMapPath); } }, // TODO:: if there is inline sourceMap in dtsFile, use that + buildInfo: { get() { return getAndCacheBuildInfo(() => textGetter(declarationMapTextOrBuildInfoPath)); } } + }); + } + else { + node.javascriptText = javascriptTextOrReadFileText; + node.javascriptMapPath = javascriptMapPath; + node.javascriptMapText = javascriptMapTextOrDeclarationPath; + node.declarationText = declarationTextOrJavascriptPath; + node.declarationMapPath = declarationMapPath; + node.declarationMapText = declarationMapTextOrBuildInfoPath; + node.javascriptPath = javascriptPath; + node.declarationPath = declarationPath; + node.buildInfoPath = buildInfoPath; + node.buildInfo = buildInfo; + node.oldFileOfCurrentEmit = oldFileOfCurrentEmit; + } + return node; + } + + export function updateBundle(node: Bundle, sourceFiles: readonly SourceFile[], prepends: readonly (UnparsedSource | InputFiles)[] = emptyArray) { + if (node.sourceFiles !== sourceFiles || node.prepends !== prepends) { + return createBundle(sourceFiles, prepends); + } + return node; + } + + // Compound nodes + + export function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[]): CallExpression; + export function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; + export function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[], param?: ParameterDeclaration, paramValue?: Expression) { + return createCall( + createFunctionExpression( + /*modifiers*/ undefined, + /*asteriskToken*/ undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, + /*parameters*/ param ? [param] : [], + /*type*/ undefined, + createBlock(statements, /*multiLine*/ true) + ), + /*typeArguments*/ undefined, + /*argumentsArray*/ paramValue ? [paramValue] : [] + ); + } + + export function createImmediatelyInvokedArrowFunction(statements: readonly Statement[]): CallExpression; + export function createImmediatelyInvokedArrowFunction(statements: readonly Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; + export function createImmediatelyInvokedArrowFunction(statements: readonly Statement[], param?: ParameterDeclaration, paramValue?: Expression) { + return createCall( + createArrowFunction( + /*modifiers*/ undefined, + /*typeParameters*/ undefined, + /*parameters*/ param ? [param] : [], + /*type*/ undefined, + /*equalsGreaterThanToken*/ undefined, + createBlock(statements, /*multiLine*/ true) + ), + /*typeArguments*/ undefined, + /*argumentsArray*/ paramValue ? [paramValue] : [] + ); + } + + + export function createComma(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.CommaToken, right); + } + + export function createLessThan(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.LessThanToken, right); + } + + export function createAssignment(left: ObjectLiteralExpression | ArrayLiteralExpression, right: Expression): DestructuringAssignment; + export function createAssignment(left: Expression, right: Expression): BinaryExpression; + export function createAssignment(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.EqualsToken, right); + } + + export function createStrictEquality(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.EqualsEqualsEqualsToken, right); + } + + export function createStrictInequality(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.ExclamationEqualsEqualsToken, right); + } + + export function createAdd(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.PlusToken, right); + } + + export function createSubtract(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.MinusToken, right); + } + + export function createPostfixIncrement(operand: Expression) { + return createPostfix(operand, SyntaxKind.PlusPlusToken); + } + + export function createLogicalAnd(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.AmpersandAmpersandToken, right); + } + + export function createLogicalOr(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.BarBarToken, right); + } + + export function createNullishCoalesce(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.QuestionQuestionToken, right); + } + + export function createLogicalNot(operand: Expression) { + return createPrefix(SyntaxKind.ExclamationToken, operand); + } + + export function createVoidZero() { + return createVoid(createLiteral(0)); + } + + export function createExportDefault(expression: Expression) { + return createExportAssignment(/*decorators*/ undefined, /*modifiers*/ undefined, /*isExportEquals*/ false, expression); + } + + export function createExternalModuleExport(exportName: Identifier) { + return createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createNamedExports([createExportSpecifier(/*propertyName*/ undefined, exportName)])); + } + + // Utilities + + function asName(name: string | T): T | Identifier { + return isString(name) ? createIdentifier(name) : name; + } + + function asExpression(value: string | number | boolean | T): T | StringLiteral | NumericLiteral | BooleanLiteral { + return typeof value === "string" ? createStringLiteral(value) : + typeof value === "number" ? createNumericLiteral(""+value) : + typeof value === "boolean" ? value ? createTrue() : createFalse() : + value; + } + + function asNodeArray(array: readonly T[]): NodeArray; + function asNodeArray(array: readonly T[] | undefined): NodeArray | undefined; + function asNodeArray(array: readonly T[] | undefined): NodeArray | undefined { + return array ? createNodeArray(array) : undefined; + } + + function asToken(value: TKind | Token): Token { + return typeof value === "number" ? createToken(value) : value; + } + + function asEmbeddedStatement(statement: T): T | EmptyStatement; + function asEmbeddedStatement(statement: T | undefined): T | EmptyStatement | undefined; + function asEmbeddedStatement(statement: T | undefined): T | EmptyStatement | undefined { + return statement && isNotEmittedStatement(statement) ? setTextRange(setOriginalNode(createEmptyStatement(), statement), statement) : statement; + } + + /** + * Clears any EmitNode entries from parse-tree nodes. + * @param sourceFile A source file. + */ + export function disposeEmitNodes(sourceFile: SourceFile) { + // During transformation we may need to annotate a parse tree node with transient + // transformation properties. As parse tree nodes live longer than transformation + // nodes, we need to make sure we reclaim any memory allocated for custom ranges + // from these nodes to ensure we do not hold onto entire subtrees just for position + // information. We also need to reset these nodes to a pre-transformation state + // for incremental parsing scenarios so that we do not impact later emit. + sourceFile = getSourceFileOfNode(getParseTreeNode(sourceFile)); + const emitNode = sourceFile && sourceFile.emitNode; + const annotatedNodes = emitNode && emitNode.annotatedNodes; + if (annotatedNodes) { + for (const node of annotatedNodes) { + node.emitNode = undefined; + } + } + } + + /** + * Associates a node with the current transformation, initializing + * various transient transformation properties. + */ + /* @internal */ + export function getOrCreateEmitNode(node: Node): EmitNode { + if (!node.emitNode) { + if (isParseTreeNode(node)) { + // To avoid holding onto transformation artifacts, we keep track of any + // parse tree node we are annotating. This allows us to clean them up after + // all transformations have completed. + if (node.kind === SyntaxKind.SourceFile) { + return node.emitNode = { annotatedNodes: [node] } as EmitNode; + } + + const sourceFile = getSourceFileOfNode(getParseTreeNode(getSourceFileOfNode(node))); + getOrCreateEmitNode(sourceFile).annotatedNodes!.push(node); + } + + node.emitNode = {} as EmitNode; + } + + return node.emitNode; + } + + /** + * Sets `EmitFlags.NoComments` on a node and removes any leading and trailing synthetic comments. + * @internal + */ + export function removeAllComments(node: T): T { + const emitNode = getOrCreateEmitNode(node); + emitNode.flags |= EmitFlags.NoComments; + emitNode.leadingComments = undefined; + emitNode.trailingComments = undefined; + return node; + } + + export function setTextRange(range: T, location: TextRange | undefined): T { + if (location) { + range.pos = location.pos; + range.end = location.end; + } + return range; + } + + /** + * Sets flags that control emit behavior of a node. + */ + export function setEmitFlags(node: T, emitFlags: EmitFlags) { + getOrCreateEmitNode(node).flags = emitFlags; + return node; + } + + /** + * Sets flags that control emit behavior of a node. + */ + /* @internal */ + export function addEmitFlags(node: T, emitFlags: EmitFlags) { + const emitNode = getOrCreateEmitNode(node); + emitNode.flags = emitNode.flags | emitFlags; + return node; + } + + /** + * Gets a custom text range to use when emitting source maps. + */ + export function getSourceMapRange(node: Node): SourceMapRange { + const emitNode = node.emitNode; + return (emitNode && emitNode.sourceMapRange) || node; + } + + /** + * Sets a custom text range to use when emitting source maps. + */ + export function setSourceMapRange(node: T, range: SourceMapRange | undefined) { + getOrCreateEmitNode(node).sourceMapRange = range; + return node; + } + + let SourceMapSource: new (fileName: string, text: string, skipTrivia?: (pos: number) => number) => SourceMapSource; + + /** + * Create an external source map source file reference + */ + export function createSourceMapSource(fileName: string, text: string, skipTrivia?: (pos: number) => number): SourceMapSource { + return new (SourceMapSource || (SourceMapSource = objectAllocator.getSourceMapSourceConstructor()))(fileName, text, skipTrivia); + } + + /** + * Gets the TextRange to use for source maps for a token of a node. + */ + export function getTokenSourceMapRange(node: Node, token: SyntaxKind): SourceMapRange | undefined { + const emitNode = node.emitNode; + const tokenSourceMapRanges = emitNode && emitNode.tokenSourceMapRanges; + return tokenSourceMapRanges && tokenSourceMapRanges[token]; + } + + /** + * Sets the TextRange to use for source maps for a token of a node. + */ + export function setTokenSourceMapRange(node: T, token: SyntaxKind, range: SourceMapRange | undefined) { + const emitNode = getOrCreateEmitNode(node); + const tokenSourceMapRanges = emitNode.tokenSourceMapRanges || (emitNode.tokenSourceMapRanges = []); + tokenSourceMapRanges[token] = range; + return node; + } + + /** + * Gets a custom text range to use when emitting comments. + */ + /*@internal*/ + export function getStartsOnNewLine(node: Node) { + const emitNode = node.emitNode; + return emitNode && emitNode.startsOnNewLine; + } + + /** + * Sets a custom text range to use when emitting comments. + */ + /*@internal*/ + export function setStartsOnNewLine(node: T, newLine: boolean) { + getOrCreateEmitNode(node).startsOnNewLine = newLine; + return node; + } + + /** + * Gets a custom text range to use when emitting comments. + */ + export function getCommentRange(node: Node) { + const emitNode = node.emitNode; + return (emitNode && emitNode.commentRange) || node; + } + + /** + * Sets a custom text range to use when emitting comments. + */ + export function setCommentRange(node: T, range: TextRange) { + getOrCreateEmitNode(node).commentRange = range; + return node; + } + + export function getSyntheticLeadingComments(node: Node): SynthesizedComment[] | undefined { + const emitNode = node.emitNode; + return emitNode && emitNode.leadingComments; + } + + export function setSyntheticLeadingComments(node: T, comments: SynthesizedComment[] | undefined) { + getOrCreateEmitNode(node).leadingComments = comments; + return node; + } + + export function addSyntheticLeadingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) { + return setSyntheticLeadingComments(node, append(getSyntheticLeadingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text })); + } + + export function getSyntheticTrailingComments(node: Node): SynthesizedComment[] | undefined { + const emitNode = node.emitNode; + return emitNode && emitNode.trailingComments; + } + + export function setSyntheticTrailingComments(node: T, comments: SynthesizedComment[] | undefined) { + getOrCreateEmitNode(node).trailingComments = comments; + return node; + } + + export function addSyntheticTrailingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) { + return setSyntheticTrailingComments(node, append(getSyntheticTrailingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text })); + } + + export function moveSyntheticComments(node: T, original: Node): T { + setSyntheticLeadingComments(node, getSyntheticLeadingComments(original)); + setSyntheticTrailingComments(node, getSyntheticTrailingComments(original)); + const emit = getOrCreateEmitNode(original); + emit.leadingComments = undefined; + emit.trailingComments = undefined; + return node; + } + + /** + * Gets the constant value to emit for an expression. + */ + export function getConstantValue(node: PropertyAccessExpression | ElementAccessExpression): string | number | undefined { + const emitNode = node.emitNode; + return emitNode && emitNode.constantValue; + } + + /** + * Sets the constant value to emit for an expression. + */ + export function setConstantValue(node: PropertyAccessExpression | ElementAccessExpression, value: string | number): PropertyAccessExpression | ElementAccessExpression { + const emitNode = getOrCreateEmitNode(node); + emitNode.constantValue = value; + return node; + } + + /** + * Adds an EmitHelper to a node. + */ + export function addEmitHelper(node: T, helper: EmitHelper): T { + const emitNode = getOrCreateEmitNode(node); + emitNode.helpers = append(emitNode.helpers, helper); + return node; + } + + /** + * Add EmitHelpers to a node. + */ + export function addEmitHelpers(node: T, helpers: EmitHelper[] | undefined): T { + if (some(helpers)) { + const emitNode = getOrCreateEmitNode(node); + for (const helper of helpers) { + emitNode.helpers = appendIfUnique(emitNode.helpers, helper); + } + } + return node; + } + + /** + * Removes an EmitHelper from a node. + */ + export function removeEmitHelper(node: Node, helper: EmitHelper): boolean { + const emitNode = node.emitNode; + if (emitNode) { + const helpers = emitNode.helpers; + if (helpers) { + return orderedRemoveItem(helpers, helper); + } + } + return false; + } + + /** + * Gets the EmitHelpers of a node. + */ + export function getEmitHelpers(node: Node): EmitHelper[] | undefined { + const emitNode = node.emitNode; + return emitNode && emitNode.helpers; + } + + /** + * Moves matching emit helpers from a source node to a target node. + */ + export function moveEmitHelpers(source: Node, target: Node, predicate: (helper: EmitHelper) => boolean) { + const sourceEmitNode = source.emitNode; + const sourceEmitHelpers = sourceEmitNode && sourceEmitNode.helpers; + if (!some(sourceEmitHelpers)) return; + + const targetEmitNode = getOrCreateEmitNode(target); + let helpersRemoved = 0; + for (let i = 0; i < sourceEmitHelpers.length; i++) { + const helper = sourceEmitHelpers[i]; + if (predicate(helper)) { + helpersRemoved++; + targetEmitNode.helpers = appendIfUnique(targetEmitNode.helpers, helper); + } + else if (helpersRemoved > 0) { + sourceEmitHelpers[i - helpersRemoved] = helper; + } + } + + if (helpersRemoved > 0) { + sourceEmitHelpers.length -= helpersRemoved; + } + } + + /* @internal */ + export function compareEmitHelpers(x: EmitHelper, y: EmitHelper) { + if (x === y) return Comparison.EqualTo; + if (x.priority === y.priority) return Comparison.EqualTo; + if (x.priority === undefined) return Comparison.GreaterThan; + if (y.priority === undefined) return Comparison.LessThan; + return compareValues(x.priority, y.priority); + } + + export function setOriginalNode(node: T, original: Node | undefined): T { + node.original = original; + if (original) { + const emitNode = original.emitNode; + if (emitNode) node.emitNode = mergeEmitNode(emitNode, node.emitNode); + } + return node; + } + + function mergeEmitNode(sourceEmitNode: EmitNode, destEmitNode: EmitNode | undefined) { + const { + flags, + leadingComments, + trailingComments, + commentRange, + sourceMapRange, + tokenSourceMapRanges, + constantValue, + helpers, + startsOnNewLine, + } = sourceEmitNode; + if (!destEmitNode) destEmitNode = {} as EmitNode; + // We are using `.slice()` here in case `destEmitNode.leadingComments` is pushed to later. + if (leadingComments) destEmitNode.leadingComments = addRange(leadingComments.slice(), destEmitNode.leadingComments); + if (trailingComments) destEmitNode.trailingComments = addRange(trailingComments.slice(), destEmitNode.trailingComments); + if (flags) destEmitNode.flags = flags; + if (commentRange) destEmitNode.commentRange = commentRange; + if (sourceMapRange) destEmitNode.sourceMapRange = sourceMapRange; + if (tokenSourceMapRanges) destEmitNode.tokenSourceMapRanges = mergeTokenSourceMapRanges(tokenSourceMapRanges, destEmitNode.tokenSourceMapRanges!); + if (constantValue !== undefined) destEmitNode.constantValue = constantValue; + if (helpers) destEmitNode.helpers = addRange(destEmitNode.helpers, helpers); + if (startsOnNewLine !== undefined) destEmitNode.startsOnNewLine = startsOnNewLine; + return destEmitNode; + } + + function mergeTokenSourceMapRanges(sourceRanges: (TextRange | undefined)[], destRanges: (TextRange | undefined)[]) { + if (!destRanges) destRanges = []; + for (const key in sourceRanges) { + destRanges[key] = sourceRanges[key]; + } + return destRanges; + } +} diff --git a/src/compiler/performance.ts b/src/compiler/performance.ts index 3471ecd0591..4a7f5c99278 100644 --- a/src/compiler/performance.ts +++ b/src/compiler/performance.ts @@ -1,10 +1,3 @@ -/*@internal*/ -namespace ts { - declare const performance: { now?(): number } | undefined; - /** Gets a timestamp with (at least) ms resolution */ - export const timestamp = typeof performance !== "undefined" && performance.now ? () => performance.now!() : Date.now ? Date.now : () => +(new Date()); -} - /*@internal*/ /** Performance measurements for the compiler. */ namespace ts.performance { diff --git a/src/compiler/performanceTimestamp.ts b/src/compiler/performanceTimestamp.ts new file mode 100644 index 00000000000..044e7a65c09 --- /dev/null +++ b/src/compiler/performanceTimestamp.ts @@ -0,0 +1,6 @@ +/*@internal*/ +namespace ts { + declare const performance: { now?(): number } | undefined; + /** Gets a timestamp with (at least) ms resolution */ + export const timestamp = typeof performance !== "undefined" && performance.now ? () => performance.now!() : Date.now ? Date.now : () => +(new Date()); +} \ No newline at end of file diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 1924be4df89..ed14804e19c 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -149,2074 +149,4 @@ namespace ts { return combinePaths(project, "tsconfig.json") as ResolvedConfigFileName; } -} - -namespace ts { - const minimumDate = new Date(-8640000000000000); - const maximumDate = new Date(8640000000000000); - - export interface BuildOptions { - dry?: boolean; - force?: boolean; - verbose?: boolean; - - /*@internal*/ clean?: boolean; - /*@internal*/ watch?: boolean; - /*@internal*/ help?: boolean; - - /*@internal*/ preserveWatchOutput?: boolean; - /*@internal*/ listEmittedFiles?: boolean; - /*@internal*/ listFiles?: boolean; - /*@internal*/ pretty?: boolean; - incremental?: boolean; - - traceResolution?: boolean; - /* @internal */ diagnostics?: boolean; - /* @internal */ extendedDiagnostics?: boolean; - /* @internal */ locale?: string; - /* @internal */ generateCpuProfile?: string; - - [option: string]: CompilerOptionsValue | undefined; - } - - enum BuildResultFlags { - None = 0, - - /** - * No errors of any kind occurred during build - */ - Success = 1 << 0, - /** - * None of the .d.ts files emitted by this build were - * different from the existing files on disk - */ - DeclarationOutputUnchanged = 1 << 1, - - ConfigFileErrors = 1 << 2, - SyntaxErrors = 1 << 3, - TypeErrors = 1 << 4, - DeclarationEmitErrors = 1 << 5, - EmitErrors = 1 << 6, - - AnyErrors = ConfigFileErrors | SyntaxErrors | TypeErrors | DeclarationEmitErrors | EmitErrors - } - - /*@internal*/ - export type ResolvedConfigFilePath = ResolvedConfigFileName & Path; - interface FileMap extends Map { - get(key: U): T | undefined; - has(key: U): boolean; - forEach(action: (value: T, key: U) => void): void; - readonly size: number; - keys(): Iterator; - values(): Iterator; - entries(): Iterator<[U, T]>; - set(key: U, value: T): this; - delete(key: U): boolean; - clear(): void; - } - type ConfigFileMap = FileMap; - function createConfigFileMap(): ConfigFileMap { - return createMap() as ConfigFileMap; - } - - function getOrCreateValueFromConfigFileMap(configFileMap: ConfigFileMap, resolved: ResolvedConfigFilePath, createT: () => T): T { - const existingValue = configFileMap.get(resolved); - let newValue: T | undefined; - if (!existingValue) { - newValue = createT(); - configFileMap.set(resolved, newValue); - } - return existingValue || newValue!; - } - - function getOrCreateValueMapFromConfigFileMap(configFileMap: ConfigFileMap>, resolved: ResolvedConfigFilePath): Map { - return getOrCreateValueFromConfigFileMap>(configFileMap, resolved, createMap); - } - - function newer(date1: Date, date2: Date): Date { - return date2 > date1 ? date2 : date1; - } - - function isDeclarationFile(fileName: string) { - return fileExtensionIs(fileName, Extension.Dts); - } - - export type ReportEmitErrorSummary = (errorCount: number) => void; - - export interface SolutionBuilderHostBase extends ProgramHost { - createDirectory?(path: string): void; - /** - * Should provide create directory and writeFile if done of invalidatedProjects is not invoked with - * writeFileCallback - */ - writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void; - - getModifiedTime(fileName: string): Date | undefined; - setModifiedTime(fileName: string, date: Date): void; - deleteFile(fileName: string): void; - getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined; - - reportDiagnostic: DiagnosticReporter; // Technically we want to move it out and allow steps of actions on Solution, but for now just merge stuff in build host here - reportSolutionBuilderStatus: DiagnosticReporter; - - // TODO: To do better with watch mode and normal build mode api that creates program and emits files - // This currently helps enable --diagnostics and --extendedDiagnostics - afterProgramEmitAndDiagnostics?(program: T): void; - - // For testing - /*@internal*/ now?(): Date; - } - - export interface SolutionBuilderHost extends SolutionBuilderHostBase { - reportErrorSummary?: ReportEmitErrorSummary; - } - - export interface SolutionBuilderWithWatchHost extends SolutionBuilderHostBase, WatchHost { - } - - /*@internal*/ - export type BuildOrder = readonly ResolvedConfigFileName[]; - /*@internal*/ - export interface CircularBuildOrder { - buildOrder: BuildOrder; - circularDiagnostics: readonly Diagnostic[]; - } - /*@internal*/ - export type AnyBuildOrder = BuildOrder | CircularBuildOrder; - - /*@internal*/ - export function isCircularBuildOrder(buildOrder: AnyBuildOrder): buildOrder is CircularBuildOrder { - return !!buildOrder && !!(buildOrder as CircularBuildOrder).buildOrder; - } - - /*@internal*/ - export function getBuildOrderFromAnyBuildOrder(anyBuildOrder: AnyBuildOrder): BuildOrder { - return isCircularBuildOrder(anyBuildOrder) ? anyBuildOrder.buildOrder : anyBuildOrder; - } - - export interface SolutionBuilder { - build(project?: string, cancellationToken?: CancellationToken): ExitStatus; - clean(project?: string): ExitStatus; - buildReferences(project: string, cancellationToken?: CancellationToken): ExitStatus; - cleanReferences(project?: string): ExitStatus; - getNextInvalidatedProject(cancellationToken?: CancellationToken): InvalidatedProject | undefined; - - // Currently used for testing but can be made public if needed: - /*@internal*/ getBuildOrder(): AnyBuildOrder; - - // Testing only - /*@internal*/ getUpToDateStatusOfProject(project: string): UpToDateStatus; - /*@internal*/ invalidateProject(configFilePath: ResolvedConfigFilePath, reloadLevel?: ConfigFileProgramReloadLevel): void; - /*@internal*/ buildNextInvalidatedProject(): void; - /*@internal*/ getAllParsedConfigs(): readonly ParsedCommandLine[]; - } - - /** - * Create a function that reports watch status by writing to the system and handles the formating of the diagnostic - */ - export function createBuilderStatusReporter(system: System, pretty?: boolean): DiagnosticReporter { - return diagnostic => { - let output = pretty ? `[${formatColorAndReset(getLocaleTimeString(system), ForegroundColorEscapeSequences.Grey)}] ` : `${getLocaleTimeString(system)} - `; - output += `${flattenDiagnosticMessageText(diagnostic.messageText, system.newLine)}${system.newLine + system.newLine}`; - system.write(output); - }; - } - - function createSolutionBuilderHostBase(system: System, createProgram: CreateProgram | undefined, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter) { - const host = createProgramHost(system, createProgram) as SolutionBuilderHostBase; - host.getModifiedTime = system.getModifiedTime ? path => system.getModifiedTime!(path) : returnUndefined; - host.setModifiedTime = system.setModifiedTime ? (path, date) => system.setModifiedTime!(path, date) : noop; - host.deleteFile = system.deleteFile ? path => system.deleteFile!(path) : noop; - host.reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); - host.reportSolutionBuilderStatus = reportSolutionBuilderStatus || createBuilderStatusReporter(system); - return host; - } - - export function createSolutionBuilderHost(system = sys, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary) { - const host = createSolutionBuilderHostBase(system, createProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderHost; - host.reportErrorSummary = reportErrorSummary; - return host; - } - - export function createSolutionBuilderWithWatchHost(system = sys, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter) { - const host = createSolutionBuilderHostBase(system, createProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderWithWatchHost; - const watchHost = createWatchHost(system, reportWatchStatus); - copyProperties(host, watchHost); - return host; - } - - function getCompilerOptionsOfBuildOptions(buildOptions: BuildOptions): CompilerOptions { - const result = {} as CompilerOptions; - commonOptionsWithBuild.forEach(option => { - if (hasProperty(buildOptions, option.name)) result[option.name] = buildOptions[option.name]; - }); - return result; - } - - export function createSolutionBuilder(host: SolutionBuilderHost, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder { - return createSolutionBuilderWorker(/*watch*/ false, host, rootNames, defaultOptions); - } - - export function createSolutionBuilderWithWatch(host: SolutionBuilderWithWatchHost, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder { - return createSolutionBuilderWorker(/*watch*/ true, host, rootNames, defaultOptions); - } - - type ConfigFileCacheEntry = ParsedCommandLine | Diagnostic; - interface SolutionBuilderStateCache { - originalReadFile: CompilerHost["readFile"]; - originalFileExists: CompilerHost["fileExists"]; - originalDirectoryExists: CompilerHost["directoryExists"]; - originalCreateDirectory: CompilerHost["createDirectory"]; - originalWriteFile: CompilerHost["writeFile"] | undefined; - originalReadFileWithCache: CompilerHost["readFile"]; - originalGetSourceFile: CompilerHost["getSourceFile"]; - } - - interface SolutionBuilderState { - readonly host: SolutionBuilderHost; - readonly hostWithWatch: SolutionBuilderWithWatchHost; - readonly currentDirectory: string; - readonly getCanonicalFileName: GetCanonicalFileName; - readonly parseConfigFileHost: ParseConfigFileHost; - readonly writeFileName: ((s: string) => void) | undefined; - - // State of solution - readonly options: BuildOptions; - readonly baseCompilerOptions: CompilerOptions; - readonly rootNames: readonly string[]; - - readonly resolvedConfigFilePaths: Map; - readonly configFileCache: ConfigFileMap; - /** Map from config file name to up-to-date status */ - readonly projectStatus: ConfigFileMap; - readonly buildInfoChecked: ConfigFileMap; - readonly extendedConfigCache: Map; - - readonly builderPrograms: ConfigFileMap; - readonly diagnostics: ConfigFileMap; - readonly projectPendingBuild: ConfigFileMap; - readonly projectErrorsReported: ConfigFileMap; - - readonly compilerHost: CompilerHost; - readonly moduleResolutionCache: ModuleResolutionCache | undefined; - - // Mutable state - buildOrder: AnyBuildOrder | undefined; - readFileWithCache: (f: string) => string | undefined; - projectCompilerOptions: CompilerOptions; - cache: SolutionBuilderStateCache | undefined; - allProjectBuildPending: boolean; - needsSummary: boolean; - watchAllProjectsPending: boolean; - currentInvalidatedProject: InvalidatedProject | undefined; - - // Watch state - readonly watch: boolean; - readonly allWatchedWildcardDirectories: ConfigFileMap>; - readonly allWatchedInputFiles: ConfigFileMap>; - readonly allWatchedConfigFiles: ConfigFileMap; - - timerToBuildInvalidatedProject: any; - reportFileChangeDetected: boolean; - watchFile: WatchFile; - watchFilePath: WatchFilePath; - watchDirectory: WatchDirectory; - writeLog: (s: string) => void; - } - - function createSolutionBuilderState(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost | SolutionBuilderWithWatchHost, rootNames: readonly string[], options: BuildOptions): SolutionBuilderState { - const host = hostOrHostWithWatch as SolutionBuilderHost; - const hostWithWatch = hostOrHostWithWatch as SolutionBuilderWithWatchHost; - const currentDirectory = host.getCurrentDirectory(); - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); - - // State of the solution - const baseCompilerOptions = getCompilerOptionsOfBuildOptions(options); - const compilerHost = createCompilerHostFromProgramHost(host, () => state.projectCompilerOptions); - setGetSourceFileAsHashVersioned(compilerHost, host); - compilerHost.getParsedCommandLine = fileName => parseConfigFile(state, fileName as ResolvedConfigFileName, toResolvedConfigFilePath(state, fileName as ResolvedConfigFileName)); - compilerHost.resolveModuleNames = maybeBind(host, host.resolveModuleNames); - compilerHost.resolveTypeReferenceDirectives = maybeBind(host, host.resolveTypeReferenceDirectives); - const moduleResolutionCache = !compilerHost.resolveModuleNames ? createModuleResolutionCache(currentDirectory, getCanonicalFileName) : undefined; - if (!compilerHost.resolveModuleNames) { - const loader = (moduleName: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFile, state.projectCompilerOptions, compilerHost, moduleResolutionCache, redirectedReference).resolvedModule!; - compilerHost.resolveModuleNames = (moduleNames, containingFile, _reusedNames, redirectedReference) => - loadWithLocalCache(Debug.assertEachDefined(moduleNames), containingFile, redirectedReference, loader); - } - - const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory(hostWithWatch, options); - - const state: SolutionBuilderState = { - host, - hostWithWatch, - currentDirectory, - getCanonicalFileName, - parseConfigFileHost: parseConfigHostFromCompilerHostLike(host), - writeFileName: host.trace ? (s: string) => host.trace!(s) : undefined, - - // State of solution - options, - baseCompilerOptions, - rootNames, - - resolvedConfigFilePaths: createMap(), - configFileCache: createConfigFileMap(), - projectStatus: createConfigFileMap(), - buildInfoChecked: createConfigFileMap(), - extendedConfigCache: createMap(), - - builderPrograms: createConfigFileMap(), - diagnostics: createConfigFileMap(), - projectPendingBuild: createConfigFileMap(), - projectErrorsReported: createConfigFileMap(), - - compilerHost, - moduleResolutionCache, - - // Mutable state - buildOrder: undefined, - readFileWithCache: f => host.readFile(f), - projectCompilerOptions: baseCompilerOptions, - cache: undefined, - allProjectBuildPending: true, - needsSummary: true, - watchAllProjectsPending: watch, - currentInvalidatedProject: undefined, - - // Watch state - watch, - allWatchedWildcardDirectories: createConfigFileMap(), - allWatchedInputFiles: createConfigFileMap(), - allWatchedConfigFiles: createConfigFileMap(), - - timerToBuildInvalidatedProject: undefined, - reportFileChangeDetected: false, - watchFile, - watchFilePath, - watchDirectory, - writeLog, - }; - - return state; - } - - function toPath(state: SolutionBuilderState, fileName: string) { - return ts.toPath(fileName, state.currentDirectory, state.getCanonicalFileName); - } - - function toResolvedConfigFilePath(state: SolutionBuilderState, fileName: ResolvedConfigFileName): ResolvedConfigFilePath { - const { resolvedConfigFilePaths } = state; - const path = resolvedConfigFilePaths.get(fileName); - if (path !== undefined) return path; - - const resolvedPath = toPath(state, fileName) as ResolvedConfigFilePath; - resolvedConfigFilePaths.set(fileName, resolvedPath); - return resolvedPath; - } - - function isParsedCommandLine(entry: ConfigFileCacheEntry): entry is ParsedCommandLine { - return !!(entry as ParsedCommandLine).options; - } - - function parseConfigFile(state: SolutionBuilderState, configFileName: ResolvedConfigFileName, configFilePath: ResolvedConfigFilePath): ParsedCommandLine | undefined { - const { configFileCache } = state; - const value = configFileCache.get(configFilePath); - if (value) { - return isParsedCommandLine(value) ? value : undefined; - } - - let diagnostic: Diagnostic | undefined; - const { parseConfigFileHost, baseCompilerOptions, extendedConfigCache, host } = state; - let parsed: ParsedCommandLine | undefined; - if (host.getParsedCommandLine) { - parsed = host.getParsedCommandLine(configFileName); - if (!parsed) diagnostic = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName); - } - else { - parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = d => diagnostic = d; - parsed = getParsedCommandLineOfConfigFile(configFileName, baseCompilerOptions, parseConfigFileHost, extendedConfigCache); - parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = noop; - } - configFileCache.set(configFilePath, parsed || diagnostic!); - return parsed; - } - - function resolveProjectName(state: SolutionBuilderState, name: string): ResolvedConfigFileName { - return resolveConfigFileProjectName(resolvePath(state.currentDirectory, name)); - } - - function createBuildOrder(state: SolutionBuilderState, roots: readonly ResolvedConfigFileName[]): AnyBuildOrder { - const temporaryMarks = createMap() as ConfigFileMap; - const permanentMarks = createMap() as ConfigFileMap; - const circularityReportStack: string[] = []; - let buildOrder: ResolvedConfigFileName[] | undefined; - let circularDiagnostics: Diagnostic[] | undefined; - for (const root of roots) { - visit(root); - } - - return circularDiagnostics ? - { buildOrder: buildOrder || emptyArray, circularDiagnostics } : - buildOrder || emptyArray; - - function visit(configFileName: ResolvedConfigFileName, inCircularContext?: boolean) { - const projPath = toResolvedConfigFilePath(state, configFileName); - // Already visited - if (permanentMarks.has(projPath)) return; - // Circular - if (temporaryMarks.has(projPath)) { - if (!inCircularContext) { - (circularDiagnostics || (circularDiagnostics = [])).push( - createCompilerDiagnostic( - Diagnostics.Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0, - circularityReportStack.join("\r\n") - ) - ); - } - return; - } - - temporaryMarks.set(projPath, true); - circularityReportStack.push(configFileName); - const parsed = parseConfigFile(state, configFileName, projPath); - if (parsed && parsed.projectReferences) { - for (const ref of parsed.projectReferences) { - const resolvedRefPath = resolveProjectName(state, ref.path); - visit(resolvedRefPath, inCircularContext || ref.circular); - } - } - - circularityReportStack.pop(); - permanentMarks.set(projPath, true); - (buildOrder || (buildOrder = [])).push(configFileName); - } - } - - function getBuildOrder(state: SolutionBuilderState) { - return state.buildOrder || createStateBuildOrder(state); - } - - function createStateBuildOrder(state: SolutionBuilderState) { - const buildOrder = createBuildOrder(state, state.rootNames.map(f => resolveProjectName(state, f))); - - // Clear all to ResolvedConfigFilePaths cache to start fresh - state.resolvedConfigFilePaths.clear(); - const currentProjects = arrayToSet( - getBuildOrderFromAnyBuildOrder(buildOrder), - resolved => toResolvedConfigFilePath(state, resolved) - ) as ConfigFileMap; - - const noopOnDelete = { onDeleteValue: noop }; - // Config file cache - mutateMapSkippingNewValues(state.configFileCache, currentProjects, noopOnDelete); - mutateMapSkippingNewValues(state.projectStatus, currentProjects, noopOnDelete); - mutateMapSkippingNewValues(state.buildInfoChecked, currentProjects, noopOnDelete); - mutateMapSkippingNewValues(state.builderPrograms, currentProjects, noopOnDelete); - mutateMapSkippingNewValues(state.diagnostics, currentProjects, noopOnDelete); - mutateMapSkippingNewValues(state.projectPendingBuild, currentProjects, noopOnDelete); - mutateMapSkippingNewValues(state.projectErrorsReported, currentProjects, noopOnDelete); - - // Remove watches for the program no longer in the solution - if (state.watch) { - mutateMapSkippingNewValues( - state.allWatchedConfigFiles, - currentProjects, - { onDeleteValue: closeFileWatcher } - ); - - mutateMapSkippingNewValues( - state.allWatchedWildcardDirectories, - currentProjects, - { onDeleteValue: existingMap => existingMap.forEach(closeFileWatcherOf) } - ); - - mutateMapSkippingNewValues( - state.allWatchedInputFiles, - currentProjects, - { onDeleteValue: existingMap => existingMap.forEach(closeFileWatcher) } - ); - } - return state.buildOrder = buildOrder; - } - - function getBuildOrderFor(state: SolutionBuilderState, project: string | undefined, onlyReferences: boolean | undefined): AnyBuildOrder | undefined { - const resolvedProject = project && resolveProjectName(state, project); - const buildOrderFromState = getBuildOrder(state); - if (isCircularBuildOrder(buildOrderFromState)) return buildOrderFromState; - if (resolvedProject) { - const projectPath = toResolvedConfigFilePath(state, resolvedProject); - const projectIndex = findIndex( - buildOrderFromState, - configFileName => toResolvedConfigFilePath(state, configFileName) === projectPath - ); - if (projectIndex === -1) return undefined; - } - const buildOrder = resolvedProject ? createBuildOrder(state, [resolvedProject]) as BuildOrder : buildOrderFromState; - Debug.assert(!isCircularBuildOrder(buildOrder)); - Debug.assert(!onlyReferences || resolvedProject !== undefined); - Debug.assert(!onlyReferences || buildOrder[buildOrder.length - 1] === resolvedProject); - return onlyReferences ? buildOrder.slice(0, buildOrder.length - 1) : buildOrder; - } - - function enableCache(state: SolutionBuilderState) { - if (state.cache) { - disableCache(state); - } - - const { compilerHost, host } = state; - - const originalReadFileWithCache = state.readFileWithCache; - const originalGetSourceFile = compilerHost.getSourceFile; - - const { - originalReadFile, originalFileExists, originalDirectoryExists, - originalCreateDirectory, originalWriteFile, - getSourceFileWithCache, readFileWithCache - } = changeCompilerHostLikeToUseCache( - host, - fileName => toPath(state, fileName), - (...args) => originalGetSourceFile.call(compilerHost, ...args) - ); - state.readFileWithCache = readFileWithCache; - compilerHost.getSourceFile = getSourceFileWithCache!; - - state.cache = { - originalReadFile, - originalFileExists, - originalDirectoryExists, - originalCreateDirectory, - originalWriteFile, - originalReadFileWithCache, - originalGetSourceFile, - }; - } - - function disableCache(state: SolutionBuilderState) { - if (!state.cache) return; - - const { cache, host, compilerHost, extendedConfigCache, moduleResolutionCache } = state; - - host.readFile = cache.originalReadFile; - host.fileExists = cache.originalFileExists; - host.directoryExists = cache.originalDirectoryExists; - host.createDirectory = cache.originalCreateDirectory; - host.writeFile = cache.originalWriteFile; - compilerHost.getSourceFile = cache.originalGetSourceFile; - state.readFileWithCache = cache.originalReadFileWithCache; - extendedConfigCache.clear(); - if (moduleResolutionCache) { - moduleResolutionCache.directoryToModuleNameMap.clear(); - moduleResolutionCache.moduleNameToDirectoryMap.clear(); - } - state.cache = undefined; - } - - function clearProjectStatus(state: SolutionBuilderState, resolved: ResolvedConfigFilePath) { - state.projectStatus.delete(resolved); - state.diagnostics.delete(resolved); - } - - function addProjToQueue({ projectPendingBuild }: SolutionBuilderState, proj: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { - const value = projectPendingBuild.get(proj); - if (value === undefined) { - projectPendingBuild.set(proj, reloadLevel); - } - else if (value < reloadLevel) { - projectPendingBuild.set(proj, reloadLevel); - } - } - - function setupInitialBuild(state: SolutionBuilderState, cancellationToken: CancellationToken | undefined) { - // Set initial build if not already built - if (!state.allProjectBuildPending) return; - state.allProjectBuildPending = false; - if (state.options.watch) { reportWatchStatus(state, Diagnostics.Starting_compilation_in_watch_mode); } - enableCache(state); - const buildOrder = getBuildOrderFromAnyBuildOrder(getBuildOrder(state)); - buildOrder.forEach(configFileName => - state.projectPendingBuild.set( - toResolvedConfigFilePath(state, configFileName), - ConfigFileProgramReloadLevel.None - ) - ); - - if (cancellationToken) { - cancellationToken.throwIfCancellationRequested(); - } - } - - export enum InvalidatedProjectKind { - Build, - UpdateBundle, - UpdateOutputFileStamps - } - - export interface InvalidatedProjectBase { - readonly kind: InvalidatedProjectKind; - readonly project: ResolvedConfigFileName; - /*@internal*/ readonly projectPath: ResolvedConfigFilePath; - /*@internal*/ readonly buildOrder: readonly ResolvedConfigFileName[]; - /** - * To dispose this project and ensure that all the necessary actions are taken and state is updated accordingly - */ - done(cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers): ExitStatus; - getCompilerOptions(): CompilerOptions; - getCurrentDirectory(): string; - } - - export interface UpdateOutputFileStampsProject extends InvalidatedProjectBase { - readonly kind: InvalidatedProjectKind.UpdateOutputFileStamps; - updateOutputFileStatmps(): void; - } - - export interface BuildInvalidedProject extends InvalidatedProjectBase { - readonly kind: InvalidatedProjectKind.Build; - /* - * Emitting with this builder program without the api provided for this project - * can result in build system going into invalid state as files written reflect the state of the project - */ - getBuilderProgram(): T | undefined; - getProgram(): Program | undefined; - getSourceFile(fileName: string): SourceFile | undefined; - getSourceFiles(): readonly SourceFile[]; - getOptionsDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; - getGlobalDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; - getConfigFileParsingDiagnostics(): readonly Diagnostic[]; - getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; - getAllDependencies(sourceFile: SourceFile): readonly string[]; - getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; - getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult; - /* - * Calling emit directly with targetSourceFile and emitOnlyDtsFiles set to true is not advised since - * emit in build system is responsible in updating status of the project - * If called with targetSourceFile and emitOnlyDtsFiles set to true, the emit just passes to underlying builder and - * wont reflect the status of file as being emitted in the builder - * (if that emit of that source file is required it would be emitted again when making sure invalidated project is completed) - * This emit is not considered actual emit (and hence uptodate status is not reflected if - */ - emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult | undefined; - // TODO(shkamat):: investigate later if we can emit even when there are declaration diagnostics - // emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult; - } - - export interface UpdateBundleProject extends InvalidatedProjectBase { - readonly kind: InvalidatedProjectKind.UpdateBundle; - emit(writeFile?: WriteFileCallback, customTransformers?: CustomTransformers): EmitResult | BuildInvalidedProject | undefined; - } - - export type InvalidatedProject = UpdateOutputFileStampsProject | BuildInvalidedProject | UpdateBundleProject; - - function doneInvalidatedProject( - state: SolutionBuilderState, - projectPath: ResolvedConfigFilePath - ) { - state.projectPendingBuild.delete(projectPath); - state.currentInvalidatedProject = undefined; - return state.diagnostics.has(projectPath) ? - ExitStatus.DiagnosticsPresent_OutputsSkipped : - ExitStatus.Success; - } - - function createUpdateOutputFileStampsProject( - state: SolutionBuilderState, - project: ResolvedConfigFileName, - projectPath: ResolvedConfigFilePath, - config: ParsedCommandLine, - buildOrder: readonly ResolvedConfigFileName[] - ): UpdateOutputFileStampsProject { - let updateOutputFileStampsPending = true; - return { - kind: InvalidatedProjectKind.UpdateOutputFileStamps, - project, - projectPath, - buildOrder, - getCompilerOptions: () => config.options, - getCurrentDirectory: () => state.currentDirectory, - updateOutputFileStatmps: () => { - updateOutputTimestamps(state, config, projectPath); - updateOutputFileStampsPending = false; - }, - done: () => { - if (updateOutputFileStampsPending) { - updateOutputTimestamps(state, config, projectPath); - } - return doneInvalidatedProject(state, projectPath); - } - }; - } - - function createBuildOrUpdateInvalidedProject( - kind: InvalidatedProjectKind.Build | InvalidatedProjectKind.UpdateBundle, - state: SolutionBuilderState, - project: ResolvedConfigFileName, - projectPath: ResolvedConfigFilePath, - projectIndex: number, - config: ParsedCommandLine, - buildOrder: readonly ResolvedConfigFileName[], - ): BuildInvalidedProject | UpdateBundleProject { - enum Step { - CreateProgram, - SyntaxDiagnostics, - SemanticDiagnostics, - Emit, - EmitBundle, - BuildInvalidatedProjectOfBundle, - QueueReferencingProjects, - Done - } - - let step = kind === InvalidatedProjectKind.Build ? Step.CreateProgram : Step.EmitBundle; - let program: T | undefined; - let buildResult: BuildResultFlags | undefined; - let invalidatedProjectOfBundle: BuildInvalidedProject | undefined; - - return kind === InvalidatedProjectKind.Build ? - { - kind, - project, - projectPath, - buildOrder, - getCompilerOptions: () => config.options, - getCurrentDirectory: () => state.currentDirectory, - getBuilderProgram: () => withProgramOrUndefined(identity), - getProgram: () => - withProgramOrUndefined( - program => program.getProgramOrUndefined() - ), - getSourceFile: fileName => - withProgramOrUndefined( - program => program.getSourceFile(fileName) - ), - getSourceFiles: () => - withProgramOrEmptyArray( - program => program.getSourceFiles() - ), - getOptionsDiagnostics: cancellationToken => - withProgramOrEmptyArray( - program => program.getOptionsDiagnostics(cancellationToken) - ), - getGlobalDiagnostics: cancellationToken => - withProgramOrEmptyArray( - program => program.getGlobalDiagnostics(cancellationToken) - ), - getConfigFileParsingDiagnostics: () => - withProgramOrEmptyArray( - program => program.getConfigFileParsingDiagnostics() - ), - getSyntacticDiagnostics: (sourceFile, cancellationToken) => - withProgramOrEmptyArray( - program => program.getSyntacticDiagnostics(sourceFile, cancellationToken) - ), - getAllDependencies: sourceFile => - withProgramOrEmptyArray( - program => program.getAllDependencies(sourceFile) - ), - getSemanticDiagnostics: (sourceFile, cancellationToken) => - withProgramOrEmptyArray( - program => program.getSemanticDiagnostics(sourceFile, cancellationToken) - ), - getSemanticDiagnosticsOfNextAffectedFile: (cancellationToken, ignoreSourceFile) => - withProgramOrUndefined( - program => - ((program as any as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile) && - (program as any as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile(cancellationToken, ignoreSourceFile) - ), - emit: (targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers) => { - if (targetSourceFile || emitOnlyDtsFiles) { - return withProgramOrUndefined( - program => program.emit(targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers) - ); - } - executeSteps(Step.SemanticDiagnostics, cancellationToken); - if (step !== Step.Emit) return undefined; - return emit(writeFile, cancellationToken, customTransformers); - }, - done - } : - { - kind, - project, - projectPath, - buildOrder, - getCompilerOptions: () => config.options, - getCurrentDirectory: () => state.currentDirectory, - emit: (writeFile: WriteFileCallback | undefined, customTransformers: CustomTransformers | undefined) => { - if (step !== Step.EmitBundle) return invalidatedProjectOfBundle; - return emitBundle(writeFile, customTransformers); - }, - done, - }; - - function done(cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers) { - executeSteps(Step.Done, cancellationToken, writeFile, customTransformers); - return doneInvalidatedProject(state, projectPath); - } - - function withProgramOrUndefined(action: (program: T) => U | undefined): U | undefined { - executeSteps(Step.CreateProgram); - return program && action(program); - } - - function withProgramOrEmptyArray(action: (program: T) => readonly U[]): readonly U[] { - return withProgramOrUndefined(action) || emptyArray; - } - - function createProgram() { - Debug.assert(program === undefined); - - if (state.options.dry) { - reportStatus(state, Diagnostics.A_non_dry_build_would_build_project_0, project); - buildResult = BuildResultFlags.Success; - step = Step.QueueReferencingProjects; - return; - } - - if (state.options.verbose) reportStatus(state, Diagnostics.Building_project_0, project); - - if (config.fileNames.length === 0) { - reportAndStoreErrors(state, projectPath, config.errors); - // Nothing to build - must be a solution file, basically - buildResult = BuildResultFlags.None; - step = Step.QueueReferencingProjects; - return; - } - - const { host, compilerHost } = state; - state.projectCompilerOptions = config.options; - // Update module resolution cache if needed - updateModuleResolutionCache(state, project, config); - - // Create program - program = host.createProgram( - config.fileNames, - config.options, - compilerHost, - getOldProgram(state, projectPath, config), - config.errors, - config.projectReferences - ); - step++; - } - - function handleDiagnostics(diagnostics: readonly Diagnostic[], errorFlags: BuildResultFlags, errorType: string) { - if (diagnostics.length) { - buildResult = buildErrors( - state, - projectPath, - program, - diagnostics, - errorFlags, - errorType - ); - step = Step.QueueReferencingProjects; - } - else { - step++; - } - } - - function getSyntaxDiagnostics(cancellationToken?: CancellationToken) { - Debug.assertDefined(program); - handleDiagnostics( - [ - ...program!.getConfigFileParsingDiagnostics(), - ...program!.getOptionsDiagnostics(cancellationToken), - ...program!.getGlobalDiagnostics(cancellationToken), - ...program!.getSyntacticDiagnostics(/*sourceFile*/ undefined, cancellationToken) - ], - BuildResultFlags.SyntaxErrors, - "Syntactic" - ); - } - - function getSemanticDiagnostics(cancellationToken?: CancellationToken) { - handleDiagnostics( - Debug.assertDefined(program).getSemanticDiagnostics(/*sourceFile*/ undefined, cancellationToken), - BuildResultFlags.TypeErrors, - "Semantic" - ); - } - - function emit(writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitResult { - Debug.assertDefined(program); - Debug.assert(step === Step.Emit); - // Before emitting lets backup state, so we can revert it back if there are declaration errors to handle emit and declaration errors correctly - program!.backupState(); - let declDiagnostics: Diagnostic[] | undefined; - const reportDeclarationDiagnostics = (d: Diagnostic) => (declDiagnostics || (declDiagnostics = [])).push(d); - const outputFiles: OutputFile[] = []; - const { emitResult } = emitFilesAndReportErrors( - program!, - reportDeclarationDiagnostics, - /*writeFileName*/ undefined, - /*reportSummary*/ undefined, - (name, text, writeByteOrderMark) => outputFiles.push({ name, text, writeByteOrderMark }), - cancellationToken, - /*emitOnlyDts*/ false, - customTransformers - ); - // Don't emit .d.ts if there are decl file errors - if (declDiagnostics) { - program!.restoreState(); - buildResult = buildErrors( - state, - projectPath, - program, - declDiagnostics, - BuildResultFlags.DeclarationEmitErrors, - "Declaration file" - ); - step = Step.QueueReferencingProjects; - return { - emitSkipped: true, - diagnostics: emitResult.diagnostics - }; - } - - // Actual Emit - const { host, compilerHost } = state; - let resultFlags = BuildResultFlags.DeclarationOutputUnchanged; - let newestDeclarationFileContentChangedTime = minimumDate; - let anyDtsChanged = false; - const emitterDiagnostics = createDiagnosticCollection(); - const emittedOutputs = createMap() as FileMap; - outputFiles.forEach(({ name, text, writeByteOrderMark }) => { - let priorChangeTime: Date | undefined; - if (!anyDtsChanged && isDeclarationFile(name)) { - // Check for unchanged .d.ts files - if (host.fileExists(name) && state.readFileWithCache(name) === text) { - priorChangeTime = host.getModifiedTime(name); - } - else { - resultFlags &= ~BuildResultFlags.DeclarationOutputUnchanged; - anyDtsChanged = true; - } - } - - emittedOutputs.set(toPath(state, name), name); - writeFile(writeFileCallback ? { writeFile: writeFileCallback } : compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); - if (priorChangeTime !== undefined) { - newestDeclarationFileContentChangedTime = newer(priorChangeTime, newestDeclarationFileContentChangedTime); - } - }); - - finishEmit( - emitterDiagnostics, - emittedOutputs, - newestDeclarationFileContentChangedTime, - /*newestDeclarationFileContentChangedTimeIsMaximumDate*/ anyDtsChanged, - outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(config, !host.useCaseSensitiveFileNames()), - resultFlags - ); - return emitResult; - } - - function finishEmit( - emitterDiagnostics: DiagnosticCollection, - emittedOutputs: FileMap, - priorNewestUpdateTime: Date, - newestDeclarationFileContentChangedTimeIsMaximumDate: boolean, - oldestOutputFileName: string, - resultFlags: BuildResultFlags - ) { - const emitDiagnostics = emitterDiagnostics.getDiagnostics(); - if (emitDiagnostics.length) { - buildResult = buildErrors( - state, - projectPath, - program, - emitDiagnostics, - BuildResultFlags.EmitErrors, - "Emit" - ); - step = Step.QueueReferencingProjects; - return emitDiagnostics; - } - - if (state.writeFileName) { - emittedOutputs.forEach(name => listEmittedFile(state, config, name)); - if (program) listFiles(program, state.writeFileName); - } - - // Update time stamps for rest of the outputs - const newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(state, config, priorNewestUpdateTime, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs); - state.diagnostics.delete(projectPath); - state.projectStatus.set(projectPath, { - type: UpToDateStatusType.UpToDate, - newestDeclarationFileContentChangedTime: newestDeclarationFileContentChangedTimeIsMaximumDate ? - maximumDate : - newestDeclarationFileContentChangedTime, - oldestOutputFileName - }); - if (program) afterProgramCreate(state, projectPath, program); - state.projectCompilerOptions = state.baseCompilerOptions; - step = Step.QueueReferencingProjects; - buildResult = resultFlags; - return emitDiagnostics; - } - - function emitBundle(writeFileCallback?: WriteFileCallback, customTransformers?: CustomTransformers): EmitResult | BuildInvalidedProject | undefined { - Debug.assert(kind === InvalidatedProjectKind.UpdateBundle); - if (state.options.dry) { - reportStatus(state, Diagnostics.A_non_dry_build_would_update_output_of_project_0, project); - buildResult = BuildResultFlags.Success; - step = Step.QueueReferencingProjects; - return undefined; - } - - if (state.options.verbose) reportStatus(state, Diagnostics.Updating_output_of_project_0, project); - - // Update js, and source map - const { compilerHost } = state; - state.projectCompilerOptions = config.options; - const outputFiles = emitUsingBuildInfo( - config, - compilerHost, - ref => { - const refName = resolveProjectName(state, ref.path); - return parseConfigFile(state, refName, toResolvedConfigFilePath(state, refName)); - }, - customTransformers - ); - - if (isString(outputFiles)) { - reportStatus(state, Diagnostics.Cannot_update_output_of_project_0_because_there_was_error_reading_file_1, project, relName(state, outputFiles)); - step = Step.BuildInvalidatedProjectOfBundle; - return invalidatedProjectOfBundle = createBuildOrUpdateInvalidedProject( - InvalidatedProjectKind.Build, - state, - project, - projectPath, - projectIndex, - config, - buildOrder - ) as BuildInvalidedProject; - } - - // Actual Emit - Debug.assert(!!outputFiles.length); - const emitterDiagnostics = createDiagnosticCollection(); - const emittedOutputs = createMap() as FileMap; - outputFiles.forEach(({ name, text, writeByteOrderMark }) => { - emittedOutputs.set(toPath(state, name), name); - writeFile(writeFileCallback ? { writeFile: writeFileCallback } : compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); - }); - - const emitDiagnostics = finishEmit( - emitterDiagnostics, - emittedOutputs, - minimumDate, - /*newestDeclarationFileContentChangedTimeIsMaximumDate*/ false, - outputFiles[0].name, - BuildResultFlags.DeclarationOutputUnchanged - ); - return { emitSkipped: false, diagnostics: emitDiagnostics }; - } - - function executeSteps(till: Step, cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers) { - while (step <= till && step < Step.Done) { - const currentStep = step; - switch (step) { - case Step.CreateProgram: - createProgram(); - break; - - case Step.SyntaxDiagnostics: - getSyntaxDiagnostics(cancellationToken); - break; - - case Step.SemanticDiagnostics: - getSemanticDiagnostics(cancellationToken); - break; - - case Step.Emit: - emit(writeFile, cancellationToken, customTransformers); - break; - - case Step.EmitBundle: - emitBundle(writeFile, customTransformers); - break; - - case Step.BuildInvalidatedProjectOfBundle: - Debug.assertDefined(invalidatedProjectOfBundle).done(cancellationToken); - step = Step.Done; - break; - - case Step.QueueReferencingProjects: - queueReferencingProjects(state, project, projectPath, projectIndex, config, buildOrder, Debug.assertDefined(buildResult)); - step++; - break; - - // Should never be done - case Step.Done: - default: - assertType(step); - - } - Debug.assert(step > currentStep); - } - } - } - - function needsBuild({ options }: SolutionBuilderState, status: UpToDateStatus, config: ParsedCommandLine) { - if (status.type !== UpToDateStatusType.OutOfDateWithPrepend || options.force) return true; - return config.fileNames.length === 0 || - !!config.errors.length || - !isIncrementalCompilation(config.options); - } - - function getNextInvalidatedProject( - state: SolutionBuilderState, - buildOrder: AnyBuildOrder, - reportQueue: boolean - ): InvalidatedProject | undefined { - if (!state.projectPendingBuild.size) return undefined; - if (isCircularBuildOrder(buildOrder)) return undefined; - if (state.currentInvalidatedProject) { - // Only if same buildOrder the currentInvalidated project can be sent again - return arrayIsEqualTo(state.currentInvalidatedProject.buildOrder, buildOrder) ? - state.currentInvalidatedProject : - undefined; - } - - const { options, projectPendingBuild } = state; - for (let projectIndex = 0; projectIndex < buildOrder.length; projectIndex++) { - const project = buildOrder[projectIndex]; - const projectPath = toResolvedConfigFilePath(state, project); - const reloadLevel = state.projectPendingBuild.get(projectPath); - if (reloadLevel === undefined) continue; - - if (reportQueue) { - reportQueue = false; - reportBuildQueue(state, buildOrder); - } - - const config = parseConfigFile(state, project, projectPath); - if (!config) { - reportParseConfigFileDiagnostic(state, projectPath); - projectPendingBuild.delete(projectPath); - continue; - } - - if (reloadLevel === ConfigFileProgramReloadLevel.Full) { - watchConfigFile(state, project, projectPath); - watchWildCardDirectories(state, project, projectPath, config); - watchInputFiles(state, project, projectPath, config); - } - else if (reloadLevel === ConfigFileProgramReloadLevel.Partial) { - // Update file names - const result = getFileNamesFromConfigSpecs(config.configFileSpecs!, getDirectoryPath(project), config.options, state.parseConfigFileHost); - updateErrorForNoInputFiles(result, project, config.configFileSpecs!, config.errors, canJsonReportNoInutFiles(config.raw)); - config.fileNames = result.fileNames; - watchInputFiles(state, project, projectPath, config); - } - - const status = getUpToDateStatus(state, config, projectPath); - verboseReportProjectStatus(state, project, status); - if (!options.force) { - if (status.type === UpToDateStatusType.UpToDate) { - reportAndStoreErrors(state, projectPath, config.errors); - projectPendingBuild.delete(projectPath); - // Up to date, skip - if (options.dry) { - // In a dry build, inform the user of this fact - reportStatus(state, Diagnostics.Project_0_is_up_to_date, project); - } - continue; - } - - if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes) { - reportAndStoreErrors(state, projectPath, config.errors); - return createUpdateOutputFileStampsProject( - state, - project, - projectPath, - config, - buildOrder - ); - } - } - - if (status.type === UpToDateStatusType.UpstreamBlocked) { - reportAndStoreErrors(state, projectPath, config.errors); - projectPendingBuild.delete(projectPath); - if (options.verbose) { - reportStatus( - state, - status.upstreamProjectBlocked ? - Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_was_not_built : - Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, - project, - status.upstreamProjectName - ); - } - continue; - } - - if (status.type === UpToDateStatusType.ContainerOnly) { - reportAndStoreErrors(state, projectPath, config.errors); - projectPendingBuild.delete(projectPath); - // Do nothing - continue; - } - - return createBuildOrUpdateInvalidedProject( - needsBuild(state, status, config) ? - InvalidatedProjectKind.Build : - InvalidatedProjectKind.UpdateBundle, - state, - project, - projectPath, - projectIndex, - config, - buildOrder, - ); - } - - return undefined; - } - - function listEmittedFile({ writeFileName }: SolutionBuilderState, proj: ParsedCommandLine, file: string) { - if (writeFileName && proj.options.listEmittedFiles) { - writeFileName(`TSFILE: ${file}`); - } - } - - function getOldProgram({ options, builderPrograms, compilerHost }: SolutionBuilderState, proj: ResolvedConfigFilePath, parsed: ParsedCommandLine) { - if (options.force) return undefined; - const value = builderPrograms.get(proj); - if (value) return value; - return readBuilderProgram(parsed.options, compilerHost) as any as T; - } - - function afterProgramCreate({ host, watch, builderPrograms }: SolutionBuilderState, proj: ResolvedConfigFilePath, program: T) { - if (host.afterProgramEmitAndDiagnostics) { - host.afterProgramEmitAndDiagnostics(program); - } - if (watch) { - program.releaseProgram(); - builderPrograms.set(proj, program); - } - } - - function buildErrors( - state: SolutionBuilderState, - resolvedPath: ResolvedConfigFilePath, - program: T | undefined, - diagnostics: readonly Diagnostic[], - errorFlags: BuildResultFlags, - errorType: string - ) { - reportAndStoreErrors(state, resolvedPath, diagnostics); - // List files if any other build error using program (emit errors already report files) - if (program && state.writeFileName) listFiles(program, state.writeFileName); - state.projectStatus.set(resolvedPath, { type: UpToDateStatusType.Unbuildable, reason: `${errorType} errors` }); - if (program) afterProgramCreate(state, resolvedPath, program); - state.projectCompilerOptions = state.baseCompilerOptions; - return errorFlags; - } - - function updateModuleResolutionCache( - state: SolutionBuilderState, - proj: ResolvedConfigFileName, - config: ParsedCommandLine - ) { - if (!state.moduleResolutionCache) return; - - // Update module resolution cache if needed - const { moduleResolutionCache } = state; - const projPath = toPath(state, proj); - if (moduleResolutionCache.directoryToModuleNameMap.redirectsMap.size === 0) { - // The own map will be for projectCompilerOptions - Debug.assert(moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.size === 0); - moduleResolutionCache.directoryToModuleNameMap.redirectsMap.set(projPath, moduleResolutionCache.directoryToModuleNameMap.ownMap); - moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.set(projPath, moduleResolutionCache.moduleNameToDirectoryMap.ownMap); - } - else { - // Set correct own map - Debug.assert(moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.size > 0); - - const ref: ResolvedProjectReference = { - sourceFile: config.options.configFile!, - commandLine: config - }; - moduleResolutionCache.directoryToModuleNameMap.setOwnMap(moduleResolutionCache.directoryToModuleNameMap.getOrCreateMapOfCacheRedirects(ref)); - moduleResolutionCache.moduleNameToDirectoryMap.setOwnMap(moduleResolutionCache.moduleNameToDirectoryMap.getOrCreateMapOfCacheRedirects(ref)); - } - moduleResolutionCache.directoryToModuleNameMap.setOwnOptions(config.options); - moduleResolutionCache.moduleNameToDirectoryMap.setOwnOptions(config.options); - } - - function checkConfigFileUpToDateStatus(state: SolutionBuilderState, configFile: string, oldestOutputFileTime: Date, oldestOutputFileName: string): Status.OutOfDateWithSelf | undefined { - // Check tsconfig time - const tsconfigTime = state.host.getModifiedTime(configFile) || missingFileModifiedTime; - if (oldestOutputFileTime < tsconfigTime) { - return { - type: UpToDateStatusType.OutOfDateWithSelf, - outOfDateOutputFileName: oldestOutputFileName, - newerInputFileName: configFile - }; - } - } - - function getUpToDateStatusWorker(state: SolutionBuilderState, project: ParsedCommandLine, resolvedPath: ResolvedConfigFilePath): UpToDateStatus { - let newestInputFileName: string = undefined!; - let newestInputFileTime = minimumDate; - const { host } = state; - // Get timestamps of input files - for (const inputFile of project.fileNames) { - if (!host.fileExists(inputFile)) { - return { - type: UpToDateStatusType.Unbuildable, - reason: `${inputFile} does not exist` - }; - } - - const inputTime = host.getModifiedTime(inputFile) || missingFileModifiedTime; - if (inputTime > newestInputFileTime) { - newestInputFileName = inputFile; - newestInputFileTime = inputTime; - } - } - - // Container if no files are specified in the project - if (!project.fileNames.length && !canJsonReportNoInutFiles(project.raw)) { - return { - type: UpToDateStatusType.ContainerOnly - }; - } - - // Collect the expected outputs of this project - const outputs = getAllProjectOutputs(project, !host.useCaseSensitiveFileNames()); - - // Now see if all outputs are newer than the newest input - let oldestOutputFileName = "(none)"; - let oldestOutputFileTime = maximumDate; - let newestOutputFileName = "(none)"; - let newestOutputFileTime = minimumDate; - let missingOutputFileName: string | undefined; - let newestDeclarationFileContentChangedTime = minimumDate; - let isOutOfDateWithInputs = false; - for (const output of outputs) { - // Output is missing; can stop checking - // Don't immediately return because we can still be upstream-blocked, which is a higher-priority status - if (!host.fileExists(output)) { - missingOutputFileName = output; - break; - } - - const outputTime = host.getModifiedTime(output) || missingFileModifiedTime; - if (outputTime < oldestOutputFileTime) { - oldestOutputFileTime = outputTime; - oldestOutputFileName = output; - } - - // If an output is older than the newest input, we can stop checking - // Don't immediately return because we can still be upstream-blocked, which is a higher-priority status - if (outputTime < newestInputFileTime) { - isOutOfDateWithInputs = true; - break; - } - - if (outputTime > newestOutputFileTime) { - newestOutputFileTime = outputTime; - newestOutputFileName = output; - } - - // Keep track of when the most recent time a .d.ts file was changed. - // In addition to file timestamps, we also keep track of when a .d.ts file - // had its file touched but not had its contents changed - this allows us - // to skip a downstream typecheck - if (isDeclarationFile(output)) { - const outputModifiedTime = host.getModifiedTime(output) || missingFileModifiedTime; - newestDeclarationFileContentChangedTime = newer(newestDeclarationFileContentChangedTime, outputModifiedTime); - } - } - - let pseudoUpToDate = false; - let usesPrepend = false; - let upstreamChangedProject: string | undefined; - if (project.projectReferences) { - state.projectStatus.set(resolvedPath, { type: UpToDateStatusType.ComputingUpstream }); - for (const ref of project.projectReferences) { - usesPrepend = usesPrepend || !!(ref.prepend); - const resolvedRef = resolveProjectReferencePath(ref); - const resolvedRefPath = toResolvedConfigFilePath(state, resolvedRef); - const refStatus = getUpToDateStatus(state, parseConfigFile(state, resolvedRef, resolvedRefPath), resolvedRefPath); - - // Its a circular reference ignore the status of this project - if (refStatus.type === UpToDateStatusType.ComputingUpstream || - refStatus.type === UpToDateStatusType.ContainerOnly) { // Container only ignore this project - continue; - } - - // An upstream project is blocked - if (refStatus.type === UpToDateStatusType.Unbuildable || - refStatus.type === UpToDateStatusType.UpstreamBlocked) { - return { - type: UpToDateStatusType.UpstreamBlocked, - upstreamProjectName: ref.path, - upstreamProjectBlocked: refStatus.type === UpToDateStatusType.UpstreamBlocked - }; - } - - // If the upstream project is out of date, then so are we (someone shouldn't have asked, though?) - if (refStatus.type !== UpToDateStatusType.UpToDate) { - return { - type: UpToDateStatusType.UpstreamOutOfDate, - upstreamProjectName: ref.path - }; - } - - // Check oldest output file name only if there is no missing output file name - if (!missingOutputFileName) { - // If the upstream project's newest file is older than our oldest output, we - // can't be out of date because of it - if (refStatus.newestInputFileTime && refStatus.newestInputFileTime <= oldestOutputFileTime) { - continue; - } - - // If the upstream project has only change .d.ts files, and we've built - // *after* those files, then we're "psuedo up to date" and eligible for a fast rebuild - if (refStatus.newestDeclarationFileContentChangedTime && refStatus.newestDeclarationFileContentChangedTime <= oldestOutputFileTime) { - pseudoUpToDate = true; - upstreamChangedProject = ref.path; - continue; - } - - // We have an output older than an upstream output - we are out of date - Debug.assert(oldestOutputFileName !== undefined, "Should have an oldest output filename here"); - return { - type: UpToDateStatusType.OutOfDateWithUpstream, - outOfDateOutputFileName: oldestOutputFileName, - newerProjectName: ref.path - }; - } - } - } - - if (missingOutputFileName !== undefined) { - return { - type: UpToDateStatusType.OutputMissing, - missingOutputFileName - }; - } - - if (isOutOfDateWithInputs) { - return { - type: UpToDateStatusType.OutOfDateWithSelf, - outOfDateOutputFileName: oldestOutputFileName, - newerInputFileName: newestInputFileName - }; - } - else { - // Check tsconfig time - const configStatus = checkConfigFileUpToDateStatus(state, project.options.configFilePath!, oldestOutputFileTime, oldestOutputFileName); - if (configStatus) return configStatus; - - // Check extended config time - const extendedConfigStatus = forEach(project.options.configFile!.extendedSourceFiles || emptyArray, configFile => checkConfigFileUpToDateStatus(state, configFile, oldestOutputFileTime, oldestOutputFileName)); - if (extendedConfigStatus) return extendedConfigStatus; - } - - if (!state.buildInfoChecked.has(resolvedPath)) { - state.buildInfoChecked.set(resolvedPath, true); - const buildInfoPath = getTsBuildInfoEmitOutputFilePath(project.options); - if (buildInfoPath) { - const value = state.readFileWithCache(buildInfoPath); - const buildInfo = value && getBuildInfo(value); - if (buildInfo && (buildInfo.bundle || buildInfo.program) && buildInfo.version !== version) { - return { - type: UpToDateStatusType.TsVersionOutputOfDate, - version: buildInfo.version - }; - } - } - } - - if (usesPrepend && pseudoUpToDate) { - return { - type: UpToDateStatusType.OutOfDateWithPrepend, - outOfDateOutputFileName: oldestOutputFileName, - newerProjectName: upstreamChangedProject! - }; - } - - // Up to date - return { - type: pseudoUpToDate ? UpToDateStatusType.UpToDateWithUpstreamTypes : UpToDateStatusType.UpToDate, - newestDeclarationFileContentChangedTime, - newestInputFileTime, - newestOutputFileTime, - newestInputFileName, - newestOutputFileName, - oldestOutputFileName - }; - } - - function getUpToDateStatus(state: SolutionBuilderState, project: ParsedCommandLine | undefined, resolvedPath: ResolvedConfigFilePath): UpToDateStatus { - if (project === undefined) { - return { type: UpToDateStatusType.Unbuildable, reason: "File deleted mid-build" }; - } - - const prior = state.projectStatus.get(resolvedPath); - if (prior !== undefined) { - return prior; - } - - const actual = getUpToDateStatusWorker(state, project, resolvedPath); - state.projectStatus.set(resolvedPath, actual); - return actual; - } - - function updateOutputTimestampsWorker(state: SolutionBuilderState, proj: ParsedCommandLine, priorNewestUpdateTime: Date, verboseMessage: DiagnosticMessage, skipOutputs?: FileMap) { - const { host } = state; - const outputs = getAllProjectOutputs(proj, !host.useCaseSensitiveFileNames()); - if (!skipOutputs || outputs.length !== skipOutputs.size) { - let reportVerbose = !!state.options.verbose; - const now = host.now ? host.now() : new Date(); - for (const file of outputs) { - if (skipOutputs && skipOutputs.has(toPath(state, file))) { - continue; - } - - if (reportVerbose) { - reportVerbose = false; - reportStatus(state, verboseMessage, proj.options.configFilePath!); - } - - if (isDeclarationFile(file)) { - priorNewestUpdateTime = newer(priorNewestUpdateTime, host.getModifiedTime(file) || missingFileModifiedTime); - } - - host.setModifiedTime(file, now); - listEmittedFile(state, proj, file); - } - } - - return priorNewestUpdateTime; - } - - function updateOutputTimestamps(state: SolutionBuilderState, proj: ParsedCommandLine, resolvedPath: ResolvedConfigFilePath) { - if (state.options.dry) { - return reportStatus(state, Diagnostics.A_non_dry_build_would_update_timestamps_for_output_of_project_0, proj.options.configFilePath!); - } - const priorNewestUpdateTime = updateOutputTimestampsWorker(state, proj, minimumDate, Diagnostics.Updating_output_timestamps_of_project_0); - state.projectStatus.set(resolvedPath, { - type: UpToDateStatusType.UpToDate, - newestDeclarationFileContentChangedTime: priorNewestUpdateTime, - oldestOutputFileName: getFirstProjectOutput(proj, !state.host.useCaseSensitiveFileNames()) - }); - } - - function queueReferencingProjects( - state: SolutionBuilderState, - project: ResolvedConfigFileName, - projectPath: ResolvedConfigFilePath, - projectIndex: number, - config: ParsedCommandLine, - buildOrder: readonly ResolvedConfigFileName[], - buildResult: BuildResultFlags - ) { - // Queue only if there are no errors - if (buildResult & BuildResultFlags.AnyErrors) return; - // Only composite projects can be referenced by other projects - if (!config.options.composite) return; - // Always use build order to queue projects - for (let index = projectIndex + 1; index < buildOrder.length; index++) { - const nextProject = buildOrder[index]; - const nextProjectPath = toResolvedConfigFilePath(state, nextProject); - if (state.projectPendingBuild.has(nextProjectPath)) continue; - - const nextProjectConfig = parseConfigFile(state, nextProject, nextProjectPath); - if (!nextProjectConfig || !nextProjectConfig.projectReferences) continue; - for (const ref of nextProjectConfig.projectReferences) { - const resolvedRefPath = resolveProjectName(state, ref.path); - if (toResolvedConfigFilePath(state, resolvedRefPath) !== projectPath) continue; - // If the project is referenced with prepend, always build downstream projects, - // If declaration output is changed, build the project - // otherwise mark the project UpToDateWithUpstreamTypes so it updates output time stamps - const status = state.projectStatus.get(nextProjectPath); - if (status) { - switch (status.type) { - case UpToDateStatusType.UpToDate: - if (buildResult & BuildResultFlags.DeclarationOutputUnchanged) { - if (ref.prepend) { - state.projectStatus.set(nextProjectPath, { - type: UpToDateStatusType.OutOfDateWithPrepend, - outOfDateOutputFileName: status.oldestOutputFileName, - newerProjectName: project - }); - } - else { - status.type = UpToDateStatusType.UpToDateWithUpstreamTypes; - } - break; - } - // falls through - - case UpToDateStatusType.UpToDateWithUpstreamTypes: - case UpToDateStatusType.OutOfDateWithPrepend: - if (!(buildResult & BuildResultFlags.DeclarationOutputUnchanged)) { - state.projectStatus.set(nextProjectPath, { - type: UpToDateStatusType.OutOfDateWithUpstream, - outOfDateOutputFileName: status.type === UpToDateStatusType.OutOfDateWithPrepend ? status.outOfDateOutputFileName : status.oldestOutputFileName, - newerProjectName: project - }); - } - break; - - case UpToDateStatusType.UpstreamBlocked: - if (toResolvedConfigFilePath(state, resolveProjectName(state, status.upstreamProjectName)) === projectPath) { - clearProjectStatus(state, nextProjectPath); - } - break; - } - } - addProjToQueue(state, nextProjectPath, ConfigFileProgramReloadLevel.None); - break; - } - } - } - - function build(state: SolutionBuilderState, project?: string, cancellationToken?: CancellationToken, onlyReferences?: boolean): ExitStatus { - const buildOrder = getBuildOrderFor(state, project, onlyReferences); - if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; - - setupInitialBuild(state, cancellationToken); - - let reportQueue = true; - let successfulProjects = 0; - while (true) { - const invalidatedProject = getNextInvalidatedProject(state, buildOrder, reportQueue); - if (!invalidatedProject) break; - reportQueue = false; - invalidatedProject.done(cancellationToken); - if (!state.diagnostics.has(invalidatedProject.projectPath)) successfulProjects++; - } - - disableCache(state); - reportErrorSummary(state, buildOrder); - startWatching(state, buildOrder); - - return isCircularBuildOrder(buildOrder) - ? ExitStatus.ProjectReferenceCycle_OutputsSkipped - : !buildOrder.some(p => state.diagnostics.has(toResolvedConfigFilePath(state, p))) - ? ExitStatus.Success - : successfulProjects - ? ExitStatus.DiagnosticsPresent_OutputsGenerated - : ExitStatus.DiagnosticsPresent_OutputsSkipped; - } - - function clean(state: SolutionBuilderState, project?: string, onlyReferences?: boolean) { - const buildOrder = getBuildOrderFor(state, project, onlyReferences); - if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; - - if (isCircularBuildOrder(buildOrder)) { - reportErrors(state, buildOrder.circularDiagnostics); - return ExitStatus.ProjectReferenceCycle_OutputsSkipped; - } - - const { options, host } = state; - const filesToDelete = options.dry ? [] as string[] : undefined; - for (const proj of buildOrder) { - const resolvedPath = toResolvedConfigFilePath(state, proj); - const parsed = parseConfigFile(state, proj, resolvedPath); - if (parsed === undefined) { - // File has gone missing; fine to ignore here - reportParseConfigFileDiagnostic(state, resolvedPath); - continue; - } - const outputs = getAllProjectOutputs(parsed, !host.useCaseSensitiveFileNames()); - for (const output of outputs) { - if (host.fileExists(output)) { - if (filesToDelete) { - filesToDelete.push(output); - } - else { - host.deleteFile(output); - invalidateProject(state, resolvedPath, ConfigFileProgramReloadLevel.None); - } - } - } - } - - if (filesToDelete) { - reportStatus(state, Diagnostics.A_non_dry_build_would_delete_the_following_files_Colon_0, filesToDelete.map(f => `\r\n * ${f}`).join("")); - } - - return ExitStatus.Success; - } - - function invalidateProject(state: SolutionBuilderState, resolved: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { - // If host implements getParsedCommandLine, we cant get list of files from parseConfigFileHost - if (state.host.getParsedCommandLine && reloadLevel === ConfigFileProgramReloadLevel.Partial) { - reloadLevel = ConfigFileProgramReloadLevel.Full; - } - if (reloadLevel === ConfigFileProgramReloadLevel.Full) { - state.configFileCache.delete(resolved); - state.buildOrder = undefined; - } - state.needsSummary = true; - clearProjectStatus(state, resolved); - addProjToQueue(state, resolved, reloadLevel); - enableCache(state); - } - - function invalidateProjectAndScheduleBuilds(state: SolutionBuilderState, resolvedPath: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { - state.reportFileChangeDetected = true; - invalidateProject(state, resolvedPath, reloadLevel); - scheduleBuildInvalidatedProject(state); - } - - function scheduleBuildInvalidatedProject(state: SolutionBuilderState) { - const { hostWithWatch } = state; - if (!hostWithWatch.setTimeout || !hostWithWatch.clearTimeout) { - return; - } - if (state.timerToBuildInvalidatedProject) { - hostWithWatch.clearTimeout(state.timerToBuildInvalidatedProject); - } - state.timerToBuildInvalidatedProject = hostWithWatch.setTimeout(buildNextInvalidatedProject, 250, state); - } - - function buildNextInvalidatedProject(state: SolutionBuilderState) { - state.timerToBuildInvalidatedProject = undefined; - if (state.reportFileChangeDetected) { - state.reportFileChangeDetected = false; - state.projectErrorsReported.clear(); - reportWatchStatus(state, Diagnostics.File_change_detected_Starting_incremental_compilation); - } - const buildOrder = getBuildOrder(state); - const invalidatedProject = getNextInvalidatedProject(state, buildOrder, /*reportQueue*/ false); - if (invalidatedProject) { - invalidatedProject.done(); - if (state.projectPendingBuild.size) { - // Schedule next project for build - if (state.watch && !state.timerToBuildInvalidatedProject) { - scheduleBuildInvalidatedProject(state); - } - return; - } - } - disableCache(state); - reportErrorSummary(state, buildOrder); - } - - function watchConfigFile(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath) { - if (!state.watch || state.allWatchedConfigFiles.has(resolvedPath)) return; - state.allWatchedConfigFiles.set(resolvedPath, state.watchFile( - state.hostWithWatch, - resolved, - () => { - invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.Full); - }, - PollingInterval.High, - WatchType.ConfigFile, - resolved - )); - } - - function isSameFile(state: SolutionBuilderState, file1: string, file2: string) { - return comparePaths(file1, file2, state.currentDirectory, !state.host.useCaseSensitiveFileNames()) === Comparison.EqualTo; - } - - function isOutputFile(state: SolutionBuilderState, fileName: string, configFile: ParsedCommandLine) { - if (configFile.options.noEmit) return false; - - // ts or tsx files are not output - if (!fileExtensionIs(fileName, Extension.Dts) && - (fileExtensionIs(fileName, Extension.Ts) || fileExtensionIs(fileName, Extension.Tsx))) { - return false; - } - - // If options have --outFile or --out, check if its that - const out = configFile.options.outFile || configFile.options.out; - if (out && (isSameFile(state, fileName, out) || isSameFile(state, fileName, removeFileExtension(out) + Extension.Dts))) { - return true; - } - - // If declarationDir is specified, return if its a file in that directory - if (configFile.options.declarationDir && containsPath(configFile.options.declarationDir, fileName, state.currentDirectory, !state.host.useCaseSensitiveFileNames())) { - return true; - } - - // If --outDir, check if file is in that directory - if (configFile.options.outDir && containsPath(configFile.options.outDir, fileName, state.currentDirectory, !state.host.useCaseSensitiveFileNames())) { - return true; - } - - return !forEach(configFile.fileNames, inputFile => isSameFile(state, fileName, inputFile)); - } - - function watchWildCardDirectories(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) { - if (!state.watch) return; - updateWatchingWildcardDirectories( - getOrCreateValueMapFromConfigFileMap(state.allWatchedWildcardDirectories, resolvedPath), - createMapFromTemplate(parsed.configFileSpecs!.wildcardDirectories), - (dir, flags) => state.watchDirectory( - state.hostWithWatch, - dir, - fileOrDirectory => { - const fileOrDirectoryPath = toPath(state, fileOrDirectory); - if (fileOrDirectoryPath !== toPath(state, dir) && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, parsed.options)) { - state.writeLog(`Project: ${resolved} Detected file add/remove of non supported extension: ${fileOrDirectory}`); - return; - } - - if (isOutputFile(state, fileOrDirectory, parsed)) { - state.writeLog(`${fileOrDirectory} is output file`); - return; - } - - invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.Partial); - }, - flags, - WatchType.WildcardDirectory, - resolved - ) - ); - } - - function watchInputFiles(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) { - if (!state.watch) return; - mutateMap( - getOrCreateValueMapFromConfigFileMap(state.allWatchedInputFiles, resolvedPath), - arrayToMap(parsed.fileNames, fileName => toPath(state, fileName)), - { - createNewValue: (path, input) => state.watchFilePath( - state.hostWithWatch, - input, - () => invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.None), - PollingInterval.Low, - path as Path, - WatchType.SourceFile, - resolved - ), - onDeleteValue: closeFileWatcher, - } - ); - } - - function startWatching(state: SolutionBuilderState, buildOrder: AnyBuildOrder) { - if (!state.watchAllProjectsPending) return; - state.watchAllProjectsPending = false; - for (const resolved of getBuildOrderFromAnyBuildOrder(buildOrder)) { - const resolvedPath = toResolvedConfigFilePath(state, resolved); - // Watch this file - watchConfigFile(state, resolved, resolvedPath); - - const cfg = parseConfigFile(state, resolved, resolvedPath); - if (cfg) { - // Update watchers for wildcard directories - watchWildCardDirectories(state, resolved, resolvedPath, cfg); - - // Watch input files - watchInputFiles(state, resolved, resolvedPath, cfg); - } - } - } - - /** - * A SolutionBuilder has an immutable set of rootNames that are the "entry point" projects, but - * can dynamically add/remove other projects based on changes on the rootNames' references - */ - function createSolutionBuilderWorker(watch: false, host: SolutionBuilderHost, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder; - function createSolutionBuilderWorker(watch: true, host: SolutionBuilderWithWatchHost, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder; - function createSolutionBuilderWorker(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost | SolutionBuilderWithWatchHost, rootNames: readonly string[], options: BuildOptions): SolutionBuilder { - const state = createSolutionBuilderState(watch, hostOrHostWithWatch, rootNames, options); - return { - build: (project, cancellationToken) => build(state, project, cancellationToken), - clean: project => clean(state, project), - buildReferences: (project, cancellationToken) => build(state, project, cancellationToken, /*onlyReferences*/ true), - cleanReferences: project => clean(state, project, /*onlyReferences*/ true), - getNextInvalidatedProject: cancellationToken => { - setupInitialBuild(state, cancellationToken); - return getNextInvalidatedProject(state, getBuildOrder(state), /*reportQueue*/ false); - }, - getBuildOrder: () => getBuildOrder(state), - getUpToDateStatusOfProject: project => { - const configFileName = resolveProjectName(state, project); - const configFilePath = toResolvedConfigFilePath(state, configFileName); - return getUpToDateStatus(state, parseConfigFile(state, configFileName, configFilePath), configFilePath); - }, - invalidateProject: (configFilePath, reloadLevel) => invalidateProject(state, configFilePath, reloadLevel || ConfigFileProgramReloadLevel.None), - buildNextInvalidatedProject: () => buildNextInvalidatedProject(state), - getAllParsedConfigs: () => arrayFrom(mapDefinedIterator( - state.configFileCache.values(), - config => isParsedCommandLine(config) ? config : undefined - )), - }; - } - - function relName(state: SolutionBuilderState, path: string): string { - return convertToRelativePath(path, state.currentDirectory, f => state.getCanonicalFileName(f)); - } - - function reportStatus(state: SolutionBuilderState, message: DiagnosticMessage, ...args: string[]) { - state.host.reportSolutionBuilderStatus(createCompilerDiagnostic(message, ...args)); - } - - function reportWatchStatus(state: SolutionBuilderState, message: DiagnosticMessage, ...args: (string | number | undefined)[]) { - if (state.hostWithWatch.onWatchStatusChange) { - state.hostWithWatch.onWatchStatusChange(createCompilerDiagnostic(message, ...args), state.host.getNewLine(), state.baseCompilerOptions); - } - } - - function reportErrors({ host }: SolutionBuilderState, errors: readonly Diagnostic[]) { - errors.forEach(err => host.reportDiagnostic(err)); - } - - function reportAndStoreErrors(state: SolutionBuilderState, proj: ResolvedConfigFilePath, errors: readonly Diagnostic[]) { - reportErrors(state, errors); - state.projectErrorsReported.set(proj, true); - if (errors.length) { - state.diagnostics.set(proj, errors); - } - } - - function reportParseConfigFileDiagnostic(state: SolutionBuilderState, proj: ResolvedConfigFilePath) { - reportAndStoreErrors(state, proj, [state.configFileCache.get(proj) as Diagnostic]); - } - - function reportErrorSummary(state: SolutionBuilderState, buildOrder: AnyBuildOrder) { - if (!state.needsSummary) return; - state.needsSummary = false; - const canReportSummary = state.watch || !!state.host.reportErrorSummary; - const { diagnostics } = state; - let totalErrors = 0; - if (isCircularBuildOrder(buildOrder)) { - reportBuildQueue(state, buildOrder.buildOrder); - reportErrors(state, buildOrder.circularDiagnostics); - if (canReportSummary) totalErrors += getErrorCountForSummary(buildOrder.circularDiagnostics); - } - else { - // Report errors from the other projects - buildOrder.forEach(project => { - const projectPath = toResolvedConfigFilePath(state, project); - if (!state.projectErrorsReported.has(projectPath)) { - reportErrors(state, diagnostics.get(projectPath) || emptyArray); - } - }); - if (canReportSummary) diagnostics.forEach(singleProjectErrors => totalErrors += getErrorCountForSummary(singleProjectErrors)); - } - - if (state.watch) { - reportWatchStatus(state, getWatchErrorSummaryDiagnosticMessage(totalErrors), totalErrors); - } - else if (state.host.reportErrorSummary) { - state.host.reportErrorSummary(totalErrors); - } - } - - /** - * Report the build ordering inferred from the current project graph if we're in verbose mode - */ - function reportBuildQueue(state: SolutionBuilderState, buildQueue: readonly ResolvedConfigFileName[]) { - if (state.options.verbose) { - reportStatus(state, Diagnostics.Projects_in_this_build_Colon_0, buildQueue.map(s => "\r\n * " + relName(state, s)).join("")); - } - } - - function reportUpToDateStatus(state: SolutionBuilderState, configFileName: string, status: UpToDateStatus) { - switch (status.type) { - case UpToDateStatusType.OutOfDateWithSelf: - return reportStatus( - state, - Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, - relName(state, configFileName), - relName(state, status.outOfDateOutputFileName), - relName(state, status.newerInputFileName) - ); - case UpToDateStatusType.OutOfDateWithUpstream: - return reportStatus( - state, - Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, - relName(state, configFileName), - relName(state, status.outOfDateOutputFileName), - relName(state, status.newerProjectName) - ); - case UpToDateStatusType.OutputMissing: - return reportStatus( - state, - Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, - relName(state, configFileName), - relName(state, status.missingOutputFileName) - ); - case UpToDateStatusType.UpToDate: - if (status.newestInputFileTime !== undefined) { - return reportStatus( - state, - Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, - relName(state, configFileName), - relName(state, status.newestInputFileName || ""), - relName(state, status.oldestOutputFileName || "") - ); - } - // Don't report anything for "up to date because it was already built" -- too verbose - break; - case UpToDateStatusType.OutOfDateWithPrepend: - return reportStatus( - state, - Diagnostics.Project_0_is_out_of_date_because_output_of_its_dependency_1_has_changed, - relName(state, configFileName), - relName(state, status.newerProjectName) - ); - case UpToDateStatusType.UpToDateWithUpstreamTypes: - return reportStatus( - state, - Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies, - relName(state, configFileName) - ); - case UpToDateStatusType.UpstreamOutOfDate: - return reportStatus( - state, - Diagnostics.Project_0_is_out_of_date_because_its_dependency_1_is_out_of_date, - relName(state, configFileName), - relName(state, status.upstreamProjectName) - ); - case UpToDateStatusType.UpstreamBlocked: - return reportStatus( - state, - status.upstreamProjectBlocked ? - Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_was_not_built : - Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_has_errors, - relName(state, configFileName), - relName(state, status.upstreamProjectName) - ); - case UpToDateStatusType.Unbuildable: - return reportStatus( - state, - Diagnostics.Failed_to_parse_file_0_Colon_1, - relName(state, configFileName), - status.reason - ); - case UpToDateStatusType.TsVersionOutputOfDate: - return reportStatus( - state, - Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, - relName(state, configFileName), - status.version, - version - ); - case UpToDateStatusType.ContainerOnly: - // Don't report status on "solution" projects - // falls through - case UpToDateStatusType.ComputingUpstream: - // Should never leak from getUptoDateStatusWorker - break; - default: - assertType(status); - } - } - - /** - * Report the up-to-date status of a project if we're in verbose mode - */ - function verboseReportProjectStatus(state: SolutionBuilderState, configFileName: string, status: UpToDateStatus) { - if (state.options.verbose) { - reportUpToDateStatus(state, configFileName, status); - } - } -} +} \ No newline at end of file diff --git a/src/compiler/tsbuildPublic.ts b/src/compiler/tsbuildPublic.ts new file mode 100644 index 00000000000..8b207a312fa --- /dev/null +++ b/src/compiler/tsbuildPublic.ts @@ -0,0 +1,2069 @@ +namespace ts { + const minimumDate = new Date(-8640000000000000); + const maximumDate = new Date(8640000000000000); + + export interface BuildOptions { + dry?: boolean; + force?: boolean; + verbose?: boolean; + + /*@internal*/ clean?: boolean; + /*@internal*/ watch?: boolean; + /*@internal*/ help?: boolean; + + /*@internal*/ preserveWatchOutput?: boolean; + /*@internal*/ listEmittedFiles?: boolean; + /*@internal*/ listFiles?: boolean; + /*@internal*/ pretty?: boolean; + incremental?: boolean; + + traceResolution?: boolean; + /* @internal */ diagnostics?: boolean; + /* @internal */ extendedDiagnostics?: boolean; + /* @internal */ locale?: string; + /* @internal */ generateCpuProfile?: string; + + [option: string]: CompilerOptionsValue | undefined; + } + + enum BuildResultFlags { + None = 0, + + /** + * No errors of any kind occurred during build + */ + Success = 1 << 0, + /** + * None of the .d.ts files emitted by this build were + * different from the existing files on disk + */ + DeclarationOutputUnchanged = 1 << 1, + + ConfigFileErrors = 1 << 2, + SyntaxErrors = 1 << 3, + TypeErrors = 1 << 4, + DeclarationEmitErrors = 1 << 5, + EmitErrors = 1 << 6, + + AnyErrors = ConfigFileErrors | SyntaxErrors | TypeErrors | DeclarationEmitErrors | EmitErrors + } + + /*@internal*/ + export type ResolvedConfigFilePath = ResolvedConfigFileName & Path; + interface FileMap extends Map { + get(key: U): T | undefined; + has(key: U): boolean; + forEach(action: (value: T, key: U) => void): void; + readonly size: number; + keys(): Iterator; + values(): Iterator; + entries(): Iterator<[U, T]>; + set(key: U, value: T): this; + delete(key: U): boolean; + clear(): void; + } + type ConfigFileMap = FileMap; + function createConfigFileMap(): ConfigFileMap { + return createMap() as ConfigFileMap; + } + + function getOrCreateValueFromConfigFileMap(configFileMap: ConfigFileMap, resolved: ResolvedConfigFilePath, createT: () => T): T { + const existingValue = configFileMap.get(resolved); + let newValue: T | undefined; + if (!existingValue) { + newValue = createT(); + configFileMap.set(resolved, newValue); + } + return existingValue || newValue!; + } + + function getOrCreateValueMapFromConfigFileMap(configFileMap: ConfigFileMap>, resolved: ResolvedConfigFilePath): Map { + return getOrCreateValueFromConfigFileMap>(configFileMap, resolved, createMap); + } + + function newer(date1: Date, date2: Date): Date { + return date2 > date1 ? date2 : date1; + } + + function isDeclarationFile(fileName: string) { + return fileExtensionIs(fileName, Extension.Dts); + } + + export type ReportEmitErrorSummary = (errorCount: number) => void; + + export interface SolutionBuilderHostBase extends ProgramHost { + createDirectory?(path: string): void; + /** + * Should provide create directory and writeFile if done of invalidatedProjects is not invoked with + * writeFileCallback + */ + writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void; + + getModifiedTime(fileName: string): Date | undefined; + setModifiedTime(fileName: string, date: Date): void; + deleteFile(fileName: string): void; + getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined; + + reportDiagnostic: DiagnosticReporter; // Technically we want to move it out and allow steps of actions on Solution, but for now just merge stuff in build host here + reportSolutionBuilderStatus: DiagnosticReporter; + + // TODO: To do better with watch mode and normal build mode api that creates program and emits files + // This currently helps enable --diagnostics and --extendedDiagnostics + afterProgramEmitAndDiagnostics?(program: T): void; + + // For testing + /*@internal*/ now?(): Date; + } + + export interface SolutionBuilderHost extends SolutionBuilderHostBase { + reportErrorSummary?: ReportEmitErrorSummary; + } + + export interface SolutionBuilderWithWatchHost extends SolutionBuilderHostBase, WatchHost { + } + + /*@internal*/ + export type BuildOrder = readonly ResolvedConfigFileName[]; + /*@internal*/ + export interface CircularBuildOrder { + buildOrder: BuildOrder; + circularDiagnostics: readonly Diagnostic[]; + } + /*@internal*/ + export type AnyBuildOrder = BuildOrder | CircularBuildOrder; + + /*@internal*/ + export function isCircularBuildOrder(buildOrder: AnyBuildOrder): buildOrder is CircularBuildOrder { + return !!buildOrder && !!(buildOrder as CircularBuildOrder).buildOrder; + } + + /*@internal*/ + export function getBuildOrderFromAnyBuildOrder(anyBuildOrder: AnyBuildOrder): BuildOrder { + return isCircularBuildOrder(anyBuildOrder) ? anyBuildOrder.buildOrder : anyBuildOrder; + } + + export interface SolutionBuilder { + build(project?: string, cancellationToken?: CancellationToken): ExitStatus; + clean(project?: string): ExitStatus; + buildReferences(project: string, cancellationToken?: CancellationToken): ExitStatus; + cleanReferences(project?: string): ExitStatus; + getNextInvalidatedProject(cancellationToken?: CancellationToken): InvalidatedProject | undefined; + + // Currently used for testing but can be made public if needed: + /*@internal*/ getBuildOrder(): AnyBuildOrder; + + // Testing only + /*@internal*/ getUpToDateStatusOfProject(project: string): UpToDateStatus; + /*@internal*/ invalidateProject(configFilePath: ResolvedConfigFilePath, reloadLevel?: ConfigFileProgramReloadLevel): void; + /*@internal*/ buildNextInvalidatedProject(): void; + /*@internal*/ getAllParsedConfigs(): readonly ParsedCommandLine[]; + } + + /** + * Create a function that reports watch status by writing to the system and handles the formating of the diagnostic + */ + export function createBuilderStatusReporter(system: System, pretty?: boolean): DiagnosticReporter { + return diagnostic => { + let output = pretty ? `[${formatColorAndReset(getLocaleTimeString(system), ForegroundColorEscapeSequences.Grey)}] ` : `${getLocaleTimeString(system)} - `; + output += `${flattenDiagnosticMessageText(diagnostic.messageText, system.newLine)}${system.newLine + system.newLine}`; + system.write(output); + }; + } + + function createSolutionBuilderHostBase(system: System, createProgram: CreateProgram | undefined, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter) { + const host = createProgramHost(system, createProgram) as SolutionBuilderHostBase; + host.getModifiedTime = system.getModifiedTime ? path => system.getModifiedTime!(path) : returnUndefined; + host.setModifiedTime = system.setModifiedTime ? (path, date) => system.setModifiedTime!(path, date) : noop; + host.deleteFile = system.deleteFile ? path => system.deleteFile!(path) : noop; + host.reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); + host.reportSolutionBuilderStatus = reportSolutionBuilderStatus || createBuilderStatusReporter(system); + return host; + } + + export function createSolutionBuilderHost(system = sys, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary) { + const host = createSolutionBuilderHostBase(system, createProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderHost; + host.reportErrorSummary = reportErrorSummary; + return host; + } + + export function createSolutionBuilderWithWatchHost(system = sys, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter) { + const host = createSolutionBuilderHostBase(system, createProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderWithWatchHost; + const watchHost = createWatchHost(system, reportWatchStatus); + copyProperties(host, watchHost); + return host; + } + + function getCompilerOptionsOfBuildOptions(buildOptions: BuildOptions): CompilerOptions { + const result = {} as CompilerOptions; + commonOptionsWithBuild.forEach(option => { + if (hasProperty(buildOptions, option.name)) result[option.name] = buildOptions[option.name]; + }); + return result; + } + + export function createSolutionBuilder(host: SolutionBuilderHost, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder { + return createSolutionBuilderWorker(/*watch*/ false, host, rootNames, defaultOptions); + } + + export function createSolutionBuilderWithWatch(host: SolutionBuilderWithWatchHost, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder { + return createSolutionBuilderWorker(/*watch*/ true, host, rootNames, defaultOptions); + } + + type ConfigFileCacheEntry = ParsedCommandLine | Diagnostic; + interface SolutionBuilderStateCache { + originalReadFile: CompilerHost["readFile"]; + originalFileExists: CompilerHost["fileExists"]; + originalDirectoryExists: CompilerHost["directoryExists"]; + originalCreateDirectory: CompilerHost["createDirectory"]; + originalWriteFile: CompilerHost["writeFile"] | undefined; + originalReadFileWithCache: CompilerHost["readFile"]; + originalGetSourceFile: CompilerHost["getSourceFile"]; + } + + interface SolutionBuilderState { + readonly host: SolutionBuilderHost; + readonly hostWithWatch: SolutionBuilderWithWatchHost; + readonly currentDirectory: string; + readonly getCanonicalFileName: GetCanonicalFileName; + readonly parseConfigFileHost: ParseConfigFileHost; + readonly writeFileName: ((s: string) => void) | undefined; + + // State of solution + readonly options: BuildOptions; + readonly baseCompilerOptions: CompilerOptions; + readonly rootNames: readonly string[]; + + readonly resolvedConfigFilePaths: Map; + readonly configFileCache: ConfigFileMap; + /** Map from config file name to up-to-date status */ + readonly projectStatus: ConfigFileMap; + readonly buildInfoChecked: ConfigFileMap; + readonly extendedConfigCache: Map; + + readonly builderPrograms: ConfigFileMap; + readonly diagnostics: ConfigFileMap; + readonly projectPendingBuild: ConfigFileMap; + readonly projectErrorsReported: ConfigFileMap; + + readonly compilerHost: CompilerHost; + readonly moduleResolutionCache: ModuleResolutionCache | undefined; + + // Mutable state + buildOrder: AnyBuildOrder | undefined; + readFileWithCache: (f: string) => string | undefined; + projectCompilerOptions: CompilerOptions; + cache: SolutionBuilderStateCache | undefined; + allProjectBuildPending: boolean; + needsSummary: boolean; + watchAllProjectsPending: boolean; + currentInvalidatedProject: InvalidatedProject | undefined; + + // Watch state + readonly watch: boolean; + readonly allWatchedWildcardDirectories: ConfigFileMap>; + readonly allWatchedInputFiles: ConfigFileMap>; + readonly allWatchedConfigFiles: ConfigFileMap; + + timerToBuildInvalidatedProject: any; + reportFileChangeDetected: boolean; + watchFile: WatchFile; + watchFilePath: WatchFilePath; + watchDirectory: WatchDirectory; + writeLog: (s: string) => void; + } + + function createSolutionBuilderState(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost | SolutionBuilderWithWatchHost, rootNames: readonly string[], options: BuildOptions): SolutionBuilderState { + const host = hostOrHostWithWatch as SolutionBuilderHost; + const hostWithWatch = hostOrHostWithWatch as SolutionBuilderWithWatchHost; + const currentDirectory = host.getCurrentDirectory(); + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); + + // State of the solution + const baseCompilerOptions = getCompilerOptionsOfBuildOptions(options); + const compilerHost = createCompilerHostFromProgramHost(host, () => state.projectCompilerOptions); + setGetSourceFileAsHashVersioned(compilerHost, host); + compilerHost.getParsedCommandLine = fileName => parseConfigFile(state, fileName as ResolvedConfigFileName, toResolvedConfigFilePath(state, fileName as ResolvedConfigFileName)); + compilerHost.resolveModuleNames = maybeBind(host, host.resolveModuleNames); + compilerHost.resolveTypeReferenceDirectives = maybeBind(host, host.resolveTypeReferenceDirectives); + const moduleResolutionCache = !compilerHost.resolveModuleNames ? createModuleResolutionCache(currentDirectory, getCanonicalFileName) : undefined; + if (!compilerHost.resolveModuleNames) { + const loader = (moduleName: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFile, state.projectCompilerOptions, compilerHost, moduleResolutionCache, redirectedReference).resolvedModule!; + compilerHost.resolveModuleNames = (moduleNames, containingFile, _reusedNames, redirectedReference) => + loadWithLocalCache(Debug.assertEachDefined(moduleNames), containingFile, redirectedReference, loader); + } + + const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory(hostWithWatch, options); + + const state: SolutionBuilderState = { + host, + hostWithWatch, + currentDirectory, + getCanonicalFileName, + parseConfigFileHost: parseConfigHostFromCompilerHostLike(host), + writeFileName: host.trace ? (s: string) => host.trace!(s) : undefined, + + // State of solution + options, + baseCompilerOptions, + rootNames, + + resolvedConfigFilePaths: createMap(), + configFileCache: createConfigFileMap(), + projectStatus: createConfigFileMap(), + buildInfoChecked: createConfigFileMap(), + extendedConfigCache: createMap(), + + builderPrograms: createConfigFileMap(), + diagnostics: createConfigFileMap(), + projectPendingBuild: createConfigFileMap(), + projectErrorsReported: createConfigFileMap(), + + compilerHost, + moduleResolutionCache, + + // Mutable state + buildOrder: undefined, + readFileWithCache: f => host.readFile(f), + projectCompilerOptions: baseCompilerOptions, + cache: undefined, + allProjectBuildPending: true, + needsSummary: true, + watchAllProjectsPending: watch, + currentInvalidatedProject: undefined, + + // Watch state + watch, + allWatchedWildcardDirectories: createConfigFileMap(), + allWatchedInputFiles: createConfigFileMap(), + allWatchedConfigFiles: createConfigFileMap(), + + timerToBuildInvalidatedProject: undefined, + reportFileChangeDetected: false, + watchFile, + watchFilePath, + watchDirectory, + writeLog, + }; + + return state; + } + + function toPath(state: SolutionBuilderState, fileName: string) { + return ts.toPath(fileName, state.currentDirectory, state.getCanonicalFileName); + } + + function toResolvedConfigFilePath(state: SolutionBuilderState, fileName: ResolvedConfigFileName): ResolvedConfigFilePath { + const { resolvedConfigFilePaths } = state; + const path = resolvedConfigFilePaths.get(fileName); + if (path !== undefined) return path; + + const resolvedPath = toPath(state, fileName) as ResolvedConfigFilePath; + resolvedConfigFilePaths.set(fileName, resolvedPath); + return resolvedPath; + } + + function isParsedCommandLine(entry: ConfigFileCacheEntry): entry is ParsedCommandLine { + return !!(entry as ParsedCommandLine).options; + } + + function parseConfigFile(state: SolutionBuilderState, configFileName: ResolvedConfigFileName, configFilePath: ResolvedConfigFilePath): ParsedCommandLine | undefined { + const { configFileCache } = state; + const value = configFileCache.get(configFilePath); + if (value) { + return isParsedCommandLine(value) ? value : undefined; + } + + let diagnostic: Diagnostic | undefined; + const { parseConfigFileHost, baseCompilerOptions, extendedConfigCache, host } = state; + let parsed: ParsedCommandLine | undefined; + if (host.getParsedCommandLine) { + parsed = host.getParsedCommandLine(configFileName); + if (!parsed) diagnostic = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName); + } + else { + parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = d => diagnostic = d; + parsed = getParsedCommandLineOfConfigFile(configFileName, baseCompilerOptions, parseConfigFileHost, extendedConfigCache); + parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = noop; + } + configFileCache.set(configFilePath, parsed || diagnostic!); + return parsed; + } + + function resolveProjectName(state: SolutionBuilderState, name: string): ResolvedConfigFileName { + return resolveConfigFileProjectName(resolvePath(state.currentDirectory, name)); + } + + function createBuildOrder(state: SolutionBuilderState, roots: readonly ResolvedConfigFileName[]): AnyBuildOrder { + const temporaryMarks = createMap() as ConfigFileMap; + const permanentMarks = createMap() as ConfigFileMap; + const circularityReportStack: string[] = []; + let buildOrder: ResolvedConfigFileName[] | undefined; + let circularDiagnostics: Diagnostic[] | undefined; + for (const root of roots) { + visit(root); + } + + return circularDiagnostics ? + { buildOrder: buildOrder || emptyArray, circularDiagnostics } : + buildOrder || emptyArray; + + function visit(configFileName: ResolvedConfigFileName, inCircularContext?: boolean) { + const projPath = toResolvedConfigFilePath(state, configFileName); + // Already visited + if (permanentMarks.has(projPath)) return; + // Circular + if (temporaryMarks.has(projPath)) { + if (!inCircularContext) { + (circularDiagnostics || (circularDiagnostics = [])).push( + createCompilerDiagnostic( + Diagnostics.Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0, + circularityReportStack.join("\r\n") + ) + ); + } + return; + } + + temporaryMarks.set(projPath, true); + circularityReportStack.push(configFileName); + const parsed = parseConfigFile(state, configFileName, projPath); + if (parsed && parsed.projectReferences) { + for (const ref of parsed.projectReferences) { + const resolvedRefPath = resolveProjectName(state, ref.path); + visit(resolvedRefPath, inCircularContext || ref.circular); + } + } + + circularityReportStack.pop(); + permanentMarks.set(projPath, true); + (buildOrder || (buildOrder = [])).push(configFileName); + } + } + + function getBuildOrder(state: SolutionBuilderState) { + return state.buildOrder || createStateBuildOrder(state); + } + + function createStateBuildOrder(state: SolutionBuilderState) { + const buildOrder = createBuildOrder(state, state.rootNames.map(f => resolveProjectName(state, f))); + + // Clear all to ResolvedConfigFilePaths cache to start fresh + state.resolvedConfigFilePaths.clear(); + const currentProjects = arrayToSet( + getBuildOrderFromAnyBuildOrder(buildOrder), + resolved => toResolvedConfigFilePath(state, resolved) + ) as ConfigFileMap; + + const noopOnDelete = { onDeleteValue: noop }; + // Config file cache + mutateMapSkippingNewValues(state.configFileCache, currentProjects, noopOnDelete); + mutateMapSkippingNewValues(state.projectStatus, currentProjects, noopOnDelete); + mutateMapSkippingNewValues(state.buildInfoChecked, currentProjects, noopOnDelete); + mutateMapSkippingNewValues(state.builderPrograms, currentProjects, noopOnDelete); + mutateMapSkippingNewValues(state.diagnostics, currentProjects, noopOnDelete); + mutateMapSkippingNewValues(state.projectPendingBuild, currentProjects, noopOnDelete); + mutateMapSkippingNewValues(state.projectErrorsReported, currentProjects, noopOnDelete); + + // Remove watches for the program no longer in the solution + if (state.watch) { + mutateMapSkippingNewValues( + state.allWatchedConfigFiles, + currentProjects, + { onDeleteValue: closeFileWatcher } + ); + + mutateMapSkippingNewValues( + state.allWatchedWildcardDirectories, + currentProjects, + { onDeleteValue: existingMap => existingMap.forEach(closeFileWatcherOf) } + ); + + mutateMapSkippingNewValues( + state.allWatchedInputFiles, + currentProjects, + { onDeleteValue: existingMap => existingMap.forEach(closeFileWatcher) } + ); + } + return state.buildOrder = buildOrder; + } + + function getBuildOrderFor(state: SolutionBuilderState, project: string | undefined, onlyReferences: boolean | undefined): AnyBuildOrder | undefined { + const resolvedProject = project && resolveProjectName(state, project); + const buildOrderFromState = getBuildOrder(state); + if (isCircularBuildOrder(buildOrderFromState)) return buildOrderFromState; + if (resolvedProject) { + const projectPath = toResolvedConfigFilePath(state, resolvedProject); + const projectIndex = findIndex( + buildOrderFromState, + configFileName => toResolvedConfigFilePath(state, configFileName) === projectPath + ); + if (projectIndex === -1) return undefined; + } + const buildOrder = resolvedProject ? createBuildOrder(state, [resolvedProject]) as BuildOrder : buildOrderFromState; + Debug.assert(!isCircularBuildOrder(buildOrder)); + Debug.assert(!onlyReferences || resolvedProject !== undefined); + Debug.assert(!onlyReferences || buildOrder[buildOrder.length - 1] === resolvedProject); + return onlyReferences ? buildOrder.slice(0, buildOrder.length - 1) : buildOrder; + } + + function enableCache(state: SolutionBuilderState) { + if (state.cache) { + disableCache(state); + } + + const { compilerHost, host } = state; + + const originalReadFileWithCache = state.readFileWithCache; + const originalGetSourceFile = compilerHost.getSourceFile; + + const { + originalReadFile, originalFileExists, originalDirectoryExists, + originalCreateDirectory, originalWriteFile, + getSourceFileWithCache, readFileWithCache + } = changeCompilerHostLikeToUseCache( + host, + fileName => toPath(state, fileName), + (...args) => originalGetSourceFile.call(compilerHost, ...args) + ); + state.readFileWithCache = readFileWithCache; + compilerHost.getSourceFile = getSourceFileWithCache!; + + state.cache = { + originalReadFile, + originalFileExists, + originalDirectoryExists, + originalCreateDirectory, + originalWriteFile, + originalReadFileWithCache, + originalGetSourceFile, + }; + } + + function disableCache(state: SolutionBuilderState) { + if (!state.cache) return; + + const { cache, host, compilerHost, extendedConfigCache, moduleResolutionCache } = state; + + host.readFile = cache.originalReadFile; + host.fileExists = cache.originalFileExists; + host.directoryExists = cache.originalDirectoryExists; + host.createDirectory = cache.originalCreateDirectory; + host.writeFile = cache.originalWriteFile; + compilerHost.getSourceFile = cache.originalGetSourceFile; + state.readFileWithCache = cache.originalReadFileWithCache; + extendedConfigCache.clear(); + if (moduleResolutionCache) { + moduleResolutionCache.directoryToModuleNameMap.clear(); + moduleResolutionCache.moduleNameToDirectoryMap.clear(); + } + state.cache = undefined; + } + + function clearProjectStatus(state: SolutionBuilderState, resolved: ResolvedConfigFilePath) { + state.projectStatus.delete(resolved); + state.diagnostics.delete(resolved); + } + + function addProjToQueue({ projectPendingBuild }: SolutionBuilderState, proj: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { + const value = projectPendingBuild.get(proj); + if (value === undefined) { + projectPendingBuild.set(proj, reloadLevel); + } + else if (value < reloadLevel) { + projectPendingBuild.set(proj, reloadLevel); + } + } + + function setupInitialBuild(state: SolutionBuilderState, cancellationToken: CancellationToken | undefined) { + // Set initial build if not already built + if (!state.allProjectBuildPending) return; + state.allProjectBuildPending = false; + if (state.options.watch) { reportWatchStatus(state, Diagnostics.Starting_compilation_in_watch_mode); } + enableCache(state); + const buildOrder = getBuildOrderFromAnyBuildOrder(getBuildOrder(state)); + buildOrder.forEach(configFileName => + state.projectPendingBuild.set( + toResolvedConfigFilePath(state, configFileName), + ConfigFileProgramReloadLevel.None + ) + ); + + if (cancellationToken) { + cancellationToken.throwIfCancellationRequested(); + } + } + + export enum InvalidatedProjectKind { + Build, + UpdateBundle, + UpdateOutputFileStamps + } + + export interface InvalidatedProjectBase { + readonly kind: InvalidatedProjectKind; + readonly project: ResolvedConfigFileName; + /*@internal*/ readonly projectPath: ResolvedConfigFilePath; + /*@internal*/ readonly buildOrder: readonly ResolvedConfigFileName[]; + /** + * To dispose this project and ensure that all the necessary actions are taken and state is updated accordingly + */ + done(cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers): ExitStatus; + getCompilerOptions(): CompilerOptions; + getCurrentDirectory(): string; + } + + export interface UpdateOutputFileStampsProject extends InvalidatedProjectBase { + readonly kind: InvalidatedProjectKind.UpdateOutputFileStamps; + updateOutputFileStatmps(): void; + } + + export interface BuildInvalidedProject extends InvalidatedProjectBase { + readonly kind: InvalidatedProjectKind.Build; + /* + * Emitting with this builder program without the api provided for this project + * can result in build system going into invalid state as files written reflect the state of the project + */ + getBuilderProgram(): T | undefined; + getProgram(): Program | undefined; + getSourceFile(fileName: string): SourceFile | undefined; + getSourceFiles(): readonly SourceFile[]; + getOptionsDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; + getGlobalDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; + getConfigFileParsingDiagnostics(): readonly Diagnostic[]; + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; + getAllDependencies(sourceFile: SourceFile): readonly string[]; + getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; + getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult; + /* + * Calling emit directly with targetSourceFile and emitOnlyDtsFiles set to true is not advised since + * emit in build system is responsible in updating status of the project + * If called with targetSourceFile and emitOnlyDtsFiles set to true, the emit just passes to underlying builder and + * wont reflect the status of file as being emitted in the builder + * (if that emit of that source file is required it would be emitted again when making sure invalidated project is completed) + * This emit is not considered actual emit (and hence uptodate status is not reflected if + */ + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult | undefined; + // TODO(shkamat):: investigate later if we can emit even when there are declaration diagnostics + // emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult; + } + + export interface UpdateBundleProject extends InvalidatedProjectBase { + readonly kind: InvalidatedProjectKind.UpdateBundle; + emit(writeFile?: WriteFileCallback, customTransformers?: CustomTransformers): EmitResult | BuildInvalidedProject | undefined; + } + + export type InvalidatedProject = UpdateOutputFileStampsProject | BuildInvalidedProject | UpdateBundleProject; + + function doneInvalidatedProject( + state: SolutionBuilderState, + projectPath: ResolvedConfigFilePath + ) { + state.projectPendingBuild.delete(projectPath); + state.currentInvalidatedProject = undefined; + return state.diagnostics.has(projectPath) ? + ExitStatus.DiagnosticsPresent_OutputsSkipped : + ExitStatus.Success; + } + + function createUpdateOutputFileStampsProject( + state: SolutionBuilderState, + project: ResolvedConfigFileName, + projectPath: ResolvedConfigFilePath, + config: ParsedCommandLine, + buildOrder: readonly ResolvedConfigFileName[] + ): UpdateOutputFileStampsProject { + let updateOutputFileStampsPending = true; + return { + kind: InvalidatedProjectKind.UpdateOutputFileStamps, + project, + projectPath, + buildOrder, + getCompilerOptions: () => config.options, + getCurrentDirectory: () => state.currentDirectory, + updateOutputFileStatmps: () => { + updateOutputTimestamps(state, config, projectPath); + updateOutputFileStampsPending = false; + }, + done: () => { + if (updateOutputFileStampsPending) { + updateOutputTimestamps(state, config, projectPath); + } + return doneInvalidatedProject(state, projectPath); + } + }; + } + + function createBuildOrUpdateInvalidedProject( + kind: InvalidatedProjectKind.Build | InvalidatedProjectKind.UpdateBundle, + state: SolutionBuilderState, + project: ResolvedConfigFileName, + projectPath: ResolvedConfigFilePath, + projectIndex: number, + config: ParsedCommandLine, + buildOrder: readonly ResolvedConfigFileName[], + ): BuildInvalidedProject | UpdateBundleProject { + enum Step { + CreateProgram, + SyntaxDiagnostics, + SemanticDiagnostics, + Emit, + EmitBundle, + BuildInvalidatedProjectOfBundle, + QueueReferencingProjects, + Done + } + + let step = kind === InvalidatedProjectKind.Build ? Step.CreateProgram : Step.EmitBundle; + let program: T | undefined; + let buildResult: BuildResultFlags | undefined; + let invalidatedProjectOfBundle: BuildInvalidedProject | undefined; + + return kind === InvalidatedProjectKind.Build ? + { + kind, + project, + projectPath, + buildOrder, + getCompilerOptions: () => config.options, + getCurrentDirectory: () => state.currentDirectory, + getBuilderProgram: () => withProgramOrUndefined(identity), + getProgram: () => + withProgramOrUndefined( + program => program.getProgramOrUndefined() + ), + getSourceFile: fileName => + withProgramOrUndefined( + program => program.getSourceFile(fileName) + ), + getSourceFiles: () => + withProgramOrEmptyArray( + program => program.getSourceFiles() + ), + getOptionsDiagnostics: cancellationToken => + withProgramOrEmptyArray( + program => program.getOptionsDiagnostics(cancellationToken) + ), + getGlobalDiagnostics: cancellationToken => + withProgramOrEmptyArray( + program => program.getGlobalDiagnostics(cancellationToken) + ), + getConfigFileParsingDiagnostics: () => + withProgramOrEmptyArray( + program => program.getConfigFileParsingDiagnostics() + ), + getSyntacticDiagnostics: (sourceFile, cancellationToken) => + withProgramOrEmptyArray( + program => program.getSyntacticDiagnostics(sourceFile, cancellationToken) + ), + getAllDependencies: sourceFile => + withProgramOrEmptyArray( + program => program.getAllDependencies(sourceFile) + ), + getSemanticDiagnostics: (sourceFile, cancellationToken) => + withProgramOrEmptyArray( + program => program.getSemanticDiagnostics(sourceFile, cancellationToken) + ), + getSemanticDiagnosticsOfNextAffectedFile: (cancellationToken, ignoreSourceFile) => + withProgramOrUndefined( + program => + ((program as any as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile) && + (program as any as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile(cancellationToken, ignoreSourceFile) + ), + emit: (targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers) => { + if (targetSourceFile || emitOnlyDtsFiles) { + return withProgramOrUndefined( + program => program.emit(targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers) + ); + } + executeSteps(Step.SemanticDiagnostics, cancellationToken); + if (step !== Step.Emit) return undefined; + return emit(writeFile, cancellationToken, customTransformers); + }, + done + } : + { + kind, + project, + projectPath, + buildOrder, + getCompilerOptions: () => config.options, + getCurrentDirectory: () => state.currentDirectory, + emit: (writeFile: WriteFileCallback | undefined, customTransformers: CustomTransformers | undefined) => { + if (step !== Step.EmitBundle) return invalidatedProjectOfBundle; + return emitBundle(writeFile, customTransformers); + }, + done, + }; + + function done(cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers) { + executeSteps(Step.Done, cancellationToken, writeFile, customTransformers); + return doneInvalidatedProject(state, projectPath); + } + + function withProgramOrUndefined(action: (program: T) => U | undefined): U | undefined { + executeSteps(Step.CreateProgram); + return program && action(program); + } + + function withProgramOrEmptyArray(action: (program: T) => readonly U[]): readonly U[] { + return withProgramOrUndefined(action) || emptyArray; + } + + function createProgram() { + Debug.assert(program === undefined); + + if (state.options.dry) { + reportStatus(state, Diagnostics.A_non_dry_build_would_build_project_0, project); + buildResult = BuildResultFlags.Success; + step = Step.QueueReferencingProjects; + return; + } + + if (state.options.verbose) reportStatus(state, Diagnostics.Building_project_0, project); + + if (config.fileNames.length === 0) { + reportAndStoreErrors(state, projectPath, config.errors); + // Nothing to build - must be a solution file, basically + buildResult = BuildResultFlags.None; + step = Step.QueueReferencingProjects; + return; + } + + const { host, compilerHost } = state; + state.projectCompilerOptions = config.options; + // Update module resolution cache if needed + updateModuleResolutionCache(state, project, config); + + // Create program + program = host.createProgram( + config.fileNames, + config.options, + compilerHost, + getOldProgram(state, projectPath, config), + config.errors, + config.projectReferences + ); + step++; + } + + function handleDiagnostics(diagnostics: readonly Diagnostic[], errorFlags: BuildResultFlags, errorType: string) { + if (diagnostics.length) { + buildResult = buildErrors( + state, + projectPath, + program, + diagnostics, + errorFlags, + errorType + ); + step = Step.QueueReferencingProjects; + } + else { + step++; + } + } + + function getSyntaxDiagnostics(cancellationToken?: CancellationToken) { + Debug.assertDefined(program); + handleDiagnostics( + [ + ...program!.getConfigFileParsingDiagnostics(), + ...program!.getOptionsDiagnostics(cancellationToken), + ...program!.getGlobalDiagnostics(cancellationToken), + ...program!.getSyntacticDiagnostics(/*sourceFile*/ undefined, cancellationToken) + ], + BuildResultFlags.SyntaxErrors, + "Syntactic" + ); + } + + function getSemanticDiagnostics(cancellationToken?: CancellationToken) { + handleDiagnostics( + Debug.assertDefined(program).getSemanticDiagnostics(/*sourceFile*/ undefined, cancellationToken), + BuildResultFlags.TypeErrors, + "Semantic" + ); + } + + function emit(writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitResult { + Debug.assertDefined(program); + Debug.assert(step === Step.Emit); + // Before emitting lets backup state, so we can revert it back if there are declaration errors to handle emit and declaration errors correctly + program!.backupState(); + let declDiagnostics: Diagnostic[] | undefined; + const reportDeclarationDiagnostics = (d: Diagnostic) => (declDiagnostics || (declDiagnostics = [])).push(d); + const outputFiles: OutputFile[] = []; + const { emitResult } = emitFilesAndReportErrors( + program!, + reportDeclarationDiagnostics, + /*writeFileName*/ undefined, + /*reportSummary*/ undefined, + (name, text, writeByteOrderMark) => outputFiles.push({ name, text, writeByteOrderMark }), + cancellationToken, + /*emitOnlyDts*/ false, + customTransformers + ); + // Don't emit .d.ts if there are decl file errors + if (declDiagnostics) { + program!.restoreState(); + buildResult = buildErrors( + state, + projectPath, + program, + declDiagnostics, + BuildResultFlags.DeclarationEmitErrors, + "Declaration file" + ); + step = Step.QueueReferencingProjects; + return { + emitSkipped: true, + diagnostics: emitResult.diagnostics + }; + } + + // Actual Emit + const { host, compilerHost } = state; + let resultFlags = BuildResultFlags.DeclarationOutputUnchanged; + let newestDeclarationFileContentChangedTime = minimumDate; + let anyDtsChanged = false; + const emitterDiagnostics = createDiagnosticCollection(); + const emittedOutputs = createMap() as FileMap; + outputFiles.forEach(({ name, text, writeByteOrderMark }) => { + let priorChangeTime: Date | undefined; + if (!anyDtsChanged && isDeclarationFile(name)) { + // Check for unchanged .d.ts files + if (host.fileExists(name) && state.readFileWithCache(name) === text) { + priorChangeTime = host.getModifiedTime(name); + } + else { + resultFlags &= ~BuildResultFlags.DeclarationOutputUnchanged; + anyDtsChanged = true; + } + } + + emittedOutputs.set(toPath(state, name), name); + writeFile(writeFileCallback ? { writeFile: writeFileCallback } : compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); + if (priorChangeTime !== undefined) { + newestDeclarationFileContentChangedTime = newer(priorChangeTime, newestDeclarationFileContentChangedTime); + } + }); + + finishEmit( + emitterDiagnostics, + emittedOutputs, + newestDeclarationFileContentChangedTime, + /*newestDeclarationFileContentChangedTimeIsMaximumDate*/ anyDtsChanged, + outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(config, !host.useCaseSensitiveFileNames()), + resultFlags + ); + return emitResult; + } + + function finishEmit( + emitterDiagnostics: DiagnosticCollection, + emittedOutputs: FileMap, + priorNewestUpdateTime: Date, + newestDeclarationFileContentChangedTimeIsMaximumDate: boolean, + oldestOutputFileName: string, + resultFlags: BuildResultFlags + ) { + const emitDiagnostics = emitterDiagnostics.getDiagnostics(); + if (emitDiagnostics.length) { + buildResult = buildErrors( + state, + projectPath, + program, + emitDiagnostics, + BuildResultFlags.EmitErrors, + "Emit" + ); + step = Step.QueueReferencingProjects; + return emitDiagnostics; + } + + if (state.writeFileName) { + emittedOutputs.forEach(name => listEmittedFile(state, config, name)); + if (program) listFiles(program, state.writeFileName); + } + + // Update time stamps for rest of the outputs + const newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(state, config, priorNewestUpdateTime, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs); + state.diagnostics.delete(projectPath); + state.projectStatus.set(projectPath, { + type: UpToDateStatusType.UpToDate, + newestDeclarationFileContentChangedTime: newestDeclarationFileContentChangedTimeIsMaximumDate ? + maximumDate : + newestDeclarationFileContentChangedTime, + oldestOutputFileName + }); + if (program) afterProgramCreate(state, projectPath, program); + state.projectCompilerOptions = state.baseCompilerOptions; + step = Step.QueueReferencingProjects; + buildResult = resultFlags; + return emitDiagnostics; + } + + function emitBundle(writeFileCallback?: WriteFileCallback, customTransformers?: CustomTransformers): EmitResult | BuildInvalidedProject | undefined { + Debug.assert(kind === InvalidatedProjectKind.UpdateBundle); + if (state.options.dry) { + reportStatus(state, Diagnostics.A_non_dry_build_would_update_output_of_project_0, project); + buildResult = BuildResultFlags.Success; + step = Step.QueueReferencingProjects; + return undefined; + } + + if (state.options.verbose) reportStatus(state, Diagnostics.Updating_output_of_project_0, project); + + // Update js, and source map + const { compilerHost } = state; + state.projectCompilerOptions = config.options; + const outputFiles = emitUsingBuildInfo( + config, + compilerHost, + ref => { + const refName = resolveProjectName(state, ref.path); + return parseConfigFile(state, refName, toResolvedConfigFilePath(state, refName)); + }, + customTransformers + ); + + if (isString(outputFiles)) { + reportStatus(state, Diagnostics.Cannot_update_output_of_project_0_because_there_was_error_reading_file_1, project, relName(state, outputFiles)); + step = Step.BuildInvalidatedProjectOfBundle; + return invalidatedProjectOfBundle = createBuildOrUpdateInvalidedProject( + InvalidatedProjectKind.Build, + state, + project, + projectPath, + projectIndex, + config, + buildOrder + ) as BuildInvalidedProject; + } + + // Actual Emit + Debug.assert(!!outputFiles.length); + const emitterDiagnostics = createDiagnosticCollection(); + const emittedOutputs = createMap() as FileMap; + outputFiles.forEach(({ name, text, writeByteOrderMark }) => { + emittedOutputs.set(toPath(state, name), name); + writeFile(writeFileCallback ? { writeFile: writeFileCallback } : compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); + }); + + const emitDiagnostics = finishEmit( + emitterDiagnostics, + emittedOutputs, + minimumDate, + /*newestDeclarationFileContentChangedTimeIsMaximumDate*/ false, + outputFiles[0].name, + BuildResultFlags.DeclarationOutputUnchanged + ); + return { emitSkipped: false, diagnostics: emitDiagnostics }; + } + + function executeSteps(till: Step, cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers) { + while (step <= till && step < Step.Done) { + const currentStep = step; + switch (step) { + case Step.CreateProgram: + createProgram(); + break; + + case Step.SyntaxDiagnostics: + getSyntaxDiagnostics(cancellationToken); + break; + + case Step.SemanticDiagnostics: + getSemanticDiagnostics(cancellationToken); + break; + + case Step.Emit: + emit(writeFile, cancellationToken, customTransformers); + break; + + case Step.EmitBundle: + emitBundle(writeFile, customTransformers); + break; + + case Step.BuildInvalidatedProjectOfBundle: + Debug.assertDefined(invalidatedProjectOfBundle).done(cancellationToken); + step = Step.Done; + break; + + case Step.QueueReferencingProjects: + queueReferencingProjects(state, project, projectPath, projectIndex, config, buildOrder, Debug.assertDefined(buildResult)); + step++; + break; + + // Should never be done + case Step.Done: + default: + assertType(step); + + } + Debug.assert(step > currentStep); + } + } + } + + function needsBuild({ options }: SolutionBuilderState, status: UpToDateStatus, config: ParsedCommandLine) { + if (status.type !== UpToDateStatusType.OutOfDateWithPrepend || options.force) return true; + return config.fileNames.length === 0 || + !!config.errors.length || + !isIncrementalCompilation(config.options); + } + + function getNextInvalidatedProject( + state: SolutionBuilderState, + buildOrder: AnyBuildOrder, + reportQueue: boolean + ): InvalidatedProject | undefined { + if (!state.projectPendingBuild.size) return undefined; + if (isCircularBuildOrder(buildOrder)) return undefined; + if (state.currentInvalidatedProject) { + // Only if same buildOrder the currentInvalidated project can be sent again + return arrayIsEqualTo(state.currentInvalidatedProject.buildOrder, buildOrder) ? + state.currentInvalidatedProject : + undefined; + } + + const { options, projectPendingBuild } = state; + for (let projectIndex = 0; projectIndex < buildOrder.length; projectIndex++) { + const project = buildOrder[projectIndex]; + const projectPath = toResolvedConfigFilePath(state, project); + const reloadLevel = state.projectPendingBuild.get(projectPath); + if (reloadLevel === undefined) continue; + + if (reportQueue) { + reportQueue = false; + reportBuildQueue(state, buildOrder); + } + + const config = parseConfigFile(state, project, projectPath); + if (!config) { + reportParseConfigFileDiagnostic(state, projectPath); + projectPendingBuild.delete(projectPath); + continue; + } + + if (reloadLevel === ConfigFileProgramReloadLevel.Full) { + watchConfigFile(state, project, projectPath); + watchWildCardDirectories(state, project, projectPath, config); + watchInputFiles(state, project, projectPath, config); + } + else if (reloadLevel === ConfigFileProgramReloadLevel.Partial) { + // Update file names + const result = getFileNamesFromConfigSpecs(config.configFileSpecs!, getDirectoryPath(project), config.options, state.parseConfigFileHost); + updateErrorForNoInputFiles(result, project, config.configFileSpecs!, config.errors, canJsonReportNoInutFiles(config.raw)); + config.fileNames = result.fileNames; + watchInputFiles(state, project, projectPath, config); + } + + const status = getUpToDateStatus(state, config, projectPath); + verboseReportProjectStatus(state, project, status); + if (!options.force) { + if (status.type === UpToDateStatusType.UpToDate) { + reportAndStoreErrors(state, projectPath, config.errors); + projectPendingBuild.delete(projectPath); + // Up to date, skip + if (options.dry) { + // In a dry build, inform the user of this fact + reportStatus(state, Diagnostics.Project_0_is_up_to_date, project); + } + continue; + } + + if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes) { + reportAndStoreErrors(state, projectPath, config.errors); + return createUpdateOutputFileStampsProject( + state, + project, + projectPath, + config, + buildOrder + ); + } + } + + if (status.type === UpToDateStatusType.UpstreamBlocked) { + reportAndStoreErrors(state, projectPath, config.errors); + projectPendingBuild.delete(projectPath); + if (options.verbose) { + reportStatus( + state, + status.upstreamProjectBlocked ? + Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_was_not_built : + Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, + project, + status.upstreamProjectName + ); + } + continue; + } + + if (status.type === UpToDateStatusType.ContainerOnly) { + reportAndStoreErrors(state, projectPath, config.errors); + projectPendingBuild.delete(projectPath); + // Do nothing + continue; + } + + return createBuildOrUpdateInvalidedProject( + needsBuild(state, status, config) ? + InvalidatedProjectKind.Build : + InvalidatedProjectKind.UpdateBundle, + state, + project, + projectPath, + projectIndex, + config, + buildOrder, + ); + } + + return undefined; + } + + function listEmittedFile({ writeFileName }: SolutionBuilderState, proj: ParsedCommandLine, file: string) { + if (writeFileName && proj.options.listEmittedFiles) { + writeFileName(`TSFILE: ${file}`); + } + } + + function getOldProgram({ options, builderPrograms, compilerHost }: SolutionBuilderState, proj: ResolvedConfigFilePath, parsed: ParsedCommandLine) { + if (options.force) return undefined; + const value = builderPrograms.get(proj); + if (value) return value; + return readBuilderProgram(parsed.options, compilerHost) as any as T; + } + + function afterProgramCreate({ host, watch, builderPrograms }: SolutionBuilderState, proj: ResolvedConfigFilePath, program: T) { + if (host.afterProgramEmitAndDiagnostics) { + host.afterProgramEmitAndDiagnostics(program); + } + if (watch) { + program.releaseProgram(); + builderPrograms.set(proj, program); + } + } + + function buildErrors( + state: SolutionBuilderState, + resolvedPath: ResolvedConfigFilePath, + program: T | undefined, + diagnostics: readonly Diagnostic[], + errorFlags: BuildResultFlags, + errorType: string + ) { + reportAndStoreErrors(state, resolvedPath, diagnostics); + // List files if any other build error using program (emit errors already report files) + if (program && state.writeFileName) listFiles(program, state.writeFileName); + state.projectStatus.set(resolvedPath, { type: UpToDateStatusType.Unbuildable, reason: `${errorType} errors` }); + if (program) afterProgramCreate(state, resolvedPath, program); + state.projectCompilerOptions = state.baseCompilerOptions; + return errorFlags; + } + + function updateModuleResolutionCache( + state: SolutionBuilderState, + proj: ResolvedConfigFileName, + config: ParsedCommandLine + ) { + if (!state.moduleResolutionCache) return; + + // Update module resolution cache if needed + const { moduleResolutionCache } = state; + const projPath = toPath(state, proj); + if (moduleResolutionCache.directoryToModuleNameMap.redirectsMap.size === 0) { + // The own map will be for projectCompilerOptions + Debug.assert(moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.size === 0); + moduleResolutionCache.directoryToModuleNameMap.redirectsMap.set(projPath, moduleResolutionCache.directoryToModuleNameMap.ownMap); + moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.set(projPath, moduleResolutionCache.moduleNameToDirectoryMap.ownMap); + } + else { + // Set correct own map + Debug.assert(moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.size > 0); + + const ref: ResolvedProjectReference = { + sourceFile: config.options.configFile!, + commandLine: config + }; + moduleResolutionCache.directoryToModuleNameMap.setOwnMap(moduleResolutionCache.directoryToModuleNameMap.getOrCreateMapOfCacheRedirects(ref)); + moduleResolutionCache.moduleNameToDirectoryMap.setOwnMap(moduleResolutionCache.moduleNameToDirectoryMap.getOrCreateMapOfCacheRedirects(ref)); + } + moduleResolutionCache.directoryToModuleNameMap.setOwnOptions(config.options); + moduleResolutionCache.moduleNameToDirectoryMap.setOwnOptions(config.options); + } + + function checkConfigFileUpToDateStatus(state: SolutionBuilderState, configFile: string, oldestOutputFileTime: Date, oldestOutputFileName: string): Status.OutOfDateWithSelf | undefined { + // Check tsconfig time + const tsconfigTime = state.host.getModifiedTime(configFile) || missingFileModifiedTime; + if (oldestOutputFileTime < tsconfigTime) { + return { + type: UpToDateStatusType.OutOfDateWithSelf, + outOfDateOutputFileName: oldestOutputFileName, + newerInputFileName: configFile + }; + } + } + + function getUpToDateStatusWorker(state: SolutionBuilderState, project: ParsedCommandLine, resolvedPath: ResolvedConfigFilePath): UpToDateStatus { + let newestInputFileName: string = undefined!; + let newestInputFileTime = minimumDate; + const { host } = state; + // Get timestamps of input files + for (const inputFile of project.fileNames) { + if (!host.fileExists(inputFile)) { + return { + type: UpToDateStatusType.Unbuildable, + reason: `${inputFile} does not exist` + }; + } + + const inputTime = host.getModifiedTime(inputFile) || missingFileModifiedTime; + if (inputTime > newestInputFileTime) { + newestInputFileName = inputFile; + newestInputFileTime = inputTime; + } + } + + // Container if no files are specified in the project + if (!project.fileNames.length && !canJsonReportNoInutFiles(project.raw)) { + return { + type: UpToDateStatusType.ContainerOnly + }; + } + + // Collect the expected outputs of this project + const outputs = getAllProjectOutputs(project, !host.useCaseSensitiveFileNames()); + + // Now see if all outputs are newer than the newest input + let oldestOutputFileName = "(none)"; + let oldestOutputFileTime = maximumDate; + let newestOutputFileName = "(none)"; + let newestOutputFileTime = minimumDate; + let missingOutputFileName: string | undefined; + let newestDeclarationFileContentChangedTime = minimumDate; + let isOutOfDateWithInputs = false; + for (const output of outputs) { + // Output is missing; can stop checking + // Don't immediately return because we can still be upstream-blocked, which is a higher-priority status + if (!host.fileExists(output)) { + missingOutputFileName = output; + break; + } + + const outputTime = host.getModifiedTime(output) || missingFileModifiedTime; + if (outputTime < oldestOutputFileTime) { + oldestOutputFileTime = outputTime; + oldestOutputFileName = output; + } + + // If an output is older than the newest input, we can stop checking + // Don't immediately return because we can still be upstream-blocked, which is a higher-priority status + if (outputTime < newestInputFileTime) { + isOutOfDateWithInputs = true; + break; + } + + if (outputTime > newestOutputFileTime) { + newestOutputFileTime = outputTime; + newestOutputFileName = output; + } + + // Keep track of when the most recent time a .d.ts file was changed. + // In addition to file timestamps, we also keep track of when a .d.ts file + // had its file touched but not had its contents changed - this allows us + // to skip a downstream typecheck + if (isDeclarationFile(output)) { + const outputModifiedTime = host.getModifiedTime(output) || missingFileModifiedTime; + newestDeclarationFileContentChangedTime = newer(newestDeclarationFileContentChangedTime, outputModifiedTime); + } + } + + let pseudoUpToDate = false; + let usesPrepend = false; + let upstreamChangedProject: string | undefined; + if (project.projectReferences) { + state.projectStatus.set(resolvedPath, { type: UpToDateStatusType.ComputingUpstream }); + for (const ref of project.projectReferences) { + usesPrepend = usesPrepend || !!(ref.prepend); + const resolvedRef = resolveProjectReferencePath(ref); + const resolvedRefPath = toResolvedConfigFilePath(state, resolvedRef); + const refStatus = getUpToDateStatus(state, parseConfigFile(state, resolvedRef, resolvedRefPath), resolvedRefPath); + + // Its a circular reference ignore the status of this project + if (refStatus.type === UpToDateStatusType.ComputingUpstream || + refStatus.type === UpToDateStatusType.ContainerOnly) { // Container only ignore this project + continue; + } + + // An upstream project is blocked + if (refStatus.type === UpToDateStatusType.Unbuildable || + refStatus.type === UpToDateStatusType.UpstreamBlocked) { + return { + type: UpToDateStatusType.UpstreamBlocked, + upstreamProjectName: ref.path, + upstreamProjectBlocked: refStatus.type === UpToDateStatusType.UpstreamBlocked + }; + } + + // If the upstream project is out of date, then so are we (someone shouldn't have asked, though?) + if (refStatus.type !== UpToDateStatusType.UpToDate) { + return { + type: UpToDateStatusType.UpstreamOutOfDate, + upstreamProjectName: ref.path + }; + } + + // Check oldest output file name only if there is no missing output file name + if (!missingOutputFileName) { + // If the upstream project's newest file is older than our oldest output, we + // can't be out of date because of it + if (refStatus.newestInputFileTime && refStatus.newestInputFileTime <= oldestOutputFileTime) { + continue; + } + + // If the upstream project has only change .d.ts files, and we've built + // *after* those files, then we're "psuedo up to date" and eligible for a fast rebuild + if (refStatus.newestDeclarationFileContentChangedTime && refStatus.newestDeclarationFileContentChangedTime <= oldestOutputFileTime) { + pseudoUpToDate = true; + upstreamChangedProject = ref.path; + continue; + } + + // We have an output older than an upstream output - we are out of date + Debug.assert(oldestOutputFileName !== undefined, "Should have an oldest output filename here"); + return { + type: UpToDateStatusType.OutOfDateWithUpstream, + outOfDateOutputFileName: oldestOutputFileName, + newerProjectName: ref.path + }; + } + } + } + + if (missingOutputFileName !== undefined) { + return { + type: UpToDateStatusType.OutputMissing, + missingOutputFileName + }; + } + + if (isOutOfDateWithInputs) { + return { + type: UpToDateStatusType.OutOfDateWithSelf, + outOfDateOutputFileName: oldestOutputFileName, + newerInputFileName: newestInputFileName + }; + } + else { + // Check tsconfig time + const configStatus = checkConfigFileUpToDateStatus(state, project.options.configFilePath!, oldestOutputFileTime, oldestOutputFileName); + if (configStatus) return configStatus; + + // Check extended config time + const extendedConfigStatus = forEach(project.options.configFile!.extendedSourceFiles || emptyArray, configFile => checkConfigFileUpToDateStatus(state, configFile, oldestOutputFileTime, oldestOutputFileName)); + if (extendedConfigStatus) return extendedConfigStatus; + } + + if (!state.buildInfoChecked.has(resolvedPath)) { + state.buildInfoChecked.set(resolvedPath, true); + const buildInfoPath = getTsBuildInfoEmitOutputFilePath(project.options); + if (buildInfoPath) { + const value = state.readFileWithCache(buildInfoPath); + const buildInfo = value && getBuildInfo(value); + if (buildInfo && (buildInfo.bundle || buildInfo.program) && buildInfo.version !== version) { + return { + type: UpToDateStatusType.TsVersionOutputOfDate, + version: buildInfo.version + }; + } + } + } + + if (usesPrepend && pseudoUpToDate) { + return { + type: UpToDateStatusType.OutOfDateWithPrepend, + outOfDateOutputFileName: oldestOutputFileName, + newerProjectName: upstreamChangedProject! + }; + } + + // Up to date + return { + type: pseudoUpToDate ? UpToDateStatusType.UpToDateWithUpstreamTypes : UpToDateStatusType.UpToDate, + newestDeclarationFileContentChangedTime, + newestInputFileTime, + newestOutputFileTime, + newestInputFileName, + newestOutputFileName, + oldestOutputFileName + }; + } + + function getUpToDateStatus(state: SolutionBuilderState, project: ParsedCommandLine | undefined, resolvedPath: ResolvedConfigFilePath): UpToDateStatus { + if (project === undefined) { + return { type: UpToDateStatusType.Unbuildable, reason: "File deleted mid-build" }; + } + + const prior = state.projectStatus.get(resolvedPath); + if (prior !== undefined) { + return prior; + } + + const actual = getUpToDateStatusWorker(state, project, resolvedPath); + state.projectStatus.set(resolvedPath, actual); + return actual; + } + + function updateOutputTimestampsWorker(state: SolutionBuilderState, proj: ParsedCommandLine, priorNewestUpdateTime: Date, verboseMessage: DiagnosticMessage, skipOutputs?: FileMap) { + const { host } = state; + const outputs = getAllProjectOutputs(proj, !host.useCaseSensitiveFileNames()); + if (!skipOutputs || outputs.length !== skipOutputs.size) { + let reportVerbose = !!state.options.verbose; + const now = host.now ? host.now() : new Date(); + for (const file of outputs) { + if (skipOutputs && skipOutputs.has(toPath(state, file))) { + continue; + } + + if (reportVerbose) { + reportVerbose = false; + reportStatus(state, verboseMessage, proj.options.configFilePath!); + } + + if (isDeclarationFile(file)) { + priorNewestUpdateTime = newer(priorNewestUpdateTime, host.getModifiedTime(file) || missingFileModifiedTime); + } + + host.setModifiedTime(file, now); + listEmittedFile(state, proj, file); + } + } + + return priorNewestUpdateTime; + } + + function updateOutputTimestamps(state: SolutionBuilderState, proj: ParsedCommandLine, resolvedPath: ResolvedConfigFilePath) { + if (state.options.dry) { + return reportStatus(state, Diagnostics.A_non_dry_build_would_update_timestamps_for_output_of_project_0, proj.options.configFilePath!); + } + const priorNewestUpdateTime = updateOutputTimestampsWorker(state, proj, minimumDate, Diagnostics.Updating_output_timestamps_of_project_0); + state.projectStatus.set(resolvedPath, { + type: UpToDateStatusType.UpToDate, + newestDeclarationFileContentChangedTime: priorNewestUpdateTime, + oldestOutputFileName: getFirstProjectOutput(proj, !state.host.useCaseSensitiveFileNames()) + }); + } + + function queueReferencingProjects( + state: SolutionBuilderState, + project: ResolvedConfigFileName, + projectPath: ResolvedConfigFilePath, + projectIndex: number, + config: ParsedCommandLine, + buildOrder: readonly ResolvedConfigFileName[], + buildResult: BuildResultFlags + ) { + // Queue only if there are no errors + if (buildResult & BuildResultFlags.AnyErrors) return; + // Only composite projects can be referenced by other projects + if (!config.options.composite) return; + // Always use build order to queue projects + for (let index = projectIndex + 1; index < buildOrder.length; index++) { + const nextProject = buildOrder[index]; + const nextProjectPath = toResolvedConfigFilePath(state, nextProject); + if (state.projectPendingBuild.has(nextProjectPath)) continue; + + const nextProjectConfig = parseConfigFile(state, nextProject, nextProjectPath); + if (!nextProjectConfig || !nextProjectConfig.projectReferences) continue; + for (const ref of nextProjectConfig.projectReferences) { + const resolvedRefPath = resolveProjectName(state, ref.path); + if (toResolvedConfigFilePath(state, resolvedRefPath) !== projectPath) continue; + // If the project is referenced with prepend, always build downstream projects, + // If declaration output is changed, build the project + // otherwise mark the project UpToDateWithUpstreamTypes so it updates output time stamps + const status = state.projectStatus.get(nextProjectPath); + if (status) { + switch (status.type) { + case UpToDateStatusType.UpToDate: + if (buildResult & BuildResultFlags.DeclarationOutputUnchanged) { + if (ref.prepend) { + state.projectStatus.set(nextProjectPath, { + type: UpToDateStatusType.OutOfDateWithPrepend, + outOfDateOutputFileName: status.oldestOutputFileName, + newerProjectName: project + }); + } + else { + status.type = UpToDateStatusType.UpToDateWithUpstreamTypes; + } + break; + } + // falls through + + case UpToDateStatusType.UpToDateWithUpstreamTypes: + case UpToDateStatusType.OutOfDateWithPrepend: + if (!(buildResult & BuildResultFlags.DeclarationOutputUnchanged)) { + state.projectStatus.set(nextProjectPath, { + type: UpToDateStatusType.OutOfDateWithUpstream, + outOfDateOutputFileName: status.type === UpToDateStatusType.OutOfDateWithPrepend ? status.outOfDateOutputFileName : status.oldestOutputFileName, + newerProjectName: project + }); + } + break; + + case UpToDateStatusType.UpstreamBlocked: + if (toResolvedConfigFilePath(state, resolveProjectName(state, status.upstreamProjectName)) === projectPath) { + clearProjectStatus(state, nextProjectPath); + } + break; + } + } + addProjToQueue(state, nextProjectPath, ConfigFileProgramReloadLevel.None); + break; + } + } + } + + function build(state: SolutionBuilderState, project?: string, cancellationToken?: CancellationToken, onlyReferences?: boolean): ExitStatus { + const buildOrder = getBuildOrderFor(state, project, onlyReferences); + if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; + + setupInitialBuild(state, cancellationToken); + + let reportQueue = true; + let successfulProjects = 0; + while (true) { + const invalidatedProject = getNextInvalidatedProject(state, buildOrder, reportQueue); + if (!invalidatedProject) break; + reportQueue = false; + invalidatedProject.done(cancellationToken); + if (!state.diagnostics.has(invalidatedProject.projectPath)) successfulProjects++; + } + + disableCache(state); + reportErrorSummary(state, buildOrder); + startWatching(state, buildOrder); + + return isCircularBuildOrder(buildOrder) + ? ExitStatus.ProjectReferenceCycle_OutputsSkipped + : !buildOrder.some(p => state.diagnostics.has(toResolvedConfigFilePath(state, p))) + ? ExitStatus.Success + : successfulProjects + ? ExitStatus.DiagnosticsPresent_OutputsGenerated + : ExitStatus.DiagnosticsPresent_OutputsSkipped; + } + + function clean(state: SolutionBuilderState, project?: string, onlyReferences?: boolean) { + const buildOrder = getBuildOrderFor(state, project, onlyReferences); + if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; + + if (isCircularBuildOrder(buildOrder)) { + reportErrors(state, buildOrder.circularDiagnostics); + return ExitStatus.ProjectReferenceCycle_OutputsSkipped; + } + + const { options, host } = state; + const filesToDelete = options.dry ? [] as string[] : undefined; + for (const proj of buildOrder) { + const resolvedPath = toResolvedConfigFilePath(state, proj); + const parsed = parseConfigFile(state, proj, resolvedPath); + if (parsed === undefined) { + // File has gone missing; fine to ignore here + reportParseConfigFileDiagnostic(state, resolvedPath); + continue; + } + const outputs = getAllProjectOutputs(parsed, !host.useCaseSensitiveFileNames()); + for (const output of outputs) { + if (host.fileExists(output)) { + if (filesToDelete) { + filesToDelete.push(output); + } + else { + host.deleteFile(output); + invalidateProject(state, resolvedPath, ConfigFileProgramReloadLevel.None); + } + } + } + } + + if (filesToDelete) { + reportStatus(state, Diagnostics.A_non_dry_build_would_delete_the_following_files_Colon_0, filesToDelete.map(f => `\r\n * ${f}`).join("")); + } + + return ExitStatus.Success; + } + + function invalidateProject(state: SolutionBuilderState, resolved: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { + // If host implements getParsedCommandLine, we cant get list of files from parseConfigFileHost + if (state.host.getParsedCommandLine && reloadLevel === ConfigFileProgramReloadLevel.Partial) { + reloadLevel = ConfigFileProgramReloadLevel.Full; + } + if (reloadLevel === ConfigFileProgramReloadLevel.Full) { + state.configFileCache.delete(resolved); + state.buildOrder = undefined; + } + state.needsSummary = true; + clearProjectStatus(state, resolved); + addProjToQueue(state, resolved, reloadLevel); + enableCache(state); + } + + function invalidateProjectAndScheduleBuilds(state: SolutionBuilderState, resolvedPath: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { + state.reportFileChangeDetected = true; + invalidateProject(state, resolvedPath, reloadLevel); + scheduleBuildInvalidatedProject(state); + } + + function scheduleBuildInvalidatedProject(state: SolutionBuilderState) { + const { hostWithWatch } = state; + if (!hostWithWatch.setTimeout || !hostWithWatch.clearTimeout) { + return; + } + if (state.timerToBuildInvalidatedProject) { + hostWithWatch.clearTimeout(state.timerToBuildInvalidatedProject); + } + state.timerToBuildInvalidatedProject = hostWithWatch.setTimeout(buildNextInvalidatedProject, 250, state); + } + + function buildNextInvalidatedProject(state: SolutionBuilderState) { + state.timerToBuildInvalidatedProject = undefined; + if (state.reportFileChangeDetected) { + state.reportFileChangeDetected = false; + state.projectErrorsReported.clear(); + reportWatchStatus(state, Diagnostics.File_change_detected_Starting_incremental_compilation); + } + const buildOrder = getBuildOrder(state); + const invalidatedProject = getNextInvalidatedProject(state, buildOrder, /*reportQueue*/ false); + if (invalidatedProject) { + invalidatedProject.done(); + if (state.projectPendingBuild.size) { + // Schedule next project for build + if (state.watch && !state.timerToBuildInvalidatedProject) { + scheduleBuildInvalidatedProject(state); + } + return; + } + } + disableCache(state); + reportErrorSummary(state, buildOrder); + } + + function watchConfigFile(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath) { + if (!state.watch || state.allWatchedConfigFiles.has(resolvedPath)) return; + state.allWatchedConfigFiles.set(resolvedPath, state.watchFile( + state.hostWithWatch, + resolved, + () => { + invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.Full); + }, + PollingInterval.High, + WatchType.ConfigFile, + resolved + )); + } + + function isSameFile(state: SolutionBuilderState, file1: string, file2: string) { + return comparePaths(file1, file2, state.currentDirectory, !state.host.useCaseSensitiveFileNames()) === Comparison.EqualTo; + } + + function isOutputFile(state: SolutionBuilderState, fileName: string, configFile: ParsedCommandLine) { + if (configFile.options.noEmit) return false; + + // ts or tsx files are not output + if (!fileExtensionIs(fileName, Extension.Dts) && + (fileExtensionIs(fileName, Extension.Ts) || fileExtensionIs(fileName, Extension.Tsx))) { + return false; + } + + // If options have --outFile or --out, check if its that + const out = configFile.options.outFile || configFile.options.out; + if (out && (isSameFile(state, fileName, out) || isSameFile(state, fileName, removeFileExtension(out) + Extension.Dts))) { + return true; + } + + // If declarationDir is specified, return if its a file in that directory + if (configFile.options.declarationDir && containsPath(configFile.options.declarationDir, fileName, state.currentDirectory, !state.host.useCaseSensitiveFileNames())) { + return true; + } + + // If --outDir, check if file is in that directory + if (configFile.options.outDir && containsPath(configFile.options.outDir, fileName, state.currentDirectory, !state.host.useCaseSensitiveFileNames())) { + return true; + } + + return !forEach(configFile.fileNames, inputFile => isSameFile(state, fileName, inputFile)); + } + + function watchWildCardDirectories(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) { + if (!state.watch) return; + updateWatchingWildcardDirectories( + getOrCreateValueMapFromConfigFileMap(state.allWatchedWildcardDirectories, resolvedPath), + createMapFromTemplate(parsed.configFileSpecs!.wildcardDirectories), + (dir, flags) => state.watchDirectory( + state.hostWithWatch, + dir, + fileOrDirectory => { + const fileOrDirectoryPath = toPath(state, fileOrDirectory); + if (fileOrDirectoryPath !== toPath(state, dir) && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, parsed.options)) { + state.writeLog(`Project: ${resolved} Detected file add/remove of non supported extension: ${fileOrDirectory}`); + return; + } + + if (isOutputFile(state, fileOrDirectory, parsed)) { + state.writeLog(`${fileOrDirectory} is output file`); + return; + } + + invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.Partial); + }, + flags, + WatchType.WildcardDirectory, + resolved + ) + ); + } + + function watchInputFiles(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) { + if (!state.watch) return; + mutateMap( + getOrCreateValueMapFromConfigFileMap(state.allWatchedInputFiles, resolvedPath), + arrayToMap(parsed.fileNames, fileName => toPath(state, fileName)), + { + createNewValue: (path, input) => state.watchFilePath( + state.hostWithWatch, + input, + () => invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.None), + PollingInterval.Low, + path as Path, + WatchType.SourceFile, + resolved + ), + onDeleteValue: closeFileWatcher, + } + ); + } + + function startWatching(state: SolutionBuilderState, buildOrder: AnyBuildOrder) { + if (!state.watchAllProjectsPending) return; + state.watchAllProjectsPending = false; + for (const resolved of getBuildOrderFromAnyBuildOrder(buildOrder)) { + const resolvedPath = toResolvedConfigFilePath(state, resolved); + // Watch this file + watchConfigFile(state, resolved, resolvedPath); + + const cfg = parseConfigFile(state, resolved, resolvedPath); + if (cfg) { + // Update watchers for wildcard directories + watchWildCardDirectories(state, resolved, resolvedPath, cfg); + + // Watch input files + watchInputFiles(state, resolved, resolvedPath, cfg); + } + } + } + + /** + * A SolutionBuilder has an immutable set of rootNames that are the "entry point" projects, but + * can dynamically add/remove other projects based on changes on the rootNames' references + */ + function createSolutionBuilderWorker(watch: false, host: SolutionBuilderHost, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder; + function createSolutionBuilderWorker(watch: true, host: SolutionBuilderWithWatchHost, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder; + function createSolutionBuilderWorker(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost | SolutionBuilderWithWatchHost, rootNames: readonly string[], options: BuildOptions): SolutionBuilder { + const state = createSolutionBuilderState(watch, hostOrHostWithWatch, rootNames, options); + return { + build: (project, cancellationToken) => build(state, project, cancellationToken), + clean: project => clean(state, project), + buildReferences: (project, cancellationToken) => build(state, project, cancellationToken, /*onlyReferences*/ true), + cleanReferences: project => clean(state, project, /*onlyReferences*/ true), + getNextInvalidatedProject: cancellationToken => { + setupInitialBuild(state, cancellationToken); + return getNextInvalidatedProject(state, getBuildOrder(state), /*reportQueue*/ false); + }, + getBuildOrder: () => getBuildOrder(state), + getUpToDateStatusOfProject: project => { + const configFileName = resolveProjectName(state, project); + const configFilePath = toResolvedConfigFilePath(state, configFileName); + return getUpToDateStatus(state, parseConfigFile(state, configFileName, configFilePath), configFilePath); + }, + invalidateProject: (configFilePath, reloadLevel) => invalidateProject(state, configFilePath, reloadLevel || ConfigFileProgramReloadLevel.None), + buildNextInvalidatedProject: () => buildNextInvalidatedProject(state), + getAllParsedConfigs: () => arrayFrom(mapDefinedIterator( + state.configFileCache.values(), + config => isParsedCommandLine(config) ? config : undefined + )), + }; + } + + function relName(state: SolutionBuilderState, path: string): string { + return convertToRelativePath(path, state.currentDirectory, f => state.getCanonicalFileName(f)); + } + + function reportStatus(state: SolutionBuilderState, message: DiagnosticMessage, ...args: string[]) { + state.host.reportSolutionBuilderStatus(createCompilerDiagnostic(message, ...args)); + } + + function reportWatchStatus(state: SolutionBuilderState, message: DiagnosticMessage, ...args: (string | number | undefined)[]) { + if (state.hostWithWatch.onWatchStatusChange) { + state.hostWithWatch.onWatchStatusChange(createCompilerDiagnostic(message, ...args), state.host.getNewLine(), state.baseCompilerOptions); + } + } + + function reportErrors({ host }: SolutionBuilderState, errors: readonly Diagnostic[]) { + errors.forEach(err => host.reportDiagnostic(err)); + } + + function reportAndStoreErrors(state: SolutionBuilderState, proj: ResolvedConfigFilePath, errors: readonly Diagnostic[]) { + reportErrors(state, errors); + state.projectErrorsReported.set(proj, true); + if (errors.length) { + state.diagnostics.set(proj, errors); + } + } + + function reportParseConfigFileDiagnostic(state: SolutionBuilderState, proj: ResolvedConfigFilePath) { + reportAndStoreErrors(state, proj, [state.configFileCache.get(proj) as Diagnostic]); + } + + function reportErrorSummary(state: SolutionBuilderState, buildOrder: AnyBuildOrder) { + if (!state.needsSummary) return; + state.needsSummary = false; + const canReportSummary = state.watch || !!state.host.reportErrorSummary; + const { diagnostics } = state; + let totalErrors = 0; + if (isCircularBuildOrder(buildOrder)) { + reportBuildQueue(state, buildOrder.buildOrder); + reportErrors(state, buildOrder.circularDiagnostics); + if (canReportSummary) totalErrors += getErrorCountForSummary(buildOrder.circularDiagnostics); + } + else { + // Report errors from the other projects + buildOrder.forEach(project => { + const projectPath = toResolvedConfigFilePath(state, project); + if (!state.projectErrorsReported.has(projectPath)) { + reportErrors(state, diagnostics.get(projectPath) || emptyArray); + } + }); + if (canReportSummary) diagnostics.forEach(singleProjectErrors => totalErrors += getErrorCountForSummary(singleProjectErrors)); + } + + if (state.watch) { + reportWatchStatus(state, getWatchErrorSummaryDiagnosticMessage(totalErrors), totalErrors); + } + else if (state.host.reportErrorSummary) { + state.host.reportErrorSummary(totalErrors); + } + } + + /** + * Report the build ordering inferred from the current project graph if we're in verbose mode + */ + function reportBuildQueue(state: SolutionBuilderState, buildQueue: readonly ResolvedConfigFileName[]) { + if (state.options.verbose) { + reportStatus(state, Diagnostics.Projects_in_this_build_Colon_0, buildQueue.map(s => "\r\n * " + relName(state, s)).join("")); + } + } + + function reportUpToDateStatus(state: SolutionBuilderState, configFileName: string, status: UpToDateStatus) { + switch (status.type) { + case UpToDateStatusType.OutOfDateWithSelf: + return reportStatus( + state, + Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, + relName(state, configFileName), + relName(state, status.outOfDateOutputFileName), + relName(state, status.newerInputFileName) + ); + case UpToDateStatusType.OutOfDateWithUpstream: + return reportStatus( + state, + Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, + relName(state, configFileName), + relName(state, status.outOfDateOutputFileName), + relName(state, status.newerProjectName) + ); + case UpToDateStatusType.OutputMissing: + return reportStatus( + state, + Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, + relName(state, configFileName), + relName(state, status.missingOutputFileName) + ); + case UpToDateStatusType.UpToDate: + if (status.newestInputFileTime !== undefined) { + return reportStatus( + state, + Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, + relName(state, configFileName), + relName(state, status.newestInputFileName || ""), + relName(state, status.oldestOutputFileName || "") + ); + } + // Don't report anything for "up to date because it was already built" -- too verbose + break; + case UpToDateStatusType.OutOfDateWithPrepend: + return reportStatus( + state, + Diagnostics.Project_0_is_out_of_date_because_output_of_its_dependency_1_has_changed, + relName(state, configFileName), + relName(state, status.newerProjectName) + ); + case UpToDateStatusType.UpToDateWithUpstreamTypes: + return reportStatus( + state, + Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies, + relName(state, configFileName) + ); + case UpToDateStatusType.UpstreamOutOfDate: + return reportStatus( + state, + Diagnostics.Project_0_is_out_of_date_because_its_dependency_1_is_out_of_date, + relName(state, configFileName), + relName(state, status.upstreamProjectName) + ); + case UpToDateStatusType.UpstreamBlocked: + return reportStatus( + state, + status.upstreamProjectBlocked ? + Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_was_not_built : + Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_has_errors, + relName(state, configFileName), + relName(state, status.upstreamProjectName) + ); + case UpToDateStatusType.Unbuildable: + return reportStatus( + state, + Diagnostics.Failed_to_parse_file_0_Colon_1, + relName(state, configFileName), + status.reason + ); + case UpToDateStatusType.TsVersionOutputOfDate: + return reportStatus( + state, + Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, + relName(state, configFileName), + status.version, + version + ); + case UpToDateStatusType.ContainerOnly: + // Don't report status on "solution" projects + // falls through + case UpToDateStatusType.ComputingUpstream: + // Should never leak from getUptoDateStatusWorker + break; + default: + assertType(status); + } + } + + /** + * Report the up-to-date status of a project if we're in verbose mode + */ + function verboseReportProjectStatus(state: SolutionBuilderState, configFileName: string, status: UpToDateStatus) { + if (state.options.verbose) { + reportUpToDateStatus(state, configFileName, status); + } + } +} diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index cef7aba0484..ac072239eeb 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -9,8 +9,10 @@ ], "files": [ + "corePublic.ts", "core.ts", "debug.ts", + "performanceTimestamp.ts", "performance.ts", "perfLogger.ts", "semver.ts", @@ -20,6 +22,7 @@ "path.ts", "diagnosticInformationMap.generated.ts", "scanner.ts", + "utilitiesPublic.ts", "utilities.ts", "parser.ts", "commandLineParser.ts", @@ -28,7 +31,9 @@ "binder.ts", "symbolWalker.ts", "checker.ts", + "factoryPublic.ts", "factory.ts", + "visitorPublic.ts", "visitor.ts", "sourcemap.ts", "transformers/utilities.ts", @@ -53,11 +58,15 @@ "emitter.ts", "watchUtilities.ts", "program.ts", + "builderStatePublic.ts", "builderState.ts", "builder.ts", + "builderPublic.ts", "resolutionCache.ts", "moduleSpecifiers.ts", "watch.ts", + "watchPublic.ts", "tsbuild.ts", + "tsbuildPublic.ts", ] } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index a7e3c8c34a0..acd9b65f366 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1,16 +1,3 @@ -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: readonly T[]): SortedReadonlyArray { - return sortAndDeduplicate(diagnostics, compareDiagnostics); - } -} - /* @internal */ namespace ts { export const resolvingEmptyArray: never[] = [] as never[]; @@ -4899,2471 +4886,7 @@ namespace ts { export function getDotOrQuestionDotToken(node: PropertyAccessExpression) { return node.questionDotToken || createNode(SyntaxKind.DotToken, node.expression.end, node.name.pos) as DotToken; } -} -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); // eslint-disable-line prefer-const - - /** - * 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: readonly TextChangeRange[]): 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, parent: Node): node is ParameterPropertyDeclaration { - return hasModifier(node, ModifierFlags.ParameterPropertyModifier) && 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 { - // making clear this is a global mutation! - // eslint-disable-next-line @typescript-eslint/no-unnecessary-qualifier - 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; - } - - /** @internal */ - export function nodeHasName(statement: Node, name: Identifier) { - if (isNamedDeclaration(statement) && isIdentifier(statement.name) && idText(statement.name as Identifier) === idText(name)) { - return true; - } - if (isVariableStatement(statement) && some(statement.declarationList.declarations, d => nodeHasName(d, name))) { - return true; - } - return false; - } - - 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 getElementOrPropertyAccessArgumentExpressionOrName((expr as BinaryExpression).left as AccessExpression); - 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; - } - case SyntaxKind.ElementAccessExpression: - const expr = declaration as ElementAccessExpression; - if (isBindableStaticElementAccessExpression(expr)) { - return expr.argumentExpression; - } - } - 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 (isAccessExpression(node.parent.left)) { - return getElementOrPropertyAccessArgumentExpressionOrName(node.parent.left); - } - } - 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): readonly JSDocParameterTag[] { - 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): readonly JSDocTemplateTag[] { - 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): readonly JSDocTag[] { - 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): readonly JSDocTag[] { - 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): readonly TypeParameterDeclaration[] { - 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 isPropertyAccessChain(node: Node): node is PropertyAccessChain { - return isPropertyAccessExpression(node) && !!(node.flags & NodeFlags.OptionalChain); - } - - export function isElementAccessExpression(node: Node): node is ElementAccessExpression { - return node.kind === SyntaxKind.ElementAccessExpression; - } - - export function isElementAccessChain(node: Node): node is ElementAccessChain { - return isElementAccessExpression(node) && !!(node.flags & NodeFlags.OptionalChain); - } - - export function isCallExpression(node: Node): node is CallExpression { - return node.kind === SyntaxKind.CallExpression; - } - - export function isCallChain(node: Node): node is CallChain { - return isCallExpression(node) && !!(node.flags & NodeFlags.OptionalChain); - } - - export function isOptionalChain(node: Node): node is PropertyAccessChain | ElementAccessChain | CallChain { - const kind = node.kind; - return !!(node.flags & NodeFlags.OptionalChain) && - (kind === SyntaxKind.PropertyAccessExpression - || kind === SyntaxKind.ElementAccessExpression - || kind === SyntaxKind.CallExpression); - } - - /* @internal */ - export function isOptionalChainRoot(node: Node): node is OptionalChainRoot { - return isOptionalChain(node) && !!node.questionDotToken; - } - - /** - * Determines whether a node is the expression preceding an optional chain (i.e. `a` in `a?.b`). - */ - /* @internal */ - export function isExpressionOfOptionalChainRoot(node: Node): node is Expression & { parent: OptionalChainRoot } { - return isOptionalChainRoot(node.parent) && node.parent.expression === node; - } - - /** - * Determines whether a node is the outermost `OptionalChain` in an ECMAScript `OptionalExpression`: - * - * 1. For `a?.b.c`, the outermost chain is `a?.b.c` (`c` is the end of the chain starting at `a?.`) - * 2. For `(a?.b.c).d`, the outermost chain is `a?.b.c` (`c` is the end of the chain starting at `a?.` since parens end the chain) - * 3. For `a?.b.c?.d`, both `a?.b.c` and `a?.b.c?.d` are outermost (`c` is the end of the chain starting at `a?.`, and `d` is - * the end of the chain starting at `c?.`) - * 4. For `a?.(b?.c).d`, both `b?.c` and `a?.(b?.c)d` are outermost (`c` is the end of the chain starting at `b`, and `d` is - * the end of the chain starting at `a?.`) - */ - /* @internal */ - export function isOutermostOptionalChain(node: OptionalChain) { - return !isOptionalChain(node.parent) // cases 1 and 2 - || isOptionalChainRoot(node.parent) // case 3 - || node !== node.parent.expression; // case 4 - } - - export function isNullishCoalesce(node: Node) { - return node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.QuestionQuestionToken; - } - - 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: Node): 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: readonly T[]): 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 isSyntheticReference(node: Node): node is SyntheticReferenceExpression { - return node.kind === SyntaxKind.SyntheticReferenceExpression; - } - - /* @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 isScopeMarker(node: Node) { - return isExportAssignment(node) || isExportDeclaration(node); - } - - /* @internal */ - export function hasScopeMarker(statements: readonly Statement[]) { - return some(statements, isScopeMarker); - } - - /* @internal */ - export function needsScopeMarker(result: Statement) { - return !isAnyImportOrReExport(result) && !isExportAssignment(result) && !hasModifier(result, ModifierFlags.Export) && !isAmbientModule(result); - } - - /* @internal */ - export function isExternalModuleIndicator(result: Statement) { - // Exported top-level member indicates moduleness - return isAnyImportOrReExport(result) || isExportAssignment(result) || hasModifier(result, ModifierFlags.Export); - } - - /* @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; } @@ -7734,10 +5257,7 @@ namespace ts { function isNodeModulesOrScopedPackageDirectory(s: string, getCanonicalFileName: GetCanonicalFileName): boolean { return getCanonicalFileName(s) === "node_modules" || startsWith(s, "@"); } -} -/* @internal */ -namespace ts { function stripLeadingDirectorySeparator(s: string): string | undefined { return isAnyDirectorySeparator(s.charCodeAt(0)) ? s.slice(1) : undefined; } diff --git a/src/compiler/utilitiesPublic.ts b/src/compiler/utilitiesPublic.ts new file mode 100644 index 00000000000..c57a9087385 --- /dev/null +++ b/src/compiler/utilitiesPublic.ts @@ -0,0 +1,2473 @@ +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: readonly T[]): SortedReadonlyArray { + return sortAndDeduplicate(diagnostics, compareDiagnostics); + } + + 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); // eslint-disable-line prefer-const + + /** + * 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: readonly TextChangeRange[]): 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, parent: Node): node is ParameterPropertyDeclaration { + return hasModifier(node, ModifierFlags.ParameterPropertyModifier) && 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 { + // making clear this is a global mutation! + // eslint-disable-next-line @typescript-eslint/no-unnecessary-qualifier + 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; + } + + /** @internal */ + export function nodeHasName(statement: Node, name: Identifier) { + if (isNamedDeclaration(statement) && isIdentifier(statement.name) && idText(statement.name as Identifier) === idText(name)) { + return true; + } + if (isVariableStatement(statement) && some(statement.declarationList.declarations, d => nodeHasName(d, name))) { + return true; + } + return false; + } + + 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 getElementOrPropertyAccessArgumentExpressionOrName((expr as BinaryExpression).left as AccessExpression); + 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; + } + case SyntaxKind.ElementAccessExpression: + const expr = declaration as ElementAccessExpression; + if (isBindableStaticElementAccessExpression(expr)) { + return expr.argumentExpression; + } + } + 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 (isAccessExpression(node.parent.left)) { + return getElementOrPropertyAccessArgumentExpressionOrName(node.parent.left); + } + } + 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): readonly JSDocParameterTag[] { + 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): readonly JSDocTemplateTag[] { + 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): readonly JSDocTag[] { + 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): readonly JSDocTag[] { + 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): readonly TypeParameterDeclaration[] { + 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; + } + + // #region + // Simple node tests of the form `node.kind === SyntaxKind.Foo`. + // 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 isPropertyAccessChain(node: Node): node is PropertyAccessChain { + return isPropertyAccessExpression(node) && !!(node.flags & NodeFlags.OptionalChain); + } + + export function isElementAccessExpression(node: Node): node is ElementAccessExpression { + return node.kind === SyntaxKind.ElementAccessExpression; + } + + export function isElementAccessChain(node: Node): node is ElementAccessChain { + return isElementAccessExpression(node) && !!(node.flags & NodeFlags.OptionalChain); + } + + export function isCallExpression(node: Node): node is CallExpression { + return node.kind === SyntaxKind.CallExpression; + } + + export function isCallChain(node: Node): node is CallChain { + return isCallExpression(node) && !!(node.flags & NodeFlags.OptionalChain); + } + + export function isOptionalChain(node: Node): node is PropertyAccessChain | ElementAccessChain | CallChain { + const kind = node.kind; + return !!(node.flags & NodeFlags.OptionalChain) && + (kind === SyntaxKind.PropertyAccessExpression + || kind === SyntaxKind.ElementAccessExpression + || kind === SyntaxKind.CallExpression); + } + + /* @internal */ + export function isOptionalChainRoot(node: Node): node is OptionalChainRoot { + return isOptionalChain(node) && !!node.questionDotToken; + } + + /** + * Determines whether a node is the expression preceding an optional chain (i.e. `a` in `a?.b`). + */ + /* @internal */ + export function isExpressionOfOptionalChainRoot(node: Node): node is Expression & { parent: OptionalChainRoot } { + return isOptionalChainRoot(node.parent) && node.parent.expression === node; + } + + /** + * Determines whether a node is the outermost `OptionalChain` in an ECMAScript `OptionalExpression`: + * + * 1. For `a?.b.c`, the outermost chain is `a?.b.c` (`c` is the end of the chain starting at `a?.`) + * 2. For `(a?.b.c).d`, the outermost chain is `a?.b.c` (`c` is the end of the chain starting at `a?.` since parens end the chain) + * 3. For `a?.b.c?.d`, both `a?.b.c` and `a?.b.c?.d` are outermost (`c` is the end of the chain starting at `a?.`, and `d` is + * the end of the chain starting at `c?.`) + * 4. For `a?.(b?.c).d`, both `b?.c` and `a?.(b?.c)d` are outermost (`c` is the end of the chain starting at `b`, and `d` is + * the end of the chain starting at `a?.`) + */ + /* @internal */ + export function isOutermostOptionalChain(node: OptionalChain) { + return !isOptionalChain(node.parent) // cases 1 and 2 + || isOptionalChainRoot(node.parent) // case 3 + || node !== node.parent.expression; // case 4 + } + + export function isNullishCoalesce(node: Node) { + return node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.QuestionQuestionToken; + } + + 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: Node): 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; + } + + // #endregion + + // #region + // Node tests + // + // All node tests in the following list should *not* reference parent pointers so that + // they may be used with transformations. + /* @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: readonly T[]): 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 isSyntheticReference(node: Node): node is SyntheticReferenceExpression { + return node.kind === SyntaxKind.SyntheticReferenceExpression; + } + + /* @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 isScopeMarker(node: Node) { + return isExportAssignment(node) || isExportDeclaration(node); + } + + /* @internal */ + export function hasScopeMarker(statements: readonly Statement[]) { + return some(statements, isScopeMarker); + } + + /* @internal */ + export function needsScopeMarker(result: Statement) { + return !isAnyImportOrReExport(result) && !isExportAssignment(result) && !hasModifier(result, ModifierFlags.Export) && !isAmbientModule(result); + } + + /* @internal */ + export function isExternalModuleIndicator(result: Statement) { + // Exported top-level member indicates moduleness + return isAnyImportOrReExport(result) || isExportAssignment(result) || hasModifier(result, ModifierFlags.Export); + } + + /* @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; + } + + // #endregion +} \ No newline at end of file diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index f9ce78293e4..cbbd3c19241 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -1,957 +1,3 @@ -namespace ts { - const isTypeNodeOrTypeParameterDeclaration = or(isTypeNode, isTypeParameterDeclaration); - - /** - * Visits a Node using the supplied visitor, possibly returning a new Node in its place. - * - * @param node The Node to visit. - * @param visitor The callback used to visit the Node. - * @param test A callback to execute to verify the Node is valid. - * @param lift An optional callback to execute to lift a NodeArray into a valid Node. - */ - export function visitNode(node: T | undefined, visitor: Visitor | undefined, test?: (node: Node) => boolean, lift?: (node: NodeArray) => T): T; - - /** - * Visits a Node using the supplied visitor, possibly returning a new Node in its place. - * - * @param node The Node to visit. - * @param visitor The callback used to visit the Node. - * @param test A callback to execute to verify the Node is valid. - * @param lift An optional callback to execute to lift a NodeArray into a valid Node. - */ - export function visitNode(node: T | undefined, visitor: Visitor | undefined, test?: (node: Node) => boolean, lift?: (node: NodeArray) => T): T | undefined; - - export function visitNode(node: T | undefined, visitor: Visitor | undefined, test?: (node: Node) => boolean, lift?: (node: NodeArray) => T): T | undefined { - if (node === undefined || visitor === undefined) { - return node; - } - - aggregateTransformFlags(node); - const visited = visitor(node); - if (visited === node) { - return node; - } - - let visitedNode: Node | undefined; - if (visited === undefined) { - return undefined; - } - else if (isArray(visited)) { - visitedNode = (lift || extractSingleNode)(visited); - } - else { - visitedNode = visited; - } - - Debug.assertNode(visitedNode, test); - aggregateTransformFlags(visitedNode!); - return visitedNode; - } - - /** - * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. - * - * @param nodes The NodeArray to visit. - * @param visitor The callback used to visit a Node. - * @param test A node test to execute for each node. - * @param start An optional value indicating the starting offset at which to start visiting. - * @param count An optional value indicating the maximum number of nodes to visit. - */ - export function visitNodes(nodes: NodeArray | undefined, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray; - - /** - * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. - * - * @param nodes The NodeArray to visit. - * @param visitor The callback used to visit a Node. - * @param test A node test to execute for each node. - * @param start An optional value indicating the starting offset at which to start visiting. - * @param count An optional value indicating the maximum number of nodes to visit. - */ - export function visitNodes(nodes: NodeArray | undefined, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray | undefined; - - /** - * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. - * - * @param nodes The NodeArray to visit. - * @param visitor The callback used to visit a Node. - * @param test A node test to execute for each node. - * @param start An optional value indicating the starting offset at which to start visiting. - * @param count An optional value indicating the maximum number of nodes to visit. - */ - export function visitNodes(nodes: NodeArray | undefined, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray | undefined { - if (nodes === undefined || visitor === undefined) { - return nodes; - } - - let updated: MutableNodeArray | undefined; - - // Ensure start and count have valid values - const length = nodes.length; - if (start === undefined || start < 0) { - start = 0; - } - - if (count === undefined || count > length - start) { - count = length - start; - } - - if (start > 0 || count < length) { - // If we are not visiting all of the original nodes, we must always create a new array. - // Since this is a fragment of a node array, we do not copy over the previous location - // and will only copy over `hasTrailingComma` if we are including the last element. - updated = createNodeArray([], /*hasTrailingComma*/ nodes.hasTrailingComma && start + count === length); - } - - // Visit each original node. - for (let i = 0; i < count; i++) { - const node = nodes[i + start]; - aggregateTransformFlags(node); - const visited = node !== undefined ? visitor(node) : undefined; - if (updated !== undefined || visited === undefined || visited !== node) { - if (updated === undefined) { - // Ensure we have a copy of `nodes`, up to the current index. - updated = createNodeArray(nodes.slice(0, i), nodes.hasTrailingComma); - setTextRange(updated, nodes); - } - if (visited) { - if (isArray(visited)) { - for (const visitedNode of visited) { - Debug.assertNode(visitedNode, test); - aggregateTransformFlags(visitedNode); - updated.push(visitedNode); - } - } - else { - Debug.assertNode(visited, test); - aggregateTransformFlags(visited); - updated.push(visited); - } - } - } - } - - return updated || nodes; - } - - /** - * Starts a new lexical environment and visits a statement list, ending the lexical environment - * and merging hoisted declarations upon completion. - */ - export function visitLexicalEnvironment(statements: NodeArray, visitor: Visitor, context: TransformationContext, start?: number, ensureUseStrict?: boolean) { - context.startLexicalEnvironment(); - statements = visitNodes(statements, visitor, isStatement, start); - if (ensureUseStrict) statements = ts.ensureUseStrict(statements); // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier - return mergeLexicalEnvironment(statements, context.endLexicalEnvironment()); - } - - /** - * Starts a new lexical environment and visits a parameter list, suspending the lexical - * environment upon completion. - */ - export function visitParameterList(nodes: NodeArray | undefined, visitor: Visitor, context: TransformationContext, nodesVisitor = visitNodes) { - context.startLexicalEnvironment(); - const updated = nodesVisitor(nodes, visitor, isParameterDeclaration); - context.suspendLexicalEnvironment(); - return updated; - } - - /** - * Resumes a suspended lexical environment and visits a function body, ending the lexical - * environment and merging hoisted declarations upon completion. - */ - export function visitFunctionBody(node: FunctionBody, visitor: Visitor, context: TransformationContext): FunctionBody; - /** - * Resumes a suspended lexical environment and visits a function body, ending the lexical - * environment and merging hoisted declarations upon completion. - */ - export function visitFunctionBody(node: FunctionBody | undefined, visitor: Visitor, context: TransformationContext): FunctionBody | undefined; - /** - * Resumes a suspended lexical environment and visits a concise body, ending the lexical - * environment and merging hoisted declarations upon completion. - */ - export function visitFunctionBody(node: ConciseBody, visitor: Visitor, context: TransformationContext): ConciseBody; - export function visitFunctionBody(node: ConciseBody | undefined, visitor: Visitor, context: TransformationContext): ConciseBody | undefined { - context.resumeLexicalEnvironment(); - const updated = visitNode(node, visitor, isConciseBody); - const declarations = context.endLexicalEnvironment(); - if (some(declarations)) { - const block = convertToFunctionBody(updated); - const statements = mergeLexicalEnvironment(block.statements, declarations); - return updateBlock(block, statements); - } - return updated; - } - - /** - * Visits each child of a Node using the supplied visitor, possibly returning a new Node of the same kind in its place. - * - * @param node The Node whose children will be visited. - * @param visitor The callback used to visit each child. - * @param context A lexical environment context for the visitor. - */ - export function visitEachChild(node: T, visitor: Visitor, context: TransformationContext): T; - - /** - * Visits each child of a Node using the supplied visitor, possibly returning a new Node of the same kind in its place. - * - * @param node The Node whose children will be visited. - * @param visitor The callback used to visit each child. - * @param context A lexical environment context for the visitor. - */ - export function visitEachChild(node: T | undefined, visitor: Visitor, context: TransformationContext, nodesVisitor?: typeof visitNodes, tokenVisitor?: Visitor): T | undefined; - - export function visitEachChild(node: Node | undefined, visitor: Visitor, context: TransformationContext, nodesVisitor = visitNodes, tokenVisitor?: Visitor): Node | undefined { - if (node === undefined) { - return undefined; - } - - const kind = node.kind; - - // No need to visit nodes with no children. - if ((kind > SyntaxKind.FirstToken && kind <= SyntaxKind.LastToken) || kind === SyntaxKind.ThisType) { - return node; - } - - switch (kind) { - // Names - - case SyntaxKind.Identifier: - return updateIdentifier(node, nodesVisitor((node).typeArguments, visitor, isTypeNodeOrTypeParameterDeclaration)); - - case SyntaxKind.QualifiedName: - return updateQualifiedName(node, - visitNode((node).left, visitor, isEntityName), - visitNode((node).right, visitor, isIdentifier)); - - case SyntaxKind.ComputedPropertyName: - return updateComputedPropertyName(node, - visitNode((node).expression, visitor, isExpression)); - - // Signature elements - case SyntaxKind.TypeParameter: - return updateTypeParameterDeclaration(node, - visitNode((node).name, visitor, isIdentifier), - visitNode((node).constraint, visitor, isTypeNode), - visitNode((node).default, visitor, isTypeNode)); - - case SyntaxKind.Parameter: - return updateParameter(node, - nodesVisitor((node).decorators, visitor, isDecorator), - nodesVisitor((node).modifiers, visitor, isModifier), - visitNode((node).dotDotDotToken, tokenVisitor, isToken), - visitNode((node).name, visitor, isBindingName), - visitNode((node).questionToken, tokenVisitor, isToken), - visitNode((node).type, visitor, isTypeNode), - visitNode((node).initializer, visitor, isExpression)); - - case SyntaxKind.Decorator: - return updateDecorator(node, - visitNode((node).expression, visitor, isExpression)); - - // Type elements - case SyntaxKind.PropertySignature: - return updatePropertySignature((node), - nodesVisitor((node).modifiers, visitor, isToken), - visitNode((node).name, visitor, isPropertyName), - visitNode((node).questionToken, tokenVisitor, isToken), - visitNode((node).type, visitor, isTypeNode), - visitNode((node).initializer, visitor, isExpression)); - - case SyntaxKind.PropertyDeclaration: - return updateProperty(node, - nodesVisitor((node).decorators, visitor, isDecorator), - nodesVisitor((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isPropertyName), - // QuestionToken and ExclamationToken is uniqued in Property Declaration and the signature of 'updateProperty' is that too - visitNode((node).questionToken || (node).exclamationToken, tokenVisitor, isToken), - visitNode((node).type, visitor, isTypeNode), - visitNode((node).initializer, visitor, isExpression)); - - case SyntaxKind.MethodSignature: - return updateMethodSignature(node, - nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), - nodesVisitor((node).parameters, visitor, isParameterDeclaration), - visitNode((node).type, visitor, isTypeNode), - visitNode((node).name, visitor, isPropertyName), - visitNode((node).questionToken, tokenVisitor, isToken)); - - case SyntaxKind.MethodDeclaration: - return updateMethod(node, - nodesVisitor((node).decorators, visitor, isDecorator), - nodesVisitor((node).modifiers, visitor, isModifier), - visitNode((node).asteriskToken, tokenVisitor, isToken), - visitNode((node).name, visitor, isPropertyName), - visitNode((node).questionToken, tokenVisitor, isToken), - nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), - visitParameterList((node).parameters, visitor, context, nodesVisitor), - visitNode((node).type, visitor, isTypeNode), - visitFunctionBody((node).body!, visitor, context)); - - case SyntaxKind.Constructor: - return updateConstructor(node, - nodesVisitor((node).decorators, visitor, isDecorator), - nodesVisitor((node).modifiers, visitor, isModifier), - visitParameterList((node).parameters, visitor, context, nodesVisitor), - visitFunctionBody((node).body!, visitor, context)); - - case SyntaxKind.GetAccessor: - return updateGetAccessor(node, - nodesVisitor((node).decorators, visitor, isDecorator), - nodesVisitor((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isPropertyName), - visitParameterList((node).parameters, visitor, context, nodesVisitor), - visitNode((node).type, visitor, isTypeNode), - visitFunctionBody((node).body!, visitor, context)); - - case SyntaxKind.SetAccessor: - return updateSetAccessor(node, - nodesVisitor((node).decorators, visitor, isDecorator), - nodesVisitor((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isPropertyName), - visitParameterList((node).parameters, visitor, context, nodesVisitor), - visitFunctionBody((node).body!, visitor, context)); - - case SyntaxKind.CallSignature: - return updateCallSignature(node, - nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), - nodesVisitor((node).parameters, visitor, isParameterDeclaration), - visitNode((node).type, visitor, isTypeNode)); - - case SyntaxKind.ConstructSignature: - return updateConstructSignature(node, - nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), - nodesVisitor((node).parameters, visitor, isParameterDeclaration), - visitNode((node).type, visitor, isTypeNode)); - - case SyntaxKind.IndexSignature: - return updateIndexSignature(node, - nodesVisitor((node).decorators, visitor, isDecorator), - nodesVisitor((node).modifiers, visitor, isModifier), - nodesVisitor((node).parameters, visitor, isParameterDeclaration), - visitNode((node).type, visitor, isTypeNode)); - - // Types - case SyntaxKind.TypePredicate: - return updateTypePredicateNodeWithModifier(node, - visitNode((node).assertsModifier, visitor), - visitNode((node).parameterName, visitor), - visitNode((node).type, visitor, isTypeNode)); - - case SyntaxKind.TypeReference: - return updateTypeReferenceNode(node, - visitNode((node).typeName, visitor, isEntityName), - nodesVisitor((node).typeArguments, visitor, isTypeNode)); - - case SyntaxKind.FunctionType: - return updateFunctionTypeNode(node, - nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), - nodesVisitor((node).parameters, visitor, isParameterDeclaration), - visitNode((node).type, visitor, isTypeNode)); - - case SyntaxKind.ConstructorType: - return updateConstructorTypeNode(node, - nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), - nodesVisitor((node).parameters, visitor, isParameterDeclaration), - visitNode((node).type, visitor, isTypeNode)); - - case SyntaxKind.TypeQuery: - return updateTypeQueryNode((node), - visitNode((node).exprName, visitor, isEntityName)); - - case SyntaxKind.TypeLiteral: - return updateTypeLiteralNode((node), - nodesVisitor((node).members, visitor, isTypeElement)); - - case SyntaxKind.ArrayType: - return updateArrayTypeNode(node, - visitNode((node).elementType, visitor, isTypeNode)); - - case SyntaxKind.TupleType: - return updateTupleTypeNode((node), - nodesVisitor((node).elementTypes, visitor, isTypeNode)); - - case SyntaxKind.OptionalType: - return updateOptionalTypeNode((node), - visitNode((node).type, visitor, isTypeNode)); - - case SyntaxKind.RestType: - return updateRestTypeNode((node), - visitNode((node).type, visitor, isTypeNode)); - - case SyntaxKind.UnionType: - return updateUnionTypeNode(node, - nodesVisitor((node).types, visitor, isTypeNode)); - - case SyntaxKind.IntersectionType: - return updateIntersectionTypeNode(node, - nodesVisitor((node).types, visitor, isTypeNode)); - - case SyntaxKind.ConditionalType: - return updateConditionalTypeNode(node, - visitNode((node).checkType, visitor, isTypeNode), - visitNode((node).extendsType, visitor, isTypeNode), - visitNode((node).trueType, visitor, isTypeNode), - visitNode((node).falseType, visitor, isTypeNode)); - - case SyntaxKind.InferType: - return updateInferTypeNode(node, - visitNode((node).typeParameter, visitor, isTypeParameterDeclaration)); - - case SyntaxKind.ImportType: - return updateImportTypeNode(node, - visitNode((node).argument, visitor, isTypeNode), - visitNode((node).qualifier, visitor, isEntityName), - visitNodes((node).typeArguments, visitor, isTypeNode), - (node).isTypeOf - ); - - case SyntaxKind.ParenthesizedType: - return updateParenthesizedType(node, - visitNode((node).type, visitor, isTypeNode)); - - case SyntaxKind.TypeOperator: - return updateTypeOperatorNode(node, - visitNode((node).type, visitor, isTypeNode)); - - case SyntaxKind.IndexedAccessType: - return updateIndexedAccessTypeNode((node), - visitNode((node).objectType, visitor, isTypeNode), - visitNode((node).indexType, visitor, isTypeNode)); - - case SyntaxKind.MappedType: - return updateMappedTypeNode((node), - visitNode((node).readonlyToken, tokenVisitor, isToken), - visitNode((node).typeParameter, visitor, isTypeParameterDeclaration), - visitNode((node).questionToken, tokenVisitor, isToken), - visitNode((node).type, visitor, isTypeNode)); - - case SyntaxKind.LiteralType: - return updateLiteralTypeNode(node, - visitNode((node).literal, visitor, isExpression)); - - // Binding patterns - case SyntaxKind.ObjectBindingPattern: - return updateObjectBindingPattern(node, - nodesVisitor((node).elements, visitor, isBindingElement)); - - case SyntaxKind.ArrayBindingPattern: - return updateArrayBindingPattern(node, - nodesVisitor((node).elements, visitor, isArrayBindingElement)); - - case SyntaxKind.BindingElement: - return updateBindingElement(node, - visitNode((node).dotDotDotToken, tokenVisitor, isToken), - visitNode((node).propertyName, visitor, isPropertyName), - visitNode((node).name, visitor, isBindingName), - visitNode((node).initializer, visitor, isExpression)); - - // Expression - case SyntaxKind.ArrayLiteralExpression: - return updateArrayLiteral(node, - nodesVisitor((node).elements, visitor, isExpression)); - - case SyntaxKind.ObjectLiteralExpression: - return updateObjectLiteral(node, - nodesVisitor((node).properties, visitor, isObjectLiteralElementLike)); - - case SyntaxKind.PropertyAccessExpression: - if (node.flags & NodeFlags.OptionalChain) { - return updatePropertyAccessChain(node, - visitNode((node).expression, visitor, isExpression), - visitNode((node).questionDotToken, visitor, isToken), - visitNode((node).name, visitor, isIdentifier)); - } - return updatePropertyAccess(node, - visitNode((node).expression, visitor, isExpression), - visitNode((node).name, visitor, isIdentifier)); - - case SyntaxKind.ElementAccessExpression: - if (node.flags & NodeFlags.OptionalChain) { - return updateElementAccessChain(node, - visitNode((node).expression, visitor, isExpression), - visitNode((node).questionDotToken, visitor, isToken), - visitNode((node).argumentExpression, visitor, isExpression)); - } - return updateElementAccess(node, - visitNode((node).expression, visitor, isExpression), - visitNode((node).argumentExpression, visitor, isExpression)); - - case SyntaxKind.CallExpression: - if (node.flags & NodeFlags.OptionalChain) { - return updateCallChain(node, - visitNode((node).expression, visitor, isExpression), - visitNode((node).questionDotToken, visitor, isToken), - nodesVisitor((node).typeArguments, visitor, isTypeNode), - nodesVisitor((node).arguments, visitor, isExpression)); - } - return updateCall(node, - visitNode((node).expression, visitor, isExpression), - nodesVisitor((node).typeArguments, visitor, isTypeNode), - nodesVisitor((node).arguments, visitor, isExpression)); - - case SyntaxKind.NewExpression: - return updateNew(node, - visitNode((node).expression, visitor, isExpression), - nodesVisitor((node).typeArguments, visitor, isTypeNode), - nodesVisitor((node).arguments, visitor, isExpression)); - - case SyntaxKind.TaggedTemplateExpression: - return updateTaggedTemplate(node, - visitNode((node).tag, visitor, isExpression), - visitNodes((node).typeArguments, visitor, isExpression), - visitNode((node).template, visitor, isTemplateLiteral)); - - case SyntaxKind.TypeAssertionExpression: - return updateTypeAssertion(node, - visitNode((node).type, visitor, isTypeNode), - visitNode((node).expression, visitor, isExpression)); - - case SyntaxKind.ParenthesizedExpression: - return updateParen(node, - visitNode((node).expression, visitor, isExpression)); - - case SyntaxKind.FunctionExpression: - return updateFunctionExpression(node, - nodesVisitor((node).modifiers, visitor, isModifier), - visitNode((node).asteriskToken, tokenVisitor, isToken), - visitNode((node).name, visitor, isIdentifier), - nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), - visitParameterList((node).parameters, visitor, context, nodesVisitor), - visitNode((node).type, visitor, isTypeNode), - visitFunctionBody((node).body, visitor, context)); - - case SyntaxKind.ArrowFunction: - return updateArrowFunction(node, - nodesVisitor((node).modifiers, visitor, isModifier), - nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), - visitParameterList((node).parameters, visitor, context, nodesVisitor), - visitNode((node).type, visitor, isTypeNode), - visitNode((node).equalsGreaterThanToken, visitor, isToken), - visitFunctionBody((node).body, visitor, context)); - - case SyntaxKind.DeleteExpression: - return updateDelete(node, - visitNode((node).expression, visitor, isExpression)); - - case SyntaxKind.TypeOfExpression: - return updateTypeOf(node, - visitNode((node).expression, visitor, isExpression)); - - case SyntaxKind.VoidExpression: - return updateVoid(node, - visitNode((node).expression, visitor, isExpression)); - - case SyntaxKind.AwaitExpression: - return updateAwait(node, - visitNode((node).expression, visitor, isExpression)); - - case SyntaxKind.PrefixUnaryExpression: - return updatePrefix(node, - visitNode((node).operand, visitor, isExpression)); - - case SyntaxKind.PostfixUnaryExpression: - return updatePostfix(node, - visitNode((node).operand, visitor, isExpression)); - - case SyntaxKind.BinaryExpression: - return updateBinary(node, - visitNode((node).left, visitor, isExpression), - visitNode((node).right, visitor, isExpression), - visitNode((node).operatorToken, visitor, isToken)); - - case SyntaxKind.ConditionalExpression: - return updateConditional(node, - visitNode((node).condition, visitor, isExpression), - visitNode((node).questionToken, visitor, isToken), - visitNode((node).whenTrue, visitor, isExpression), - visitNode((node).colonToken, visitor, isToken), - visitNode((node).whenFalse, visitor, isExpression)); - - case SyntaxKind.TemplateExpression: - return updateTemplateExpression(node, - visitNode((node).head, visitor, isTemplateHead), - nodesVisitor((node).templateSpans, visitor, isTemplateSpan)); - - case SyntaxKind.YieldExpression: - return updateYield(node, - visitNode((node).asteriskToken, tokenVisitor, isToken), - visitNode((node).expression, visitor, isExpression)); - - case SyntaxKind.SpreadElement: - return updateSpread(node, - visitNode((node).expression, visitor, isExpression)); - - case SyntaxKind.ClassExpression: - return updateClassExpression(node, - nodesVisitor((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isIdentifier), - nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), - nodesVisitor((node).heritageClauses, visitor, isHeritageClause), - nodesVisitor((node).members, visitor, isClassElement)); - - case SyntaxKind.ExpressionWithTypeArguments: - return updateExpressionWithTypeArguments(node, - nodesVisitor((node).typeArguments, visitor, isTypeNode), - visitNode((node).expression, visitor, isExpression)); - - case SyntaxKind.AsExpression: - return updateAsExpression(node, - visitNode((node).expression, visitor, isExpression), - visitNode((node).type, visitor, isTypeNode)); - - case SyntaxKind.NonNullExpression: - return updateNonNullExpression(node, - visitNode((node).expression, visitor, isExpression)); - - case SyntaxKind.MetaProperty: - return updateMetaProperty(node, - visitNode((node).name, visitor, isIdentifier)); - - // Misc - case SyntaxKind.TemplateSpan: - return updateTemplateSpan(node, - visitNode((node).expression, visitor, isExpression), - visitNode((node).literal, visitor, isTemplateMiddleOrTemplateTail)); - - // Element - case SyntaxKind.Block: - return updateBlock(node, - nodesVisitor((node).statements, visitor, isStatement)); - - case SyntaxKind.VariableStatement: - return updateVariableStatement(node, - nodesVisitor((node).modifiers, visitor, isModifier), - visitNode((node).declarationList, visitor, isVariableDeclarationList)); - - case SyntaxKind.ExpressionStatement: - return updateExpressionStatement(node, - visitNode((node).expression, visitor, isExpression)); - - case SyntaxKind.IfStatement: - return updateIf(node, - visitNode((node).expression, visitor, isExpression), - visitNode((node).thenStatement, visitor, isStatement, liftToBlock), - visitNode((node).elseStatement, visitor, isStatement, liftToBlock)); - - case SyntaxKind.DoStatement: - return updateDo(node, - visitNode((node).statement, visitor, isStatement, liftToBlock), - visitNode((node).expression, visitor, isExpression)); - - case SyntaxKind.WhileStatement: - return updateWhile(node, - visitNode((node).expression, visitor, isExpression), - visitNode((node).statement, visitor, isStatement, liftToBlock)); - - case SyntaxKind.ForStatement: - return updateFor(node, - visitNode((node).initializer, visitor, isForInitializer), - visitNode((node).condition, visitor, isExpression), - visitNode((node).incrementor, visitor, isExpression), - visitNode((node).statement, visitor, isStatement, liftToBlock)); - - case SyntaxKind.ForInStatement: - return updateForIn(node, - visitNode((node).initializer, visitor, isForInitializer), - visitNode((node).expression, visitor, isExpression), - visitNode((node).statement, visitor, isStatement, liftToBlock)); - - case SyntaxKind.ForOfStatement: - return updateForOf(node, - visitNode((node).awaitModifier, visitor, isToken), - visitNode((node).initializer, visitor, isForInitializer), - visitNode((node).expression, visitor, isExpression), - visitNode((node).statement, visitor, isStatement, liftToBlock)); - - case SyntaxKind.ContinueStatement: - return updateContinue(node, - visitNode((node).label, visitor, isIdentifier)); - - case SyntaxKind.BreakStatement: - return updateBreak(node, - visitNode((node).label, visitor, isIdentifier)); - - case SyntaxKind.ReturnStatement: - return updateReturn(node, - visitNode((node).expression, visitor, isExpression)); - - case SyntaxKind.WithStatement: - return updateWith(node, - visitNode((node).expression, visitor, isExpression), - visitNode((node).statement, visitor, isStatement, liftToBlock)); - - case SyntaxKind.SwitchStatement: - return updateSwitch(node, - visitNode((node).expression, visitor, isExpression), - visitNode((node).caseBlock, visitor, isCaseBlock)); - - case SyntaxKind.LabeledStatement: - return updateLabel(node, - visitNode((node).label, visitor, isIdentifier), - visitNode((node).statement, visitor, isStatement, liftToBlock)); - - case SyntaxKind.ThrowStatement: - return updateThrow(node, - visitNode((node).expression, visitor, isExpression)); - - case SyntaxKind.TryStatement: - return updateTry(node, - visitNode((node).tryBlock, visitor, isBlock), - visitNode((node).catchClause, visitor, isCatchClause), - visitNode((node).finallyBlock, visitor, isBlock)); - - case SyntaxKind.VariableDeclaration: - return updateTypeScriptVariableDeclaration(node, - visitNode((node).name, visitor, isBindingName), - visitNode((node).exclamationToken, tokenVisitor, isToken), - visitNode((node).type, visitor, isTypeNode), - visitNode((node).initializer, visitor, isExpression)); - - case SyntaxKind.VariableDeclarationList: - return updateVariableDeclarationList(node, - nodesVisitor((node).declarations, visitor, isVariableDeclaration)); - - case SyntaxKind.FunctionDeclaration: - return updateFunctionDeclaration(node, - nodesVisitor((node).decorators, visitor, isDecorator), - nodesVisitor((node).modifiers, visitor, isModifier), - visitNode((node).asteriskToken, tokenVisitor, isToken), - visitNode((node).name, visitor, isIdentifier), - nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), - visitParameterList((node).parameters, visitor, context, nodesVisitor), - visitNode((node).type, visitor, isTypeNode), - visitFunctionBody((node).body, visitor, context)); - - case SyntaxKind.ClassDeclaration: - return updateClassDeclaration(node, - nodesVisitor((node).decorators, visitor, isDecorator), - nodesVisitor((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isIdentifier), - nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), - nodesVisitor((node).heritageClauses, visitor, isHeritageClause), - nodesVisitor((node).members, visitor, isClassElement)); - - case SyntaxKind.InterfaceDeclaration: - return updateInterfaceDeclaration(node, - nodesVisitor((node).decorators, visitor, isDecorator), - nodesVisitor((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isIdentifier), - nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), - nodesVisitor((node).heritageClauses, visitor, isHeritageClause), - nodesVisitor((node).members, visitor, isTypeElement)); - - case SyntaxKind.TypeAliasDeclaration: - return updateTypeAliasDeclaration(node, - nodesVisitor((node).decorators, visitor, isDecorator), - nodesVisitor((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isIdentifier), - nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), - visitNode((node).type, visitor, isTypeNode)); - - case SyntaxKind.EnumDeclaration: - return updateEnumDeclaration(node, - nodesVisitor((node).decorators, visitor, isDecorator), - nodesVisitor((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isIdentifier), - nodesVisitor((node).members, visitor, isEnumMember)); - - case SyntaxKind.ModuleDeclaration: - return updateModuleDeclaration(node, - nodesVisitor((node).decorators, visitor, isDecorator), - nodesVisitor((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isIdentifier), - visitNode((node).body, visitor, isModuleBody)); - - case SyntaxKind.ModuleBlock: - return updateModuleBlock(node, - nodesVisitor((node).statements, visitor, isStatement)); - - case SyntaxKind.CaseBlock: - return updateCaseBlock(node, - nodesVisitor((node).clauses, visitor, isCaseOrDefaultClause)); - - case SyntaxKind.NamespaceExportDeclaration: - return updateNamespaceExportDeclaration(node, - visitNode((node).name, visitor, isIdentifier)); - - case SyntaxKind.ImportEqualsDeclaration: - return updateImportEqualsDeclaration(node, - nodesVisitor((node).decorators, visitor, isDecorator), - nodesVisitor((node).modifiers, visitor, isModifier), - visitNode((node).name, visitor, isIdentifier), - visitNode((node).moduleReference, visitor, isModuleReference)); - - case SyntaxKind.ImportDeclaration: - return updateImportDeclaration(node, - nodesVisitor((node).decorators, visitor, isDecorator), - nodesVisitor((node).modifiers, visitor, isModifier), - visitNode((node).importClause, visitor, isImportClause), - visitNode((node).moduleSpecifier, visitor, isExpression)); - - case SyntaxKind.ImportClause: - return updateImportClause(node, - visitNode((node).name, visitor, isIdentifier), - visitNode((node).namedBindings, visitor, isNamedImportBindings)); - - case SyntaxKind.NamespaceImport: - return updateNamespaceImport(node, - visitNode((node).name, visitor, isIdentifier)); - - case SyntaxKind.NamedImports: - return updateNamedImports(node, - nodesVisitor((node).elements, visitor, isImportSpecifier)); - - case SyntaxKind.ImportSpecifier: - return updateImportSpecifier(node, - visitNode((node).propertyName, visitor, isIdentifier), - visitNode((node).name, visitor, isIdentifier)); - - case SyntaxKind.ExportAssignment: - return updateExportAssignment(node, - nodesVisitor((node).decorators, visitor, isDecorator), - nodesVisitor((node).modifiers, visitor, isModifier), - visitNode((node).expression, visitor, isExpression)); - - case SyntaxKind.ExportDeclaration: - return updateExportDeclaration(node, - nodesVisitor((node).decorators, visitor, isDecorator), - nodesVisitor((node).modifiers, visitor, isModifier), - visitNode((node).exportClause, visitor, isNamedExports), - visitNode((node).moduleSpecifier, visitor, isExpression)); - - case SyntaxKind.NamedExports: - return updateNamedExports(node, - nodesVisitor((node).elements, visitor, isExportSpecifier)); - - case SyntaxKind.ExportSpecifier: - return updateExportSpecifier(node, - visitNode((node).propertyName, visitor, isIdentifier), - visitNode((node).name, visitor, isIdentifier)); - - // Module references - case SyntaxKind.ExternalModuleReference: - return updateExternalModuleReference(node, - visitNode((node).expression, visitor, isExpression)); - - // JSX - case SyntaxKind.JsxElement: - return updateJsxElement(node, - visitNode((node).openingElement, visitor, isJsxOpeningElement), - nodesVisitor((node).children, visitor, isJsxChild), - visitNode((node).closingElement, visitor, isJsxClosingElement)); - - case SyntaxKind.JsxSelfClosingElement: - return updateJsxSelfClosingElement(node, - visitNode((node).tagName, visitor, isJsxTagNameExpression), - nodesVisitor((node).typeArguments, visitor, isTypeNode), - visitNode((node).attributes, visitor, isJsxAttributes)); - - case SyntaxKind.JsxOpeningElement: - return updateJsxOpeningElement(node, - visitNode((node).tagName, visitor, isJsxTagNameExpression), - nodesVisitor((node).typeArguments, visitor, isTypeNode), - visitNode((node).attributes, visitor, isJsxAttributes)); - - case SyntaxKind.JsxClosingElement: - return updateJsxClosingElement(node, - visitNode((node).tagName, visitor, isJsxTagNameExpression)); - - case SyntaxKind.JsxFragment: - return updateJsxFragment(node, - visitNode((node).openingFragment, visitor, isJsxOpeningFragment), - nodesVisitor((node).children, visitor, isJsxChild), - visitNode((node).closingFragment, visitor, isJsxClosingFragment)); - - case SyntaxKind.JsxAttribute: - return updateJsxAttribute(node, - visitNode((node).name, visitor, isIdentifier), - visitNode((node).initializer, visitor, isStringLiteralOrJsxExpression)); - - case SyntaxKind.JsxAttributes: - return updateJsxAttributes(node, - nodesVisitor((node).properties, visitor, isJsxAttributeLike)); - - case SyntaxKind.JsxSpreadAttribute: - return updateJsxSpreadAttribute(node, - visitNode((node).expression, visitor, isExpression)); - - case SyntaxKind.JsxExpression: - return updateJsxExpression(node, - visitNode((node).expression, visitor, isExpression)); - - // Clauses - case SyntaxKind.CaseClause: - return updateCaseClause(node, - visitNode((node).expression, visitor, isExpression), - nodesVisitor((node).statements, visitor, isStatement)); - - case SyntaxKind.DefaultClause: - return updateDefaultClause(node, - nodesVisitor((node).statements, visitor, isStatement)); - - case SyntaxKind.HeritageClause: - return updateHeritageClause(node, - nodesVisitor((node).types, visitor, isExpressionWithTypeArguments)); - - case SyntaxKind.CatchClause: - return updateCatchClause(node, - visitNode((node).variableDeclaration, visitor, isVariableDeclaration), - visitNode((node).block, visitor, isBlock)); - - // Property assignments - case SyntaxKind.PropertyAssignment: - return updatePropertyAssignment(node, - visitNode((node).name, visitor, isPropertyName), - visitNode((node).initializer, visitor, isExpression)); - - case SyntaxKind.ShorthandPropertyAssignment: - return updateShorthandPropertyAssignment(node, - visitNode((node).name, visitor, isIdentifier), - visitNode((node).objectAssignmentInitializer, visitor, isExpression)); - - case SyntaxKind.SpreadAssignment: - return updateSpreadAssignment(node, - visitNode((node).expression, visitor, isExpression)); - - // Enum - case SyntaxKind.EnumMember: - return updateEnumMember(node, - visitNode((node).name, visitor, isPropertyName), - visitNode((node).initializer, visitor, isExpression)); - - // Top-level nodes - case SyntaxKind.SourceFile: - return updateSourceFileNode(node, - visitLexicalEnvironment((node).statements, visitor, context)); - - // Transformation nodes - case SyntaxKind.PartiallyEmittedExpression: - return updatePartiallyEmittedExpression(node, - visitNode((node).expression, visitor, isExpression)); - - case SyntaxKind.CommaListExpression: - return updateCommaList(node, - nodesVisitor((node).elements, visitor, isExpression)); - - default: - // No need to visit nodes with no children. - return node; - } - - } - - /** - * Extracts the single node from a NodeArray. - * - * @param nodes The NodeArray. - */ - function extractSingleNode(nodes: readonly Node[]): Node | undefined { - Debug.assert(nodes.length <= 1, "Too many nodes written to output."); - return singleOrUndefined(nodes); - } -} - /* @internal */ namespace ts { function reduceNode(node: Node | undefined, f: (memo: T, node: Node) => T, initial: T) { @@ -1564,4 +610,4 @@ namespace ts { function aggregateTransformFlagsForChildNodes(transformFlags: TransformFlags, nodes: NodeArray): TransformFlags { return transformFlags | aggregateTransformFlagsForNodeArray(nodes); } -} +} \ No newline at end of file diff --git a/src/compiler/visitorPublic.ts b/src/compiler/visitorPublic.ts new file mode 100644 index 00000000000..7d21af87ef9 --- /dev/null +++ b/src/compiler/visitorPublic.ts @@ -0,0 +1,953 @@ +namespace ts { + const isTypeNodeOrTypeParameterDeclaration = or(isTypeNode, isTypeParameterDeclaration); + + /** + * Visits a Node using the supplied visitor, possibly returning a new Node in its place. + * + * @param node The Node to visit. + * @param visitor The callback used to visit the Node. + * @param test A callback to execute to verify the Node is valid. + * @param lift An optional callback to execute to lift a NodeArray into a valid Node. + */ + export function visitNode(node: T | undefined, visitor: Visitor | undefined, test?: (node: Node) => boolean, lift?: (node: NodeArray) => T): T; + + /** + * Visits a Node using the supplied visitor, possibly returning a new Node in its place. + * + * @param node The Node to visit. + * @param visitor The callback used to visit the Node. + * @param test A callback to execute to verify the Node is valid. + * @param lift An optional callback to execute to lift a NodeArray into a valid Node. + */ + export function visitNode(node: T | undefined, visitor: Visitor | undefined, test?: (node: Node) => boolean, lift?: (node: NodeArray) => T): T | undefined; + + export function visitNode(node: T | undefined, visitor: Visitor | undefined, test?: (node: Node) => boolean, lift?: (node: NodeArray) => T): T | undefined { + if (node === undefined || visitor === undefined) { + return node; + } + + aggregateTransformFlags(node); + const visited = visitor(node); + if (visited === node) { + return node; + } + + let visitedNode: Node | undefined; + if (visited === undefined) { + return undefined; + } + else if (isArray(visited)) { + visitedNode = (lift || extractSingleNode)(visited); + } + else { + visitedNode = visited; + } + + Debug.assertNode(visitedNode, test); + aggregateTransformFlags(visitedNode!); + return visitedNode; + } + + /** + * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. + * + * @param nodes The NodeArray to visit. + * @param visitor The callback used to visit a Node. + * @param test A node test to execute for each node. + * @param start An optional value indicating the starting offset at which to start visiting. + * @param count An optional value indicating the maximum number of nodes to visit. + */ + export function visitNodes(nodes: NodeArray | undefined, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray; + + /** + * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. + * + * @param nodes The NodeArray to visit. + * @param visitor The callback used to visit a Node. + * @param test A node test to execute for each node. + * @param start An optional value indicating the starting offset at which to start visiting. + * @param count An optional value indicating the maximum number of nodes to visit. + */ + export function visitNodes(nodes: NodeArray | undefined, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray | undefined; + + /** + * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. + * + * @param nodes The NodeArray to visit. + * @param visitor The callback used to visit a Node. + * @param test A node test to execute for each node. + * @param start An optional value indicating the starting offset at which to start visiting. + * @param count An optional value indicating the maximum number of nodes to visit. + */ + export function visitNodes(nodes: NodeArray | undefined, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray | undefined { + if (nodes === undefined || visitor === undefined) { + return nodes; + } + + let updated: MutableNodeArray | undefined; + + // Ensure start and count have valid values + const length = nodes.length; + if (start === undefined || start < 0) { + start = 0; + } + + if (count === undefined || count > length - start) { + count = length - start; + } + + if (start > 0 || count < length) { + // If we are not visiting all of the original nodes, we must always create a new array. + // Since this is a fragment of a node array, we do not copy over the previous location + // and will only copy over `hasTrailingComma` if we are including the last element. + updated = createNodeArray([], /*hasTrailingComma*/ nodes.hasTrailingComma && start + count === length); + } + + // Visit each original node. + for (let i = 0; i < count; i++) { + const node = nodes[i + start]; + aggregateTransformFlags(node); + const visited = node !== undefined ? visitor(node) : undefined; + if (updated !== undefined || visited === undefined || visited !== node) { + if (updated === undefined) { + // Ensure we have a copy of `nodes`, up to the current index. + updated = createNodeArray(nodes.slice(0, i), nodes.hasTrailingComma); + setTextRange(updated, nodes); + } + if (visited) { + if (isArray(visited)) { + for (const visitedNode of visited) { + Debug.assertNode(visitedNode, test); + aggregateTransformFlags(visitedNode); + updated.push(visitedNode); + } + } + else { + Debug.assertNode(visited, test); + aggregateTransformFlags(visited); + updated.push(visited); + } + } + } + } + + return updated || nodes; + } + + /** + * Starts a new lexical environment and visits a statement list, ending the lexical environment + * and merging hoisted declarations upon completion. + */ + export function visitLexicalEnvironment(statements: NodeArray, visitor: Visitor, context: TransformationContext, start?: number, ensureUseStrict?: boolean) { + context.startLexicalEnvironment(); + statements = visitNodes(statements, visitor, isStatement, start); + if (ensureUseStrict) statements = ts.ensureUseStrict(statements); // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier + return mergeLexicalEnvironment(statements, context.endLexicalEnvironment()); + } + + /** + * Starts a new lexical environment and visits a parameter list, suspending the lexical + * environment upon completion. + */ + export function visitParameterList(nodes: NodeArray | undefined, visitor: Visitor, context: TransformationContext, nodesVisitor = visitNodes) { + context.startLexicalEnvironment(); + const updated = nodesVisitor(nodes, visitor, isParameterDeclaration); + context.suspendLexicalEnvironment(); + return updated; + } + + /** + * Resumes a suspended lexical environment and visits a function body, ending the lexical + * environment and merging hoisted declarations upon completion. + */ + export function visitFunctionBody(node: FunctionBody, visitor: Visitor, context: TransformationContext): FunctionBody; + /** + * Resumes a suspended lexical environment and visits a function body, ending the lexical + * environment and merging hoisted declarations upon completion. + */ + export function visitFunctionBody(node: FunctionBody | undefined, visitor: Visitor, context: TransformationContext): FunctionBody | undefined; + /** + * Resumes a suspended lexical environment and visits a concise body, ending the lexical + * environment and merging hoisted declarations upon completion. + */ + export function visitFunctionBody(node: ConciseBody, visitor: Visitor, context: TransformationContext): ConciseBody; + export function visitFunctionBody(node: ConciseBody | undefined, visitor: Visitor, context: TransformationContext): ConciseBody | undefined { + context.resumeLexicalEnvironment(); + const updated = visitNode(node, visitor, isConciseBody); + const declarations = context.endLexicalEnvironment(); + if (some(declarations)) { + const block = convertToFunctionBody(updated); + const statements = mergeLexicalEnvironment(block.statements, declarations); + return updateBlock(block, statements); + } + return updated; + } + + /** + * Visits each child of a Node using the supplied visitor, possibly returning a new Node of the same kind in its place. + * + * @param node The Node whose children will be visited. + * @param visitor The callback used to visit each child. + * @param context A lexical environment context for the visitor. + */ + export function visitEachChild(node: T, visitor: Visitor, context: TransformationContext): T; + + /** + * Visits each child of a Node using the supplied visitor, possibly returning a new Node of the same kind in its place. + * + * @param node The Node whose children will be visited. + * @param visitor The callback used to visit each child. + * @param context A lexical environment context for the visitor. + */ + export function visitEachChild(node: T | undefined, visitor: Visitor, context: TransformationContext, nodesVisitor?: typeof visitNodes, tokenVisitor?: Visitor): T | undefined; + + export function visitEachChild(node: Node | undefined, visitor: Visitor, context: TransformationContext, nodesVisitor = visitNodes, tokenVisitor?: Visitor): Node | undefined { + if (node === undefined) { + return undefined; + } + + const kind = node.kind; + + // No need to visit nodes with no children. + if ((kind > SyntaxKind.FirstToken && kind <= SyntaxKind.LastToken) || kind === SyntaxKind.ThisType) { + return node; + } + + switch (kind) { + // Names + + case SyntaxKind.Identifier: + return updateIdentifier(node, nodesVisitor((node).typeArguments, visitor, isTypeNodeOrTypeParameterDeclaration)); + + case SyntaxKind.QualifiedName: + return updateQualifiedName(node, + visitNode((node).left, visitor, isEntityName), + visitNode((node).right, visitor, isIdentifier)); + + case SyntaxKind.ComputedPropertyName: + return updateComputedPropertyName(node, + visitNode((node).expression, visitor, isExpression)); + + // Signature elements + case SyntaxKind.TypeParameter: + return updateTypeParameterDeclaration(node, + visitNode((node).name, visitor, isIdentifier), + visitNode((node).constraint, visitor, isTypeNode), + visitNode((node).default, visitor, isTypeNode)); + + case SyntaxKind.Parameter: + return updateParameter(node, + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), + visitNode((node).dotDotDotToken, tokenVisitor, isToken), + visitNode((node).name, visitor, isBindingName), + visitNode((node).questionToken, tokenVisitor, isToken), + visitNode((node).type, visitor, isTypeNode), + visitNode((node).initializer, visitor, isExpression)); + + case SyntaxKind.Decorator: + return updateDecorator(node, + visitNode((node).expression, visitor, isExpression)); + + // Type elements + case SyntaxKind.PropertySignature: + return updatePropertySignature((node), + nodesVisitor((node).modifiers, visitor, isToken), + visitNode((node).name, visitor, isPropertyName), + visitNode((node).questionToken, tokenVisitor, isToken), + visitNode((node).type, visitor, isTypeNode), + visitNode((node).initializer, visitor, isExpression)); + + case SyntaxKind.PropertyDeclaration: + return updateProperty(node, + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isPropertyName), + // QuestionToken and ExclamationToken is uniqued in Property Declaration and the signature of 'updateProperty' is that too + visitNode((node).questionToken || (node).exclamationToken, tokenVisitor, isToken), + visitNode((node).type, visitor, isTypeNode), + visitNode((node).initializer, visitor, isExpression)); + + case SyntaxKind.MethodSignature: + return updateMethodSignature(node, + nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), + nodesVisitor((node).parameters, visitor, isParameterDeclaration), + visitNode((node).type, visitor, isTypeNode), + visitNode((node).name, visitor, isPropertyName), + visitNode((node).questionToken, tokenVisitor, isToken)); + + case SyntaxKind.MethodDeclaration: + return updateMethod(node, + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), + visitNode((node).asteriskToken, tokenVisitor, isToken), + visitNode((node).name, visitor, isPropertyName), + visitNode((node).questionToken, tokenVisitor, isToken), + nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), + visitParameterList((node).parameters, visitor, context, nodesVisitor), + visitNode((node).type, visitor, isTypeNode), + visitFunctionBody((node).body!, visitor, context)); + + case SyntaxKind.Constructor: + return updateConstructor(node, + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), + visitParameterList((node).parameters, visitor, context, nodesVisitor), + visitFunctionBody((node).body!, visitor, context)); + + case SyntaxKind.GetAccessor: + return updateGetAccessor(node, + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isPropertyName), + visitParameterList((node).parameters, visitor, context, nodesVisitor), + visitNode((node).type, visitor, isTypeNode), + visitFunctionBody((node).body!, visitor, context)); + + case SyntaxKind.SetAccessor: + return updateSetAccessor(node, + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isPropertyName), + visitParameterList((node).parameters, visitor, context, nodesVisitor), + visitFunctionBody((node).body!, visitor, context)); + + case SyntaxKind.CallSignature: + return updateCallSignature(node, + nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), + nodesVisitor((node).parameters, visitor, isParameterDeclaration), + visitNode((node).type, visitor, isTypeNode)); + + case SyntaxKind.ConstructSignature: + return updateConstructSignature(node, + nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), + nodesVisitor((node).parameters, visitor, isParameterDeclaration), + visitNode((node).type, visitor, isTypeNode)); + + case SyntaxKind.IndexSignature: + return updateIndexSignature(node, + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), + nodesVisitor((node).parameters, visitor, isParameterDeclaration), + visitNode((node).type, visitor, isTypeNode)); + + // Types + case SyntaxKind.TypePredicate: + return updateTypePredicateNodeWithModifier(node, + visitNode((node).assertsModifier, visitor), + visitNode((node).parameterName, visitor), + visitNode((node).type, visitor, isTypeNode)); + + case SyntaxKind.TypeReference: + return updateTypeReferenceNode(node, + visitNode((node).typeName, visitor, isEntityName), + nodesVisitor((node).typeArguments, visitor, isTypeNode)); + + case SyntaxKind.FunctionType: + return updateFunctionTypeNode(node, + nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), + nodesVisitor((node).parameters, visitor, isParameterDeclaration), + visitNode((node).type, visitor, isTypeNode)); + + case SyntaxKind.ConstructorType: + return updateConstructorTypeNode(node, + nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), + nodesVisitor((node).parameters, visitor, isParameterDeclaration), + visitNode((node).type, visitor, isTypeNode)); + + case SyntaxKind.TypeQuery: + return updateTypeQueryNode((node), + visitNode((node).exprName, visitor, isEntityName)); + + case SyntaxKind.TypeLiteral: + return updateTypeLiteralNode((node), + nodesVisitor((node).members, visitor, isTypeElement)); + + case SyntaxKind.ArrayType: + return updateArrayTypeNode(node, + visitNode((node).elementType, visitor, isTypeNode)); + + case SyntaxKind.TupleType: + return updateTupleTypeNode((node), + nodesVisitor((node).elementTypes, visitor, isTypeNode)); + + case SyntaxKind.OptionalType: + return updateOptionalTypeNode((node), + visitNode((node).type, visitor, isTypeNode)); + + case SyntaxKind.RestType: + return updateRestTypeNode((node), + visitNode((node).type, visitor, isTypeNode)); + + case SyntaxKind.UnionType: + return updateUnionTypeNode(node, + nodesVisitor((node).types, visitor, isTypeNode)); + + case SyntaxKind.IntersectionType: + return updateIntersectionTypeNode(node, + nodesVisitor((node).types, visitor, isTypeNode)); + + case SyntaxKind.ConditionalType: + return updateConditionalTypeNode(node, + visitNode((node).checkType, visitor, isTypeNode), + visitNode((node).extendsType, visitor, isTypeNode), + visitNode((node).trueType, visitor, isTypeNode), + visitNode((node).falseType, visitor, isTypeNode)); + + case SyntaxKind.InferType: + return updateInferTypeNode(node, + visitNode((node).typeParameter, visitor, isTypeParameterDeclaration)); + + case SyntaxKind.ImportType: + return updateImportTypeNode(node, + visitNode((node).argument, visitor, isTypeNode), + visitNode((node).qualifier, visitor, isEntityName), + visitNodes((node).typeArguments, visitor, isTypeNode), + (node).isTypeOf + ); + + case SyntaxKind.ParenthesizedType: + return updateParenthesizedType(node, + visitNode((node).type, visitor, isTypeNode)); + + case SyntaxKind.TypeOperator: + return updateTypeOperatorNode(node, + visitNode((node).type, visitor, isTypeNode)); + + case SyntaxKind.IndexedAccessType: + return updateIndexedAccessTypeNode((node), + visitNode((node).objectType, visitor, isTypeNode), + visitNode((node).indexType, visitor, isTypeNode)); + + case SyntaxKind.MappedType: + return updateMappedTypeNode((node), + visitNode((node).readonlyToken, tokenVisitor, isToken), + visitNode((node).typeParameter, visitor, isTypeParameterDeclaration), + visitNode((node).questionToken, tokenVisitor, isToken), + visitNode((node).type, visitor, isTypeNode)); + + case SyntaxKind.LiteralType: + return updateLiteralTypeNode(node, + visitNode((node).literal, visitor, isExpression)); + + // Binding patterns + case SyntaxKind.ObjectBindingPattern: + return updateObjectBindingPattern(node, + nodesVisitor((node).elements, visitor, isBindingElement)); + + case SyntaxKind.ArrayBindingPattern: + return updateArrayBindingPattern(node, + nodesVisitor((node).elements, visitor, isArrayBindingElement)); + + case SyntaxKind.BindingElement: + return updateBindingElement(node, + visitNode((node).dotDotDotToken, tokenVisitor, isToken), + visitNode((node).propertyName, visitor, isPropertyName), + visitNode((node).name, visitor, isBindingName), + visitNode((node).initializer, visitor, isExpression)); + + // Expression + case SyntaxKind.ArrayLiteralExpression: + return updateArrayLiteral(node, + nodesVisitor((node).elements, visitor, isExpression)); + + case SyntaxKind.ObjectLiteralExpression: + return updateObjectLiteral(node, + nodesVisitor((node).properties, visitor, isObjectLiteralElementLike)); + + case SyntaxKind.PropertyAccessExpression: + if (node.flags & NodeFlags.OptionalChain) { + return updatePropertyAccessChain(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).questionDotToken, visitor, isToken), + visitNode((node).name, visitor, isIdentifier)); + } + return updatePropertyAccess(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).name, visitor, isIdentifier)); + + case SyntaxKind.ElementAccessExpression: + if (node.flags & NodeFlags.OptionalChain) { + return updateElementAccessChain(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).questionDotToken, visitor, isToken), + visitNode((node).argumentExpression, visitor, isExpression)); + } + return updateElementAccess(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).argumentExpression, visitor, isExpression)); + + case SyntaxKind.CallExpression: + if (node.flags & NodeFlags.OptionalChain) { + return updateCallChain(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).questionDotToken, visitor, isToken), + nodesVisitor((node).typeArguments, visitor, isTypeNode), + nodesVisitor((node).arguments, visitor, isExpression)); + } + return updateCall(node, + visitNode((node).expression, visitor, isExpression), + nodesVisitor((node).typeArguments, visitor, isTypeNode), + nodesVisitor((node).arguments, visitor, isExpression)); + + case SyntaxKind.NewExpression: + return updateNew(node, + visitNode((node).expression, visitor, isExpression), + nodesVisitor((node).typeArguments, visitor, isTypeNode), + nodesVisitor((node).arguments, visitor, isExpression)); + + case SyntaxKind.TaggedTemplateExpression: + return updateTaggedTemplate(node, + visitNode((node).tag, visitor, isExpression), + visitNodes((node).typeArguments, visitor, isExpression), + visitNode((node).template, visitor, isTemplateLiteral)); + + case SyntaxKind.TypeAssertionExpression: + return updateTypeAssertion(node, + visitNode((node).type, visitor, isTypeNode), + visitNode((node).expression, visitor, isExpression)); + + case SyntaxKind.ParenthesizedExpression: + return updateParen(node, + visitNode((node).expression, visitor, isExpression)); + + case SyntaxKind.FunctionExpression: + return updateFunctionExpression(node, + nodesVisitor((node).modifiers, visitor, isModifier), + visitNode((node).asteriskToken, tokenVisitor, isToken), + visitNode((node).name, visitor, isIdentifier), + nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), + visitParameterList((node).parameters, visitor, context, nodesVisitor), + visitNode((node).type, visitor, isTypeNode), + visitFunctionBody((node).body, visitor, context)); + + case SyntaxKind.ArrowFunction: + return updateArrowFunction(node, + nodesVisitor((node).modifiers, visitor, isModifier), + nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), + visitParameterList((node).parameters, visitor, context, nodesVisitor), + visitNode((node).type, visitor, isTypeNode), + visitNode((node).equalsGreaterThanToken, visitor, isToken), + visitFunctionBody((node).body, visitor, context)); + + case SyntaxKind.DeleteExpression: + return updateDelete(node, + visitNode((node).expression, visitor, isExpression)); + + case SyntaxKind.TypeOfExpression: + return updateTypeOf(node, + visitNode((node).expression, visitor, isExpression)); + + case SyntaxKind.VoidExpression: + return updateVoid(node, + visitNode((node).expression, visitor, isExpression)); + + case SyntaxKind.AwaitExpression: + return updateAwait(node, + visitNode((node).expression, visitor, isExpression)); + + case SyntaxKind.PrefixUnaryExpression: + return updatePrefix(node, + visitNode((node).operand, visitor, isExpression)); + + case SyntaxKind.PostfixUnaryExpression: + return updatePostfix(node, + visitNode((node).operand, visitor, isExpression)); + + case SyntaxKind.BinaryExpression: + return updateBinary(node, + visitNode((node).left, visitor, isExpression), + visitNode((node).right, visitor, isExpression), + visitNode((node).operatorToken, visitor, isToken)); + + case SyntaxKind.ConditionalExpression: + return updateConditional(node, + visitNode((node).condition, visitor, isExpression), + visitNode((node).questionToken, visitor, isToken), + visitNode((node).whenTrue, visitor, isExpression), + visitNode((node).colonToken, visitor, isToken), + visitNode((node).whenFalse, visitor, isExpression)); + + case SyntaxKind.TemplateExpression: + return updateTemplateExpression(node, + visitNode((node).head, visitor, isTemplateHead), + nodesVisitor((node).templateSpans, visitor, isTemplateSpan)); + + case SyntaxKind.YieldExpression: + return updateYield(node, + visitNode((node).asteriskToken, tokenVisitor, isToken), + visitNode((node).expression, visitor, isExpression)); + + case SyntaxKind.SpreadElement: + return updateSpread(node, + visitNode((node).expression, visitor, isExpression)); + + case SyntaxKind.ClassExpression: + return updateClassExpression(node, + nodesVisitor((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isIdentifier), + nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), + nodesVisitor((node).heritageClauses, visitor, isHeritageClause), + nodesVisitor((node).members, visitor, isClassElement)); + + case SyntaxKind.ExpressionWithTypeArguments: + return updateExpressionWithTypeArguments(node, + nodesVisitor((node).typeArguments, visitor, isTypeNode), + visitNode((node).expression, visitor, isExpression)); + + case SyntaxKind.AsExpression: + return updateAsExpression(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).type, visitor, isTypeNode)); + + case SyntaxKind.NonNullExpression: + return updateNonNullExpression(node, + visitNode((node).expression, visitor, isExpression)); + + case SyntaxKind.MetaProperty: + return updateMetaProperty(node, + visitNode((node).name, visitor, isIdentifier)); + + // Misc + case SyntaxKind.TemplateSpan: + return updateTemplateSpan(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).literal, visitor, isTemplateMiddleOrTemplateTail)); + + // Element + case SyntaxKind.Block: + return updateBlock(node, + nodesVisitor((node).statements, visitor, isStatement)); + + case SyntaxKind.VariableStatement: + return updateVariableStatement(node, + nodesVisitor((node).modifiers, visitor, isModifier), + visitNode((node).declarationList, visitor, isVariableDeclarationList)); + + case SyntaxKind.ExpressionStatement: + return updateExpressionStatement(node, + visitNode((node).expression, visitor, isExpression)); + + case SyntaxKind.IfStatement: + return updateIf(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).thenStatement, visitor, isStatement, liftToBlock), + visitNode((node).elseStatement, visitor, isStatement, liftToBlock)); + + case SyntaxKind.DoStatement: + return updateDo(node, + visitNode((node).statement, visitor, isStatement, liftToBlock), + visitNode((node).expression, visitor, isExpression)); + + case SyntaxKind.WhileStatement: + return updateWhile(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).statement, visitor, isStatement, liftToBlock)); + + case SyntaxKind.ForStatement: + return updateFor(node, + visitNode((node).initializer, visitor, isForInitializer), + visitNode((node).condition, visitor, isExpression), + visitNode((node).incrementor, visitor, isExpression), + visitNode((node).statement, visitor, isStatement, liftToBlock)); + + case SyntaxKind.ForInStatement: + return updateForIn(node, + visitNode((node).initializer, visitor, isForInitializer), + visitNode((node).expression, visitor, isExpression), + visitNode((node).statement, visitor, isStatement, liftToBlock)); + + case SyntaxKind.ForOfStatement: + return updateForOf(node, + visitNode((node).awaitModifier, visitor, isToken), + visitNode((node).initializer, visitor, isForInitializer), + visitNode((node).expression, visitor, isExpression), + visitNode((node).statement, visitor, isStatement, liftToBlock)); + + case SyntaxKind.ContinueStatement: + return updateContinue(node, + visitNode((node).label, visitor, isIdentifier)); + + case SyntaxKind.BreakStatement: + return updateBreak(node, + visitNode((node).label, visitor, isIdentifier)); + + case SyntaxKind.ReturnStatement: + return updateReturn(node, + visitNode((node).expression, visitor, isExpression)); + + case SyntaxKind.WithStatement: + return updateWith(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).statement, visitor, isStatement, liftToBlock)); + + case SyntaxKind.SwitchStatement: + return updateSwitch(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).caseBlock, visitor, isCaseBlock)); + + case SyntaxKind.LabeledStatement: + return updateLabel(node, + visitNode((node).label, visitor, isIdentifier), + visitNode((node).statement, visitor, isStatement, liftToBlock)); + + case SyntaxKind.ThrowStatement: + return updateThrow(node, + visitNode((node).expression, visitor, isExpression)); + + case SyntaxKind.TryStatement: + return updateTry(node, + visitNode((node).tryBlock, visitor, isBlock), + visitNode((node).catchClause, visitor, isCatchClause), + visitNode((node).finallyBlock, visitor, isBlock)); + + case SyntaxKind.VariableDeclaration: + return updateTypeScriptVariableDeclaration(node, + visitNode((node).name, visitor, isBindingName), + visitNode((node).exclamationToken, tokenVisitor, isToken), + visitNode((node).type, visitor, isTypeNode), + visitNode((node).initializer, visitor, isExpression)); + + case SyntaxKind.VariableDeclarationList: + return updateVariableDeclarationList(node, + nodesVisitor((node).declarations, visitor, isVariableDeclaration)); + + case SyntaxKind.FunctionDeclaration: + return updateFunctionDeclaration(node, + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), + visitNode((node).asteriskToken, tokenVisitor, isToken), + visitNode((node).name, visitor, isIdentifier), + nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), + visitParameterList((node).parameters, visitor, context, nodesVisitor), + visitNode((node).type, visitor, isTypeNode), + visitFunctionBody((node).body, visitor, context)); + + case SyntaxKind.ClassDeclaration: + return updateClassDeclaration(node, + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isIdentifier), + nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), + nodesVisitor((node).heritageClauses, visitor, isHeritageClause), + nodesVisitor((node).members, visitor, isClassElement)); + + case SyntaxKind.InterfaceDeclaration: + return updateInterfaceDeclaration(node, + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isIdentifier), + nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), + nodesVisitor((node).heritageClauses, visitor, isHeritageClause), + nodesVisitor((node).members, visitor, isTypeElement)); + + case SyntaxKind.TypeAliasDeclaration: + return updateTypeAliasDeclaration(node, + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isIdentifier), + nodesVisitor((node).typeParameters, visitor, isTypeParameterDeclaration), + visitNode((node).type, visitor, isTypeNode)); + + case SyntaxKind.EnumDeclaration: + return updateEnumDeclaration(node, + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isIdentifier), + nodesVisitor((node).members, visitor, isEnumMember)); + + case SyntaxKind.ModuleDeclaration: + return updateModuleDeclaration(node, + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isIdentifier), + visitNode((node).body, visitor, isModuleBody)); + + case SyntaxKind.ModuleBlock: + return updateModuleBlock(node, + nodesVisitor((node).statements, visitor, isStatement)); + + case SyntaxKind.CaseBlock: + return updateCaseBlock(node, + nodesVisitor((node).clauses, visitor, isCaseOrDefaultClause)); + + case SyntaxKind.NamespaceExportDeclaration: + return updateNamespaceExportDeclaration(node, + visitNode((node).name, visitor, isIdentifier)); + + case SyntaxKind.ImportEqualsDeclaration: + return updateImportEqualsDeclaration(node, + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), + visitNode((node).name, visitor, isIdentifier), + visitNode((node).moduleReference, visitor, isModuleReference)); + + case SyntaxKind.ImportDeclaration: + return updateImportDeclaration(node, + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), + visitNode((node).importClause, visitor, isImportClause), + visitNode((node).moduleSpecifier, visitor, isExpression)); + + case SyntaxKind.ImportClause: + return updateImportClause(node, + visitNode((node).name, visitor, isIdentifier), + visitNode((node).namedBindings, visitor, isNamedImportBindings)); + + case SyntaxKind.NamespaceImport: + return updateNamespaceImport(node, + visitNode((node).name, visitor, isIdentifier)); + + case SyntaxKind.NamedImports: + return updateNamedImports(node, + nodesVisitor((node).elements, visitor, isImportSpecifier)); + + case SyntaxKind.ImportSpecifier: + return updateImportSpecifier(node, + visitNode((node).propertyName, visitor, isIdentifier), + visitNode((node).name, visitor, isIdentifier)); + + case SyntaxKind.ExportAssignment: + return updateExportAssignment(node, + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), + visitNode((node).expression, visitor, isExpression)); + + case SyntaxKind.ExportDeclaration: + return updateExportDeclaration(node, + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), + visitNode((node).exportClause, visitor, isNamedExports), + visitNode((node).moduleSpecifier, visitor, isExpression)); + + case SyntaxKind.NamedExports: + return updateNamedExports(node, + nodesVisitor((node).elements, visitor, isExportSpecifier)); + + case SyntaxKind.ExportSpecifier: + return updateExportSpecifier(node, + visitNode((node).propertyName, visitor, isIdentifier), + visitNode((node).name, visitor, isIdentifier)); + + // Module references + case SyntaxKind.ExternalModuleReference: + return updateExternalModuleReference(node, + visitNode((node).expression, visitor, isExpression)); + + // JSX + case SyntaxKind.JsxElement: + return updateJsxElement(node, + visitNode((node).openingElement, visitor, isJsxOpeningElement), + nodesVisitor((node).children, visitor, isJsxChild), + visitNode((node).closingElement, visitor, isJsxClosingElement)); + + case SyntaxKind.JsxSelfClosingElement: + return updateJsxSelfClosingElement(node, + visitNode((node).tagName, visitor, isJsxTagNameExpression), + nodesVisitor((node).typeArguments, visitor, isTypeNode), + visitNode((node).attributes, visitor, isJsxAttributes)); + + case SyntaxKind.JsxOpeningElement: + return updateJsxOpeningElement(node, + visitNode((node).tagName, visitor, isJsxTagNameExpression), + nodesVisitor((node).typeArguments, visitor, isTypeNode), + visitNode((node).attributes, visitor, isJsxAttributes)); + + case SyntaxKind.JsxClosingElement: + return updateJsxClosingElement(node, + visitNode((node).tagName, visitor, isJsxTagNameExpression)); + + case SyntaxKind.JsxFragment: + return updateJsxFragment(node, + visitNode((node).openingFragment, visitor, isJsxOpeningFragment), + nodesVisitor((node).children, visitor, isJsxChild), + visitNode((node).closingFragment, visitor, isJsxClosingFragment)); + + case SyntaxKind.JsxAttribute: + return updateJsxAttribute(node, + visitNode((node).name, visitor, isIdentifier), + visitNode((node).initializer, visitor, isStringLiteralOrJsxExpression)); + + case SyntaxKind.JsxAttributes: + return updateJsxAttributes(node, + nodesVisitor((node).properties, visitor, isJsxAttributeLike)); + + case SyntaxKind.JsxSpreadAttribute: + return updateJsxSpreadAttribute(node, + visitNode((node).expression, visitor, isExpression)); + + case SyntaxKind.JsxExpression: + return updateJsxExpression(node, + visitNode((node).expression, visitor, isExpression)); + + // Clauses + case SyntaxKind.CaseClause: + return updateCaseClause(node, + visitNode((node).expression, visitor, isExpression), + nodesVisitor((node).statements, visitor, isStatement)); + + case SyntaxKind.DefaultClause: + return updateDefaultClause(node, + nodesVisitor((node).statements, visitor, isStatement)); + + case SyntaxKind.HeritageClause: + return updateHeritageClause(node, + nodesVisitor((node).types, visitor, isExpressionWithTypeArguments)); + + case SyntaxKind.CatchClause: + return updateCatchClause(node, + visitNode((node).variableDeclaration, visitor, isVariableDeclaration), + visitNode((node).block, visitor, isBlock)); + + // Property assignments + case SyntaxKind.PropertyAssignment: + return updatePropertyAssignment(node, + visitNode((node).name, visitor, isPropertyName), + visitNode((node).initializer, visitor, isExpression)); + + case SyntaxKind.ShorthandPropertyAssignment: + return updateShorthandPropertyAssignment(node, + visitNode((node).name, visitor, isIdentifier), + visitNode((node).objectAssignmentInitializer, visitor, isExpression)); + + case SyntaxKind.SpreadAssignment: + return updateSpreadAssignment(node, + visitNode((node).expression, visitor, isExpression)); + + // Enum + case SyntaxKind.EnumMember: + return updateEnumMember(node, + visitNode((node).name, visitor, isPropertyName), + visitNode((node).initializer, visitor, isExpression)); + + // Top-level nodes + case SyntaxKind.SourceFile: + return updateSourceFileNode(node, + visitLexicalEnvironment((node).statements, visitor, context)); + + // Transformation nodes + case SyntaxKind.PartiallyEmittedExpression: + return updatePartiallyEmittedExpression(node, + visitNode((node).expression, visitor, isExpression)); + + case SyntaxKind.CommaListExpression: + return updateCommaList(node, + nodesVisitor((node).elements, visitor, isExpression)); + + default: + // No need to visit nodes with no children. + return node; + } + + } + + /** + * Extracts the single node from a NodeArray. + * + * @param nodes The NodeArray. + */ + function extractSingleNode(nodes: readonly Node[]): Node | undefined { + Debug.assert(nodes.length <= 1, "Too many nodes written to output."); + return singleOrUndefined(nodes); + } +} diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 52789783d01..cda62511ead 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -454,720 +454,3 @@ namespace ts { return exitStatus; } } - -namespace ts { - export interface ReadBuildProgramHost { - useCaseSensitiveFileNames(): boolean; - getCurrentDirectory(): string; - readFile(fileName: string): string | undefined; - } - export function readBuilderProgram(compilerOptions: CompilerOptions, host: ReadBuildProgramHost) { - if (compilerOptions.out || compilerOptions.outFile) return undefined; - const buildInfoPath = getTsBuildInfoEmitOutputFilePath(compilerOptions); - if (!buildInfoPath) return undefined; - const content = host.readFile(buildInfoPath); - if (!content) return undefined; - const buildInfo = getBuildInfo(content); - if (buildInfo.version !== version) return undefined; - if (!buildInfo.program) return undefined; - return createBuildProgramUsingProgramBuildInfo(buildInfo.program, buildInfoPath, host); - } - - export function createIncrementalCompilerHost(options: CompilerOptions, system = sys): CompilerHost { - const host = createCompilerHostWorker(options, /*setParentNodes*/ undefined, system); - host.createHash = maybeBind(system, system.createHash); - setGetSourceFileAsHashVersioned(host, system); - changeCompilerHostLikeToUseCache(host, fileName => toPath(fileName, host.getCurrentDirectory(), host.getCanonicalFileName)); - return host; - } - - export interface IncrementalProgramOptions { - rootNames: readonly string[]; - options: CompilerOptions; - configFileParsingDiagnostics?: readonly Diagnostic[]; - projectReferences?: readonly ProjectReference[]; - host?: CompilerHost; - createProgram?: CreateProgram; - } - - export function createIncrementalProgram({ - rootNames, options, configFileParsingDiagnostics, projectReferences, host, createProgram - }: IncrementalProgramOptions): T { - host = host || createIncrementalCompilerHost(options); - createProgram = createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram; - const oldProgram = readBuilderProgram(options, host) as any as T; - return createProgram(rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences); - } - - export type WatchStatusReporter = (diagnostic: Diagnostic, newLine: string, options: CompilerOptions, errorCount?: number) => void; - /** Create the program with rootNames and options, if they are undefined, oldProgram and new configFile diagnostics create new program */ - export type CreateProgram = (rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: T, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[] | undefined) => T; - - /** Host that has watch functionality used in --watch mode */ - export interface WatchHost { - /** If provided, called with Diagnostic message that informs about change in watch status */ - onWatchStatusChange?(diagnostic: Diagnostic, newLine: string, options: CompilerOptions, errorCount?: number): void; - - /** Used to watch changes in source files, missing files needed to update the program or config file */ - watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; - /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ - watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; - /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */ - setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; - /** If provided, will be used to reset existing delayed compilation */ - clearTimeout?(timeoutId: any): void; - } - export interface ProgramHost { - /** - * Used to create the program when need for program creation or recreation detected - */ - createProgram: CreateProgram; - - // Sub set of compiler host methods to read and generate new program - useCaseSensitiveFileNames(): boolean; - getNewLine(): string; - getCurrentDirectory(): string; - getDefaultLibFileName(options: CompilerOptions): string; - getDefaultLibLocation?(): string; - createHash?(data: string): string; - - /** - * Use to check file presence for source files and - * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well - */ - fileExists(path: string): boolean; - /** - * Use to read file text for source files and - * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well - */ - readFile(path: string, encoding?: string): string | undefined; - - /** If provided, used for module resolution as well as to handle directory structure */ - directoryExists?(path: string): boolean; - /** If provided, used in resolutions as well as handling directory structure */ - getDirectories?(path: string): string[]; - /** If provided, used to cache and handle directory structure modifications */ - readDirectory?(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; - - /** Symbol links resolution */ - realpath?(path: string): string; - /** If provided would be used to write log about compilation */ - trace?(s: string): void; - /** If provided is used to get the environment variable */ - getEnvironmentVariable?(name: string): string | undefined; - - /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedModule | undefined)[]; - /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */ - resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedTypeReferenceDirective | undefined)[]; - } - /** Internal interface used to wire emit through same host */ - - /*@internal*/ - export interface ProgramHost { - // TODO: GH#18217 Optional methods are frequently asserted - createDirectory?(path: string): void; - writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void; - onCachedDirectoryStructureHostCreate?(host: CachedDirectoryStructureHost): void; - } - - export interface WatchCompilerHost extends ProgramHost, WatchHost { - /** If provided, callback to invoke after every new program creation */ - afterProgramCreate?(program: T): void; - - // Only for testing - /*@internal*/ - maxNumberOfFilesToIterateForInvalidation?: number; - } - - /** - * Host to create watch with root files and options - */ - export interface WatchCompilerHostOfFilesAndCompilerOptions extends WatchCompilerHost { - /** root files to use to generate program */ - rootFiles: string[]; - - /** Compiler options */ - options: CompilerOptions; - - /** Project References */ - projectReferences?: readonly ProjectReference[]; - } - - /** - * Host to create watch with config file - */ - export interface WatchCompilerHostOfConfigFile extends WatchCompilerHost, ConfigFileDiagnosticsReporter { - /** Name of the config file to compile */ - configFileName: string; - - /** Options to extend */ - optionsToExtend?: CompilerOptions; - - /** - * Used to generate source file names from the config file and its include, exclude, files rules - * and also to cache the directory stucture - */ - readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; - } - - /** - * Host to create watch with config file that is already parsed (from tsc) - */ - /*@internal*/ - export interface WatchCompilerHostOfConfigFile extends WatchCompilerHost { - optionsToExtend?: CompilerOptions; - configFileParsingResult?: ParsedCommandLine; - } - - export interface Watch { - /** Synchronize with host and get updated program */ - getProgram(): T; - /** Gets the existing program without synchronizing with changes on host */ - /*@internal*/ - getCurrentProgram(): T; - /** Closes the watch */ - close(): void; - } - - /** - * Creates the watch what generates program using the config file - */ - export interface WatchOfConfigFile extends Watch { - } - - /** - * Creates the watch that generates program using the root files and compiler options - */ - export interface WatchOfFilesAndCompilerOptions extends Watch { - /** Updates the root files in the program, only if this is not config file compilation */ - updateRootFileNames(fileNames: string[]): void; - } - - /** - * Create the watch compiler host for either configFile or fileNames and its options - */ - export function createWatchCompilerHost(configFileName: string, optionsToExtend: CompilerOptions | undefined, system: System, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHostOfConfigFile; - export function createWatchCompilerHost(rootFiles: string[], options: CompilerOptions, system: System, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferences?: readonly ProjectReference[]): WatchCompilerHostOfFilesAndCompilerOptions; - export function createWatchCompilerHost(rootFilesOrConfigFileName: string | string[], options: CompilerOptions | undefined, system: System, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferences?: readonly ProjectReference[]): WatchCompilerHostOfFilesAndCompilerOptions | WatchCompilerHostOfConfigFile { - if (isArray(rootFilesOrConfigFileName)) { - return createWatchCompilerHostOfFilesAndCompilerOptions(rootFilesOrConfigFileName, options!, system, createProgram, reportDiagnostic, reportWatchStatus, projectReferences); // TODO: GH#18217 - } - else { - return createWatchCompilerHostOfConfigFile(rootFilesOrConfigFileName, options, system, createProgram, reportDiagnostic, reportWatchStatus); - } - } - - /** - * Creates the watch from the host for root files and compiler options - */ - export function createWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions): WatchOfFilesAndCompilerOptions; - /** - * Creates the watch from the host for config file - */ - export function createWatchProgram(host: WatchCompilerHostOfConfigFile): WatchOfConfigFile; - export function createWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & WatchCompilerHostOfConfigFile): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { - interface FilePresentOnHost { - version: string; - sourceFile: SourceFile; - fileWatcher: FileWatcher; - } - type FileMissingOnHost = false; - interface FilePresenceUnknownOnHost { - version: false; - fileWatcher?: FileWatcher; - } - type FileMayBePresentOnHost = FilePresentOnHost | FilePresenceUnknownOnHost; - type HostFileInfo = FilePresentOnHost | FileMissingOnHost | FilePresenceUnknownOnHost; - - let builderProgram: T; - let reloadLevel: ConfigFileProgramReloadLevel; // level to indicate if the program needs to be reloaded from config file/just filenames etc - let missingFilesMap: Map; // Map of file watchers for the missing files - let watchedWildcardDirectories: Map; // map of watchers for the wild card directories in the config file - let timerToUpdateProgram: any; // timer callback to recompile the program - - const sourceFilesCache = createMap(); // Cache that stores the source file and version info - let missingFilePathsRequestedForRelease: Path[] | undefined; // These paths are held temparirly so that we can remove the entry from source file cache if the file is not tracked by missing files - let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations - let hasChangedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed - - const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames(); - const currentDirectory = host.getCurrentDirectory(); - const { configFileName, optionsToExtend: optionsToExtendForConfigFile = {}, createProgram } = host; - let { rootFiles: rootFileNames, options: compilerOptions, projectReferences } = host; - let configFileSpecs: ConfigFileSpecs; - let configFileParsingDiagnostics: Diagnostic[] | undefined; - let canConfigFileJsonReportNoInputFiles = false; - let hasChangedConfigFileParsingErrors = false; - - const cachedDirectoryStructureHost = configFileName === undefined ? undefined : createCachedDirectoryStructureHost(host, currentDirectory, useCaseSensitiveFileNames); - if (cachedDirectoryStructureHost && host.onCachedDirectoryStructureHostCreate) { - host.onCachedDirectoryStructureHostCreate(cachedDirectoryStructureHost); - } - const directoryStructureHost: DirectoryStructureHost = cachedDirectoryStructureHost || host; - const parseConfigFileHost = parseConfigHostFromCompilerHostLike(host, directoryStructureHost); - - // From tsc we want to get already parsed result and hence check for rootFileNames - let newLine = updateNewLine(); - if (configFileName && host.configFileParsingResult) { - setConfigFileParsingResult(host.configFileParsingResult); - newLine = updateNewLine(); - } - reportWatchDiagnostic(Diagnostics.Starting_compilation_in_watch_mode); - if (configFileName && !host.configFileParsingResult) { - newLine = getNewLineCharacter(optionsToExtendForConfigFile, () => host.getNewLine()); - Debug.assert(!rootFileNames); - parseConfigFile(); - newLine = updateNewLine(); - } - - const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory(host, compilerOptions); - const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); - - writeLog(`Current directory: ${currentDirectory} CaseSensitiveFileNames: ${useCaseSensitiveFileNames}`); - let configFileWatcher: FileWatcher | undefined; - if (configFileName) { - configFileWatcher = watchFile(host, configFileName, scheduleProgramReload, PollingInterval.High, WatchType.ConfigFile); - } - - const compilerHost = createCompilerHostFromProgramHost(host, () => compilerOptions, directoryStructureHost) as CompilerHost & ResolutionCacheHost; - setGetSourceFileAsHashVersioned(compilerHost, host); - // Members for CompilerHost - const getNewSourceFile = compilerHost.getSourceFile; - compilerHost.getSourceFile = (fileName, ...args) => getVersionedSourceFileByPath(fileName, toPath(fileName), ...args); - compilerHost.getSourceFileByPath = getVersionedSourceFileByPath; - compilerHost.getNewLine = () => newLine; - compilerHost.fileExists = fileExists; - compilerHost.onReleaseOldSourceFile = onReleaseOldSourceFile; - // Members for ResolutionCacheHost - compilerHost.toPath = toPath; - compilerHost.getCompilationSettings = () => compilerOptions; - compilerHost.watchDirectoryOfFailedLookupLocation = (dir, cb, flags) => watchDirectory(host, dir, cb, flags, WatchType.FailedLookupLocations); - compilerHost.watchTypeRootsDirectory = (dir, cb, flags) => watchDirectory(host, dir, cb, flags, WatchType.TypeRoots); - compilerHost.getCachedDirectoryStructureHost = () => cachedDirectoryStructureHost; - compilerHost.onInvalidatedResolution = scheduleProgramUpdate; - compilerHost.onChangedAutomaticTypeDirectiveNames = () => { - hasChangedAutomaticTypeDirectiveNames = true; - scheduleProgramUpdate(); - }; - compilerHost.fileIsOpen = returnFalse; - compilerHost.maxNumberOfFilesToIterateForInvalidation = host.maxNumberOfFilesToIterateForInvalidation; - compilerHost.getCurrentProgram = getCurrentProgram; - compilerHost.writeLog = writeLog; - - // Cache for the module resolution - const resolutionCache = createResolutionCache(compilerHost, - configFileName ? - getDirectoryPath(getNormalizedAbsolutePath(configFileName, currentDirectory)) : - currentDirectory, - /*logChangesWhenResolvingModule*/ false - ); - // Resolve module using host module resolution strategy if provided otherwise use resolution cache to resolve module names - compilerHost.resolveModuleNames = host.resolveModuleNames ? - ((...args) => host.resolveModuleNames!(...args)) : - ((moduleNames, containingFile, reusedNames, redirectedReference) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, redirectedReference)); - compilerHost.resolveTypeReferenceDirectives = host.resolveTypeReferenceDirectives ? - ((...args) => host.resolveTypeReferenceDirectives!(...args)) : - ((typeDirectiveNames, containingFile, redirectedReference) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile, redirectedReference)); - const userProvidedResolution = !!host.resolveModuleNames || !!host.resolveTypeReferenceDirectives; - - builderProgram = readBuilderProgram(compilerOptions, compilerHost) as any as T; - synchronizeProgram(); - - // Update the wild card directory watch - watchConfigFileWildCardDirectories(); - - return configFileName ? - { getCurrentProgram: getCurrentBuilderProgram, getProgram: synchronizeProgram, close } : - { getCurrentProgram: getCurrentBuilderProgram, getProgram: synchronizeProgram, updateRootFileNames, close }; - - function close() { - resolutionCache.clear(); - clearMap(sourceFilesCache, value => { - if (value && value.fileWatcher) { - value.fileWatcher.close(); - value.fileWatcher = undefined; - } - }); - if (configFileWatcher) { - configFileWatcher.close(); - configFileWatcher = undefined; - } - if (watchedWildcardDirectories) { - clearMap(watchedWildcardDirectories, closeFileWatcherOf); - watchedWildcardDirectories = undefined!; - } - if (missingFilesMap) { - clearMap(missingFilesMap, closeFileWatcher); - missingFilesMap = undefined!; - } - } - - function getCurrentBuilderProgram() { - return builderProgram; - } - - function getCurrentProgram() { - return builderProgram && builderProgram.getProgramOrUndefined(); - } - - function synchronizeProgram() { - writeLog(`Synchronizing program`); - - const program = getCurrentBuilderProgram(); - if (hasChangedCompilerOptions) { - newLine = updateNewLine(); - if (program && changesAffectModuleResolution(program.getCompilerOptions(), compilerOptions)) { - resolutionCache.clear(); - } - } - - // All resolutions are invalid if user provided resolutions - const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(userProvidedResolution); - if (isProgramUptoDate(getCurrentProgram(), rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames, projectReferences)) { - if (hasChangedConfigFileParsingErrors) { - builderProgram = createProgram(/*rootNames*/ undefined, /*options*/ undefined, compilerHost, builderProgram, configFileParsingDiagnostics, projectReferences); - hasChangedConfigFileParsingErrors = false; - } - } - else { - createNewProgram(hasInvalidatedResolution); - } - - if (host.afterProgramCreate) { - host.afterProgramCreate(builderProgram); - } - - return builderProgram; - } - - function createNewProgram(hasInvalidatedResolution: HasInvalidatedResolution) { - // Compile the program - writeLog("CreatingProgramWith::"); - writeLog(` roots: ${JSON.stringify(rootFileNames)}`); - writeLog(` options: ${JSON.stringify(compilerOptions)}`); - - const needsUpdateInTypeRootWatch = hasChangedCompilerOptions || !getCurrentProgram(); - hasChangedCompilerOptions = false; - hasChangedConfigFileParsingErrors = false; - resolutionCache.startCachingPerDirectoryResolution(); - compilerHost.hasInvalidatedResolution = hasInvalidatedResolution; - compilerHost.hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames; - builderProgram = createProgram(rootFileNames, compilerOptions, compilerHost, builderProgram, configFileParsingDiagnostics, projectReferences); - resolutionCache.finishCachingPerDirectoryResolution(); - - // Update watches - updateMissingFilePathsWatch(builderProgram.getProgram(), missingFilesMap || (missingFilesMap = createMap()), watchMissingFilePath); - if (needsUpdateInTypeRootWatch) { - resolutionCache.updateTypeRootsWatch(); - } - - if (missingFilePathsRequestedForRelease) { - // These are the paths that program creater told us as not in use any more but were missing on the disk. - // We didnt remove the entry for them from sourceFiles cache so that we dont have to do File IO, - // if there is already watcher for it (for missing files) - // At this point our watches were updated, hence now we know that these paths are not tracked and need to be removed - // so that at later time we have correct result of their presence - for (const missingFilePath of missingFilePathsRequestedForRelease) { - if (!missingFilesMap.has(missingFilePath)) { - sourceFilesCache.delete(missingFilePath); - } - } - missingFilePathsRequestedForRelease = undefined; - } - } - - function updateRootFileNames(files: string[]) { - Debug.assert(!configFileName, "Cannot update root file names with config file watch mode"); - rootFileNames = files; - scheduleProgramUpdate(); - } - - function updateNewLine() { - return getNewLineCharacter(compilerOptions || optionsToExtendForConfigFile, () => host.getNewLine()); - } - - function toPath(fileName: string) { - return ts.toPath(fileName, currentDirectory, getCanonicalFileName); - } - - function isFileMissingOnHost(hostSourceFile: HostFileInfo | undefined): hostSourceFile is FileMissingOnHost { - return typeof hostSourceFile === "boolean"; - } - - function isFilePresenceUnknownOnHost(hostSourceFile: FileMayBePresentOnHost): hostSourceFile is FilePresenceUnknownOnHost { - return typeof (hostSourceFile as FilePresenceUnknownOnHost).version === "boolean"; - } - - function fileExists(fileName: string) { - const path = toPath(fileName); - // If file is missing on host from cache, we can definitely say file doesnt exist - // otherwise we need to ensure from the disk - if (isFileMissingOnHost(sourceFilesCache.get(path))) { - return true; - } - - return directoryStructureHost.fileExists(fileName); - } - - function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined { - const hostSourceFile = sourceFilesCache.get(path); - // No source file on the host - if (isFileMissingOnHost(hostSourceFile)) { - return undefined; - } - - // Create new source file if requested or the versions dont match - if (hostSourceFile === undefined || shouldCreateNewSourceFile || isFilePresenceUnknownOnHost(hostSourceFile)) { - const sourceFile = getNewSourceFile(fileName, languageVersion, onError); - if (hostSourceFile) { - if (sourceFile) { - // Set the source file and create file watcher now that file was present on the disk - (hostSourceFile as FilePresentOnHost).sourceFile = sourceFile; - hostSourceFile.version = sourceFile.version; - if (!hostSourceFile.fileWatcher) { - hostSourceFile.fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, path, WatchType.SourceFile); - } - } - else { - // There is no source file on host any more, close the watch, missing file paths will track it - if (hostSourceFile.fileWatcher) { - hostSourceFile.fileWatcher.close(); - } - sourceFilesCache.set(path, false); - } - } - else { - if (sourceFile) { - const fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, path, WatchType.SourceFile); - sourceFilesCache.set(path, { sourceFile, version: sourceFile.version, fileWatcher }); - } - else { - sourceFilesCache.set(path, false); - } - } - return sourceFile; - } - return hostSourceFile.sourceFile; - } - - function nextSourceFileVersion(path: Path) { - const hostSourceFile = sourceFilesCache.get(path); - if (hostSourceFile !== undefined) { - if (isFileMissingOnHost(hostSourceFile)) { - // The next version, lets set it as presence unknown file - sourceFilesCache.set(path, { version: false }); - } - else { - (hostSourceFile as FilePresenceUnknownOnHost).version = false; - } - } - } - - function getSourceVersion(path: Path): string | undefined { - const hostSourceFile = sourceFilesCache.get(path); - return !hostSourceFile || !hostSourceFile.version ? undefined : hostSourceFile.version; - } - - function onReleaseOldSourceFile(oldSourceFile: SourceFile, _oldOptions: CompilerOptions, hasSourceFileByPath: boolean) { - const hostSourceFileInfo = sourceFilesCache.get(oldSourceFile.resolvedPath); - // If this is the source file thats in the cache and new program doesnt need it, - // remove the cached entry. - // Note we arent deleting entry if file became missing in new program or - // there was version update and new source file was created. - if (hostSourceFileInfo !== undefined) { - // record the missing file paths so they can be removed later if watchers arent tracking them - if (isFileMissingOnHost(hostSourceFileInfo)) { - (missingFilePathsRequestedForRelease || (missingFilePathsRequestedForRelease = [])).push(oldSourceFile.path); - } - else if ((hostSourceFileInfo as FilePresentOnHost).sourceFile === oldSourceFile) { - if (hostSourceFileInfo.fileWatcher) { - hostSourceFileInfo.fileWatcher.close(); - } - sourceFilesCache.delete(oldSourceFile.resolvedPath); - if (!hasSourceFileByPath) { - resolutionCache.removeResolutionsOfFile(oldSourceFile.path); - } - } - } - } - - function reportWatchDiagnostic(message: DiagnosticMessage) { - if (host.onWatchStatusChange) { - host.onWatchStatusChange(createCompilerDiagnostic(message), newLine, compilerOptions || optionsToExtendForConfigFile); - } - } - - // Upon detecting a file change, wait for 250ms and then perform a recompilation. This gives batch - // operations (such as saving all modified files in an editor) a chance to complete before we kick - // off a new compilation. - function scheduleProgramUpdate() { - if (!host.setTimeout || !host.clearTimeout) { - return; - } - - if (timerToUpdateProgram) { - host.clearTimeout(timerToUpdateProgram); - } - writeLog("Scheduling update"); - timerToUpdateProgram = host.setTimeout(updateProgram, 250); - } - - function scheduleProgramReload() { - Debug.assert(!!configFileName); - reloadLevel = ConfigFileProgramReloadLevel.Full; - scheduleProgramUpdate(); - } - - function updateProgram() { - timerToUpdateProgram = undefined; - reportWatchDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation); - - switch (reloadLevel) { - case ConfigFileProgramReloadLevel.Partial: - perfLogger.logStartUpdateProgram("PartialConfigReload"); - reloadFileNamesFromConfigFile(); - break; - case ConfigFileProgramReloadLevel.Full: - perfLogger.logStartUpdateProgram("FullConfigReload"); - reloadConfigFile(); - break; - default: - perfLogger.logStartUpdateProgram("SynchronizeProgram"); - synchronizeProgram(); - break; - } - perfLogger.logStopUpdateProgram("Done"); - } - - function reloadFileNamesFromConfigFile() { - writeLog("Reloading new file names and options"); - const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, parseConfigFileHost); - if (updateErrorForNoInputFiles(result, configFileName, configFileSpecs, configFileParsingDiagnostics!, canConfigFileJsonReportNoInputFiles)) { - hasChangedConfigFileParsingErrors = true; - } - rootFileNames = result.fileNames; - - // Update the program - synchronizeProgram(); - } - - function reloadConfigFile() { - writeLog(`Reloading config file: ${configFileName}`); - reloadLevel = ConfigFileProgramReloadLevel.None; - - if (cachedDirectoryStructureHost) { - cachedDirectoryStructureHost.clearCache(); - } - parseConfigFile(); - hasChangedCompilerOptions = true; - synchronizeProgram(); - - // Update the wild card directory watch - watchConfigFileWildCardDirectories(); - } - - function parseConfigFile() { - setConfigFileParsingResult(getParsedCommandLineOfConfigFile(configFileName, optionsToExtendForConfigFile, parseConfigFileHost)!); // TODO: GH#18217 - } - - function setConfigFileParsingResult(configFileParseResult: ParsedCommandLine) { - rootFileNames = configFileParseResult.fileNames; - compilerOptions = configFileParseResult.options; - configFileSpecs = configFileParseResult.configFileSpecs!; // TODO: GH#18217 - projectReferences = configFileParseResult.projectReferences; - configFileParsingDiagnostics = getConfigFileParsingDiagnostics(configFileParseResult).slice(); - canConfigFileJsonReportNoInputFiles = canJsonReportNoInutFiles(configFileParseResult.raw); - hasChangedConfigFileParsingErrors = true; - } - - function onSourceFileChange(fileName: string, eventKind: FileWatcherEventKind, path: Path) { - updateCachedSystemWithFile(fileName, path, eventKind); - - // Update the source file cache - if (eventKind === FileWatcherEventKind.Deleted && sourceFilesCache.has(path)) { - resolutionCache.invalidateResolutionOfFile(path); - } - resolutionCache.removeResolutionsFromProjectReferenceRedirects(path); - nextSourceFileVersion(path); - - // Update the program - scheduleProgramUpdate(); - } - - function updateCachedSystemWithFile(fileName: string, path: Path, eventKind: FileWatcherEventKind) { - if (cachedDirectoryStructureHost) { - cachedDirectoryStructureHost.addOrDeleteFile(fileName, path, eventKind); - } - } - - function watchMissingFilePath(missingFilePath: Path) { - return watchFilePath(host, missingFilePath, onMissingFileChange, PollingInterval.Medium, missingFilePath, WatchType.MissingFile); - } - - function onMissingFileChange(fileName: string, eventKind: FileWatcherEventKind, missingFilePath: Path) { - updateCachedSystemWithFile(fileName, missingFilePath, eventKind); - - if (eventKind === FileWatcherEventKind.Created && missingFilesMap.has(missingFilePath)) { - missingFilesMap.get(missingFilePath)!.close(); - missingFilesMap.delete(missingFilePath); - - // Delete the entry in the source files cache so that new source file is created - nextSourceFileVersion(missingFilePath); - - // When a missing file is created, we should update the graph. - scheduleProgramUpdate(); - } - } - - function watchConfigFileWildCardDirectories() { - if (configFileSpecs) { - updateWatchingWildcardDirectories( - watchedWildcardDirectories || (watchedWildcardDirectories = createMap()), - createMapFromTemplate(configFileSpecs.wildcardDirectories), - watchWildcardDirectory - ); - } - else if (watchedWildcardDirectories) { - clearMap(watchedWildcardDirectories, closeFileWatcherOf); - } - } - - function watchWildcardDirectory(directory: string, flags: WatchDirectoryFlags) { - return watchDirectory( - host, - directory, - fileOrDirectory => { - Debug.assert(!!configFileName); - - const fileOrDirectoryPath = toPath(fileOrDirectory); - - // Since the file existance changed, update the sourceFiles cache - if (cachedDirectoryStructureHost) { - cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); - } - nextSourceFileVersion(fileOrDirectoryPath); - - if (isPathIgnored(fileOrDirectoryPath)) return; - - // If the the added or created file or directory is not supported file name, ignore the file - // But when watched directory is added/removed, we need to reload the file list - if (fileOrDirectoryPath !== directory && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, compilerOptions)) { - writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrDirectory}`); - return; - } - - // Reload is pending, do the reload - if (reloadLevel !== ConfigFileProgramReloadLevel.Full) { - reloadLevel = ConfigFileProgramReloadLevel.Partial; - - // Schedule Update the program - scheduleProgramUpdate(); - } - }, - flags, - WatchType.WildcardDirectory - ); - } - } -} diff --git a/src/compiler/watchPublic.ts b/src/compiler/watchPublic.ts new file mode 100644 index 00000000000..c7820717de9 --- /dev/null +++ b/src/compiler/watchPublic.ts @@ -0,0 +1,716 @@ +namespace ts { + export interface ReadBuildProgramHost { + useCaseSensitiveFileNames(): boolean; + getCurrentDirectory(): string; + readFile(fileName: string): string | undefined; + } + export function readBuilderProgram(compilerOptions: CompilerOptions, host: ReadBuildProgramHost) { + if (compilerOptions.out || compilerOptions.outFile) return undefined; + const buildInfoPath = getTsBuildInfoEmitOutputFilePath(compilerOptions); + if (!buildInfoPath) return undefined; + const content = host.readFile(buildInfoPath); + if (!content) return undefined; + const buildInfo = getBuildInfo(content); + if (buildInfo.version !== version) return undefined; + if (!buildInfo.program) return undefined; + return createBuildProgramUsingProgramBuildInfo(buildInfo.program, buildInfoPath, host); + } + + export function createIncrementalCompilerHost(options: CompilerOptions, system = sys): CompilerHost { + const host = createCompilerHostWorker(options, /*setParentNodes*/ undefined, system); + host.createHash = maybeBind(system, system.createHash); + setGetSourceFileAsHashVersioned(host, system); + changeCompilerHostLikeToUseCache(host, fileName => toPath(fileName, host.getCurrentDirectory(), host.getCanonicalFileName)); + return host; + } + + export interface IncrementalProgramOptions { + rootNames: readonly string[]; + options: CompilerOptions; + configFileParsingDiagnostics?: readonly Diagnostic[]; + projectReferences?: readonly ProjectReference[]; + host?: CompilerHost; + createProgram?: CreateProgram; + } + + export function createIncrementalProgram({ + rootNames, options, configFileParsingDiagnostics, projectReferences, host, createProgram + }: IncrementalProgramOptions): T { + host = host || createIncrementalCompilerHost(options); + createProgram = createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram; + const oldProgram = readBuilderProgram(options, host) as any as T; + return createProgram(rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences); + } + + export type WatchStatusReporter = (diagnostic: Diagnostic, newLine: string, options: CompilerOptions, errorCount?: number) => void; + /** Create the program with rootNames and options, if they are undefined, oldProgram and new configFile diagnostics create new program */ + export type CreateProgram = (rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: T, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[] | undefined) => T; + + /** Host that has watch functionality used in --watch mode */ + export interface WatchHost { + /** If provided, called with Diagnostic message that informs about change in watch status */ + onWatchStatusChange?(diagnostic: Diagnostic, newLine: string, options: CompilerOptions, errorCount?: number): void; + + /** Used to watch changes in source files, missing files needed to update the program or config file */ + watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; + /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ + watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; + /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */ + setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; + /** If provided, will be used to reset existing delayed compilation */ + clearTimeout?(timeoutId: any): void; + } + export interface ProgramHost { + /** + * Used to create the program when need for program creation or recreation detected + */ + createProgram: CreateProgram; + + // Sub set of compiler host methods to read and generate new program + useCaseSensitiveFileNames(): boolean; + getNewLine(): string; + getCurrentDirectory(): string; + getDefaultLibFileName(options: CompilerOptions): string; + getDefaultLibLocation?(): string; + createHash?(data: string): string; + + /** + * Use to check file presence for source files and + * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well + */ + fileExists(path: string): boolean; + /** + * Use to read file text for source files and + * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well + */ + readFile(path: string, encoding?: string): string | undefined; + + /** If provided, used for module resolution as well as to handle directory structure */ + directoryExists?(path: string): boolean; + /** If provided, used in resolutions as well as handling directory structure */ + getDirectories?(path: string): string[]; + /** If provided, used to cache and handle directory structure modifications */ + readDirectory?(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; + + /** Symbol links resolution */ + realpath?(path: string): string; + /** If provided would be used to write log about compilation */ + trace?(s: string): void; + /** If provided is used to get the environment variable */ + getEnvironmentVariable?(name: string): string | undefined; + + /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedModule | undefined)[]; + /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */ + resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedTypeReferenceDirective | undefined)[]; + } + /** Internal interface used to wire emit through same host */ + + /*@internal*/ + export interface ProgramHost { + // TODO: GH#18217 Optional methods are frequently asserted + createDirectory?(path: string): void; + writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void; + onCachedDirectoryStructureHostCreate?(host: CachedDirectoryStructureHost): void; + } + + export interface WatchCompilerHost extends ProgramHost, WatchHost { + /** If provided, callback to invoke after every new program creation */ + afterProgramCreate?(program: T): void; + + // Only for testing + /*@internal*/ + maxNumberOfFilesToIterateForInvalidation?: number; + } + + /** + * Host to create watch with root files and options + */ + export interface WatchCompilerHostOfFilesAndCompilerOptions extends WatchCompilerHost { + /** root files to use to generate program */ + rootFiles: string[]; + + /** Compiler options */ + options: CompilerOptions; + + /** Project References */ + projectReferences?: readonly ProjectReference[]; + } + + /** + * Host to create watch with config file + */ + export interface WatchCompilerHostOfConfigFile extends WatchCompilerHost, ConfigFileDiagnosticsReporter { + /** Name of the config file to compile */ + configFileName: string; + + /** Options to extend */ + optionsToExtend?: CompilerOptions; + + /** + * Used to generate source file names from the config file and its include, exclude, files rules + * and also to cache the directory stucture + */ + readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; + } + + /** + * Host to create watch with config file that is already parsed (from tsc) + */ + /*@internal*/ + export interface WatchCompilerHostOfConfigFile extends WatchCompilerHost { + optionsToExtend?: CompilerOptions; + configFileParsingResult?: ParsedCommandLine; + } + + export interface Watch { + /** Synchronize with host and get updated program */ + getProgram(): T; + /** Gets the existing program without synchronizing with changes on host */ + /*@internal*/ + getCurrentProgram(): T; + /** Closes the watch */ + close(): void; + } + + /** + * Creates the watch what generates program using the config file + */ + export interface WatchOfConfigFile extends Watch { + } + + /** + * Creates the watch that generates program using the root files and compiler options + */ + export interface WatchOfFilesAndCompilerOptions extends Watch { + /** Updates the root files in the program, only if this is not config file compilation */ + updateRootFileNames(fileNames: string[]): void; + } + + /** + * Create the watch compiler host for either configFile or fileNames and its options + */ + export function createWatchCompilerHost(configFileName: string, optionsToExtend: CompilerOptions | undefined, system: System, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHostOfConfigFile; + export function createWatchCompilerHost(rootFiles: string[], options: CompilerOptions, system: System, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferences?: readonly ProjectReference[]): WatchCompilerHostOfFilesAndCompilerOptions; + export function createWatchCompilerHost(rootFilesOrConfigFileName: string | string[], options: CompilerOptions | undefined, system: System, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferences?: readonly ProjectReference[]): WatchCompilerHostOfFilesAndCompilerOptions | WatchCompilerHostOfConfigFile { + if (isArray(rootFilesOrConfigFileName)) { + return createWatchCompilerHostOfFilesAndCompilerOptions(rootFilesOrConfigFileName, options!, system, createProgram, reportDiagnostic, reportWatchStatus, projectReferences); // TODO: GH#18217 + } + else { + return createWatchCompilerHostOfConfigFile(rootFilesOrConfigFileName, options, system, createProgram, reportDiagnostic, reportWatchStatus); + } + } + + /** + * Creates the watch from the host for root files and compiler options + */ + export function createWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions): WatchOfFilesAndCompilerOptions; + /** + * Creates the watch from the host for config file + */ + export function createWatchProgram(host: WatchCompilerHostOfConfigFile): WatchOfConfigFile; + export function createWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & WatchCompilerHostOfConfigFile): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { + interface FilePresentOnHost { + version: string; + sourceFile: SourceFile; + fileWatcher: FileWatcher; + } + type FileMissingOnHost = false; + interface FilePresenceUnknownOnHost { + version: false; + fileWatcher?: FileWatcher; + } + type FileMayBePresentOnHost = FilePresentOnHost | FilePresenceUnknownOnHost; + type HostFileInfo = FilePresentOnHost | FileMissingOnHost | FilePresenceUnknownOnHost; + + let builderProgram: T; + let reloadLevel: ConfigFileProgramReloadLevel; // level to indicate if the program needs to be reloaded from config file/just filenames etc + let missingFilesMap: Map; // Map of file watchers for the missing files + let watchedWildcardDirectories: Map; // map of watchers for the wild card directories in the config file + let timerToUpdateProgram: any; // timer callback to recompile the program + + const sourceFilesCache = createMap(); // Cache that stores the source file and version info + let missingFilePathsRequestedForRelease: Path[] | undefined; // These paths are held temparirly so that we can remove the entry from source file cache if the file is not tracked by missing files + let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations + let hasChangedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed + + const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames(); + const currentDirectory = host.getCurrentDirectory(); + const { configFileName, optionsToExtend: optionsToExtendForConfigFile = {}, createProgram } = host; + let { rootFiles: rootFileNames, options: compilerOptions, projectReferences } = host; + let configFileSpecs: ConfigFileSpecs; + let configFileParsingDiagnostics: Diagnostic[] | undefined; + let canConfigFileJsonReportNoInputFiles = false; + let hasChangedConfigFileParsingErrors = false; + + const cachedDirectoryStructureHost = configFileName === undefined ? undefined : createCachedDirectoryStructureHost(host, currentDirectory, useCaseSensitiveFileNames); + if (cachedDirectoryStructureHost && host.onCachedDirectoryStructureHostCreate) { + host.onCachedDirectoryStructureHostCreate(cachedDirectoryStructureHost); + } + const directoryStructureHost: DirectoryStructureHost = cachedDirectoryStructureHost || host; + const parseConfigFileHost = parseConfigHostFromCompilerHostLike(host, directoryStructureHost); + + // From tsc we want to get already parsed result and hence check for rootFileNames + let newLine = updateNewLine(); + if (configFileName && host.configFileParsingResult) { + setConfigFileParsingResult(host.configFileParsingResult); + newLine = updateNewLine(); + } + reportWatchDiagnostic(Diagnostics.Starting_compilation_in_watch_mode); + if (configFileName && !host.configFileParsingResult) { + newLine = getNewLineCharacter(optionsToExtendForConfigFile, () => host.getNewLine()); + Debug.assert(!rootFileNames); + parseConfigFile(); + newLine = updateNewLine(); + } + + const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory(host, compilerOptions); + const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); + + writeLog(`Current directory: ${currentDirectory} CaseSensitiveFileNames: ${useCaseSensitiveFileNames}`); + let configFileWatcher: FileWatcher | undefined; + if (configFileName) { + configFileWatcher = watchFile(host, configFileName, scheduleProgramReload, PollingInterval.High, WatchType.ConfigFile); + } + + const compilerHost = createCompilerHostFromProgramHost(host, () => compilerOptions, directoryStructureHost) as CompilerHost & ResolutionCacheHost; + setGetSourceFileAsHashVersioned(compilerHost, host); + // Members for CompilerHost + const getNewSourceFile = compilerHost.getSourceFile; + compilerHost.getSourceFile = (fileName, ...args) => getVersionedSourceFileByPath(fileName, toPath(fileName), ...args); + compilerHost.getSourceFileByPath = getVersionedSourceFileByPath; + compilerHost.getNewLine = () => newLine; + compilerHost.fileExists = fileExists; + compilerHost.onReleaseOldSourceFile = onReleaseOldSourceFile; + // Members for ResolutionCacheHost + compilerHost.toPath = toPath; + compilerHost.getCompilationSettings = () => compilerOptions; + compilerHost.watchDirectoryOfFailedLookupLocation = (dir, cb, flags) => watchDirectory(host, dir, cb, flags, WatchType.FailedLookupLocations); + compilerHost.watchTypeRootsDirectory = (dir, cb, flags) => watchDirectory(host, dir, cb, flags, WatchType.TypeRoots); + compilerHost.getCachedDirectoryStructureHost = () => cachedDirectoryStructureHost; + compilerHost.onInvalidatedResolution = scheduleProgramUpdate; + compilerHost.onChangedAutomaticTypeDirectiveNames = () => { + hasChangedAutomaticTypeDirectiveNames = true; + scheduleProgramUpdate(); + }; + compilerHost.fileIsOpen = returnFalse; + compilerHost.maxNumberOfFilesToIterateForInvalidation = host.maxNumberOfFilesToIterateForInvalidation; + compilerHost.getCurrentProgram = getCurrentProgram; + compilerHost.writeLog = writeLog; + + // Cache for the module resolution + const resolutionCache = createResolutionCache(compilerHost, + configFileName ? + getDirectoryPath(getNormalizedAbsolutePath(configFileName, currentDirectory)) : + currentDirectory, + /*logChangesWhenResolvingModule*/ false + ); + // Resolve module using host module resolution strategy if provided otherwise use resolution cache to resolve module names + compilerHost.resolveModuleNames = host.resolveModuleNames ? + ((...args) => host.resolveModuleNames!(...args)) : + ((moduleNames, containingFile, reusedNames, redirectedReference) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, redirectedReference)); + compilerHost.resolveTypeReferenceDirectives = host.resolveTypeReferenceDirectives ? + ((...args) => host.resolveTypeReferenceDirectives!(...args)) : + ((typeDirectiveNames, containingFile, redirectedReference) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile, redirectedReference)); + const userProvidedResolution = !!host.resolveModuleNames || !!host.resolveTypeReferenceDirectives; + + builderProgram = readBuilderProgram(compilerOptions, compilerHost) as any as T; + synchronizeProgram(); + + // Update the wild card directory watch + watchConfigFileWildCardDirectories(); + + return configFileName ? + { getCurrentProgram: getCurrentBuilderProgram, getProgram: synchronizeProgram, close } : + { getCurrentProgram: getCurrentBuilderProgram, getProgram: synchronizeProgram, updateRootFileNames, close }; + + function close() { + resolutionCache.clear(); + clearMap(sourceFilesCache, value => { + if (value && value.fileWatcher) { + value.fileWatcher.close(); + value.fileWatcher = undefined; + } + }); + if (configFileWatcher) { + configFileWatcher.close(); + configFileWatcher = undefined; + } + if (watchedWildcardDirectories) { + clearMap(watchedWildcardDirectories, closeFileWatcherOf); + watchedWildcardDirectories = undefined!; + } + if (missingFilesMap) { + clearMap(missingFilesMap, closeFileWatcher); + missingFilesMap = undefined!; + } + } + + function getCurrentBuilderProgram() { + return builderProgram; + } + + function getCurrentProgram() { + return builderProgram && builderProgram.getProgramOrUndefined(); + } + + function synchronizeProgram() { + writeLog(`Synchronizing program`); + + const program = getCurrentBuilderProgram(); + if (hasChangedCompilerOptions) { + newLine = updateNewLine(); + if (program && changesAffectModuleResolution(program.getCompilerOptions(), compilerOptions)) { + resolutionCache.clear(); + } + } + + // All resolutions are invalid if user provided resolutions + const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(userProvidedResolution); + if (isProgramUptoDate(getCurrentProgram(), rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames, projectReferences)) { + if (hasChangedConfigFileParsingErrors) { + builderProgram = createProgram(/*rootNames*/ undefined, /*options*/ undefined, compilerHost, builderProgram, configFileParsingDiagnostics, projectReferences); + hasChangedConfigFileParsingErrors = false; + } + } + else { + createNewProgram(hasInvalidatedResolution); + } + + if (host.afterProgramCreate) { + host.afterProgramCreate(builderProgram); + } + + return builderProgram; + } + + function createNewProgram(hasInvalidatedResolution: HasInvalidatedResolution) { + // Compile the program + writeLog("CreatingProgramWith::"); + writeLog(` roots: ${JSON.stringify(rootFileNames)}`); + writeLog(` options: ${JSON.stringify(compilerOptions)}`); + + const needsUpdateInTypeRootWatch = hasChangedCompilerOptions || !getCurrentProgram(); + hasChangedCompilerOptions = false; + hasChangedConfigFileParsingErrors = false; + resolutionCache.startCachingPerDirectoryResolution(); + compilerHost.hasInvalidatedResolution = hasInvalidatedResolution; + compilerHost.hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames; + builderProgram = createProgram(rootFileNames, compilerOptions, compilerHost, builderProgram, configFileParsingDiagnostics, projectReferences); + resolutionCache.finishCachingPerDirectoryResolution(); + + // Update watches + updateMissingFilePathsWatch(builderProgram.getProgram(), missingFilesMap || (missingFilesMap = createMap()), watchMissingFilePath); + if (needsUpdateInTypeRootWatch) { + resolutionCache.updateTypeRootsWatch(); + } + + if (missingFilePathsRequestedForRelease) { + // These are the paths that program creater told us as not in use any more but were missing on the disk. + // We didnt remove the entry for them from sourceFiles cache so that we dont have to do File IO, + // if there is already watcher for it (for missing files) + // At this point our watches were updated, hence now we know that these paths are not tracked and need to be removed + // so that at later time we have correct result of their presence + for (const missingFilePath of missingFilePathsRequestedForRelease) { + if (!missingFilesMap.has(missingFilePath)) { + sourceFilesCache.delete(missingFilePath); + } + } + missingFilePathsRequestedForRelease = undefined; + } + } + + function updateRootFileNames(files: string[]) { + Debug.assert(!configFileName, "Cannot update root file names with config file watch mode"); + rootFileNames = files; + scheduleProgramUpdate(); + } + + function updateNewLine() { + return getNewLineCharacter(compilerOptions || optionsToExtendForConfigFile, () => host.getNewLine()); + } + + function toPath(fileName: string) { + return ts.toPath(fileName, currentDirectory, getCanonicalFileName); + } + + function isFileMissingOnHost(hostSourceFile: HostFileInfo | undefined): hostSourceFile is FileMissingOnHost { + return typeof hostSourceFile === "boolean"; + } + + function isFilePresenceUnknownOnHost(hostSourceFile: FileMayBePresentOnHost): hostSourceFile is FilePresenceUnknownOnHost { + return typeof (hostSourceFile as FilePresenceUnknownOnHost).version === "boolean"; + } + + function fileExists(fileName: string) { + const path = toPath(fileName); + // If file is missing on host from cache, we can definitely say file doesnt exist + // otherwise we need to ensure from the disk + if (isFileMissingOnHost(sourceFilesCache.get(path))) { + return true; + } + + return directoryStructureHost.fileExists(fileName); + } + + function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined { + const hostSourceFile = sourceFilesCache.get(path); + // No source file on the host + if (isFileMissingOnHost(hostSourceFile)) { + return undefined; + } + + // Create new source file if requested or the versions dont match + if (hostSourceFile === undefined || shouldCreateNewSourceFile || isFilePresenceUnknownOnHost(hostSourceFile)) { + const sourceFile = getNewSourceFile(fileName, languageVersion, onError); + if (hostSourceFile) { + if (sourceFile) { + // Set the source file and create file watcher now that file was present on the disk + (hostSourceFile as FilePresentOnHost).sourceFile = sourceFile; + hostSourceFile.version = sourceFile.version; + if (!hostSourceFile.fileWatcher) { + hostSourceFile.fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, path, WatchType.SourceFile); + } + } + else { + // There is no source file on host any more, close the watch, missing file paths will track it + if (hostSourceFile.fileWatcher) { + hostSourceFile.fileWatcher.close(); + } + sourceFilesCache.set(path, false); + } + } + else { + if (sourceFile) { + const fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, path, WatchType.SourceFile); + sourceFilesCache.set(path, { sourceFile, version: sourceFile.version, fileWatcher }); + } + else { + sourceFilesCache.set(path, false); + } + } + return sourceFile; + } + return hostSourceFile.sourceFile; + } + + function nextSourceFileVersion(path: Path) { + const hostSourceFile = sourceFilesCache.get(path); + if (hostSourceFile !== undefined) { + if (isFileMissingOnHost(hostSourceFile)) { + // The next version, lets set it as presence unknown file + sourceFilesCache.set(path, { version: false }); + } + else { + (hostSourceFile as FilePresenceUnknownOnHost).version = false; + } + } + } + + function getSourceVersion(path: Path): string | undefined { + const hostSourceFile = sourceFilesCache.get(path); + return !hostSourceFile || !hostSourceFile.version ? undefined : hostSourceFile.version; + } + + function onReleaseOldSourceFile(oldSourceFile: SourceFile, _oldOptions: CompilerOptions, hasSourceFileByPath: boolean) { + const hostSourceFileInfo = sourceFilesCache.get(oldSourceFile.resolvedPath); + // If this is the source file thats in the cache and new program doesnt need it, + // remove the cached entry. + // Note we arent deleting entry if file became missing in new program or + // there was version update and new source file was created. + if (hostSourceFileInfo !== undefined) { + // record the missing file paths so they can be removed later if watchers arent tracking them + if (isFileMissingOnHost(hostSourceFileInfo)) { + (missingFilePathsRequestedForRelease || (missingFilePathsRequestedForRelease = [])).push(oldSourceFile.path); + } + else if ((hostSourceFileInfo as FilePresentOnHost).sourceFile === oldSourceFile) { + if (hostSourceFileInfo.fileWatcher) { + hostSourceFileInfo.fileWatcher.close(); + } + sourceFilesCache.delete(oldSourceFile.resolvedPath); + if (!hasSourceFileByPath) { + resolutionCache.removeResolutionsOfFile(oldSourceFile.path); + } + } + } + } + + function reportWatchDiagnostic(message: DiagnosticMessage) { + if (host.onWatchStatusChange) { + host.onWatchStatusChange(createCompilerDiagnostic(message), newLine, compilerOptions || optionsToExtendForConfigFile); + } + } + + // Upon detecting a file change, wait for 250ms and then perform a recompilation. This gives batch + // operations (such as saving all modified files in an editor) a chance to complete before we kick + // off a new compilation. + function scheduleProgramUpdate() { + if (!host.setTimeout || !host.clearTimeout) { + return; + } + + if (timerToUpdateProgram) { + host.clearTimeout(timerToUpdateProgram); + } + writeLog("Scheduling update"); + timerToUpdateProgram = host.setTimeout(updateProgram, 250); + } + + function scheduleProgramReload() { + Debug.assert(!!configFileName); + reloadLevel = ConfigFileProgramReloadLevel.Full; + scheduleProgramUpdate(); + } + + function updateProgram() { + timerToUpdateProgram = undefined; + reportWatchDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation); + + switch (reloadLevel) { + case ConfigFileProgramReloadLevel.Partial: + perfLogger.logStartUpdateProgram("PartialConfigReload"); + reloadFileNamesFromConfigFile(); + break; + case ConfigFileProgramReloadLevel.Full: + perfLogger.logStartUpdateProgram("FullConfigReload"); + reloadConfigFile(); + break; + default: + perfLogger.logStartUpdateProgram("SynchronizeProgram"); + synchronizeProgram(); + break; + } + perfLogger.logStopUpdateProgram("Done"); + } + + function reloadFileNamesFromConfigFile() { + writeLog("Reloading new file names and options"); + const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, parseConfigFileHost); + if (updateErrorForNoInputFiles(result, configFileName, configFileSpecs, configFileParsingDiagnostics!, canConfigFileJsonReportNoInputFiles)) { + hasChangedConfigFileParsingErrors = true; + } + rootFileNames = result.fileNames; + + // Update the program + synchronizeProgram(); + } + + function reloadConfigFile() { + writeLog(`Reloading config file: ${configFileName}`); + reloadLevel = ConfigFileProgramReloadLevel.None; + + if (cachedDirectoryStructureHost) { + cachedDirectoryStructureHost.clearCache(); + } + parseConfigFile(); + hasChangedCompilerOptions = true; + synchronizeProgram(); + + // Update the wild card directory watch + watchConfigFileWildCardDirectories(); + } + + function parseConfigFile() { + setConfigFileParsingResult(getParsedCommandLineOfConfigFile(configFileName, optionsToExtendForConfigFile, parseConfigFileHost)!); // TODO: GH#18217 + } + + function setConfigFileParsingResult(configFileParseResult: ParsedCommandLine) { + rootFileNames = configFileParseResult.fileNames; + compilerOptions = configFileParseResult.options; + configFileSpecs = configFileParseResult.configFileSpecs!; // TODO: GH#18217 + projectReferences = configFileParseResult.projectReferences; + configFileParsingDiagnostics = getConfigFileParsingDiagnostics(configFileParseResult).slice(); + canConfigFileJsonReportNoInputFiles = canJsonReportNoInutFiles(configFileParseResult.raw); + hasChangedConfigFileParsingErrors = true; + } + + function onSourceFileChange(fileName: string, eventKind: FileWatcherEventKind, path: Path) { + updateCachedSystemWithFile(fileName, path, eventKind); + + // Update the source file cache + if (eventKind === FileWatcherEventKind.Deleted && sourceFilesCache.has(path)) { + resolutionCache.invalidateResolutionOfFile(path); + } + resolutionCache.removeResolutionsFromProjectReferenceRedirects(path); + nextSourceFileVersion(path); + + // Update the program + scheduleProgramUpdate(); + } + + function updateCachedSystemWithFile(fileName: string, path: Path, eventKind: FileWatcherEventKind) { + if (cachedDirectoryStructureHost) { + cachedDirectoryStructureHost.addOrDeleteFile(fileName, path, eventKind); + } + } + + function watchMissingFilePath(missingFilePath: Path) { + return watchFilePath(host, missingFilePath, onMissingFileChange, PollingInterval.Medium, missingFilePath, WatchType.MissingFile); + } + + function onMissingFileChange(fileName: string, eventKind: FileWatcherEventKind, missingFilePath: Path) { + updateCachedSystemWithFile(fileName, missingFilePath, eventKind); + + if (eventKind === FileWatcherEventKind.Created && missingFilesMap.has(missingFilePath)) { + missingFilesMap.get(missingFilePath)!.close(); + missingFilesMap.delete(missingFilePath); + + // Delete the entry in the source files cache so that new source file is created + nextSourceFileVersion(missingFilePath); + + // When a missing file is created, we should update the graph. + scheduleProgramUpdate(); + } + } + + function watchConfigFileWildCardDirectories() { + if (configFileSpecs) { + updateWatchingWildcardDirectories( + watchedWildcardDirectories || (watchedWildcardDirectories = createMap()), + createMapFromTemplate(configFileSpecs.wildcardDirectories), + watchWildcardDirectory + ); + } + else if (watchedWildcardDirectories) { + clearMap(watchedWildcardDirectories, closeFileWatcherOf); + } + } + + function watchWildcardDirectory(directory: string, flags: WatchDirectoryFlags) { + return watchDirectory( + host, + directory, + fileOrDirectory => { + Debug.assert(!!configFileName); + + const fileOrDirectoryPath = toPath(fileOrDirectory); + + // Since the file existance changed, update the sourceFiles cache + if (cachedDirectoryStructureHost) { + cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + } + nextSourceFileVersion(fileOrDirectoryPath); + + if (isPathIgnored(fileOrDirectoryPath)) return; + + // If the the added or created file or directory is not supported file name, ignore the file + // But when watched directory is added/removed, we need to reload the file list + if (fileOrDirectoryPath !== directory && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, compilerOptions)) { + writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrDirectory}`); + return; + } + + // Reload is pending, do the reload + if (reloadLevel !== ConfigFileProgramReloadLevel.Full) { + reloadLevel = ConfigFileProgramReloadLevel.Partial; + + // Schedule Update the program + scheduleProgramUpdate(); + } + }, + flags, + WatchType.WildcardDirectory + ); + } + } +} \ No newline at end of file diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 68a0387f376..d30d2fa3503 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -3800,1609 +3800,3 @@ ${code} return `\n${expectMsg}:\n${expectedString}\n\n${actualMsg}:\n${actualString}`; } } - -namespace FourSlashInterface { - export class Test { - constructor(private state: FourSlash.TestState) { - } - - public markers(): FourSlash.Marker[] { - return this.state.getMarkers(); - } - - public markerNames(): string[] { - return this.state.getMarkerNames(); - } - - public marker(name: string): FourSlash.Marker { - return this.state.getMarkerByName(name); - } - - public markerName(m: FourSlash.Marker) { - return this.state.markerName(m); - } - - public ranges(): FourSlash.Range[] { - return this.state.getRanges(); - } - - public spans(): ts.TextSpan[] { - return this.ranges().map(r => ts.createTextSpan(r.pos, r.end - r.pos)); - } - - public rangesByText(): ts.Map { - return this.state.rangesByText(); - } - - public markerByName(s: string): FourSlash.Marker { - return this.state.getMarkerByName(s); - } - - public symbolsInScope(range: FourSlash.Range): ts.Symbol[] { - return this.state.symbolsInScope(range); - } - - public setTypesRegistry(map: ts.MapLike): void { - this.state.setTypesRegistry(map); - } - } - - export class Plugins { - constructor(private state: FourSlash.TestState) { - } - - public configurePlugin(pluginName: string, configuration: any): void { - this.state.configurePlugin(pluginName, configuration); - } - } - - export class GoTo { - constructor(private state: FourSlash.TestState) { - } - // Moves the caret to the specified marker, - // or the anonymous marker ('/**/') if no name - // is given - public marker(name?: string | FourSlash.Marker) { - this.state.goToMarker(name); - } - - public eachMarker(markers: readonly string[], action: (marker: FourSlash.Marker, index: number) => void): void; - public eachMarker(action: (marker: FourSlash.Marker, index: number) => void): void; - public eachMarker(a: readonly string[] | ((marker: FourSlash.Marker, index: number) => void), b?: (marker: FourSlash.Marker, index: number) => void): void { - const markers = typeof a === "function" ? this.state.getMarkers() : a.map(m => this.state.getMarkerByName(m)); - this.state.goToEachMarker(markers, typeof a === "function" ? a : b!); - } - - - public rangeStart(range: FourSlash.Range) { - this.state.goToRangeStart(range); - } - - public eachRange(action: (range: FourSlash.Range) => void) { - this.state.goToEachRange(action); - } - - public bof() { - this.state.goToBOF(); - } - - public eof() { - this.state.goToEOF(); - } - - public implementation() { - this.state.goToImplementation(); - } - - public position(positionOrLineAndCharacter: number | ts.LineAndCharacter, fileNameOrIndex?: string | number): void { - if (fileNameOrIndex !== undefined) { - this.file(fileNameOrIndex); - } - this.state.goToPosition(positionOrLineAndCharacter); - } - - // Opens a file, given either its index as it - // appears in the test source, or its filename - // as specified in the test metadata - public file(indexOrName: number | string, content?: string, scriptKindName?: string): void { - this.state.openFile(indexOrName, content, scriptKindName); - } - - public select(startMarker: string, endMarker: string) { - this.state.select(startMarker, endMarker); - } - - public selectAllInFile(fileName: string) { - this.state.selectAllInFile(fileName); - } - - public selectRange(range: FourSlash.Range): void { - this.state.selectRange(range); - } - } - - export class VerifyNegatable { - public not: VerifyNegatable | undefined; - - constructor(protected state: FourSlash.TestState, private negative = false) { - if (!negative) { - this.not = new VerifyNegatable(state, true); - } - } - - public assertHasRanges(ranges: FourSlash.Range[]) { - assert(ranges.length !== 0, "Array of ranges is expected to be non-empty"); - } - - public noSignatureHelp(...markers: (string | FourSlash.Marker)[]): void { - this.state.verifySignatureHelpPresence(/*expectPresent*/ false, /*triggerReason*/ undefined, markers); - } - - public noSignatureHelpForTriggerReason(reason: ts.SignatureHelpTriggerReason, ...markers: (string | FourSlash.Marker)[]): void { - this.state.verifySignatureHelpPresence(/*expectPresent*/ false, reason, markers); - } - - public signatureHelpPresentForTriggerReason(reason: ts.SignatureHelpTriggerReason, ...markers: (string | FourSlash.Marker)[]): void { - this.state.verifySignatureHelpPresence(/*expectPresent*/ true, reason, markers); - } - - public signatureHelp(...options: VerifySignatureHelpOptions[]): void { - this.state.verifySignatureHelp(options); - } - - public errorExistsBetweenMarkers(startMarker: string, endMarker: string) { - this.state.verifyErrorExistsBetweenMarkers(startMarker, endMarker, !this.negative); - } - - public errorExistsAfterMarker(markerName = "") { - this.state.verifyErrorExistsAfterMarker(markerName, !this.negative, /*after*/ true); - } - - public errorExistsBeforeMarker(markerName = "") { - this.state.verifyErrorExistsAfterMarker(markerName, !this.negative, /*after*/ false); - } - - public quickInfoExists() { - this.state.verifyQuickInfoExists(this.negative); - } - - public typeDefinitionCountIs(expectedCount: number) { - this.state.verifyTypeDefinitionsCount(this.negative, expectedCount); - } - - public implementationListIsEmpty() { - this.state.verifyImplementationListIsEmpty(this.negative); - } - - public isValidBraceCompletionAtPosition(openingBrace: string) { - this.state.verifyBraceCompletionAtPosition(this.negative, openingBrace); - } - - public jsxClosingTag(map: { [markerName: string]: ts.JsxClosingTagInfo | undefined }): void { - this.state.verifyJsxClosingTag(map); - } - - public isInCommentAtPosition(onlyMultiLineDiverges?: boolean) { - this.state.verifySpanOfEnclosingComment(this.negative, onlyMultiLineDiverges); - } - - public codeFix(options: VerifyCodeFixOptions) { - this.state.verifyCodeFix(options); - } - - public codeFixAvailable(options?: VerifyCodeFixAvailableOptions[]) { - this.state.verifyCodeFixAvailable(this.negative, options); - } - - public applicableRefactorAvailableAtMarker(markerName: string) { - this.state.verifyApplicableRefactorAvailableAtMarker(this.negative, markerName); - } - - public applicableRefactorAvailableForRange() { - this.state.verifyApplicableRefactorAvailableForRange(this.negative); - } - - public refactorsAvailable(names: readonly string[]): void { - this.state.verifyRefactorsAvailable(names); - } - - public refactorAvailable(name: string, actionName?: string) { - this.state.verifyRefactorAvailable(this.negative, name, actionName); - } - } - - export class Verify extends VerifyNegatable { - constructor(state: FourSlash.TestState) { - super(state); - } - - public completions(...optionsArray: VerifyCompletionsOptions[]) { - for (const options of optionsArray) { - this.state.verifyCompletions(options); - } - } - - public quickInfoIs(expectedText: string, expectedDocumentation?: string) { - this.state.verifyQuickInfoString(expectedText, expectedDocumentation); - } - - public quickInfoAt(markerName: string | FourSlash.Range, expectedText: string, expectedDocumentation?: string) { - this.state.verifyQuickInfoAt(markerName, expectedText, expectedDocumentation); - } - - public quickInfos(namesAndTexts: { [name: string]: string }) { - this.state.verifyQuickInfos(namesAndTexts); - } - - public caretAtMarker(markerName?: string) { - this.state.verifyCaretAtMarker(markerName); - } - - public indentationIs(numberOfSpaces: number) { - this.state.verifyIndentationAtCurrentPosition(numberOfSpaces); - } - - public indentationAtPositionIs(fileName: string, position: number, numberOfSpaces: number, indentStyle = ts.IndentStyle.Smart, baseIndentSize = 0) { - this.state.verifyIndentationAtPosition(fileName, position, numberOfSpaces, indentStyle, baseIndentSize); - } - - public textAtCaretIs(text: string) { - this.state.verifyTextAtCaretIs(text); - } - - /** - * Compiles the current file and evaluates 'expr' in a context containing - * the emitted output, then compares (using ===) the result of that expression - * to 'value'. Do not use this function with external modules as it is not supported. - */ - public eval(expr: string, value: any) { - this.state.verifyEval(expr, value); - } - - public currentLineContentIs(text: string) { - this.state.verifyCurrentLineContent(text); - } - - public currentFileContentIs(text: string) { - this.state.verifyCurrentFileContent(text); - } - - public formatDocumentChangesNothing(): void { - this.state.verifyFormatDocumentChangesNothing(); - } - - public goToDefinitionIs(endMarkers: ArrayOrSingle) { - this.state.verifyGoToDefinitionIs(endMarkers); - } - - public goToDefinition(startMarkerName: ArrayOrSingle, endMarkerName: ArrayOrSingle, range?: FourSlash.Range): void; - public goToDefinition(startsAndEnds: [ArrayOrSingle, ArrayOrSingle][] | { [startMarkerName: string]: ArrayOrSingle }): void; - public goToDefinition(arg0: any, endMarkerName?: ArrayOrSingle) { - this.state.verifyGoToDefinition(arg0, endMarkerName); - } - - public goToType(startMarkerName: ArrayOrSingle, endMarkerName: ArrayOrSingle): void; - public goToType(startsAndEnds: [ArrayOrSingle, ArrayOrSingle][] | { [startMarkerName: string]: ArrayOrSingle }): void; - public goToType(arg0: any, endMarkerName?: ArrayOrSingle) { - this.state.verifyGoToType(arg0, endMarkerName); - } - - public goToDefinitionForMarkers(...markerNames: string[]) { - this.state.verifyGoToDefinitionForMarkers(markerNames); - } - - public goToDefinitionName(name: string, containerName: string) { - this.state.verifyGoToDefinitionName(name, containerName); - } - - public verifyGetEmitOutputForCurrentFile(expected: string): void { - this.state.verifyGetEmitOutputForCurrentFile(expected); - } - - public verifyGetEmitOutputContentsForCurrentFile(expected: ts.OutputFile[]): void { - this.state.verifyGetEmitOutputContentsForCurrentFile(expected); - } - - public symbolAtLocation(startRange: FourSlash.Range, ...declarationRanges: FourSlash.Range[]) { - this.state.verifySymbolAtLocation(startRange, declarationRanges); - } - - public typeOfSymbolAtLocation(range: FourSlash.Range, symbol: ts.Symbol, expected: string) { - this.state.verifyTypeOfSymbolAtLocation(range, symbol, expected); - } - - public referenceGroups(starts: ArrayOrSingle | ArrayOrSingle, parts: ReferenceGroup[]) { - this.state.verifyReferenceGroups(starts, parts); - } - - public noReferences(markerNameOrRange?: string | FourSlash.Range) { - this.state.verifyNoReferences(markerNameOrRange); - } - - public getReferencesForServerTest(expected: readonly ts.ReferenceEntry[]) { - this.state.verifyGetReferencesForServerTest(expected); - } - - public singleReferenceGroup(definition: ReferenceGroupDefinition, ranges?: FourSlash.Range[] | string) { - this.state.verifySingleReferenceGroup(definition, ranges); - } - - public findReferencesDefinitionDisplayPartsAtCaretAre(expected: ts.SymbolDisplayPart[]) { - this.state.verifyDisplayPartsOfReferencedSymbol(expected); - } - - public noErrors() { - this.state.verifyNoErrors(); - } - - public errorExistsAtRange(range: FourSlash.Range, code: number, message?: string) { - this.state.verifyErrorExistsAtRange(range, code, message); - } - - public numberOfErrorsInCurrentFile(expected: number) { - this.state.verifyNumberOfErrorsInCurrentFile(expected); - } - - public baselineCurrentFileBreakpointLocations() { - this.state.baselineCurrentFileBreakpointLocations(); - } - - public baselineCurrentFileNameOrDottedNameSpans() { - this.state.baselineCurrentFileNameOrDottedNameSpans(); - } - - public getEmitOutput(expectedOutputFiles: readonly string[]): void { - this.state.verifyGetEmitOutput(expectedOutputFiles); - } - - public baselineGetEmitOutput() { - this.state.baselineGetEmitOutput(); - } - - public baselineQuickInfo() { - this.state.baselineQuickInfo(); - } - - public baselineSmartSelection() { - this.state.baselineSmartSelection(); - } - - public baselineSyntacticDiagnostics() { - this.state.baselineSyntacticDiagnostics(); - } - - public baselineSyntacticAndSemanticDiagnostics() { - this.state.baselineSyntacticAndSemanticDiagnostics(); - } - - public nameOrDottedNameSpanTextIs(text: string) { - this.state.verifyCurrentNameOrDottedNameSpanText(text); - } - - public outliningSpansInCurrentFile(spans: FourSlash.Range[], kind?: "comment" | "region" | "code" | "imports") { - this.state.verifyOutliningSpans(spans, kind); - } - - public outliningHintSpansInCurrentFile(spans: FourSlash.Range[]) { - this.state.verifyOutliningHintSpans(spans); - } - - public todoCommentsInCurrentFile(descriptors: string[]) { - this.state.verifyTodoComments(descriptors, this.state.getRanges()); - } - - public matchingBracePositionInCurrentFile(bracePosition: number, expectedMatchPosition: number) { - this.state.verifyMatchingBracePosition(bracePosition, expectedMatchPosition); - } - - public noMatchingBracePositionInCurrentFile(bracePosition: number) { - this.state.verifyNoMatchingBracePosition(bracePosition); - } - - public docCommentTemplateAt(marker: string | FourSlash.Marker, expectedOffset: number, expectedText: string) { - this.state.goToMarker(marker); - this.state.verifyDocCommentTemplate({ newText: expectedText.replace(/\r?\n/g, "\r\n"), caretOffset: expectedOffset }); - } - - public noDocCommentTemplateAt(marker: string | FourSlash.Marker) { - this.state.goToMarker(marker); - this.state.verifyDocCommentTemplate(/*expected*/ undefined); - } - - public rangeAfterCodeFix(expectedText: string, includeWhiteSpace?: boolean, errorCode?: number, index?: number): void { - this.state.verifyRangeAfterCodeFix(expectedText, includeWhiteSpace, errorCode, index); - } - - public codeFixAll(options: VerifyCodeFixAllOptions): void { - this.state.verifyCodeFixAll(options); - } - - public fileAfterApplyingRefactorAtMarker(markerName: string, expectedContent: string, refactorNameToApply: string, actionName: string, formattingOptions?: ts.FormatCodeSettings): void { - this.state.verifyFileAfterApplyingRefactorAtMarker(markerName, expectedContent, refactorNameToApply, actionName, formattingOptions); - } - - public rangeIs(expectedText: string, includeWhiteSpace?: boolean): void { - this.state.verifyRangeIs(expectedText, includeWhiteSpace); - } - - public getAndApplyCodeFix(errorCode?: number, index?: number): void { - this.state.getAndApplyCodeActions(errorCode, index); - } - - public applyCodeActionFromCompletion(markerName: string, options: VerifyCompletionActionOptions): void { - this.state.applyCodeActionFromCompletion(markerName, options); - } - - public importFixAtPosition(expectedTextArray: string[], errorCode?: number, preferences?: ts.UserPreferences): void { - this.state.verifyImportFixAtPosition(expectedTextArray, errorCode, preferences); - } - - public navigationBar(json: any, options?: { checkSpans?: boolean }) { - this.state.verifyNavigationBar(json, options); - } - - public navigationTree(json: any, options?: { checkSpans?: boolean }) { - this.state.verifyNavigationTree(json, options); - } - - public navigateTo(...options: VerifyNavigateToOptions[]): void { - this.state.verifyNavigateTo(options); - } - - public occurrencesAtPositionContains(range: FourSlash.Range, isWriteAccess?: boolean) { - this.state.verifyOccurrencesAtPositionListContains(range.fileName, range.pos, range.end, isWriteAccess); - } - - public occurrencesAtPositionCount(expectedCount: number) { - this.state.verifyOccurrencesAtPositionListCount(expectedCount); - } - - public rangesAreOccurrences(isWriteAccess?: boolean, ranges?: FourSlash.Range[]) { - this.state.verifyRangesAreOccurrences(isWriteAccess, ranges); - } - - public rangesWithSameTextAreRenameLocations(...texts: string[]) { - this.state.verifyRangesWithSameTextAreRenameLocations(...texts); - } - - public rangesAreRenameLocations(options?: FourSlash.Range[] | { findInStrings?: boolean, findInComments?: boolean, ranges?: FourSlash.Range[] }) { - this.state.verifyRangesAreRenameLocations(options); - } - - public rangesAreDocumentHighlights(ranges?: FourSlash.Range[], options?: VerifyDocumentHighlightsOptions) { - this.state.verifyRangesAreDocumentHighlights(ranges, options); - } - - public rangesWithSameTextAreDocumentHighlights() { - this.state.verifyRangesWithSameTextAreDocumentHighlights(); - } - - public documentHighlightsOf(startRange: FourSlash.Range, ranges: FourSlash.Range[], options?: VerifyDocumentHighlightsOptions) { - this.state.verifyDocumentHighlightsOf(startRange, ranges, options); - } - - public noDocumentHighlights(startRange: FourSlash.Range) { - this.state.verifyNoDocumentHighlights(startRange); - } - - /** - * This method *requires* a contiguous, complete, and ordered stream of classifications for a file. - */ - public syntacticClassificationsAre(...classifications: { classificationType: string; text: string }[]) { - this.state.verifySyntacticClassifications(classifications); - } - - /** - * This method *requires* an ordered stream of classifications for a file, and spans are highly recommended. - */ - public semanticClassificationsAre(...classifications: Classification[]) { - this.state.verifySemanticClassifications(classifications); - } - - public renameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string, fileToRename?: string, expectedRange?: FourSlash.Range, options?: ts.RenameInfoOptions) { - this.state.verifyRenameInfoSucceeded(displayName, fullDisplayName, kind, kindModifiers, fileToRename, expectedRange, options); - } - - public renameInfoFailed(message?: string, allowRenameOfImportPath?: boolean) { - this.state.verifyRenameInfoFailed(message, allowRenameOfImportPath); - } - - public renameLocations(startRanges: ArrayOrSingle, options: RenameLocationsOptions) { - this.state.verifyRenameLocations(startRanges, options); - } - - public verifyQuickInfoDisplayParts(kind: string, kindModifiers: string, textSpan: FourSlash.TextSpan, - displayParts: ts.SymbolDisplayPart[], documentation: ts.SymbolDisplayPart[], tags: ts.JSDocTagInfo[]) { - this.state.verifyQuickInfoDisplayParts(kind, kindModifiers, textSpan, displayParts, documentation, tags); - } - - public getSyntacticDiagnostics(expected: readonly Diagnostic[]) { - this.state.getSyntacticDiagnostics(expected); - } - - public getSemanticDiagnostics(expected: readonly Diagnostic[]) { - this.state.getSemanticDiagnostics(expected); - } - - public getSuggestionDiagnostics(expected: readonly Diagnostic[]) { - this.state.getSuggestionDiagnostics(expected); - } - - public ProjectInfo(expected: string[]) { - this.state.verifyProjectInfo(expected); - } - - public allRangesAppearInImplementationList(markerName: string) { - this.state.verifyRangesInImplementationList(markerName); - } - - public getEditsForFileRename(options: GetEditsForFileRenameOptions) { - this.state.getEditsForFileRename(options); - } - - public moveToNewFile(options: MoveToNewFileOptions): void { - this.state.moveToNewFile(options); - } - public noMoveToNewFile(): void { - this.state.noMoveToNewFile(); - } - } - - export class Edit { - constructor(private state: FourSlash.TestState) { - } - public backspace(count?: number) { - this.state.deleteCharBehindMarker(count); - } - - public deleteAtCaret(times?: number) { - this.state.deleteChar(times); - } - - public replace(start: number, length: number, text: string) { - this.state.replace(start, length, text); - } - - public paste(text: string) { - this.state.paste(text); - } - - public insert(text: string) { - this.insertLines(text); - } - - public insertLine(text: string) { - this.insertLines(text + "\n"); - } - - public insertLines(...lines: string[]) { - this.state.type(lines.join("\n")); - } - - public deleteLine(index: number) { - this.deleteLineRange(index, index); - } - - public deleteLineRange(startIndex: number, endIndexInclusive: number) { - this.state.deleteLineRange(startIndex, endIndexInclusive); - } - - public replaceLine(index: number, text: string) { - this.state.selectLine(index); - this.state.type(text); - } - - public moveRight(count?: number) { - this.state.moveCaretRight(count); - } - - public moveLeft(count?: number) { - if (typeof count === "undefined") { - count = 1; - } - this.state.moveCaretRight(count * -1); - } - - public enableFormatting() { - this.state.enableFormatting = true; - } - - public disableFormatting() { - this.state.enableFormatting = false; - } - - public applyRefactor(options: ApplyRefactorOptions) { - this.state.applyRefactor(options); - } - } - - export class Debug { - constructor(private state: FourSlash.TestState) { - } - - public printCurrentParameterHelp() { - this.state.printCurrentParameterHelp(); - } - - public printCurrentFileState() { - this.state.printCurrentFileState(/*showWhitespace*/ false, /*makeCaretVisible*/ true); - } - - public printCurrentFileStateWithWhitespace() { - this.state.printCurrentFileState(/*showWhitespace*/ true, /*makeCaretVisible*/ true); - } - - public printCurrentFileStateWithoutCaret() { - this.state.printCurrentFileState(/*showWhitespace*/ false, /*makeCaretVisible*/ false); - } - - public printCurrentQuickInfo() { - this.state.printCurrentQuickInfo(); - } - - public printCurrentSignatureHelp() { - this.state.printCurrentSignatureHelp(); - } - - public printCompletionListMembers(options: ts.UserPreferences | undefined) { - this.state.printCompletionListMembers(options); - } - - public printAvailableCodeFixes() { - this.state.printAvailableCodeFixes(); - } - - public printBreakpointLocation(pos: number) { - this.state.printBreakpointLocation(pos); - } - public printBreakpointAtCurrentLocation() { - this.state.printBreakpointAtCurrentLocation(); - } - - public printNameOrDottedNameSpans(pos: number) { - this.state.printNameOrDottedNameSpans(pos); - } - - public printErrorList() { - this.state.printErrorList(); - } - - public printNavigationItems(searchValue = ".*") { - this.state.printNavigationItems(searchValue); - } - - public printNavigationBar() { - this.state.printNavigationBar(); - } - - public printContext() { - this.state.printContext(); - } - - public printOutliningSpans() { - this.state.printOutliningSpans(); - } - } - - export class Format { - constructor(private state: FourSlash.TestState) { - } - - public document() { - this.state.formatDocument(); - } - - public copyFormatOptions(): ts.FormatCodeSettings { - return this.state.copyFormatOptions(); - } - - public setFormatOptions(options: ts.FormatCodeOptions) { - return this.state.setFormatOptions(options); - } - - public selection(startMarker: string, endMarker: string) { - this.state.formatSelection(this.state.getMarkerByName(startMarker).position, this.state.getMarkerByName(endMarker).position); - } - - public onType(posMarker: string, key: string) { - this.state.formatOnType(this.state.getMarkerByName(posMarker).position, key); - } - - public setOption(name: keyof ts.FormatCodeSettings, value: number | string | boolean): void { - this.state.formatCodeSettings = { ...this.state.formatCodeSettings, [name]: value }; - } - } - - export class Cancellation { - constructor(private state: FourSlash.TestState) { - } - - public resetCancelled() { - this.state.resetCancelled(); - } - - public setCancelled(numberOfCalls = 0) { - this.state.setCancelled(numberOfCalls); - } - } - - interface Classification { - classificationType: ts.ClassificationTypeNames; - text: string; - textSpan?: FourSlash.TextSpan; - } - export namespace Classification { - export function comment(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.comment, text, position); - } - - export function identifier(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.identifier, text, position); - } - - export function keyword(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.keyword, text, position); - } - - export function numericLiteral(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.numericLiteral, text, position); - } - - export function operator(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.operator, text, position); - } - - export function stringLiteral(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.stringLiteral, text, position); - } - - export function whiteSpace(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.whiteSpace, text, position); - } - - export function text(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.text, text, position); - } - - export function punctuation(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.punctuation, text, position); - } - - export function docCommentTagName(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.docCommentTagName, text, position); - } - - export function className(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.className, text, position); - } - - export function enumName(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.enumName, text, position); - } - - export function interfaceName(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.interfaceName, text, position); - } - - export function moduleName(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.moduleName, text, position); - } - - export function typeParameterName(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.typeParameterName, text, position); - } - - export function parameterName(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.parameterName, text, position); - } - - export function typeAliasName(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.typeAliasName, text, position); - } - - export function jsxOpenTagName(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.jsxOpenTagName, text, position); - } - - export function jsxCloseTagName(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.jsxCloseTagName, text, position); - } - - export function jsxSelfClosingTagName(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.jsxSelfClosingTagName, text, position); - } - - export function jsxAttribute(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.jsxAttribute, text, position); - } - - export function jsxText(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.jsxText, text, position); - } - - export function jsxAttributeStringLiteralValue(text: string, position?: number): Classification { - return getClassification(ts.ClassificationTypeNames.jsxAttributeStringLiteralValue, text, position); - } - - function getClassification(classificationType: ts.ClassificationTypeNames, text: string, position?: number): Classification { - const textSpan = position === undefined ? undefined : { start: position, end: position + text.length }; - return { classificationType, text, textSpan }; - } - } - export namespace Completion { - export import SortText = ts.Completions.SortText; - - const functionEntry = (name: string): ExpectedCompletionEntryObject => ({ - name, - kind: "function", - kindModifiers: "declare", - sortText: SortText.GlobalsOrKeywords - }); - const varEntry = (name: string): ExpectedCompletionEntryObject => ({ - name, - kind: "var", - kindModifiers: "declare", - sortText: SortText.GlobalsOrKeywords - }); - const moduleEntry = (name: string): ExpectedCompletionEntryObject => ({ - name, - kind: "module", - kindModifiers: "declare", - sortText: SortText.GlobalsOrKeywords - }); - const keywordEntry = (name: string): ExpectedCompletionEntryObject => ({ - name, - kind: "keyword", - sortText: SortText.GlobalsOrKeywords - }); - const methodEntry = (name: string): ExpectedCompletionEntryObject => ({ - name, - kind: "method", - kindModifiers: "declare", - sortText: SortText.LocationPriority - }); - const propertyEntry = (name: string): ExpectedCompletionEntryObject => ({ - name, - kind: "property", - kindModifiers: "declare", - sortText: SortText.LocationPriority - }); - const interfaceEntry = (name: string): ExpectedCompletionEntryObject => ({ - name, - kind: "interface", - kindModifiers: "declare", - sortText: SortText.GlobalsOrKeywords - }); - const typeEntry = (name: string): ExpectedCompletionEntryObject => ({ - name, - kind: "type", - kindModifiers: "declare", - sortText: SortText.GlobalsOrKeywords - }); - - const res: ExpectedCompletionEntryObject[] = []; - for (let i = ts.SyntaxKind.FirstKeyword; i <= ts.SyntaxKind.LastKeyword; i++) { - res.push({ - name: ts.Debug.assertDefined(ts.tokenToString(i)), - kind: "keyword", - sortText: SortText.GlobalsOrKeywords - }); - } - export const keywordsWithUndefined: readonly ExpectedCompletionEntryObject[] = res; - export const keywords: readonly ExpectedCompletionEntryObject[] = keywordsWithUndefined.filter(k => k.name !== "undefined"); - - export const typeKeywords: readonly ExpectedCompletionEntryObject[] = - ["false", "null", "true", "void", "any", "boolean", "keyof", "never", "readonly", "number", "object", "string", "symbol", "undefined", "unique", "unknown", "bigint"].map(keywordEntry); - - const globalTypeDecls: readonly ExpectedCompletionEntryObject[] = [ - interfaceEntry("Symbol"), - typeEntry("PropertyKey"), - interfaceEntry("PropertyDescriptor"), - interfaceEntry("PropertyDescriptorMap"), - varEntry("Object"), - interfaceEntry("ObjectConstructor"), - varEntry("Function"), - interfaceEntry("FunctionConstructor"), - typeEntry("ThisParameterType"), - typeEntry("OmitThisParameter"), - interfaceEntry("CallableFunction"), - interfaceEntry("NewableFunction"), - interfaceEntry("IArguments"), - varEntry("String"), - interfaceEntry("StringConstructor"), - varEntry("Boolean"), - interfaceEntry("BooleanConstructor"), - varEntry("Number"), - interfaceEntry("NumberConstructor"), - interfaceEntry("TemplateStringsArray"), - interfaceEntry("ImportMeta"), - varEntry("Math"), - varEntry("Date"), - interfaceEntry("DateConstructor"), - interfaceEntry("RegExpMatchArray"), - interfaceEntry("RegExpExecArray"), - varEntry("RegExp"), - interfaceEntry("RegExpConstructor"), - varEntry("Error"), - interfaceEntry("ErrorConstructor"), - varEntry("EvalError"), - interfaceEntry("EvalErrorConstructor"), - varEntry("RangeError"), - interfaceEntry("RangeErrorConstructor"), - varEntry("ReferenceError"), - interfaceEntry("ReferenceErrorConstructor"), - varEntry("SyntaxError"), - interfaceEntry("SyntaxErrorConstructor"), - varEntry("TypeError"), - interfaceEntry("TypeErrorConstructor"), - varEntry("URIError"), - interfaceEntry("URIErrorConstructor"), - varEntry("JSON"), - interfaceEntry("ReadonlyArray"), - interfaceEntry("ConcatArray"), - varEntry("Array"), - interfaceEntry("ArrayConstructor"), - interfaceEntry("TypedPropertyDescriptor"), - typeEntry("ClassDecorator"), - typeEntry("PropertyDecorator"), - typeEntry("MethodDecorator"), - typeEntry("ParameterDecorator"), - typeEntry("PromiseConstructorLike"), - interfaceEntry("PromiseLike"), - interfaceEntry("Promise"), - interfaceEntry("ArrayLike"), - typeEntry("Partial"), - typeEntry("Required"), - typeEntry("Readonly"), - typeEntry("Pick"), - typeEntry("Record"), - typeEntry("Exclude"), - typeEntry("Extract"), - typeEntry("Omit"), - typeEntry("NonNullable"), - typeEntry("Parameters"), - typeEntry("ConstructorParameters"), - typeEntry("ReturnType"), - typeEntry("InstanceType"), - interfaceEntry("ThisType"), - varEntry("ArrayBuffer"), - interfaceEntry("ArrayBufferTypes"), - typeEntry("ArrayBufferLike"), - interfaceEntry("ArrayBufferConstructor"), - interfaceEntry("ArrayBufferView"), - varEntry("DataView"), - interfaceEntry("DataViewConstructor"), - varEntry("Int8Array"), - interfaceEntry("Int8ArrayConstructor"), - varEntry("Uint8Array"), - interfaceEntry("Uint8ArrayConstructor"), - varEntry("Uint8ClampedArray"), - interfaceEntry("Uint8ClampedArrayConstructor"), - varEntry("Int16Array"), - interfaceEntry("Int16ArrayConstructor"), - varEntry("Uint16Array"), - interfaceEntry("Uint16ArrayConstructor"), - varEntry("Int32Array"), - interfaceEntry("Int32ArrayConstructor"), - varEntry("Uint32Array"), - interfaceEntry("Uint32ArrayConstructor"), - varEntry("Float32Array"), - interfaceEntry("Float32ArrayConstructor"), - varEntry("Float64Array"), - interfaceEntry("Float64ArrayConstructor"), - moduleEntry("Intl"), - ]; - - export const globalThisEntry: ExpectedCompletionEntry = { - name: "globalThis", - kind: "module", - sortText: SortText.GlobalsOrKeywords - }; - export const globalTypes = globalTypesPlus([]); - export function globalTypesPlus(plus: readonly ExpectedCompletionEntry[]): readonly ExpectedCompletionEntry[] { - return [ - globalThisEntry, - ...globalTypeDecls, - ...plus, - ...typeKeywords, - ]; - } - - export const typeAssertionKeywords: readonly ExpectedCompletionEntry[] = - globalTypesPlus([keywordEntry("const")]); - - function getInJsKeywords(keywords: readonly ExpectedCompletionEntryObject[]): readonly ExpectedCompletionEntryObject[] { - return keywords.filter(keyword => { - switch (keyword.name) { - case "enum": - case "interface": - case "implements": - case "private": - case "protected": - case "public": - case "abstract": - case "any": - case "boolean": - case "declare": - case "infer": - case "is": - case "keyof": - case "module": - case "namespace": - case "never": - case "readonly": - case "number": - case "object": - case "string": - case "symbol": - case "type": - case "unique": - case "unknown": - case "global": - case "bigint": - return false; - default: - return true; - } - }); - } - - export const classElementKeywords: readonly ExpectedCompletionEntryObject[] = - ["private", "protected", "public", "static", "abstract", "async", "constructor", "get", "readonly", "set"].map(keywordEntry); - - export const classElementInJsKeywords = getInJsKeywords(classElementKeywords); - - export const constructorParameterKeywords: readonly ExpectedCompletionEntryObject[] = - ["private", "protected", "public", "readonly"].map((name): ExpectedCompletionEntryObject => ({ - name, - kind: "keyword", - sortText: SortText.GlobalsOrKeywords - })); - - export const functionMembers: readonly ExpectedCompletionEntryObject[] = [ - methodEntry("apply"), - methodEntry("call"), - methodEntry("bind"), - methodEntry("toString"), - propertyEntry("length"), - { name: "arguments", kind: "property", kindModifiers: "declare", text: "(property) Function.arguments: any" }, - propertyEntry("caller"), - ]; - - export const stringMembers: readonly ExpectedCompletionEntryObject[] = [ - methodEntry("toString"), - methodEntry("charAt"), - methodEntry("charCodeAt"), - methodEntry("concat"), - methodEntry("indexOf"), - methodEntry("lastIndexOf"), - methodEntry("localeCompare"), - methodEntry("match"), - methodEntry("replace"), - methodEntry("search"), - methodEntry("slice"), - methodEntry("split"), - methodEntry("substring"), - methodEntry("toLowerCase"), - methodEntry("toLocaleLowerCase"), - methodEntry("toUpperCase"), - methodEntry("toLocaleUpperCase"), - methodEntry("trim"), - propertyEntry("length"), - methodEntry("substr"), - methodEntry("valueOf"), - ]; - - export const functionMembersWithPrototype: readonly ExpectedCompletionEntryObject[] = [ - ...functionMembers.slice(0, 4), - propertyEntry("prototype"), - ...functionMembers.slice(4), - ]; - - // TODO: Shouldn't propose type keywords in statement position - export const statementKeywordsWithTypes: readonly ExpectedCompletionEntryObject[] = [ - "break", - "case", - "catch", - "class", - "const", - "continue", - "debugger", - "default", - "delete", - "do", - "else", - "enum", - "export", - "extends", - "false", - "finally", - "for", - "function", - "if", - "import", - "in", - "instanceof", - "new", - "null", - "return", - "super", - "switch", - "this", - "throw", - "true", - "try", - "typeof", - "var", - "void", - "while", - "with", - "implements", - "interface", - "let", - "package", - "yield", - "any", - "async", - "await", - "boolean", - "declare", - "keyof", - "module", - "namespace", - "never", - "readonly", - "number", - "object", - "string", - "symbol", - "type", - "unique", - "unknown", - "bigint", - ].map(keywordEntry); - - export const statementKeywords: readonly ExpectedCompletionEntryObject[] = statementKeywordsWithTypes.filter(k => { - const name = k.name; - switch (name) { - case "false": - case "true": - case "null": - case "void": - return true; - case "declare": - case "module": - return false; - default: - return !ts.contains(typeKeywords, k); - } - }); - - export const statementInJsKeywords = getInJsKeywords(statementKeywords); - - export const globalsVars: readonly ExpectedCompletionEntryObject[] = [ - functionEntry("eval"), - functionEntry("parseInt"), - functionEntry("parseFloat"), - functionEntry("isNaN"), - functionEntry("isFinite"), - functionEntry("decodeURI"), - functionEntry("decodeURIComponent"), - functionEntry("encodeURI"), - functionEntry("encodeURIComponent"), - functionEntry("escape"), - functionEntry("unescape"), - varEntry("NaN"), - varEntry("Infinity"), - varEntry("Object"), - varEntry("Function"), - varEntry("String"), - varEntry("Boolean"), - varEntry("Number"), - varEntry("Math"), - varEntry("Date"), - varEntry("RegExp"), - varEntry("Error"), - varEntry("EvalError"), - varEntry("RangeError"), - varEntry("ReferenceError"), - varEntry("SyntaxError"), - varEntry("TypeError"), - varEntry("URIError"), - varEntry("JSON"), - varEntry("Array"), - varEntry("ArrayBuffer"), - varEntry("DataView"), - varEntry("Int8Array"), - varEntry("Uint8Array"), - varEntry("Uint8ClampedArray"), - varEntry("Int16Array"), - varEntry("Uint16Array"), - varEntry("Int32Array"), - varEntry("Uint32Array"), - varEntry("Float32Array"), - varEntry("Float64Array"), - moduleEntry("Intl"), - ]; - - const globalKeywordsInsideFunction: readonly ExpectedCompletionEntryObject[] = [ - "break", - "case", - "catch", - "class", - "const", - "continue", - "debugger", - "default", - "delete", - "do", - "else", - "enum", - "export", - "extends", - "false", - "finally", - "for", - "function", - "if", - "import", - "in", - "instanceof", - "new", - "null", - "return", - "super", - "switch", - "this", - "throw", - "true", - "try", - "typeof", - "var", - "void", - "while", - "with", - "implements", - "interface", - "let", - "package", - "yield", - "async", - "await", - ].map(keywordEntry); - - export const undefinedVarEntry: ExpectedCompletionEntry = { - name: "undefined", - kind: "var", - sortText: SortText.GlobalsOrKeywords - }; - // TODO: many of these are inappropriate to always provide - export const globalsInsideFunction = (plus: readonly ExpectedCompletionEntry[]): readonly ExpectedCompletionEntry[] => [ - { name: "arguments", kind: "local var" }, - ...plus, - globalThisEntry, - ...globalsVars, - undefinedVarEntry, - ...globalKeywordsInsideFunction, - ]; - - const globalInJsKeywordsInsideFunction = getInJsKeywords(globalKeywordsInsideFunction); - - // TODO: many of these are inappropriate to always provide - export const globalsInJsInsideFunction = (plus: readonly ExpectedCompletionEntry[]): readonly ExpectedCompletionEntry[] => [ - { name: "arguments", kind: "local var" }, - globalThisEntry, - ...globalsVars, - ...plus, - undefinedVarEntry, - ...globalInJsKeywordsInsideFunction, - ]; - - // TODO: many of these are inappropriate to always provide - export const globalKeywords: readonly ExpectedCompletionEntryObject[] = [ - "break", - "case", - "catch", - "class", - "const", - "continue", - "debugger", - "default", - "delete", - "do", - "else", - "enum", - "export", - "extends", - "false", - "finally", - "for", - "function", - "if", - "import", - "in", - "instanceof", - "new", - "null", - "return", - "super", - "switch", - "this", - "throw", - "true", - "try", - "typeof", - "var", - "void", - "while", - "with", - "implements", - "interface", - "let", - "package", - "yield", - "any", - "async", - "await", - "boolean", - "declare", - "keyof", - "module", - "namespace", - "never", - "readonly", - "number", - "object", - "string", - "symbol", - "type", - "unique", - "unknown", - "bigint", - ].map(keywordEntry); - - export const globalInJsKeywords = getInJsKeywords(globalKeywords); - - export const insideMethodKeywords: readonly ExpectedCompletionEntryObject[] = [ - "break", - "case", - "catch", - "class", - "const", - "continue", - "debugger", - "default", - "delete", - "do", - "else", - "enum", - "export", - "extends", - "false", - "finally", - "for", - "function", - "if", - "import", - "in", - "instanceof", - "new", - "null", - "return", - "super", - "switch", - "this", - "throw", - "true", - "try", - "typeof", - "var", - "void", - "while", - "with", - "implements", - "interface", - "let", - "package", - "yield", - "async", - "await", - ].map(keywordEntry); - - export const insideMethodInJsKeywords = getInJsKeywords(insideMethodKeywords); - - export const globals: readonly ExpectedCompletionEntryObject[] = [ - globalThisEntry, - ...globalsVars, - undefinedVarEntry, - ...globalKeywords - ]; - - export const globalsInJs: readonly ExpectedCompletionEntryObject[] = [ - globalThisEntry, - ...globalsVars, - undefinedVarEntry, - ...globalInJsKeywords - ]; - - export function globalsPlus(plus: readonly ExpectedCompletionEntry[]): readonly ExpectedCompletionEntry[] { - return [ - globalThisEntry, - ...globalsVars, - ...plus, - undefinedVarEntry, - ...globalKeywords]; - } - - export function globalsInJsPlus(plus: readonly ExpectedCompletionEntry[]): readonly ExpectedCompletionEntry[] { - return [ - globalThisEntry, - ...globalsVars, - ...plus, - undefinedVarEntry, - ...globalInJsKeywords]; - } - } - - export interface ReferenceGroup { - definition: ReferenceGroupDefinition; - ranges: FourSlash.Range[]; - } - - export type ReferenceGroupDefinition = string | { text: string, range: FourSlash.Range }; - - export interface ApplyRefactorOptions { - refactorName: string; - actionName: string; - actionDescription: string; - newContent: NewFileContent; - } - - export type ExpectedCompletionEntry = string | ExpectedCompletionEntryObject; - export interface ExpectedCompletionEntryObject { - readonly name: string; - readonly source?: string; - readonly insertText?: string; - readonly replacementSpan?: FourSlash.Range; - readonly hasAction?: boolean; // If not specified, will assert that this is false. - readonly isRecommended?: boolean; // If not specified, will assert that this is false. - readonly kind?: string; // If not specified, won't assert about this - readonly kindModifiers?: string; // Must be paired with 'kind' - readonly text?: string; - readonly documentation?: string; - readonly sourceDisplay?: string; - readonly tags?: readonly ts.JSDocTagInfo[]; - readonly sortText?: ts.Completions.SortText; - } - - export interface VerifyCompletionsOptions { - readonly marker?: ArrayOrSingle; - readonly isNewIdentifierLocation?: boolean; // Always tested - readonly isGlobalCompletion?: boolean; // Only tested if set - readonly exact?: ArrayOrSingle; - readonly includes?: ArrayOrSingle; - readonly excludes?: ArrayOrSingle; - readonly preferences?: ts.UserPreferences; - readonly triggerCharacter?: ts.CompletionsTriggerCharacter; - } - - export interface VerifySignatureHelpOptions { - readonly marker?: ArrayOrSingle; - /** @default 1 */ - readonly overloadsCount?: number; - /** @default undefined */ - readonly docComment?: string; - readonly text?: string; - readonly parameterName?: string; - readonly parameterSpan?: string; - /** @default undefined */ - readonly parameterDocComment?: string; - readonly parameterCount?: number; - readonly argumentCount?: number; - /** @default false */ - readonly isVariadic?: boolean; - /** @default ts.emptyArray */ - readonly tags?: readonly ts.JSDocTagInfo[]; - readonly triggerReason?: ts.SignatureHelpTriggerReason; - } - - export interface VerifyNavigateToOptions { - readonly pattern: string; - readonly fileName?: string; - readonly expected: readonly ExpectedNavigateToItem[]; - } - - export interface ExpectedNavigateToItem { - readonly name: string; - readonly kind: ts.ScriptElementKind; - readonly kindModifiers?: string; - readonly matchKind?: keyof typeof ts.PatternMatchKind; - readonly isCaseSensitive?: boolean; - readonly range: FourSlash.Range; - readonly containerName?: string; - readonly containerKind?: ts.ScriptElementKind; - } - - export type ArrayOrSingle = T | readonly T[]; - - export interface VerifyCompletionListContainsOptions extends ts.UserPreferences { - triggerCharacter?: ts.CompletionsTriggerCharacter; - sourceDisplay: string; - isRecommended?: true; - insertText?: string; - replacementSpan?: FourSlash.Range; - } - - export interface VerifyDocumentHighlightsOptions { - filesToSearch?: readonly string[]; - } - - export type NewFileContent = string | { readonly [filename: string]: string }; - - export interface NewContentOptions { - // Exactly one of these should be defined. - newFileContent?: NewFileContent; - newRangeContent?: string; - } - - export interface VerifyCodeFixOptions extends NewContentOptions { - readonly description: string; - readonly errorCode?: number; - readonly index?: number; - readonly preferences?: ts.UserPreferences; - readonly applyChanges?: boolean; - readonly commands?: readonly ts.CodeActionCommand[]; - } - - export interface VerifyCodeFixAvailableOptions { - description: string; - commands?: ts.CodeActionCommand[]; - } - - export interface VerifyCodeFixAllOptions { - fixId: string; - fixAllDescription: string; - newFileContent: NewFileContent; - commands: readonly {}[]; - } - - export interface VerifyRefactorOptions { - name: string; - actionName: string; - refactors: readonly ts.ApplicableRefactorInfo[]; - } - - export interface VerifyCompletionActionOptions extends NewContentOptions { - name: string; - source?: string; - description: string; - preferences?: ts.UserPreferences; - } - - export interface Diagnostic { - message: string; - range?: FourSlash.Range; - code: number; - reportsUnnecessary?: true; - } - - export interface GetEditsForFileRenameOptions { - readonly oldPath: string; - readonly newPath: string; - readonly newFileContents: { readonly [fileName: string]: string }; - readonly preferences?: ts.UserPreferences; - } - - export interface MoveToNewFileOptions { - readonly newFileContents: { readonly [fileName: string]: string }; - readonly preferences?: ts.UserPreferences; - } - - export type RenameLocationsOptions = readonly RenameLocationOptions[] | { - readonly findInStrings?: boolean; - readonly findInComments?: boolean; - readonly ranges: readonly RenameLocationOptions[]; - readonly providePrefixAndSuffixTextForRename?: boolean; - }; - export type RenameLocationOptions = FourSlash.Range | { readonly range: FourSlash.Range, readonly prefixText?: string, readonly suffixText?: string }; -} diff --git a/src/harness/fourslashInterface.ts b/src/harness/fourslashInterface.ts new file mode 100644 index 00000000000..8129fd373e4 --- /dev/null +++ b/src/harness/fourslashInterface.ts @@ -0,0 +1,1605 @@ +namespace FourSlashInterface { + export class Test { + constructor(private state: FourSlash.TestState) { + } + + public markers(): FourSlash.Marker[] { + return this.state.getMarkers(); + } + + public markerNames(): string[] { + return this.state.getMarkerNames(); + } + + public marker(name: string): FourSlash.Marker { + return this.state.getMarkerByName(name); + } + + public markerName(m: FourSlash.Marker) { + return this.state.markerName(m); + } + + public ranges(): FourSlash.Range[] { + return this.state.getRanges(); + } + + public spans(): ts.TextSpan[] { + return this.ranges().map(r => ts.createTextSpan(r.pos, r.end - r.pos)); + } + + public rangesByText(): ts.Map { + return this.state.rangesByText(); + } + + public markerByName(s: string): FourSlash.Marker { + return this.state.getMarkerByName(s); + } + + public symbolsInScope(range: FourSlash.Range): ts.Symbol[] { + return this.state.symbolsInScope(range); + } + + public setTypesRegistry(map: ts.MapLike): void { + this.state.setTypesRegistry(map); + } + } + + export class Plugins { + constructor(private state: FourSlash.TestState) { + } + + public configurePlugin(pluginName: string, configuration: any): void { + this.state.configurePlugin(pluginName, configuration); + } + } + + export class GoTo { + constructor(private state: FourSlash.TestState) { + } + // Moves the caret to the specified marker, + // or the anonymous marker ('/**/') if no name + // is given + public marker(name?: string | FourSlash.Marker) { + this.state.goToMarker(name); + } + + public eachMarker(markers: readonly string[], action: (marker: FourSlash.Marker, index: number) => void): void; + public eachMarker(action: (marker: FourSlash.Marker, index: number) => void): void; + public eachMarker(a: readonly string[] | ((marker: FourSlash.Marker, index: number) => void), b?: (marker: FourSlash.Marker, index: number) => void): void { + const markers = typeof a === "function" ? this.state.getMarkers() : a.map(m => this.state.getMarkerByName(m)); + this.state.goToEachMarker(markers, typeof a === "function" ? a : b!); + } + + + public rangeStart(range: FourSlash.Range) { + this.state.goToRangeStart(range); + } + + public eachRange(action: (range: FourSlash.Range) => void) { + this.state.goToEachRange(action); + } + + public bof() { + this.state.goToBOF(); + } + + public eof() { + this.state.goToEOF(); + } + + public implementation() { + this.state.goToImplementation(); + } + + public position(positionOrLineAndCharacter: number | ts.LineAndCharacter, fileNameOrIndex?: string | number): void { + if (fileNameOrIndex !== undefined) { + this.file(fileNameOrIndex); + } + this.state.goToPosition(positionOrLineAndCharacter); + } + + // Opens a file, given either its index as it + // appears in the test source, or its filename + // as specified in the test metadata + public file(indexOrName: number | string, content?: string, scriptKindName?: string): void { + this.state.openFile(indexOrName, content, scriptKindName); + } + + public select(startMarker: string, endMarker: string) { + this.state.select(startMarker, endMarker); + } + + public selectAllInFile(fileName: string) { + this.state.selectAllInFile(fileName); + } + + public selectRange(range: FourSlash.Range): void { + this.state.selectRange(range); + } + } + + export class VerifyNegatable { + public not: VerifyNegatable | undefined; + + constructor(protected state: FourSlash.TestState, private negative = false) { + if (!negative) { + this.not = new VerifyNegatable(state, true); + } + } + + public assertHasRanges(ranges: FourSlash.Range[]) { + assert(ranges.length !== 0, "Array of ranges is expected to be non-empty"); + } + + public noSignatureHelp(...markers: (string | FourSlash.Marker)[]): void { + this.state.verifySignatureHelpPresence(/*expectPresent*/ false, /*triggerReason*/ undefined, markers); + } + + public noSignatureHelpForTriggerReason(reason: ts.SignatureHelpTriggerReason, ...markers: (string | FourSlash.Marker)[]): void { + this.state.verifySignatureHelpPresence(/*expectPresent*/ false, reason, markers); + } + + public signatureHelpPresentForTriggerReason(reason: ts.SignatureHelpTriggerReason, ...markers: (string | FourSlash.Marker)[]): void { + this.state.verifySignatureHelpPresence(/*expectPresent*/ true, reason, markers); + } + + public signatureHelp(...options: VerifySignatureHelpOptions[]): void { + this.state.verifySignatureHelp(options); + } + + public errorExistsBetweenMarkers(startMarker: string, endMarker: string) { + this.state.verifyErrorExistsBetweenMarkers(startMarker, endMarker, !this.negative); + } + + public errorExistsAfterMarker(markerName = "") { + this.state.verifyErrorExistsAfterMarker(markerName, !this.negative, /*after*/ true); + } + + public errorExistsBeforeMarker(markerName = "") { + this.state.verifyErrorExistsAfterMarker(markerName, !this.negative, /*after*/ false); + } + + public quickInfoExists() { + this.state.verifyQuickInfoExists(this.negative); + } + + public typeDefinitionCountIs(expectedCount: number) { + this.state.verifyTypeDefinitionsCount(this.negative, expectedCount); + } + + public implementationListIsEmpty() { + this.state.verifyImplementationListIsEmpty(this.negative); + } + + public isValidBraceCompletionAtPosition(openingBrace: string) { + this.state.verifyBraceCompletionAtPosition(this.negative, openingBrace); + } + + public jsxClosingTag(map: { [markerName: string]: ts.JsxClosingTagInfo | undefined }): void { + this.state.verifyJsxClosingTag(map); + } + + public isInCommentAtPosition(onlyMultiLineDiverges?: boolean) { + this.state.verifySpanOfEnclosingComment(this.negative, onlyMultiLineDiverges); + } + + public codeFix(options: VerifyCodeFixOptions) { + this.state.verifyCodeFix(options); + } + + public codeFixAvailable(options?: VerifyCodeFixAvailableOptions[]) { + this.state.verifyCodeFixAvailable(this.negative, options); + } + + public applicableRefactorAvailableAtMarker(markerName: string) { + this.state.verifyApplicableRefactorAvailableAtMarker(this.negative, markerName); + } + + public applicableRefactorAvailableForRange() { + this.state.verifyApplicableRefactorAvailableForRange(this.negative); + } + + public refactorsAvailable(names: readonly string[]): void { + this.state.verifyRefactorsAvailable(names); + } + + public refactorAvailable(name: string, actionName?: string) { + this.state.verifyRefactorAvailable(this.negative, name, actionName); + } + } + + export class Verify extends VerifyNegatable { + constructor(state: FourSlash.TestState) { + super(state); + } + + public completions(...optionsArray: VerifyCompletionsOptions[]) { + for (const options of optionsArray) { + this.state.verifyCompletions(options); + } + } + + public quickInfoIs(expectedText: string, expectedDocumentation?: string) { + this.state.verifyQuickInfoString(expectedText, expectedDocumentation); + } + + public quickInfoAt(markerName: string | FourSlash.Range, expectedText: string, expectedDocumentation?: string) { + this.state.verifyQuickInfoAt(markerName, expectedText, expectedDocumentation); + } + + public quickInfos(namesAndTexts: { [name: string]: string }) { + this.state.verifyQuickInfos(namesAndTexts); + } + + public caretAtMarker(markerName?: string) { + this.state.verifyCaretAtMarker(markerName); + } + + public indentationIs(numberOfSpaces: number) { + this.state.verifyIndentationAtCurrentPosition(numberOfSpaces); + } + + public indentationAtPositionIs(fileName: string, position: number, numberOfSpaces: number, indentStyle = ts.IndentStyle.Smart, baseIndentSize = 0) { + this.state.verifyIndentationAtPosition(fileName, position, numberOfSpaces, indentStyle, baseIndentSize); + } + + public textAtCaretIs(text: string) { + this.state.verifyTextAtCaretIs(text); + } + + /** + * Compiles the current file and evaluates 'expr' in a context containing + * the emitted output, then compares (using ===) the result of that expression + * to 'value'. Do not use this function with external modules as it is not supported. + */ + public eval(expr: string, value: any) { + this.state.verifyEval(expr, value); + } + + public currentLineContentIs(text: string) { + this.state.verifyCurrentLineContent(text); + } + + public currentFileContentIs(text: string) { + this.state.verifyCurrentFileContent(text); + } + + public formatDocumentChangesNothing(): void { + this.state.verifyFormatDocumentChangesNothing(); + } + + public goToDefinitionIs(endMarkers: ArrayOrSingle) { + this.state.verifyGoToDefinitionIs(endMarkers); + } + + public goToDefinition(startMarkerName: ArrayOrSingle, endMarkerName: ArrayOrSingle, range?: FourSlash.Range): void; + public goToDefinition(startsAndEnds: [ArrayOrSingle, ArrayOrSingle][] | { [startMarkerName: string]: ArrayOrSingle }): void; + public goToDefinition(arg0: any, endMarkerName?: ArrayOrSingle) { + this.state.verifyGoToDefinition(arg0, endMarkerName); + } + + public goToType(startMarkerName: ArrayOrSingle, endMarkerName: ArrayOrSingle): void; + public goToType(startsAndEnds: [ArrayOrSingle, ArrayOrSingle][] | { [startMarkerName: string]: ArrayOrSingle }): void; + public goToType(arg0: any, endMarkerName?: ArrayOrSingle) { + this.state.verifyGoToType(arg0, endMarkerName); + } + + public goToDefinitionForMarkers(...markerNames: string[]) { + this.state.verifyGoToDefinitionForMarkers(markerNames); + } + + public goToDefinitionName(name: string, containerName: string) { + this.state.verifyGoToDefinitionName(name, containerName); + } + + public verifyGetEmitOutputForCurrentFile(expected: string): void { + this.state.verifyGetEmitOutputForCurrentFile(expected); + } + + public verifyGetEmitOutputContentsForCurrentFile(expected: ts.OutputFile[]): void { + this.state.verifyGetEmitOutputContentsForCurrentFile(expected); + } + + public symbolAtLocation(startRange: FourSlash.Range, ...declarationRanges: FourSlash.Range[]) { + this.state.verifySymbolAtLocation(startRange, declarationRanges); + } + + public typeOfSymbolAtLocation(range: FourSlash.Range, symbol: ts.Symbol, expected: string) { + this.state.verifyTypeOfSymbolAtLocation(range, symbol, expected); + } + + public referenceGroups(starts: ArrayOrSingle | ArrayOrSingle, parts: ReferenceGroup[]) { + this.state.verifyReferenceGroups(starts, parts); + } + + public noReferences(markerNameOrRange?: string | FourSlash.Range) { + this.state.verifyNoReferences(markerNameOrRange); + } + + public getReferencesForServerTest(expected: readonly ts.ReferenceEntry[]) { + this.state.verifyGetReferencesForServerTest(expected); + } + + public singleReferenceGroup(definition: ReferenceGroupDefinition, ranges?: FourSlash.Range[] | string) { + this.state.verifySingleReferenceGroup(definition, ranges); + } + + public findReferencesDefinitionDisplayPartsAtCaretAre(expected: ts.SymbolDisplayPart[]) { + this.state.verifyDisplayPartsOfReferencedSymbol(expected); + } + + public noErrors() { + this.state.verifyNoErrors(); + } + + public errorExistsAtRange(range: FourSlash.Range, code: number, message?: string) { + this.state.verifyErrorExistsAtRange(range, code, message); + } + + public numberOfErrorsInCurrentFile(expected: number) { + this.state.verifyNumberOfErrorsInCurrentFile(expected); + } + + public baselineCurrentFileBreakpointLocations() { + this.state.baselineCurrentFileBreakpointLocations(); + } + + public baselineCurrentFileNameOrDottedNameSpans() { + this.state.baselineCurrentFileNameOrDottedNameSpans(); + } + + public getEmitOutput(expectedOutputFiles: readonly string[]): void { + this.state.verifyGetEmitOutput(expectedOutputFiles); + } + + public baselineGetEmitOutput() { + this.state.baselineGetEmitOutput(); + } + + public baselineQuickInfo() { + this.state.baselineQuickInfo(); + } + + public baselineSmartSelection() { + this.state.baselineSmartSelection(); + } + + public baselineSyntacticDiagnostics() { + this.state.baselineSyntacticDiagnostics(); + } + + public baselineSyntacticAndSemanticDiagnostics() { + this.state.baselineSyntacticAndSemanticDiagnostics(); + } + + public nameOrDottedNameSpanTextIs(text: string) { + this.state.verifyCurrentNameOrDottedNameSpanText(text); + } + + public outliningSpansInCurrentFile(spans: FourSlash.Range[], kind?: "comment" | "region" | "code" | "imports") { + this.state.verifyOutliningSpans(spans, kind); + } + + public outliningHintSpansInCurrentFile(spans: FourSlash.Range[]) { + this.state.verifyOutliningHintSpans(spans); + } + + public todoCommentsInCurrentFile(descriptors: string[]) { + this.state.verifyTodoComments(descriptors, this.state.getRanges()); + } + + public matchingBracePositionInCurrentFile(bracePosition: number, expectedMatchPosition: number) { + this.state.verifyMatchingBracePosition(bracePosition, expectedMatchPosition); + } + + public noMatchingBracePositionInCurrentFile(bracePosition: number) { + this.state.verifyNoMatchingBracePosition(bracePosition); + } + + public docCommentTemplateAt(marker: string | FourSlash.Marker, expectedOffset: number, expectedText: string) { + this.state.goToMarker(marker); + this.state.verifyDocCommentTemplate({ newText: expectedText.replace(/\r?\n/g, "\r\n"), caretOffset: expectedOffset }); + } + + public noDocCommentTemplateAt(marker: string | FourSlash.Marker) { + this.state.goToMarker(marker); + this.state.verifyDocCommentTemplate(/*expected*/ undefined); + } + + public rangeAfterCodeFix(expectedText: string, includeWhiteSpace?: boolean, errorCode?: number, index?: number): void { + this.state.verifyRangeAfterCodeFix(expectedText, includeWhiteSpace, errorCode, index); + } + + public codeFixAll(options: VerifyCodeFixAllOptions): void { + this.state.verifyCodeFixAll(options); + } + + public fileAfterApplyingRefactorAtMarker(markerName: string, expectedContent: string, refactorNameToApply: string, actionName: string, formattingOptions?: ts.FormatCodeSettings): void { + this.state.verifyFileAfterApplyingRefactorAtMarker(markerName, expectedContent, refactorNameToApply, actionName, formattingOptions); + } + + public rangeIs(expectedText: string, includeWhiteSpace?: boolean): void { + this.state.verifyRangeIs(expectedText, includeWhiteSpace); + } + + public getAndApplyCodeFix(errorCode?: number, index?: number): void { + this.state.getAndApplyCodeActions(errorCode, index); + } + + public applyCodeActionFromCompletion(markerName: string, options: VerifyCompletionActionOptions): void { + this.state.applyCodeActionFromCompletion(markerName, options); + } + + public importFixAtPosition(expectedTextArray: string[], errorCode?: number, preferences?: ts.UserPreferences): void { + this.state.verifyImportFixAtPosition(expectedTextArray, errorCode, preferences); + } + + public navigationBar(json: any, options?: { checkSpans?: boolean }) { + this.state.verifyNavigationBar(json, options); + } + + public navigationTree(json: any, options?: { checkSpans?: boolean }) { + this.state.verifyNavigationTree(json, options); + } + + public navigateTo(...options: VerifyNavigateToOptions[]): void { + this.state.verifyNavigateTo(options); + } + + public occurrencesAtPositionContains(range: FourSlash.Range, isWriteAccess?: boolean) { + this.state.verifyOccurrencesAtPositionListContains(range.fileName, range.pos, range.end, isWriteAccess); + } + + public occurrencesAtPositionCount(expectedCount: number) { + this.state.verifyOccurrencesAtPositionListCount(expectedCount); + } + + public rangesAreOccurrences(isWriteAccess?: boolean, ranges?: FourSlash.Range[]) { + this.state.verifyRangesAreOccurrences(isWriteAccess, ranges); + } + + public rangesWithSameTextAreRenameLocations(...texts: string[]) { + this.state.verifyRangesWithSameTextAreRenameLocations(...texts); + } + + public rangesAreRenameLocations(options?: FourSlash.Range[] | { findInStrings?: boolean, findInComments?: boolean, ranges?: FourSlash.Range[] }) { + this.state.verifyRangesAreRenameLocations(options); + } + + public rangesAreDocumentHighlights(ranges?: FourSlash.Range[], options?: VerifyDocumentHighlightsOptions) { + this.state.verifyRangesAreDocumentHighlights(ranges, options); + } + + public rangesWithSameTextAreDocumentHighlights() { + this.state.verifyRangesWithSameTextAreDocumentHighlights(); + } + + public documentHighlightsOf(startRange: FourSlash.Range, ranges: FourSlash.Range[], options?: VerifyDocumentHighlightsOptions) { + this.state.verifyDocumentHighlightsOf(startRange, ranges, options); + } + + public noDocumentHighlights(startRange: FourSlash.Range) { + this.state.verifyNoDocumentHighlights(startRange); + } + + /** + * This method *requires* a contiguous, complete, and ordered stream of classifications for a file. + */ + public syntacticClassificationsAre(...classifications: { classificationType: string; text: string }[]) { + this.state.verifySyntacticClassifications(classifications); + } + + /** + * This method *requires* an ordered stream of classifications for a file, and spans are highly recommended. + */ + public semanticClassificationsAre(...classifications: Classification[]) { + this.state.verifySemanticClassifications(classifications); + } + + public renameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string, fileToRename?: string, expectedRange?: FourSlash.Range, options?: ts.RenameInfoOptions) { + this.state.verifyRenameInfoSucceeded(displayName, fullDisplayName, kind, kindModifiers, fileToRename, expectedRange, options); + } + + public renameInfoFailed(message?: string, allowRenameOfImportPath?: boolean) { + this.state.verifyRenameInfoFailed(message, allowRenameOfImportPath); + } + + public renameLocations(startRanges: ArrayOrSingle, options: RenameLocationsOptions) { + this.state.verifyRenameLocations(startRanges, options); + } + + public verifyQuickInfoDisplayParts(kind: string, kindModifiers: string, textSpan: FourSlash.TextSpan, + displayParts: ts.SymbolDisplayPart[], documentation: ts.SymbolDisplayPart[], tags: ts.JSDocTagInfo[]) { + this.state.verifyQuickInfoDisplayParts(kind, kindModifiers, textSpan, displayParts, documentation, tags); + } + + public getSyntacticDiagnostics(expected: readonly Diagnostic[]) { + this.state.getSyntacticDiagnostics(expected); + } + + public getSemanticDiagnostics(expected: readonly Diagnostic[]) { + this.state.getSemanticDiagnostics(expected); + } + + public getSuggestionDiagnostics(expected: readonly Diagnostic[]) { + this.state.getSuggestionDiagnostics(expected); + } + + public ProjectInfo(expected: string[]) { + this.state.verifyProjectInfo(expected); + } + + public allRangesAppearInImplementationList(markerName: string) { + this.state.verifyRangesInImplementationList(markerName); + } + + public getEditsForFileRename(options: GetEditsForFileRenameOptions) { + this.state.getEditsForFileRename(options); + } + + public moveToNewFile(options: MoveToNewFileOptions): void { + this.state.moveToNewFile(options); + } + public noMoveToNewFile(): void { + this.state.noMoveToNewFile(); + } + } + + export class Edit { + constructor(private state: FourSlash.TestState) { + } + public backspace(count?: number) { + this.state.deleteCharBehindMarker(count); + } + + public deleteAtCaret(times?: number) { + this.state.deleteChar(times); + } + + public replace(start: number, length: number, text: string) { + this.state.replace(start, length, text); + } + + public paste(text: string) { + this.state.paste(text); + } + + public insert(text: string) { + this.insertLines(text); + } + + public insertLine(text: string) { + this.insertLines(text + "\n"); + } + + public insertLines(...lines: string[]) { + this.state.type(lines.join("\n")); + } + + public deleteLine(index: number) { + this.deleteLineRange(index, index); + } + + public deleteLineRange(startIndex: number, endIndexInclusive: number) { + this.state.deleteLineRange(startIndex, endIndexInclusive); + } + + public replaceLine(index: number, text: string) { + this.state.selectLine(index); + this.state.type(text); + } + + public moveRight(count?: number) { + this.state.moveCaretRight(count); + } + + public moveLeft(count?: number) { + if (typeof count === "undefined") { + count = 1; + } + this.state.moveCaretRight(count * -1); + } + + public enableFormatting() { + this.state.enableFormatting = true; + } + + public disableFormatting() { + this.state.enableFormatting = false; + } + + public applyRefactor(options: ApplyRefactorOptions) { + this.state.applyRefactor(options); + } + } + + export class Debug { + constructor(private state: FourSlash.TestState) { + } + + public printCurrentParameterHelp() { + this.state.printCurrentParameterHelp(); + } + + public printCurrentFileState() { + this.state.printCurrentFileState(/*showWhitespace*/ false, /*makeCaretVisible*/ true); + } + + public printCurrentFileStateWithWhitespace() { + this.state.printCurrentFileState(/*showWhitespace*/ true, /*makeCaretVisible*/ true); + } + + public printCurrentFileStateWithoutCaret() { + this.state.printCurrentFileState(/*showWhitespace*/ false, /*makeCaretVisible*/ false); + } + + public printCurrentQuickInfo() { + this.state.printCurrentQuickInfo(); + } + + public printCurrentSignatureHelp() { + this.state.printCurrentSignatureHelp(); + } + + public printCompletionListMembers(options: ts.UserPreferences | undefined) { + this.state.printCompletionListMembers(options); + } + + public printAvailableCodeFixes() { + this.state.printAvailableCodeFixes(); + } + + public printBreakpointLocation(pos: number) { + this.state.printBreakpointLocation(pos); + } + public printBreakpointAtCurrentLocation() { + this.state.printBreakpointAtCurrentLocation(); + } + + public printNameOrDottedNameSpans(pos: number) { + this.state.printNameOrDottedNameSpans(pos); + } + + public printErrorList() { + this.state.printErrorList(); + } + + public printNavigationItems(searchValue = ".*") { + this.state.printNavigationItems(searchValue); + } + + public printNavigationBar() { + this.state.printNavigationBar(); + } + + public printContext() { + this.state.printContext(); + } + + public printOutliningSpans() { + this.state.printOutliningSpans(); + } + } + + export class Format { + constructor(private state: FourSlash.TestState) { + } + + public document() { + this.state.formatDocument(); + } + + public copyFormatOptions(): ts.FormatCodeSettings { + return this.state.copyFormatOptions(); + } + + public setFormatOptions(options: ts.FormatCodeOptions) { + return this.state.setFormatOptions(options); + } + + public selection(startMarker: string, endMarker: string) { + this.state.formatSelection(this.state.getMarkerByName(startMarker).position, this.state.getMarkerByName(endMarker).position); + } + + public onType(posMarker: string, key: string) { + this.state.formatOnType(this.state.getMarkerByName(posMarker).position, key); + } + + public setOption(name: keyof ts.FormatCodeSettings, value: number | string | boolean): void { + this.state.formatCodeSettings = { ...this.state.formatCodeSettings, [name]: value }; + } + } + + export class Cancellation { + constructor(private state: FourSlash.TestState) { + } + + public resetCancelled() { + this.state.resetCancelled(); + } + + public setCancelled(numberOfCalls = 0) { + this.state.setCancelled(numberOfCalls); + } + } + + interface Classification { + classificationType: ts.ClassificationTypeNames; + text: string; + textSpan?: FourSlash.TextSpan; + } + export namespace Classification { + export function comment(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.comment, text, position); + } + + export function identifier(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.identifier, text, position); + } + + export function keyword(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.keyword, text, position); + } + + export function numericLiteral(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.numericLiteral, text, position); + } + + export function operator(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.operator, text, position); + } + + export function stringLiteral(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.stringLiteral, text, position); + } + + export function whiteSpace(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.whiteSpace, text, position); + } + + export function text(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.text, text, position); + } + + export function punctuation(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.punctuation, text, position); + } + + export function docCommentTagName(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.docCommentTagName, text, position); + } + + export function className(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.className, text, position); + } + + export function enumName(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.enumName, text, position); + } + + export function interfaceName(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.interfaceName, text, position); + } + + export function moduleName(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.moduleName, text, position); + } + + export function typeParameterName(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.typeParameterName, text, position); + } + + export function parameterName(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.parameterName, text, position); + } + + export function typeAliasName(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.typeAliasName, text, position); + } + + export function jsxOpenTagName(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.jsxOpenTagName, text, position); + } + + export function jsxCloseTagName(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.jsxCloseTagName, text, position); + } + + export function jsxSelfClosingTagName(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.jsxSelfClosingTagName, text, position); + } + + export function jsxAttribute(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.jsxAttribute, text, position); + } + + export function jsxText(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.jsxText, text, position); + } + + export function jsxAttributeStringLiteralValue(text: string, position?: number): Classification { + return getClassification(ts.ClassificationTypeNames.jsxAttributeStringLiteralValue, text, position); + } + + function getClassification(classificationType: ts.ClassificationTypeNames, text: string, position?: number): Classification { + const textSpan = position === undefined ? undefined : { start: position, end: position + text.length }; + return { classificationType, text, textSpan }; + } + } + export namespace Completion { + export import SortText = ts.Completions.SortText; + + const functionEntry = (name: string): ExpectedCompletionEntryObject => ({ + name, + kind: "function", + kindModifiers: "declare", + sortText: SortText.GlobalsOrKeywords + }); + const varEntry = (name: string): ExpectedCompletionEntryObject => ({ + name, + kind: "var", + kindModifiers: "declare", + sortText: SortText.GlobalsOrKeywords + }); + const moduleEntry = (name: string): ExpectedCompletionEntryObject => ({ + name, + kind: "module", + kindModifiers: "declare", + sortText: SortText.GlobalsOrKeywords + }); + const keywordEntry = (name: string): ExpectedCompletionEntryObject => ({ + name, + kind: "keyword", + sortText: SortText.GlobalsOrKeywords + }); + const methodEntry = (name: string): ExpectedCompletionEntryObject => ({ + name, + kind: "method", + kindModifiers: "declare", + sortText: SortText.LocationPriority + }); + const propertyEntry = (name: string): ExpectedCompletionEntryObject => ({ + name, + kind: "property", + kindModifiers: "declare", + sortText: SortText.LocationPriority + }); + const interfaceEntry = (name: string): ExpectedCompletionEntryObject => ({ + name, + kind: "interface", + kindModifiers: "declare", + sortText: SortText.GlobalsOrKeywords + }); + const typeEntry = (name: string): ExpectedCompletionEntryObject => ({ + name, + kind: "type", + kindModifiers: "declare", + sortText: SortText.GlobalsOrKeywords + }); + + const res: ExpectedCompletionEntryObject[] = []; + for (let i = ts.SyntaxKind.FirstKeyword; i <= ts.SyntaxKind.LastKeyword; i++) { + res.push({ + name: ts.Debug.assertDefined(ts.tokenToString(i)), + kind: "keyword", + sortText: SortText.GlobalsOrKeywords + }); + } + export const keywordsWithUndefined: readonly ExpectedCompletionEntryObject[] = res; + export const keywords: readonly ExpectedCompletionEntryObject[] = keywordsWithUndefined.filter(k => k.name !== "undefined"); + + export const typeKeywords: readonly ExpectedCompletionEntryObject[] = + ["false", "null", "true", "void", "any", "boolean", "keyof", "never", "readonly", "number", "object", "string", "symbol", "undefined", "unique", "unknown", "bigint"].map(keywordEntry); + + const globalTypeDecls: readonly ExpectedCompletionEntryObject[] = [ + interfaceEntry("Symbol"), + typeEntry("PropertyKey"), + interfaceEntry("PropertyDescriptor"), + interfaceEntry("PropertyDescriptorMap"), + varEntry("Object"), + interfaceEntry("ObjectConstructor"), + varEntry("Function"), + interfaceEntry("FunctionConstructor"), + typeEntry("ThisParameterType"), + typeEntry("OmitThisParameter"), + interfaceEntry("CallableFunction"), + interfaceEntry("NewableFunction"), + interfaceEntry("IArguments"), + varEntry("String"), + interfaceEntry("StringConstructor"), + varEntry("Boolean"), + interfaceEntry("BooleanConstructor"), + varEntry("Number"), + interfaceEntry("NumberConstructor"), + interfaceEntry("TemplateStringsArray"), + interfaceEntry("ImportMeta"), + varEntry("Math"), + varEntry("Date"), + interfaceEntry("DateConstructor"), + interfaceEntry("RegExpMatchArray"), + interfaceEntry("RegExpExecArray"), + varEntry("RegExp"), + interfaceEntry("RegExpConstructor"), + varEntry("Error"), + interfaceEntry("ErrorConstructor"), + varEntry("EvalError"), + interfaceEntry("EvalErrorConstructor"), + varEntry("RangeError"), + interfaceEntry("RangeErrorConstructor"), + varEntry("ReferenceError"), + interfaceEntry("ReferenceErrorConstructor"), + varEntry("SyntaxError"), + interfaceEntry("SyntaxErrorConstructor"), + varEntry("TypeError"), + interfaceEntry("TypeErrorConstructor"), + varEntry("URIError"), + interfaceEntry("URIErrorConstructor"), + varEntry("JSON"), + interfaceEntry("ReadonlyArray"), + interfaceEntry("ConcatArray"), + varEntry("Array"), + interfaceEntry("ArrayConstructor"), + interfaceEntry("TypedPropertyDescriptor"), + typeEntry("ClassDecorator"), + typeEntry("PropertyDecorator"), + typeEntry("MethodDecorator"), + typeEntry("ParameterDecorator"), + typeEntry("PromiseConstructorLike"), + interfaceEntry("PromiseLike"), + interfaceEntry("Promise"), + interfaceEntry("ArrayLike"), + typeEntry("Partial"), + typeEntry("Required"), + typeEntry("Readonly"), + typeEntry("Pick"), + typeEntry("Record"), + typeEntry("Exclude"), + typeEntry("Extract"), + typeEntry("Omit"), + typeEntry("NonNullable"), + typeEntry("Parameters"), + typeEntry("ConstructorParameters"), + typeEntry("ReturnType"), + typeEntry("InstanceType"), + interfaceEntry("ThisType"), + varEntry("ArrayBuffer"), + interfaceEntry("ArrayBufferTypes"), + typeEntry("ArrayBufferLike"), + interfaceEntry("ArrayBufferConstructor"), + interfaceEntry("ArrayBufferView"), + varEntry("DataView"), + interfaceEntry("DataViewConstructor"), + varEntry("Int8Array"), + interfaceEntry("Int8ArrayConstructor"), + varEntry("Uint8Array"), + interfaceEntry("Uint8ArrayConstructor"), + varEntry("Uint8ClampedArray"), + interfaceEntry("Uint8ClampedArrayConstructor"), + varEntry("Int16Array"), + interfaceEntry("Int16ArrayConstructor"), + varEntry("Uint16Array"), + interfaceEntry("Uint16ArrayConstructor"), + varEntry("Int32Array"), + interfaceEntry("Int32ArrayConstructor"), + varEntry("Uint32Array"), + interfaceEntry("Uint32ArrayConstructor"), + varEntry("Float32Array"), + interfaceEntry("Float32ArrayConstructor"), + varEntry("Float64Array"), + interfaceEntry("Float64ArrayConstructor"), + moduleEntry("Intl"), + ]; + + export const globalThisEntry: ExpectedCompletionEntry = { + name: "globalThis", + kind: "module", + sortText: SortText.GlobalsOrKeywords + }; + export const globalTypes = globalTypesPlus([]); + export function globalTypesPlus(plus: readonly ExpectedCompletionEntry[]): readonly ExpectedCompletionEntry[] { + return [ + globalThisEntry, + ...globalTypeDecls, + ...plus, + ...typeKeywords, + ]; + } + + export const typeAssertionKeywords: readonly ExpectedCompletionEntry[] = + globalTypesPlus([keywordEntry("const")]); + + function getInJsKeywords(keywords: readonly ExpectedCompletionEntryObject[]): readonly ExpectedCompletionEntryObject[] { + return keywords.filter(keyword => { + switch (keyword.name) { + case "enum": + case "interface": + case "implements": + case "private": + case "protected": + case "public": + case "abstract": + case "any": + case "boolean": + case "declare": + case "infer": + case "is": + case "keyof": + case "module": + case "namespace": + case "never": + case "readonly": + case "number": + case "object": + case "string": + case "symbol": + case "type": + case "unique": + case "unknown": + case "global": + case "bigint": + return false; + default: + return true; + } + }); + } + + export const classElementKeywords: readonly ExpectedCompletionEntryObject[] = + ["private", "protected", "public", "static", "abstract", "async", "constructor", "get", "readonly", "set"].map(keywordEntry); + + export const classElementInJsKeywords = getInJsKeywords(classElementKeywords); + + export const constructorParameterKeywords: readonly ExpectedCompletionEntryObject[] = + ["private", "protected", "public", "readonly"].map((name): ExpectedCompletionEntryObject => ({ + name, + kind: "keyword", + sortText: SortText.GlobalsOrKeywords + })); + + export const functionMembers: readonly ExpectedCompletionEntryObject[] = [ + methodEntry("apply"), + methodEntry("call"), + methodEntry("bind"), + methodEntry("toString"), + propertyEntry("length"), + { name: "arguments", kind: "property", kindModifiers: "declare", text: "(property) Function.arguments: any" }, + propertyEntry("caller"), + ]; + + export const stringMembers: readonly ExpectedCompletionEntryObject[] = [ + methodEntry("toString"), + methodEntry("charAt"), + methodEntry("charCodeAt"), + methodEntry("concat"), + methodEntry("indexOf"), + methodEntry("lastIndexOf"), + methodEntry("localeCompare"), + methodEntry("match"), + methodEntry("replace"), + methodEntry("search"), + methodEntry("slice"), + methodEntry("split"), + methodEntry("substring"), + methodEntry("toLowerCase"), + methodEntry("toLocaleLowerCase"), + methodEntry("toUpperCase"), + methodEntry("toLocaleUpperCase"), + methodEntry("trim"), + propertyEntry("length"), + methodEntry("substr"), + methodEntry("valueOf"), + ]; + + export const functionMembersWithPrototype: readonly ExpectedCompletionEntryObject[] = [ + ...functionMembers.slice(0, 4), + propertyEntry("prototype"), + ...functionMembers.slice(4), + ]; + + // TODO: Shouldn't propose type keywords in statement position + export const statementKeywordsWithTypes: readonly ExpectedCompletionEntryObject[] = [ + "break", + "case", + "catch", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "else", + "enum", + "export", + "extends", + "false", + "finally", + "for", + "function", + "if", + "import", + "in", + "instanceof", + "new", + "null", + "return", + "super", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "var", + "void", + "while", + "with", + "implements", + "interface", + "let", + "package", + "yield", + "any", + "async", + "await", + "boolean", + "declare", + "keyof", + "module", + "namespace", + "never", + "readonly", + "number", + "object", + "string", + "symbol", + "type", + "unique", + "unknown", + "bigint", + ].map(keywordEntry); + + export const statementKeywords: readonly ExpectedCompletionEntryObject[] = statementKeywordsWithTypes.filter(k => { + const name = k.name; + switch (name) { + case "false": + case "true": + case "null": + case "void": + return true; + case "declare": + case "module": + return false; + default: + return !ts.contains(typeKeywords, k); + } + }); + + export const statementInJsKeywords = getInJsKeywords(statementKeywords); + + export const globalsVars: readonly ExpectedCompletionEntryObject[] = [ + functionEntry("eval"), + functionEntry("parseInt"), + functionEntry("parseFloat"), + functionEntry("isNaN"), + functionEntry("isFinite"), + functionEntry("decodeURI"), + functionEntry("decodeURIComponent"), + functionEntry("encodeURI"), + functionEntry("encodeURIComponent"), + functionEntry("escape"), + functionEntry("unescape"), + varEntry("NaN"), + varEntry("Infinity"), + varEntry("Object"), + varEntry("Function"), + varEntry("String"), + varEntry("Boolean"), + varEntry("Number"), + varEntry("Math"), + varEntry("Date"), + varEntry("RegExp"), + varEntry("Error"), + varEntry("EvalError"), + varEntry("RangeError"), + varEntry("ReferenceError"), + varEntry("SyntaxError"), + varEntry("TypeError"), + varEntry("URIError"), + varEntry("JSON"), + varEntry("Array"), + varEntry("ArrayBuffer"), + varEntry("DataView"), + varEntry("Int8Array"), + varEntry("Uint8Array"), + varEntry("Uint8ClampedArray"), + varEntry("Int16Array"), + varEntry("Uint16Array"), + varEntry("Int32Array"), + varEntry("Uint32Array"), + varEntry("Float32Array"), + varEntry("Float64Array"), + moduleEntry("Intl"), + ]; + + const globalKeywordsInsideFunction: readonly ExpectedCompletionEntryObject[] = [ + "break", + "case", + "catch", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "else", + "enum", + "export", + "extends", + "false", + "finally", + "for", + "function", + "if", + "import", + "in", + "instanceof", + "new", + "null", + "return", + "super", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "var", + "void", + "while", + "with", + "implements", + "interface", + "let", + "package", + "yield", + "async", + "await", + ].map(keywordEntry); + + export const undefinedVarEntry: ExpectedCompletionEntry = { + name: "undefined", + kind: "var", + sortText: SortText.GlobalsOrKeywords + }; + // TODO: many of these are inappropriate to always provide + export const globalsInsideFunction = (plus: readonly ExpectedCompletionEntry[]): readonly ExpectedCompletionEntry[] => [ + { name: "arguments", kind: "local var" }, + ...plus, + globalThisEntry, + ...globalsVars, + undefinedVarEntry, + ...globalKeywordsInsideFunction, + ]; + + const globalInJsKeywordsInsideFunction = getInJsKeywords(globalKeywordsInsideFunction); + + // TODO: many of these are inappropriate to always provide + export const globalsInJsInsideFunction = (plus: readonly ExpectedCompletionEntry[]): readonly ExpectedCompletionEntry[] => [ + { name: "arguments", kind: "local var" }, + globalThisEntry, + ...globalsVars, + ...plus, + undefinedVarEntry, + ...globalInJsKeywordsInsideFunction, + ]; + + // TODO: many of these are inappropriate to always provide + export const globalKeywords: readonly ExpectedCompletionEntryObject[] = [ + "break", + "case", + "catch", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "else", + "enum", + "export", + "extends", + "false", + "finally", + "for", + "function", + "if", + "import", + "in", + "instanceof", + "new", + "null", + "return", + "super", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "var", + "void", + "while", + "with", + "implements", + "interface", + "let", + "package", + "yield", + "any", + "async", + "await", + "boolean", + "declare", + "keyof", + "module", + "namespace", + "never", + "readonly", + "number", + "object", + "string", + "symbol", + "type", + "unique", + "unknown", + "bigint", + ].map(keywordEntry); + + export const globalInJsKeywords = getInJsKeywords(globalKeywords); + + export const insideMethodKeywords: readonly ExpectedCompletionEntryObject[] = [ + "break", + "case", + "catch", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "else", + "enum", + "export", + "extends", + "false", + "finally", + "for", + "function", + "if", + "import", + "in", + "instanceof", + "new", + "null", + "return", + "super", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "var", + "void", + "while", + "with", + "implements", + "interface", + "let", + "package", + "yield", + "async", + "await", + ].map(keywordEntry); + + export const insideMethodInJsKeywords = getInJsKeywords(insideMethodKeywords); + + export const globals: readonly ExpectedCompletionEntryObject[] = [ + globalThisEntry, + ...globalsVars, + undefinedVarEntry, + ...globalKeywords + ]; + + export const globalsInJs: readonly ExpectedCompletionEntryObject[] = [ + globalThisEntry, + ...globalsVars, + undefinedVarEntry, + ...globalInJsKeywords + ]; + + export function globalsPlus(plus: readonly ExpectedCompletionEntry[]): readonly ExpectedCompletionEntry[] { + return [ + globalThisEntry, + ...globalsVars, + ...plus, + undefinedVarEntry, + ...globalKeywords]; + } + + export function globalsInJsPlus(plus: readonly ExpectedCompletionEntry[]): readonly ExpectedCompletionEntry[] { + return [ + globalThisEntry, + ...globalsVars, + ...plus, + undefinedVarEntry, + ...globalInJsKeywords]; + } + } + + export interface ReferenceGroup { + definition: ReferenceGroupDefinition; + ranges: FourSlash.Range[]; + } + + export type ReferenceGroupDefinition = string | { text: string, range: FourSlash.Range }; + + export interface ApplyRefactorOptions { + refactorName: string; + actionName: string; + actionDescription: string; + newContent: NewFileContent; + } + + export type ExpectedCompletionEntry = string | ExpectedCompletionEntryObject; + export interface ExpectedCompletionEntryObject { + readonly name: string; + readonly source?: string; + readonly insertText?: string; + readonly replacementSpan?: FourSlash.Range; + readonly hasAction?: boolean; // If not specified, will assert that this is false. + readonly isRecommended?: boolean; // If not specified, will assert that this is false. + readonly kind?: string; // If not specified, won't assert about this + readonly kindModifiers?: string; // Must be paired with 'kind' + readonly text?: string; + readonly documentation?: string; + readonly sourceDisplay?: string; + readonly tags?: readonly ts.JSDocTagInfo[]; + readonly sortText?: ts.Completions.SortText; + } + + export interface VerifyCompletionsOptions { + readonly marker?: ArrayOrSingle; + readonly isNewIdentifierLocation?: boolean; // Always tested + readonly isGlobalCompletion?: boolean; // Only tested if set + readonly exact?: ArrayOrSingle; + readonly includes?: ArrayOrSingle; + readonly excludes?: ArrayOrSingle; + readonly preferences?: ts.UserPreferences; + readonly triggerCharacter?: ts.CompletionsTriggerCharacter; + } + + export interface VerifySignatureHelpOptions { + readonly marker?: ArrayOrSingle; + /** @default 1 */ + readonly overloadsCount?: number; + /** @default undefined */ + readonly docComment?: string; + readonly text?: string; + readonly parameterName?: string; + readonly parameterSpan?: string; + /** @default undefined */ + readonly parameterDocComment?: string; + readonly parameterCount?: number; + readonly argumentCount?: number; + /** @default false */ + readonly isVariadic?: boolean; + /** @default ts.emptyArray */ + readonly tags?: readonly ts.JSDocTagInfo[]; + readonly triggerReason?: ts.SignatureHelpTriggerReason; + } + + export interface VerifyNavigateToOptions { + readonly pattern: string; + readonly fileName?: string; + readonly expected: readonly ExpectedNavigateToItem[]; + } + + export interface ExpectedNavigateToItem { + readonly name: string; + readonly kind: ts.ScriptElementKind; + readonly kindModifiers?: string; + readonly matchKind?: keyof typeof ts.PatternMatchKind; + readonly isCaseSensitive?: boolean; + readonly range: FourSlash.Range; + readonly containerName?: string; + readonly containerKind?: ts.ScriptElementKind; + } + + export type ArrayOrSingle = T | readonly T[]; + + export interface VerifyCompletionListContainsOptions extends ts.UserPreferences { + triggerCharacter?: ts.CompletionsTriggerCharacter; + sourceDisplay: string; + isRecommended?: true; + insertText?: string; + replacementSpan?: FourSlash.Range; + } + + export interface VerifyDocumentHighlightsOptions { + filesToSearch?: readonly string[]; + } + + export type NewFileContent = string | { readonly [filename: string]: string }; + + export interface NewContentOptions { + // Exactly one of these should be defined. + newFileContent?: NewFileContent; + newRangeContent?: string; + } + + export interface VerifyCodeFixOptions extends NewContentOptions { + readonly description: string; + readonly errorCode?: number; + readonly index?: number; + readonly preferences?: ts.UserPreferences; + readonly applyChanges?: boolean; + readonly commands?: readonly ts.CodeActionCommand[]; + } + + export interface VerifyCodeFixAvailableOptions { + description: string; + commands?: ts.CodeActionCommand[]; + } + + export interface VerifyCodeFixAllOptions { + fixId: string; + fixAllDescription: string; + newFileContent: NewFileContent; + commands: readonly {}[]; + } + + export interface VerifyRefactorOptions { + name: string; + actionName: string; + refactors: readonly ts.ApplicableRefactorInfo[]; + } + + export interface VerifyCompletionActionOptions extends NewContentOptions { + name: string; + source?: string; + description: string; + preferences?: ts.UserPreferences; + } + + export interface Diagnostic { + message: string; + range?: FourSlash.Range; + code: number; + reportsUnnecessary?: true; + } + + export interface GetEditsForFileRenameOptions { + readonly oldPath: string; + readonly newPath: string; + readonly newFileContents: { readonly [fileName: string]: string }; + readonly preferences?: ts.UserPreferences; + } + + export interface MoveToNewFileOptions { + readonly newFileContents: { readonly [fileName: string]: string }; + readonly preferences?: ts.UserPreferences; + } + + export type RenameLocationsOptions = readonly RenameLocationOptions[] | { + readonly findInStrings?: boolean; + readonly findInComments?: boolean; + readonly ranges: readonly RenameLocationOptions[]; + readonly providePrefixAndSuffixTextForRename?: boolean; + }; + export type RenameLocationOptions = FourSlash.Range | { readonly range: FourSlash.Range, readonly prefixText?: string, readonly suffixText?: string }; +} \ No newline at end of file diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 198e79dd9c3..56bcc60122f 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1,444 +1,3 @@ -// Block scoped definitions work poorly for global variables, temporarily enable var -/* eslint-disable no-var */ - -// this will work in the browser via browserify -var _chai: typeof chai = require("chai"); -var assert: typeof _chai.assert = _chai.assert; -{ - // chai's builtin `assert.isFalse` is featureful but slow - we don't use those features, - // so we'll just overwrite it as an alterative to migrating a bunch of code off of chai - assert.isFalse = (expr: any, msg: string) => { if (expr !== false) throw new Error(msg); }; - - const assertDeepImpl = assert.deepEqual; - assert.deepEqual = (a, b, msg) => { - if (ts.isArray(a) && ts.isArray(b)) { - assertDeepImpl(arrayExtraKeysObject(a), arrayExtraKeysObject(b), "Array extra keys differ"); - } - assertDeepImpl(a, b, msg); - - function arrayExtraKeysObject(a: readonly ({} | null | undefined)[]): object { - const obj: { [key: string]: {} | null | undefined } = {}; - for (const key in a) { - if (Number.isNaN(Number(key))) { - obj[key] = a[key]; - } - } - return obj; - } - }; -} - -var global: NodeJS.Global = Function("return this").call(undefined); // eslint-disable-line no-new-func - -declare var window: {}; -declare var XMLHttpRequest: new() => XMLHttpRequest; - -interface XMLHttpRequest { - readonly readyState: number; - readonly responseText: string; - readonly status: number; - readonly statusText: string; - open(method: string, url: string, async?: boolean, user?: string, password?: string): void; - send(data?: string): void; - setRequestHeader(header: string, value: string): void; - getAllResponseHeaders(): string; - getResponseHeader(header: string): string | null; - overrideMimeType(mime: string): void; -} -/* eslint-enable no-var */ - -namespace Utils { - export function encodeString(s: string): string { - return ts.sys.bufferFrom!(s).toString("utf8"); - } - - export function byteLength(s: string, encoding?: string): number { - // stub implementation if Buffer is not available (in-browser case) - return Buffer.byteLength(s, encoding as ts.BufferEncoding | undefined); - } - - export function evalFile(fileContents: string, fileName: string, nodeContext?: any) { - const vm = require("vm"); - if (nodeContext) { - vm.runInNewContext(fileContents, nodeContext, fileName); - } - else { - vm.runInThisContext(fileContents, fileName); - } - } - - /** Splits the given string on \r\n, or on only \n if that fails, or on only \r if *that* fails. */ - export function splitContentByNewlines(content: string) { - // Split up the input file by line - // Note: IE JS engine incorrectly handles consecutive delimiters here when using RegExp split, so - // we have to use string-based splitting instead and try to figure out the delimiting chars - let lines = content.split("\r\n"); - if (lines.length === 1) { - lines = content.split("\n"); - - if (lines.length === 1) { - lines = content.split("\r"); - } - } - return lines; - } - - /** Reads a file under /tests */ - export function readTestFile(path: string) { - if (path.indexOf("tests") < 0) { - path = "tests/" + path; - } - - let content: string | undefined; - try { - content = Harness.IO.readFile(Harness.userSpecifiedRoot + path); - } - catch (err) { - return undefined; - } - - return content; - } - - export function memoize(f: T, memoKey: (...anything: any[]) => string): T { - const cache = ts.createMap(); - - return (function(this: any, ...args: any[]) { - const key = memoKey(...args); - if (cache.has(key)) { - return cache.get(key); - } - else { - const value = f.apply(this, args); - cache.set(key, value); - return value; - } - }); - } - - export const canonicalizeForHarness = ts.createGetCanonicalFileName(/*caseSensitive*/ false); // This is done so tests work on windows _and_ linux - - export function assertInvariants(node: ts.Node | undefined, parent: ts.Node | undefined): void { - if (node) { - assert.isFalse(node.pos < 0, "node.pos < 0"); - assert.isFalse(node.end < 0, "node.end < 0"); - assert.isFalse(node.end < node.pos, "node.end < node.pos"); - assert.equal(node.parent, parent, "node.parent !== parent"); - - if (parent) { - // Make sure each child is contained within the parent. - assert.isFalse(node.pos < parent.pos, "node.pos < parent.pos"); - assert.isFalse(node.end > parent.end, "node.end > parent.end"); - } - - ts.forEachChild(node, child => { - assertInvariants(child, node); - }); - - // Make sure each of the children is in order. - let currentPos = 0; - ts.forEachChild(node, - child => { - assert.isFalse(child.pos < currentPos, "child.pos < currentPos"); - currentPos = child.end; - }, - array => { - assert.isFalse(array.pos < node.pos, "array.pos < node.pos"); - assert.isFalse(array.end > node.end, "array.end > node.end"); - assert.isFalse(array.pos < currentPos, "array.pos < currentPos"); - - for (const item of array) { - assert.isFalse(item.pos < currentPos, "array[i].pos < currentPos"); - currentPos = item.end; - } - - currentPos = array.end; - }); - - const childNodesAndArrays: any[] = []; - ts.forEachChild(node, child => { childNodesAndArrays.push(child); }, array => { childNodesAndArrays.push(array); }); - - for (const childName in node) { - if (childName === "parent" || childName === "nextContainer" || childName === "modifiers" || childName === "externalModuleIndicator" || - // for now ignore jsdoc comments - childName === "jsDocComment" || childName === "checkJsDirective" || childName === "commonJsModuleIndicator") { - continue; - } - const child = (node)[childName]; - if (isNodeOrArray(child)) { - assert.isFalse(childNodesAndArrays.indexOf(child) < 0, - "Missing child when forEach'ing over node: " + (ts).SyntaxKind[node.kind] + "-" + childName); - } - } - } - } - - function isNodeOrArray(a: any): boolean { - return a !== undefined && typeof a.pos === "number"; - } - - export function convertDiagnostics(diagnostics: readonly ts.Diagnostic[]) { - return diagnostics.map(convertDiagnostic); - } - - function convertDiagnostic(diagnostic: ts.Diagnostic) { - return { - start: diagnostic.start, - length: diagnostic.length, - messageText: ts.flattenDiagnosticMessageText(diagnostic.messageText, Harness.IO.newLine()), - category: ts.diagnosticCategoryName(diagnostic, /*lowerCase*/ false), - code: diagnostic.code - }; - } - - export function sourceFileToJSON(file: ts.Node): string { - return JSON.stringify(file, (_, v) => isNodeOrArray(v) ? serializeNode(v) : v, " "); - - function getKindName(k: number | string): string { - if (ts.isString(k)) { - return k; - } - - // For some markers in SyntaxKind, we should print its original syntax name instead of - // the marker name in tests. - if (k === (ts).SyntaxKind.FirstJSDocNode || - k === (ts).SyntaxKind.LastJSDocNode || - k === (ts).SyntaxKind.FirstJSDocTagNode || - k === (ts).SyntaxKind.LastJSDocTagNode) { - for (const kindName in (ts).SyntaxKind) { - if ((ts).SyntaxKind[kindName] === k) { - return kindName; - } - } - } - - return (ts).SyntaxKind[k]; - } - - function getFlagName(flags: any, f: number): any { - if (f === 0) { - return 0; - } - - let result = ""; - ts.forEach(Object.getOwnPropertyNames(flags), (v: any) => { - if (isFinite(v)) { - v = +v; - if (f === +v) { - result = flags[v]; - return true; - } - else if ((f & v) > 0) { - if (result.length) { - result += " | "; - } - result += flags[v]; - return false; - } - } - }); - return result; - } - - function getNodeFlagName(f: number) { return getFlagName((ts).NodeFlags, f); } - - function serializeNode(n: ts.Node): any { - const o: any = { kind: getKindName(n.kind) }; - if (ts.containsParseError(n)) { - o.containsParseError = true; - } - - for (const propertyName of Object.getOwnPropertyNames(n) as readonly (keyof ts.SourceFile | keyof ts.Identifier)[]) { - switch (propertyName) { - case "parent": - case "symbol": - case "locals": - case "localSymbol": - case "kind": - case "id": - case "nodeCount": - case "symbolCount": - case "identifierCount": - case "scriptSnapshot": - // Blacklist of items we never put in the baseline file. - break; - - case "originalKeywordKind": - o[propertyName] = getKindName((n)[propertyName]); - break; - - case "flags": - // Clear the flags that are produced by aggregating child values. That is ephemeral - // data we don't care about in the dump. We only care what the parser set directly - // on the AST. - const flags = n.flags & ~(ts.NodeFlags.JavaScriptFile | ts.NodeFlags.HasAggregatedChildData); - if (flags) { - o[propertyName] = getNodeFlagName(flags); - } - break; - - case "parseDiagnostics": - o[propertyName] = convertDiagnostics((n)[propertyName]); - break; - - case "nextContainer": - if (n.nextContainer) { - o[propertyName] = { kind: n.nextContainer.kind, pos: n.nextContainer.pos, end: n.nextContainer.end }; - } - break; - - case "text": - // Include 'text' field for identifiers/literals, but not for source files. - if (n.kind !== ts.SyntaxKind.SourceFile) { - o[propertyName] = (n)[propertyName]; - } - break; - - default: - o[propertyName] = (n)[propertyName]; - } - } - - return o; - } - } - - export function assertDiagnosticsEquals(array1: readonly ts.Diagnostic[], array2: readonly ts.Diagnostic[]) { - if (array1 === array2) { - return; - } - - assert(array1, "array1"); - assert(array2, "array2"); - - assert.equal(array1.length, array2.length, "array1.length !== array2.length"); - - for (let i = 0; i < array1.length; i++) { - const d1 = array1[i]; - const d2 = array2[i]; - - assert.equal(d1.start, d2.start, "d1.start !== d2.start"); - assert.equal(d1.length, d2.length, "d1.length !== d2.length"); - assert.equal( - ts.flattenDiagnosticMessageText(d1.messageText, Harness.IO.newLine()), - ts.flattenDiagnosticMessageText(d2.messageText, Harness.IO.newLine()), "d1.messageText !== d2.messageText"); - assert.equal(d1.category, d2.category, "d1.category !== d2.category"); - assert.equal(d1.code, d2.code, "d1.code !== d2.code"); - } - } - - export function assertStructuralEquals(node1: ts.Node, node2: ts.Node) { - if (node1 === node2) { - return; - } - - assert(node1, "node1"); - assert(node2, "node2"); - assert.equal(node1.pos, node2.pos, "node1.pos !== node2.pos"); - assert.equal(node1.end, node2.end, "node1.end !== node2.end"); - assert.equal(node1.kind, node2.kind, "node1.kind !== node2.kind"); - - // call this on both nodes to ensure all propagated flags have been set (and thus can be - // compared). - assert.equal(ts.containsParseError(node1), ts.containsParseError(node2)); - assert.equal(node1.flags & ~ts.NodeFlags.ReachabilityAndEmitFlags, node2.flags & ~ts.NodeFlags.ReachabilityAndEmitFlags, "node1.flags !== node2.flags"); - - ts.forEachChild(node1, - child1 => { - const childName = findChildName(node1, child1); - const child2: ts.Node = (node2)[childName]; - - assertStructuralEquals(child1, child2); - }, - array1 => { - const childName = findChildName(node1, array1); - const array2: ts.NodeArray = (node2)[childName]; - - assertArrayStructuralEquals(array1, array2); - }); - } - - function assertArrayStructuralEquals(array1: ts.NodeArray, array2: ts.NodeArray) { - if (array1 === array2) { - return; - } - - assert(array1, "array1"); - assert(array2, "array2"); - assert.equal(array1.pos, array2.pos, "array1.pos !== array2.pos"); - assert.equal(array1.end, array2.end, "array1.end !== array2.end"); - assert.equal(array1.length, array2.length, "array1.length !== array2.length"); - - for (let i = 0; i < array1.length; i++) { - assertStructuralEquals(array1[i], array2[i]); - } - } - - function findChildName(parent: any, child: any) { - for (const name in parent) { - if (parent.hasOwnProperty(name) && parent[name] === child) { - return name; - } - } - - throw new Error("Could not find child in parent"); - } - - const maxHarnessFrames = 1; - - export function filterStack(error: Error, stackTraceLimit = Infinity) { - const stack = (error).stack; - if (stack) { - const lines = stack.split(/\r\n?|\n/g); - const filtered: string[] = []; - let frameCount = 0; - let harnessFrameCount = 0; - for (let line of lines) { - if (isStackFrame(line)) { - if (frameCount >= stackTraceLimit - || isMocha(line) - || isNode(line)) { - continue; - } - - if (isHarness(line)) { - if (harnessFrameCount >= maxHarnessFrames) { - continue; - } - - harnessFrameCount++; - } - - line = line.replace(/\bfile:\/\/\/(.*?)(?=(:\d+)*($|\)))/, (_, path) => ts.sys.resolvePath(path)); - frameCount++; - } - - filtered.push(line); - } - - (error).stack = filtered.join(Harness.IO.newLine()); - } - - return error; - } - - function isStackFrame(line: string) { - return /^\s+at\s/.test(line); - } - - function isMocha(line: string) { - return /[\\/](node_modules|components)[\\/]mocha(js)?[\\/]|[\\/]mocha\.js/.test(line); - } - - function isNode(line: string) { - return /\((timers|events|node|module)\.js:/.test(line); - } - - function isHarness(line: string) { - return /[\\/]src[\\/]harness[\\/]|[\\/]run\.js/.test(line); - } -} - namespace Harness { // eslint-disable-next-line @typescript-eslint/interface-name-prefix export interface IO { @@ -602,13 +161,11 @@ namespace Harness { } IO = createNodeIO(); -} -if (Harness.IO.tryEnableSourceMapsForHost && /^development$/i.test(Harness.IO.getEnvironmentVariable!("NODE_ENV"))) { - Harness.IO.tryEnableSourceMapsForHost(); -} + if (IO.tryEnableSourceMapsForHost && /^development$/i.test(IO.getEnvironmentVariable!("NODE_ENV"))) { + IO.tryEnableSourceMapsForHost(); + } -namespace Harness { export const libFolder = "built/local/"; const tcServicesFileName = ts.combinePaths(libFolder, "typescriptServices.js"); export const tcServicesFile = IO.readFile(tcServicesFileName) + IO.newLine() + `//# sourceURL=${IO.resolvePath(tcServicesFileName)}`; diff --git a/src/harness/harnessGlobals.ts b/src/harness/harnessGlobals.ts new file mode 100644 index 00000000000..d0bd588dba5 --- /dev/null +++ b/src/harness/harnessGlobals.ts @@ -0,0 +1,48 @@ +// Block scoped definitions work poorly for global variables, temporarily enable var +/* eslint-disable no-var */ + +// this will work in the browser via browserify +var _chai: typeof chai = require("chai"); +var assert: typeof _chai.assert = _chai.assert; +{ + // chai's builtin `assert.isFalse` is featureful but slow - we don't use those features, + // so we'll just overwrite it as an alterative to migrating a bunch of code off of chai + assert.isFalse = (expr: any, msg: string) => { if (expr !== false) throw new Error(msg); }; + + const assertDeepImpl = assert.deepEqual; + assert.deepEqual = (a, b, msg) => { + if (ts.isArray(a) && ts.isArray(b)) { + assertDeepImpl(arrayExtraKeysObject(a), arrayExtraKeysObject(b), "Array extra keys differ"); + } + assertDeepImpl(a, b, msg); + + function arrayExtraKeysObject(a: readonly ({} | null | undefined)[]): object { + const obj: { [key: string]: {} | null | undefined } = {}; + for (const key in a) { + if (Number.isNaN(Number(key))) { + obj[key] = a[key]; + } + } + return obj; + } + }; +} + +var global: NodeJS.Global = Function("return this").call(undefined); // eslint-disable-line no-new-func + +declare var window: {}; +declare var XMLHttpRequest: new() => XMLHttpRequest; + +interface XMLHttpRequest { + readonly readyState: number; + readonly responseText: string; + readonly status: number; + readonly statusText: string; + open(method: string, url: string, async?: boolean, user?: string, password?: string): void; + send(data?: string): void; + setRequestHeader(header: string, value: string): void; + getAllResponseHeaders(): string; + getResponseHeader(header: string): string | null; + overrideMimeType(mime: string): void; +} +/* eslint-enable no-var */ \ No newline at end of file diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index e355a7c6647..c3ba14669bf 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -597,7 +597,7 @@ namespace Harness.LanguageService { private factory: ts.TypeScriptServicesFactory; constructor(preprocessToResolve: boolean, cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { this.host = new ShimLanguageServiceHost(preprocessToResolve, cancellationToken, options); - this.factory = new TypeScript.Services.TypeScriptServicesFactory(); + this.factory = new ts.TypeScriptServicesFactory(); } getHost() { return this.host; } getLanguageService(): ts.LanguageService { return new LanguageServiceShimProxy(this.factory.createLanguageServiceShim(this.host)); } diff --git a/src/harness/harnessUtils.ts b/src/harness/harnessUtils.ts new file mode 100644 index 00000000000..44cbe512aac --- /dev/null +++ b/src/harness/harnessUtils.ts @@ -0,0 +1,391 @@ +namespace Utils { + export function encodeString(s: string): string { + return ts.sys.bufferFrom!(s).toString("utf8"); + } + + export function byteLength(s: string, encoding?: string): number { + // stub implementation if Buffer is not available (in-browser case) + return Buffer.byteLength(s, encoding as ts.BufferEncoding | undefined); + } + + export function evalFile(fileContents: string, fileName: string, nodeContext?: any) { + const vm = require("vm"); + if (nodeContext) { + vm.runInNewContext(fileContents, nodeContext, fileName); + } + else { + vm.runInThisContext(fileContents, fileName); + } + } + + /** Splits the given string on \r\n, or on only \n if that fails, or on only \r if *that* fails. */ + export function splitContentByNewlines(content: string) { + // Split up the input file by line + // Note: IE JS engine incorrectly handles consecutive delimiters here when using RegExp split, so + // we have to use string-based splitting instead and try to figure out the delimiting chars + let lines = content.split("\r\n"); + if (lines.length === 1) { + lines = content.split("\n"); + + if (lines.length === 1) { + lines = content.split("\r"); + } + } + return lines; + } + + /** Reads a file under /tests */ + export function readTestFile(path: string) { + if (path.indexOf("tests") < 0) { + path = "tests/" + path; + } + + let content: string | undefined; + try { + content = Harness.IO.readFile(Harness.userSpecifiedRoot + path); + } + catch (err) { + return undefined; + } + + return content; + } + + export function memoize(f: T, memoKey: (...anything: any[]) => string): T { + const cache = ts.createMap(); + + return (function(this: any, ...args: any[]) { + const key = memoKey(...args); + if (cache.has(key)) { + return cache.get(key); + } + else { + const value = f.apply(this, args); + cache.set(key, value); + return value; + } + }); + } + + export const canonicalizeForHarness = ts.createGetCanonicalFileName(/*caseSensitive*/ false); // This is done so tests work on windows _and_ linux + + export function assertInvariants(node: ts.Node | undefined, parent: ts.Node | undefined): void { + if (node) { + assert.isFalse(node.pos < 0, "node.pos < 0"); + assert.isFalse(node.end < 0, "node.end < 0"); + assert.isFalse(node.end < node.pos, "node.end < node.pos"); + assert.equal(node.parent, parent, "node.parent !== parent"); + + if (parent) { + // Make sure each child is contained within the parent. + assert.isFalse(node.pos < parent.pos, "node.pos < parent.pos"); + assert.isFalse(node.end > parent.end, "node.end > parent.end"); + } + + ts.forEachChild(node, child => { + assertInvariants(child, node); + }); + + // Make sure each of the children is in order. + let currentPos = 0; + ts.forEachChild(node, + child => { + assert.isFalse(child.pos < currentPos, "child.pos < currentPos"); + currentPos = child.end; + }, + array => { + assert.isFalse(array.pos < node.pos, "array.pos < node.pos"); + assert.isFalse(array.end > node.end, "array.end > node.end"); + assert.isFalse(array.pos < currentPos, "array.pos < currentPos"); + + for (const item of array) { + assert.isFalse(item.pos < currentPos, "array[i].pos < currentPos"); + currentPos = item.end; + } + + currentPos = array.end; + }); + + const childNodesAndArrays: any[] = []; + ts.forEachChild(node, child => { childNodesAndArrays.push(child); }, array => { childNodesAndArrays.push(array); }); + + for (const childName in node) { + if (childName === "parent" || childName === "nextContainer" || childName === "modifiers" || childName === "externalModuleIndicator" || + // for now ignore jsdoc comments + childName === "jsDocComment" || childName === "checkJsDirective" || childName === "commonJsModuleIndicator") { + continue; + } + const child = (node)[childName]; + if (isNodeOrArray(child)) { + assert.isFalse(childNodesAndArrays.indexOf(child) < 0, + "Missing child when forEach'ing over node: " + (ts).SyntaxKind[node.kind] + "-" + childName); + } + } + } + } + + function isNodeOrArray(a: any): boolean { + return a !== undefined && typeof a.pos === "number"; + } + + export function convertDiagnostics(diagnostics: readonly ts.Diagnostic[]) { + return diagnostics.map(convertDiagnostic); + } + + function convertDiagnostic(diagnostic: ts.Diagnostic) { + return { + start: diagnostic.start, + length: diagnostic.length, + messageText: ts.flattenDiagnosticMessageText(diagnostic.messageText, Harness.IO.newLine()), + category: ts.diagnosticCategoryName(diagnostic, /*lowerCase*/ false), + code: diagnostic.code + }; + } + + export function sourceFileToJSON(file: ts.Node): string { + return JSON.stringify(file, (_, v) => isNodeOrArray(v) ? serializeNode(v) : v, " "); + + function getKindName(k: number | string): string { + if (ts.isString(k)) { + return k; + } + + // For some markers in SyntaxKind, we should print its original syntax name instead of + // the marker name in tests. + if (k === (ts).SyntaxKind.FirstJSDocNode || + k === (ts).SyntaxKind.LastJSDocNode || + k === (ts).SyntaxKind.FirstJSDocTagNode || + k === (ts).SyntaxKind.LastJSDocTagNode) { + for (const kindName in (ts).SyntaxKind) { + if ((ts).SyntaxKind[kindName] === k) { + return kindName; + } + } + } + + return (ts).SyntaxKind[k]; + } + + function getFlagName(flags: any, f: number): any { + if (f === 0) { + return 0; + } + + let result = ""; + ts.forEach(Object.getOwnPropertyNames(flags), (v: any) => { + if (isFinite(v)) { + v = +v; + if (f === +v) { + result = flags[v]; + return true; + } + else if ((f & v) > 0) { + if (result.length) { + result += " | "; + } + result += flags[v]; + return false; + } + } + }); + return result; + } + + function getNodeFlagName(f: number) { return getFlagName((ts).NodeFlags, f); } + + function serializeNode(n: ts.Node): any { + const o: any = { kind: getKindName(n.kind) }; + if (ts.containsParseError(n)) { + o.containsParseError = true; + } + + for (const propertyName of Object.getOwnPropertyNames(n) as readonly (keyof ts.SourceFile | keyof ts.Identifier)[]) { + switch (propertyName) { + case "parent": + case "symbol": + case "locals": + case "localSymbol": + case "kind": + case "id": + case "nodeCount": + case "symbolCount": + case "identifierCount": + case "scriptSnapshot": + // Blacklist of items we never put in the baseline file. + break; + + case "originalKeywordKind": + o[propertyName] = getKindName((n)[propertyName]); + break; + + case "flags": + // Clear the flags that are produced by aggregating child values. That is ephemeral + // data we don't care about in the dump. We only care what the parser set directly + // on the AST. + const flags = n.flags & ~(ts.NodeFlags.JavaScriptFile | ts.NodeFlags.HasAggregatedChildData); + if (flags) { + o[propertyName] = getNodeFlagName(flags); + } + break; + + case "parseDiagnostics": + o[propertyName] = convertDiagnostics((n)[propertyName]); + break; + + case "nextContainer": + if (n.nextContainer) { + o[propertyName] = { kind: n.nextContainer.kind, pos: n.nextContainer.pos, end: n.nextContainer.end }; + } + break; + + case "text": + // Include 'text' field for identifiers/literals, but not for source files. + if (n.kind !== ts.SyntaxKind.SourceFile) { + o[propertyName] = (n)[propertyName]; + } + break; + + default: + o[propertyName] = (n)[propertyName]; + } + } + + return o; + } + } + + export function assertDiagnosticsEquals(array1: readonly ts.Diagnostic[], array2: readonly ts.Diagnostic[]) { + if (array1 === array2) { + return; + } + + assert(array1, "array1"); + assert(array2, "array2"); + + assert.equal(array1.length, array2.length, "array1.length !== array2.length"); + + for (let i = 0; i < array1.length; i++) { + const d1 = array1[i]; + const d2 = array2[i]; + + assert.equal(d1.start, d2.start, "d1.start !== d2.start"); + assert.equal(d1.length, d2.length, "d1.length !== d2.length"); + assert.equal( + ts.flattenDiagnosticMessageText(d1.messageText, Harness.IO.newLine()), + ts.flattenDiagnosticMessageText(d2.messageText, Harness.IO.newLine()), "d1.messageText !== d2.messageText"); + assert.equal(d1.category, d2.category, "d1.category !== d2.category"); + assert.equal(d1.code, d2.code, "d1.code !== d2.code"); + } + } + + export function assertStructuralEquals(node1: ts.Node, node2: ts.Node) { + if (node1 === node2) { + return; + } + + assert(node1, "node1"); + assert(node2, "node2"); + assert.equal(node1.pos, node2.pos, "node1.pos !== node2.pos"); + assert.equal(node1.end, node2.end, "node1.end !== node2.end"); + assert.equal(node1.kind, node2.kind, "node1.kind !== node2.kind"); + + // call this on both nodes to ensure all propagated flags have been set (and thus can be + // compared). + assert.equal(ts.containsParseError(node1), ts.containsParseError(node2)); + assert.equal(node1.flags & ~ts.NodeFlags.ReachabilityAndEmitFlags, node2.flags & ~ts.NodeFlags.ReachabilityAndEmitFlags, "node1.flags !== node2.flags"); + + ts.forEachChild(node1, + child1 => { + const childName = findChildName(node1, child1); + const child2: ts.Node = (node2)[childName]; + + assertStructuralEquals(child1, child2); + }, + array1 => { + const childName = findChildName(node1, array1); + const array2: ts.NodeArray = (node2)[childName]; + + assertArrayStructuralEquals(array1, array2); + }); + } + + function assertArrayStructuralEquals(array1: ts.NodeArray, array2: ts.NodeArray) { + if (array1 === array2) { + return; + } + + assert(array1, "array1"); + assert(array2, "array2"); + assert.equal(array1.pos, array2.pos, "array1.pos !== array2.pos"); + assert.equal(array1.end, array2.end, "array1.end !== array2.end"); + assert.equal(array1.length, array2.length, "array1.length !== array2.length"); + + for (let i = 0; i < array1.length; i++) { + assertStructuralEquals(array1[i], array2[i]); + } + } + + function findChildName(parent: any, child: any) { + for (const name in parent) { + if (parent.hasOwnProperty(name) && parent[name] === child) { + return name; + } + } + + throw new Error("Could not find child in parent"); + } + + const maxHarnessFrames = 1; + + export function filterStack(error: Error, stackTraceLimit = Infinity) { + const stack = (error).stack; + if (stack) { + const lines = stack.split(/\r\n?|\n/g); + const filtered: string[] = []; + let frameCount = 0; + let harnessFrameCount = 0; + for (let line of lines) { + if (isStackFrame(line)) { + if (frameCount >= stackTraceLimit + || isMocha(line) + || isNode(line)) { + continue; + } + + if (isHarness(line)) { + if (harnessFrameCount >= maxHarnessFrames) { + continue; + } + + harnessFrameCount++; + } + + line = line.replace(/\bfile:\/\/\/(.*?)(?=(:\d+)*($|\)))/, (_, path) => ts.sys.resolvePath(path)); + frameCount++; + } + + filtered.push(line); + } + + (error).stack = filtered.join(Harness.IO.newLine()); + } + + return error; + } + + function isStackFrame(line: string) { + return /^\s+at\s/.test(line); + } + + function isMocha(line: string) { + return /[\\/](node_modules|components)[\\/]mocha(js)?[\\/]|[\\/]mocha\.js/.test(line); + } + + function isNode(line: string) { + return /\((timers|events|node|module)\.js:/.test(line); + } + + function isHarness(line: string) { + return /[\\/]src[\\/]harness[\\/]|[\\/]run\.js/.test(line); + } +} \ No newline at end of file diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index c162e6bdb52..1b4627475b3 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -31,10 +31,13 @@ "runnerbase.ts", "sourceMapRecorder.ts", + "harnessGlobals.ts", + "harnessUtils.ts", "harness.ts", "harnessLanguageService.ts", "virtualFileSystemWithWatch.ts", "fourslash.ts", + "fourslashInterface.ts", "typeWriter.ts", "loggedIO.ts" ] diff --git a/src/server/tsconfig.json b/src/server/tsconfig.json index ec4fb403925..1b16e58da69 100644 --- a/src/server/tsconfig.json +++ b/src/server/tsconfig.json @@ -15,7 +15,9 @@ ], "files": [ "types.ts", + "utilitiesPublic.ts", "utilities.ts", + "watchType.ts" "protocol.ts", "scriptInfo.ts", "typingsCache.ts", diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 4ce74ff14d4..23f44685500 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -1,126 +1,3 @@ -namespace ts.server { - export enum LogLevel { - terse, - normal, - requestTime, - verbose - } - - export const emptyArray: SortedReadonlyArray = createSortedArray(); - - export interface Logger { - close(): void; - hasLevel(level: LogLevel): boolean; - loggingEnabled(): boolean; - perftrc(s: string): void; - info(s: string): void; - startGroup(): void; - endGroup(): void; - msg(s: string, type?: Msg): void; - getLogFileName(): string | undefined; - } - - // TODO: Use a const enum (https://github.com/Microsoft/TypeScript/issues/16804) - export enum Msg { - Err = "Err", - Info = "Info", - Perf = "Perf", - } - export namespace Msg { - /** @deprecated Only here for backwards-compatibility. Prefer just `Msg`. */ - export type Types = Msg; - } - - export function createInstallTypingsRequest(project: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray, cachePath?: string): DiscoverTypings { - return { - projectName: project.getProjectName(), - fileNames: project.getFileNames(/*excludeFilesFromExternalLibraries*/ true, /*excludeConfigFiles*/ true).concat(project.getExcludedFiles() as NormalizedPath[]), - compilerOptions: project.getCompilationSettings(), - typeAcquisition, - unresolvedImports, - projectRootPath: project.getCurrentDirectory() as Path, - cachePath, - kind: "discover" - }; - } - - export namespace Errors { - export function ThrowNoProject(): never { - throw new Error("No Project."); - } - export function ThrowProjectLanguageServiceDisabled(): never { - throw new Error("The project's language service is disabled."); - } - export function ThrowProjectDoesNotContainDocument(fileName: string, project: Project): never { - throw new Error(`Project '${project.getProjectName()}' does not contain document '${fileName}'`); - } - } - - export type NormalizedPath = string & { __normalizedPathTag: any }; - - export function toNormalizedPath(fileName: string): NormalizedPath { - return normalizePath(fileName); - } - - export function normalizedPathToPath(normalizedPath: NormalizedPath, currentDirectory: string, getCanonicalFileName: (f: string) => string): Path { - const f = isRootedDiskPath(normalizedPath) ? normalizedPath : getNormalizedAbsolutePath(normalizedPath, currentDirectory); - return getCanonicalFileName(f); - } - - export function asNormalizedPath(fileName: string): NormalizedPath { - return fileName; - } - - export interface NormalizedPathMap { - get(path: NormalizedPath): T | undefined; - set(path: NormalizedPath, value: T): void; - contains(path: NormalizedPath): boolean; - remove(path: NormalizedPath): void; - } - - export function createNormalizedPathMap(): NormalizedPathMap { - const map = createMap(); - return { - get(path) { - return map.get(path); - }, - set(path, value) { - map.set(path, value); - }, - contains(path) { - return map.has(path); - }, - remove(path) { - map.delete(path); - } - }; - } - - /*@internal*/ - export interface ProjectOptions { - configHasExtendsProperty: boolean; - /** - * true if config file explicitly listed files - */ - configHasFilesProperty: boolean; - configHasIncludeProperty: boolean; - configHasExcludeProperty: boolean; - } - - export function isInferredProjectName(name: string) { - // POSIX defines /dev/null as a device - there should be no file with this prefix - return /dev\/null\/inferredProject\d+\*/.test(name); - } - - export function makeInferredProjectName(counter: number) { - return `/dev/null/inferredProject${counter}*`; - } - - export function createSortedArray(): SortedArray { - return [] as any as SortedArray; // TODO: GH#19873 - } -} - /* @internal */ namespace ts.server { export class ThrottledOperations { @@ -221,17 +98,3 @@ namespace ts.server { return indentStr + JSON.stringify(json); } } - -/* @internal */ -namespace ts { - // Additional tsserver specific watch information - export const enum WatchType { - ClosedScriptInfo = "Closed Script info", - ConfigFileForInferredRoot = "Config file for the inferred project root", - NodeModulesForClosedScriptInfo = "node_modules for closed script infos in them", - MissingSourceMapFile = "Missing source map file", - NoopConfigFileForInferredRoot = "Noop Config file for the inferred project root", - MissingGeneratedFile = "Missing generated file", - PackageJsonFile = "package.json file for import suggestions" - } -} diff --git a/src/server/utilitiesPublic.ts b/src/server/utilitiesPublic.ts new file mode 100644 index 00000000000..522266a2232 --- /dev/null +++ b/src/server/utilitiesPublic.ts @@ -0,0 +1,122 @@ +namespace ts.server { + export enum LogLevel { + terse, + normal, + requestTime, + verbose + } + + export const emptyArray: SortedReadonlyArray = createSortedArray(); + + export interface Logger { + close(): void; + hasLevel(level: LogLevel): boolean; + loggingEnabled(): boolean; + perftrc(s: string): void; + info(s: string): void; + startGroup(): void; + endGroup(): void; + msg(s: string, type?: Msg): void; + getLogFileName(): string | undefined; + } + + // TODO: Use a const enum (https://github.com/Microsoft/TypeScript/issues/16804) + export enum Msg { + Err = "Err", + Info = "Info", + Perf = "Perf", + } + export namespace Msg { + /** @deprecated Only here for backwards-compatibility. Prefer just `Msg`. */ + export type Types = Msg; + } + + export function createInstallTypingsRequest(project: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray, cachePath?: string): DiscoverTypings { + return { + projectName: project.getProjectName(), + fileNames: project.getFileNames(/*excludeFilesFromExternalLibraries*/ true, /*excludeConfigFiles*/ true).concat(project.getExcludedFiles() as NormalizedPath[]), + compilerOptions: project.getCompilationSettings(), + typeAcquisition, + unresolvedImports, + projectRootPath: project.getCurrentDirectory() as Path, + cachePath, + kind: "discover" + }; + } + + export namespace Errors { + export function ThrowNoProject(): never { + throw new Error("No Project."); + } + export function ThrowProjectLanguageServiceDisabled(): never { + throw new Error("The project's language service is disabled."); + } + export function ThrowProjectDoesNotContainDocument(fileName: string, project: Project): never { + throw new Error(`Project '${project.getProjectName()}' does not contain document '${fileName}'`); + } + } + + export type NormalizedPath = string & { __normalizedPathTag: any }; + + export function toNormalizedPath(fileName: string): NormalizedPath { + return normalizePath(fileName); + } + + export function normalizedPathToPath(normalizedPath: NormalizedPath, currentDirectory: string, getCanonicalFileName: (f: string) => string): Path { + const f = isRootedDiskPath(normalizedPath) ? normalizedPath : getNormalizedAbsolutePath(normalizedPath, currentDirectory); + return getCanonicalFileName(f); + } + + export function asNormalizedPath(fileName: string): NormalizedPath { + return fileName; + } + + export interface NormalizedPathMap { + get(path: NormalizedPath): T | undefined; + set(path: NormalizedPath, value: T): void; + contains(path: NormalizedPath): boolean; + remove(path: NormalizedPath): void; + } + + export function createNormalizedPathMap(): NormalizedPathMap { + const map = createMap(); + return { + get(path) { + return map.get(path); + }, + set(path, value) { + map.set(path, value); + }, + contains(path) { + return map.has(path); + }, + remove(path) { + map.delete(path); + } + }; + } + + /*@internal*/ + export interface ProjectOptions { + configHasExtendsProperty: boolean; + /** + * true if config file explicitly listed files + */ + configHasFilesProperty: boolean; + configHasIncludeProperty: boolean; + configHasExcludeProperty: boolean; + } + + export function isInferredProjectName(name: string) { + // POSIX defines /dev/null as a device - there should be no file with this prefix + return /dev\/null\/inferredProject\d+\*/.test(name); + } + + export function makeInferredProjectName(counter: number) { + return `/dev/null/inferredProject${counter}*`; + } + + export function createSortedArray(): SortedArray { + return [] as any as SortedArray; // TODO: GH#19873 + } +} \ No newline at end of file diff --git a/src/server/watchType.ts b/src/server/watchType.ts new file mode 100644 index 00000000000..28cef0221bc --- /dev/null +++ b/src/server/watchType.ts @@ -0,0 +1,13 @@ +/* @internal */ +namespace ts { + // Additional tsserver specific watch information + export const enum WatchType { + ClosedScriptInfo = "Closed Script info", + ConfigFileForInferredRoot = "Config file for the inferred project root", + NodeModulesForClosedScriptInfo = "node_modules for closed script infos in them", + MissingSourceMapFile = "Missing source map file", + NoopConfigFileForInferredRoot = "Noop Config file for the inferred project root", + MissingGeneratedFile = "Missing generated file", + PackageJsonFile = "package.json file for import suggestions" + } +} \ No newline at end of file diff --git a/src/services/exportAsModule.ts b/src/services/exportAsModule.ts new file mode 100644 index 00000000000..6760e9c38c2 --- /dev/null +++ b/src/services/exportAsModule.ts @@ -0,0 +1,7 @@ +// Here we expose the TypeScript services as an external module +// so that it may be consumed easily like a node module. +// @ts-ignore +/* @internal */ declare const module: { exports: {} }; +if (typeof module !== "undefined" && module.exports) { + module.exports = ts; +} \ No newline at end of file diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 8d41211439b..a0260eac2a5 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -526,1522 +526,1521 @@ namespace ts.FindAllReferences { return Debug.failBadSyntaxKind(decl); } } -} -/** Encapsulates the core find-all-references algorithm. */ -/* @internal */ -namespace ts.FindAllReferences.Core { - /** Core find-all-references algorithm. Handles special cases before delegating to `getReferencedSymbolsForSymbol`. */ - export function getReferencedSymbolsForNode(position: number, node: Node, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options = {}, sourceFilesSet: ReadonlyMap = arrayToSet(sourceFiles, f => f.fileName)): readonly SymbolAndEntries[] | undefined { - if (isSourceFile(node)) { - const reference = GoToDefinition.getReferenceAtPosition(node, position, program); - const moduleSymbol = reference && program.getTypeChecker().getMergedSymbol(reference.file.symbol); - return moduleSymbol && getReferencedSymbolsForModule(program, moduleSymbol, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet); - } - - if (!options.implementations) { - const special = getReferencedSymbolsSpecial(node, sourceFiles, cancellationToken); - if (special) { - return special; + /** Encapsulates the core find-all-references algorithm. */ + export namespace Core { + /** Core find-all-references algorithm. Handles special cases before delegating to `getReferencedSymbolsForSymbol`. */ + export function getReferencedSymbolsForNode(position: number, node: Node, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options = {}, sourceFilesSet: ReadonlyMap = arrayToSet(sourceFiles, f => f.fileName)): readonly SymbolAndEntries[] | undefined { + if (isSourceFile(node)) { + const reference = GoToDefinition.getReferenceAtPosition(node, position, program); + const moduleSymbol = reference && program.getTypeChecker().getMergedSymbol(reference.file.symbol); + return moduleSymbol && getReferencedSymbolsForModule(program, moduleSymbol, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet); } - } - const checker = program.getTypeChecker(); - const symbol = checker.getSymbolAtLocation(node); - - // Could not find a symbol e.g. unknown identifier - if (!symbol) { - // String literal might be a property (and thus have a symbol), so do this here rather than in getReferencedSymbolsSpecial. - return !options.implementations && isStringLiteral(node) ? getReferencesForStringLiteral(node, sourceFiles, cancellationToken) : undefined; - } - - if (symbol.escapedName === InternalSymbolName.ExportEquals) { - return getReferencedSymbolsForModule(program, symbol.parent!, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet); - } - - const moduleReferences = getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol, program, sourceFiles, cancellationToken, options, sourceFilesSet); - if (moduleReferences && !(symbol.flags & SymbolFlags.Transient)) { - return moduleReferences; - } - - const aliasedSymbol = getMergedAliasedSymbolOfNamespaceExportDeclaration(node, symbol, checker); - const moduleReferencesOfExportTarget = aliasedSymbol && - getReferencedSymbolsForModuleIfDeclaredBySourceFile(aliasedSymbol, program, sourceFiles, cancellationToken, options, sourceFilesSet); - - const references = getReferencedSymbolsForSymbol(symbol, node, sourceFiles, sourceFilesSet, checker, cancellationToken, options); - return mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget); - } - - function getMergedAliasedSymbolOfNamespaceExportDeclaration(node: Node, symbol: Symbol, checker: TypeChecker) { - if (node.parent && isNamespaceExportDeclaration(node.parent)) { - const aliasedSymbol = checker.getAliasedSymbol(symbol); - const targetSymbol = checker.getMergedSymbol(aliasedSymbol); - if (aliasedSymbol !== targetSymbol) { - return targetSymbol; + if (!options.implementations) { + const special = getReferencedSymbolsSpecial(node, sourceFiles, cancellationToken); + if (special) { + return special; + } } - } - return undefined; - } - function getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol: Symbol, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options, sourceFilesSet: ReadonlyMap) { - const moduleSourceFile = (symbol.flags & SymbolFlags.Module) && symbol.declarations && find(symbol.declarations, isSourceFile); - if (!moduleSourceFile) return undefined; - const exportEquals = symbol.exports!.get(InternalSymbolName.ExportEquals); - // If !!exportEquals, we're about to add references to `import("mod")` anyway, so don't double-count them. - const moduleReferences = getReferencedSymbolsForModule(program, symbol, !!exportEquals, sourceFiles, sourceFilesSet); - if (!exportEquals || !sourceFilesSet.has(moduleSourceFile.fileName)) return moduleReferences; - // Continue to get references to 'export ='. - const checker = program.getTypeChecker(); - symbol = skipAlias(exportEquals, checker); - return mergeReferences(program, moduleReferences, getReferencedSymbolsForSymbol(symbol, /*node*/ undefined, sourceFiles, sourceFilesSet, checker, cancellationToken, options)); - } + const checker = program.getTypeChecker(); + const symbol = checker.getSymbolAtLocation(node); - /** - * Merges the references by sorting them (by file index in sourceFiles and their location in it) that point to same definition symbol - */ - function mergeReferences(program: Program, ...referencesToMerge: (SymbolAndEntries[] | undefined)[]): SymbolAndEntries[] | undefined { - let result: SymbolAndEntries[] | undefined; - for (const references of referencesToMerge) { - if (!references || !references.length) continue; - if (!result) { - result = references; - continue; + // Could not find a symbol e.g. unknown identifier + if (!symbol) { + // String literal might be a property (and thus have a symbol), so do this here rather than in getReferencedSymbolsSpecial. + return !options.implementations && isStringLiteral(node) ? getReferencesForStringLiteral(node, sourceFiles, cancellationToken) : undefined; } - for (const entry of references) { - if (!entry.definition || entry.definition.type !== DefinitionKind.Symbol) { - result.push(entry); - continue; - } - const symbol = entry.definition.symbol; - const refIndex = findIndex(result, ref => !!ref.definition && - ref.definition.type === DefinitionKind.Symbol && - ref.definition.symbol === symbol); - if (refIndex === -1) { - result.push(entry); + + if (symbol.escapedName === InternalSymbolName.ExportEquals) { + return getReferencedSymbolsForModule(program, symbol.parent!, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet); + } + + const moduleReferences = getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol, program, sourceFiles, cancellationToken, options, sourceFilesSet); + if (moduleReferences && !(symbol.flags & SymbolFlags.Transient)) { + return moduleReferences; + } + + const aliasedSymbol = getMergedAliasedSymbolOfNamespaceExportDeclaration(node, symbol, checker); + const moduleReferencesOfExportTarget = aliasedSymbol && + getReferencedSymbolsForModuleIfDeclaredBySourceFile(aliasedSymbol, program, sourceFiles, cancellationToken, options, sourceFilesSet); + + const references = getReferencedSymbolsForSymbol(symbol, node, sourceFiles, sourceFilesSet, checker, cancellationToken, options); + return mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget); + } + + function getMergedAliasedSymbolOfNamespaceExportDeclaration(node: Node, symbol: Symbol, checker: TypeChecker) { + if (node.parent && isNamespaceExportDeclaration(node.parent)) { + const aliasedSymbol = checker.getAliasedSymbol(symbol); + const targetSymbol = checker.getMergedSymbol(aliasedSymbol); + if (aliasedSymbol !== targetSymbol) { + return targetSymbol; + } + } + return undefined; + } + + function getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol: Symbol, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options, sourceFilesSet: ReadonlyMap) { + const moduleSourceFile = (symbol.flags & SymbolFlags.Module) && symbol.declarations && find(symbol.declarations, isSourceFile); + if (!moduleSourceFile) return undefined; + const exportEquals = symbol.exports!.get(InternalSymbolName.ExportEquals); + // If !!exportEquals, we're about to add references to `import("mod")` anyway, so don't double-count them. + const moduleReferences = getReferencedSymbolsForModule(program, symbol, !!exportEquals, sourceFiles, sourceFilesSet); + if (!exportEquals || !sourceFilesSet.has(moduleSourceFile.fileName)) return moduleReferences; + // Continue to get references to 'export ='. + const checker = program.getTypeChecker(); + symbol = skipAlias(exportEquals, checker); + return mergeReferences(program, moduleReferences, getReferencedSymbolsForSymbol(symbol, /*node*/ undefined, sourceFiles, sourceFilesSet, checker, cancellationToken, options)); + } + + /** + * Merges the references by sorting them (by file index in sourceFiles and their location in it) that point to same definition symbol + */ + function mergeReferences(program: Program, ...referencesToMerge: (SymbolAndEntries[] | undefined)[]): SymbolAndEntries[] | undefined { + let result: SymbolAndEntries[] | undefined; + for (const references of referencesToMerge) { + if (!references || !references.length) continue; + if (!result) { + result = references; continue; } + for (const entry of references) { + if (!entry.definition || entry.definition.type !== DefinitionKind.Symbol) { + result.push(entry); + continue; + } + const symbol = entry.definition.symbol; + const refIndex = findIndex(result, ref => !!ref.definition && + ref.definition.type === DefinitionKind.Symbol && + ref.definition.symbol === symbol); + if (refIndex === -1) { + result.push(entry); + continue; + } - const reference = result[refIndex]; - result[refIndex] = { - definition: reference.definition, - references: reference.references.concat(entry.references).sort((entry1, entry2) => { - const entry1File = getSourceFileIndexOfEntry(program, entry1); - const entry2File = getSourceFileIndexOfEntry(program, entry2); - if (entry1File !== entry2File) { - return compareValues(entry1File, entry2File); + const reference = result[refIndex]; + result[refIndex] = { + definition: reference.definition, + references: reference.references.concat(entry.references).sort((entry1, entry2) => { + const entry1File = getSourceFileIndexOfEntry(program, entry1); + const entry2File = getSourceFileIndexOfEntry(program, entry2); + if (entry1File !== entry2File) { + return compareValues(entry1File, entry2File); + } + + const entry1Span = getTextSpanOfEntry(entry1); + const entry2Span = getTextSpanOfEntry(entry2); + return entry1Span.start !== entry2Span.start ? + compareValues(entry1Span.start, entry2Span.start) : + compareValues(entry1Span.length, entry2Span.length); + }) + }; + } + } + return result; + } + + function getSourceFileIndexOfEntry(program: Program, entry: Entry) { + const sourceFile = entry.kind === EntryKind.Span ? + program.getSourceFile(entry.fileName)! : + entry.node.getSourceFile(); + return program.getSourceFiles().indexOf(sourceFile); + } + + function getReferencedSymbolsForModule(program: Program, symbol: Symbol, excludeImportTypeOfExportEquals: boolean, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlyMap): SymbolAndEntries[] { + Debug.assert(!!symbol.valueDeclaration); + + const references = mapDefined(findModuleReferences(program, sourceFiles, symbol), reference => { + if (reference.kind === "import") { + const parent = reference.literal.parent; + if (isLiteralTypeNode(parent)) { + const importType = cast(parent.parent, isImportTypeNode); + if (excludeImportTypeOfExportEquals && !importType.qualifier) { + return undefined; } + } + // import("foo") with no qualifier will reference the `export =` of the module, which may be referenced anyway. + return nodeEntry(reference.literal); + } + else { + return { + kind: EntryKind.Span, + fileName: reference.referencingFile.fileName, + textSpan: createTextSpanFromRange(reference.ref), + }; + } + }); - const entry1Span = getTextSpanOfEntry(entry1); - const entry2Span = getTextSpanOfEntry(entry2); - return entry1Span.start !== entry2Span.start ? - compareValues(entry1Span.start, entry2Span.start) : - compareValues(entry1Span.length, entry2Span.length); - }) - }; + for (const decl of symbol.declarations) { + switch (decl.kind) { + case SyntaxKind.SourceFile: + // Don't include the source file itself. (This may not be ideal behavior, but awkward to include an entire file as a reference.) + break; + case SyntaxKind.ModuleDeclaration: + if (sourceFilesSet.has(decl.getSourceFile().fileName)) { + references.push(nodeEntry((decl as ModuleDeclaration).name)); + } + break; + default: + // This may be merged with something. + Debug.assert(!!(symbol.flags & SymbolFlags.Transient), "Expected a module symbol to be declared by a SourceFile or ModuleDeclaration."); + } } - } - return result; - } - function getSourceFileIndexOfEntry(program: Program, entry: Entry) { - const sourceFile = entry.kind === EntryKind.Span ? - program.getSourceFile(entry.fileName)! : - entry.node.getSourceFile(); - return program.getSourceFiles().indexOf(sourceFile); - } - - function getReferencedSymbolsForModule(program: Program, symbol: Symbol, excludeImportTypeOfExportEquals: boolean, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlyMap): SymbolAndEntries[] { - Debug.assert(!!symbol.valueDeclaration); - - const references = mapDefined(findModuleReferences(program, sourceFiles, symbol), reference => { - if (reference.kind === "import") { - const parent = reference.literal.parent; - if (isLiteralTypeNode(parent)) { - const importType = cast(parent.parent, isImportTypeNode); - if (excludeImportTypeOfExportEquals && !importType.qualifier) { - return undefined; + const exported = symbol.exports!.get(InternalSymbolName.ExportEquals); + if (exported) { + for (const decl of exported.declarations) { + const sourceFile = decl.getSourceFile(); + if (sourceFilesSet.has(sourceFile.fileName)) { + // At `module.exports = ...`, reference node is `module` + const node = isBinaryExpression(decl) && isPropertyAccessExpression(decl.left) ? decl.left.expression : + isExportAssignment(decl) ? Debug.assertDefined(findChildOfKind(decl, SyntaxKind.ExportKeyword, sourceFile)) : + getNameOfDeclaration(decl) || decl; + references.push(nodeEntry(node)); } } - // import("foo") with no qualifier will reference the `export =` of the module, which may be referenced anyway. - return nodeEntry(reference.literal); + } + + return references.length ? [{ definition: { type: DefinitionKind.Symbol, symbol }, references }] : emptyArray; + } + + /** As in a `readonly prop: any` or `constructor(readonly prop: any)`, not a `readonly any[]`. */ + function isReadonlyTypeOperator(node: Node): boolean { + return node.kind === SyntaxKind.ReadonlyKeyword + && isTypeOperatorNode(node.parent) + && node.parent.operator === SyntaxKind.ReadonlyKeyword; + } + + /** getReferencedSymbols for special node kinds. */ + function getReferencedSymbolsSpecial(node: Node, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined { + if (isTypeKeyword(node.kind)) { + // A modifier readonly (like on a property declaration) is not special; + // a readonly type keyword (like `readonly string[]`) is. + if (node.kind === SyntaxKind.ReadonlyKeyword && !isReadonlyTypeOperator(node)) { + return undefined; + } + // Likewise, when we *are* looking for a special keyword, make sure we + // *don’t* include readonly member modifiers. + return getAllReferencesForKeyword( + sourceFiles, + node.kind, + cancellationToken, + node.kind === SyntaxKind.ReadonlyKeyword ? isReadonlyTypeOperator : undefined); + } + + // Labels + if (isJumpStatementTarget(node)) { + const labelDefinition = getTargetLabel(node.parent, node.text); + // if we have a label definition, look within its statement for references, if not, then + // the label is undefined and we have no results.. + return labelDefinition && getLabelReferencesInNode(labelDefinition.parent, labelDefinition); + } + else if (isLabelOfLabeledStatement(node)) { + // it is a label definition and not a target, search within the parent labeledStatement + return getLabelReferencesInNode(node.parent, node); + } + + if (isThis(node)) { + return getReferencesForThisKeyword(node, sourceFiles, cancellationToken); + } + + if (node.kind === SyntaxKind.SuperKeyword) { + return getReferencesForSuperKeyword(node); + } + + return undefined; + } + + /** Core find-all-references algorithm for a normal symbol. */ + function getReferencedSymbolsForSymbol(originalSymbol: Symbol, node: Node | undefined, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlyMap, checker: TypeChecker, cancellationToken: CancellationToken, options: Options): SymbolAndEntries[] { + const symbol = node && skipPastExportOrImportSpecifierOrUnion(originalSymbol, node, checker, /*useLocalSymbolForExportSpecifier*/ !isForRenameWithPrefixAndSuffixText(options)) || originalSymbol; + + // Compute the meaning from the location and the symbol it references + const searchMeaning = node ? getIntersectingMeaningFromDeclarations(node, symbol) : SemanticMeaning.All; + + const result: SymbolAndEntries[] = []; + const state = new State(sourceFiles, sourceFilesSet, node ? getSpecialSearchKind(node) : SpecialSearchKind.None, checker, cancellationToken, searchMeaning, options, result); + + const exportSpecifier = !isForRenameWithPrefixAndSuffixText(options) ? undefined : find(symbol.declarations, isExportSpecifier); + if (exportSpecifier) { + // When renaming at an export specifier, rename the export and not the thing being exported. + getReferencesAtExportSpecifier(exportSpecifier.name, symbol, exportSpecifier, state.createSearch(node, originalSymbol, /*comingFrom*/ undefined), state, /*addReferencesHere*/ true, /*alwaysGetReferences*/ true); + } + else if (node && node.kind === SyntaxKind.DefaultKeyword) { + addReference(node, symbol, state); + searchForImportsOfExport(node, symbol, { exportingModuleSymbol: Debug.assertDefined(symbol.parent, "Expected export symbol to have a parent"), exportKind: ExportKind.Default }, state); } else { - return { - kind: EntryKind.Span, - fileName: reference.referencingFile.fileName, - textSpan: createTextSpanFromRange(reference.ref), - }; + const search = state.createSearch(node, symbol, /*comingFrom*/ undefined, { allSearchSymbols: node ? populateSearchSymbolSet(symbol, node, checker, !!options.isForRename, !!options.providePrefixAndSuffixTextForRename, !!options.implementations) : [symbol] }); + getReferencesInContainerOrFiles(symbol, state, search); } - }); - for (const decl of symbol.declarations) { - switch (decl.kind) { - case SyntaxKind.SourceFile: - // Don't include the source file itself. (This may not be ideal behavior, but awkward to include an entire file as a reference.) - break; - case SyntaxKind.ModuleDeclaration: - if (sourceFilesSet.has(decl.getSourceFile().fileName)) { - references.push(nodeEntry((decl as ModuleDeclaration).name)); + return result; + } + + function getReferencesInContainerOrFiles(symbol: Symbol, state: State, search: Search): void { + // Try to get the smallest valid scope that we can limit our search to; + // otherwise we'll need to search globally (i.e. include each file). + const scope = getSymbolScope(symbol); + if (scope) { + getReferencesInContainer(scope, scope.getSourceFile(), search, state, /*addReferencesHere*/ !(isSourceFile(scope) && !contains(state.sourceFiles, scope))); + } + else { + // Global search + for (const sourceFile of state.sourceFiles) { + state.cancellationToken.throwIfCancellationRequested(); + searchForName(sourceFile, search, state); + } + } + } + + function getSpecialSearchKind(node: Node): SpecialSearchKind { + switch (node.kind) { + case SyntaxKind.ConstructorKeyword: + return SpecialSearchKind.Constructor; + case SyntaxKind.Identifier: + if (isClassLike(node.parent)) { + Debug.assert(node.parent.name === node); + return SpecialSearchKind.Class; } - break; + // falls through default: - // This may be merged with something. - Debug.assert(!!(symbol.flags & SymbolFlags.Transient), "Expected a module symbol to be declared by a SourceFile or ModuleDeclaration."); + return SpecialSearchKind.None; } } - const exported = symbol.exports!.get(InternalSymbolName.ExportEquals); - if (exported) { - for (const decl of exported.declarations) { - const sourceFile = decl.getSourceFile(); - if (sourceFilesSet.has(sourceFile.fileName)) { - // At `module.exports = ...`, reference node is `module` - const node = isBinaryExpression(decl) && isPropertyAccessExpression(decl.left) ? decl.left.expression : - isExportAssignment(decl) ? Debug.assertDefined(findChildOfKind(decl, SyntaxKind.ExportKeyword, sourceFile)) : - getNameOfDeclaration(decl) || decl; - references.push(nodeEntry(node)); + /** Handle a few special cases relating to export/import specifiers. */ + function skipPastExportOrImportSpecifierOrUnion(symbol: Symbol, node: Node, checker: TypeChecker, useLocalSymbolForExportSpecifier: boolean): Symbol | undefined { + const { parent } = node; + if (isExportSpecifier(parent) && useLocalSymbolForExportSpecifier) { + return getLocalSymbolForExportSpecifier(node as Identifier, symbol, parent, checker); + } + // If the symbol is declared as part of a declaration like `{ type: "a" } | { type: "b" }`, use the property on the union type to get more references. + return firstDefined(symbol.declarations, decl => { + if (!decl.parent) { + // Ignore UMD module and global merge + if (symbol.flags & SymbolFlags.Transient) return undefined; + // Assertions for GH#21814. We should be handling SourceFile symbols in `getReferencedSymbolsForModule` instead of getting here. + Debug.fail(`Unexpected symbol at ${Debug.formatSyntaxKind(node.kind)}: ${Debug.formatSymbol(symbol)}`); } - } - } - - return references.length ? [{ definition: { type: DefinitionKind.Symbol, symbol }, references }] : emptyArray; - } - - /** As in a `readonly prop: any` or `constructor(readonly prop: any)`, not a `readonly any[]`. */ - function isReadonlyTypeOperator(node: Node): boolean { - return node.kind === SyntaxKind.ReadonlyKeyword - && isTypeOperatorNode(node.parent) - && node.parent.operator === SyntaxKind.ReadonlyKeyword; - } - - /** getReferencedSymbols for special node kinds. */ - function getReferencedSymbolsSpecial(node: Node, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined { - if (isTypeKeyword(node.kind)) { - // A modifier readonly (like on a property declaration) is not special; - // a readonly type keyword (like `readonly string[]`) is. - if (node.kind === SyntaxKind.ReadonlyKeyword && !isReadonlyTypeOperator(node)) { - return undefined; - } - // Likewise, when we *are* looking for a special keyword, make sure we - // *don’t* include readonly member modifiers. - return getAllReferencesForKeyword( - sourceFiles, - node.kind, - cancellationToken, - node.kind === SyntaxKind.ReadonlyKeyword ? isReadonlyTypeOperator : undefined); - } - - // Labels - if (isJumpStatementTarget(node)) { - const labelDefinition = getTargetLabel(node.parent, node.text); - // if we have a label definition, look within its statement for references, if not, then - // the label is undefined and we have no results.. - return labelDefinition && getLabelReferencesInNode(labelDefinition.parent, labelDefinition); - } - else if (isLabelOfLabeledStatement(node)) { - // it is a label definition and not a target, search within the parent labeledStatement - return getLabelReferencesInNode(node.parent, node); - } - - if (isThis(node)) { - return getReferencesForThisKeyword(node, sourceFiles, cancellationToken); - } - - if (node.kind === SyntaxKind.SuperKeyword) { - return getReferencesForSuperKeyword(node); - } - - return undefined; - } - - /** Core find-all-references algorithm for a normal symbol. */ - function getReferencedSymbolsForSymbol(originalSymbol: Symbol, node: Node | undefined, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlyMap, checker: TypeChecker, cancellationToken: CancellationToken, options: Options): SymbolAndEntries[] { - const symbol = node && skipPastExportOrImportSpecifierOrUnion(originalSymbol, node, checker, /*useLocalSymbolForExportSpecifier*/ !isForRenameWithPrefixAndSuffixText(options)) || originalSymbol; - - // Compute the meaning from the location and the symbol it references - const searchMeaning = node ? getIntersectingMeaningFromDeclarations(node, symbol) : SemanticMeaning.All; - - const result: SymbolAndEntries[] = []; - const state = new State(sourceFiles, sourceFilesSet, node ? getSpecialSearchKind(node) : SpecialSearchKind.None, checker, cancellationToken, searchMeaning, options, result); - - const exportSpecifier = !isForRenameWithPrefixAndSuffixText(options) ? undefined : find(symbol.declarations, isExportSpecifier); - if (exportSpecifier) { - // When renaming at an export specifier, rename the export and not the thing being exported. - getReferencesAtExportSpecifier(exportSpecifier.name, symbol, exportSpecifier, state.createSearch(node, originalSymbol, /*comingFrom*/ undefined), state, /*addReferencesHere*/ true, /*alwaysGetReferences*/ true); - } - else if (node && node.kind === SyntaxKind.DefaultKeyword) { - addReference(node, symbol, state); - searchForImportsOfExport(node, symbol, { exportingModuleSymbol: Debug.assertDefined(symbol.parent, "Expected export symbol to have a parent"), exportKind: ExportKind.Default }, state); - } - else { - const search = state.createSearch(node, symbol, /*comingFrom*/ undefined, { allSearchSymbols: node ? populateSearchSymbolSet(symbol, node, checker, !!options.isForRename, !!options.providePrefixAndSuffixTextForRename, !!options.implementations) : [symbol] }); - getReferencesInContainerOrFiles(symbol, state, search); - } - - return result; - } - - function getReferencesInContainerOrFiles(symbol: Symbol, state: State, search: Search): void { - // Try to get the smallest valid scope that we can limit our search to; - // otherwise we'll need to search globally (i.e. include each file). - const scope = getSymbolScope(symbol); - if (scope) { - getReferencesInContainer(scope, scope.getSourceFile(), search, state, /*addReferencesHere*/ !(isSourceFile(scope) && !contains(state.sourceFiles, scope))); - } - else { - // Global search - for (const sourceFile of state.sourceFiles) { - state.cancellationToken.throwIfCancellationRequested(); - searchForName(sourceFile, search, state); - } - } - } - - function getSpecialSearchKind(node: Node): SpecialSearchKind { - switch (node.kind) { - case SyntaxKind.ConstructorKeyword: - return SpecialSearchKind.Constructor; - case SyntaxKind.Identifier: - if (isClassLike(node.parent)) { - Debug.assert(node.parent.name === node); - return SpecialSearchKind.Class; - } - // falls through - default: - return SpecialSearchKind.None; - } - } - - /** Handle a few special cases relating to export/import specifiers. */ - function skipPastExportOrImportSpecifierOrUnion(symbol: Symbol, node: Node, checker: TypeChecker, useLocalSymbolForExportSpecifier: boolean): Symbol | undefined { - const { parent } = node; - if (isExportSpecifier(parent) && useLocalSymbolForExportSpecifier) { - return getLocalSymbolForExportSpecifier(node as Identifier, symbol, parent, checker); - } - // If the symbol is declared as part of a declaration like `{ type: "a" } | { type: "b" }`, use the property on the union type to get more references. - return firstDefined(symbol.declarations, decl => { - if (!decl.parent) { - // Ignore UMD module and global merge - if (symbol.flags & SymbolFlags.Transient) return undefined; - // Assertions for GH#21814. We should be handling SourceFile symbols in `getReferencedSymbolsForModule` instead of getting here. - Debug.fail(`Unexpected symbol at ${Debug.formatSyntaxKind(node.kind)}: ${Debug.formatSymbol(symbol)}`); - } - return isTypeLiteralNode(decl.parent) && isUnionTypeNode(decl.parent.parent) - ? checker.getPropertyOfType(checker.getTypeFromTypeNode(decl.parent.parent), symbol.name) - : undefined; - }); - } - - /** - * Symbol that is currently being searched for. - * This will be replaced if we find an alias for the symbol. - */ - interface Search { - /** If coming from an export, we will not recursively search for the imported symbol (since that's where we came from). */ - readonly comingFrom?: ImportExport; - - readonly symbol: Symbol; - readonly text: string; - readonly escapedText: __String; - /** Only set if `options.implementations` is true. These are the symbols checked to get the implementations of a property access. */ - readonly parents: readonly Symbol[] | undefined; - readonly allSearchSymbols: readonly Symbol[]; - - /** - * Whether a symbol is in the search set. - * Do not compare directly to `symbol` because there may be related symbols to search for. See `populateSearchSymbolSet`. - */ - includes(symbol: Symbol): boolean; - } - - const enum SpecialSearchKind { - None, - Constructor, - Class, - } - - function getNonModuleSymbolOfMergedModuleSymbol(symbol: Symbol) { - if (!(symbol.flags & (SymbolFlags.Module | SymbolFlags.Transient))) return undefined; - const decl = symbol.declarations && find(symbol.declarations, d => !isSourceFile(d) && !isModuleDeclaration(d)); - return decl && decl.symbol; - } - - /** - * Holds all state needed for the finding references. - * Unlike `Search`, there is only one `State`. - */ - class State { - /** Cache for `explicitlyinheritsFrom`. */ - readonly inheritsFromCache = createMap(); - - /** - * Type nodes can contain multiple references to the same type. For example: - * let x: Foo & (Foo & Bar) = ... - * Because we are returning the implementation locations and not the identifier locations, - * duplicate entries would be returned here as each of the type references is part of - * the same implementation. For that reason, check before we add a new entry. - */ - readonly markSeenContainingTypeReference = nodeSeenTracker(); - - /** - * It's possible that we will encounter the right side of `export { foo as bar } from "x";` more than once. - * For example: - * // b.ts - * export { foo as bar } from "./a"; - * import { bar } from "./b"; - * - * Normally at `foo as bar` we directly add `foo` and do not locally search for it (since it doesn't declare a local). - * But another reference to it may appear in the same source file. - * See `tests/cases/fourslash/transitiveExportImports3.ts`. - */ - readonly markSeenReExportRHS = nodeSeenTracker(); - - constructor( - readonly sourceFiles: readonly SourceFile[], - readonly sourceFilesSet: ReadonlyMap, - readonly specialSearchKind: SpecialSearchKind, - readonly checker: TypeChecker, - readonly cancellationToken: CancellationToken, - readonly searchMeaning: SemanticMeaning, - readonly options: Options, - private readonly result: Push) { - } - - includesSourceFile(sourceFile: SourceFile): boolean { - return this.sourceFilesSet.has(sourceFile.fileName); - } - - private importTracker: ImportTracker | undefined; - /** Gets every place to look for references of an exported symbols. See `ImportsResult` in `importTracker.ts` for more documentation. */ - getImportSearches(exportSymbol: Symbol, exportInfo: ExportInfo): ImportsResult { - if (!this.importTracker) this.importTracker = createImportTracker(this.sourceFiles, this.sourceFilesSet, this.checker, this.cancellationToken); - return this.importTracker(exportSymbol, exportInfo, !!this.options.isForRename); - } - - /** @param allSearchSymbols set of additional symbols for use by `includes`. */ - createSearch(location: Node | undefined, symbol: Symbol, comingFrom: ImportExport | undefined, searchOptions: { text?: string, allSearchSymbols?: Symbol[] } = {}): Search { - // Note: if this is an external module symbol, the name doesn't include quotes. - // Note: getLocalSymbolForExportDefault handles `export default class C {}`, but not `export default C` or `export { C as default }`. - // The other two forms seem to be handled downstream (e.g. in `skipPastExportOrImportSpecifier`), so special-casing the first form - // here appears to be intentional). - const { - text = stripQuotes(unescapeLeadingUnderscores((getLocalSymbolForExportDefault(symbol) || getNonModuleSymbolOfMergedModuleSymbol(symbol) || symbol).escapedName)), - allSearchSymbols = [symbol], - } = searchOptions; - const escapedText = escapeLeadingUnderscores(text); - const parents = this.options.implementations && location ? getParentSymbolsOfPropertyAccess(location, symbol, this.checker) : undefined; - return { symbol, comingFrom, text, escapedText, parents, allSearchSymbols, includes: sym => contains(allSearchSymbols, sym) }; - } - - private readonly symbolIdToReferences: Entry[][] = []; - /** - * Callback to add references for a particular searched symbol. - * This initializes a reference group, so only call this if you will add at least one reference. - */ - referenceAdder(searchSymbol: Symbol): (node: Node, kind?: NodeEntryKind) => void { - const symbolId = getSymbolId(searchSymbol); - let references = this.symbolIdToReferences[symbolId]; - if (!references) { - references = this.symbolIdToReferences[symbolId] = []; - this.result.push({ definition: { type: DefinitionKind.Symbol, symbol: searchSymbol }, references }); - } - return (node, kind) => references.push(nodeEntry(node, kind)); - } - - /** Add a reference with no associated definition. */ - addStringOrCommentReference(fileName: string, textSpan: TextSpan): void { - this.result.push({ - definition: undefined, - references: [{ kind: EntryKind.Span, fileName, textSpan }] + return isTypeLiteralNode(decl.parent) && isUnionTypeNode(decl.parent.parent) + ? checker.getPropertyOfType(checker.getTypeFromTypeNode(decl.parent.parent), symbol.name) + : undefined; }); } - // Source file ID → symbol ID → Whether the symbol has been searched for in the source file. - private readonly sourceFileToSeenSymbols: Map[] = []; - /** Returns `true` the first time we search for a symbol in a file and `false` afterwards. */ - markSearchedSymbols(sourceFile: SourceFile, symbols: readonly Symbol[]): boolean { - const sourceId = getNodeId(sourceFile); - const seenSymbols = this.sourceFileToSeenSymbols[sourceId] || (this.sourceFileToSeenSymbols[sourceId] = createMap()); + /** + * Symbol that is currently being searched for. + * This will be replaced if we find an alias for the symbol. + */ + interface Search { + /** If coming from an export, we will not recursively search for the imported symbol (since that's where we came from). */ + readonly comingFrom?: ImportExport; - let anyNewSymbols = false; - for (const sym of symbols) { - anyNewSymbols = addToSeen(seenSymbols, getSymbolId(sym)) || anyNewSymbols; - } - return anyNewSymbols; + readonly symbol: Symbol; + readonly text: string; + readonly escapedText: __String; + /** Only set if `options.implementations` is true. These are the symbols checked to get the implementations of a property access. */ + readonly parents: readonly Symbol[] | undefined; + readonly allSearchSymbols: readonly Symbol[]; + + /** + * Whether a symbol is in the search set. + * Do not compare directly to `symbol` because there may be related symbols to search for. See `populateSearchSymbolSet`. + */ + includes(symbol: Symbol): boolean; } - } - /** Search for all imports of a given exported symbol using `State.getImportSearches`. */ - function searchForImportsOfExport(exportLocation: Node, exportSymbol: Symbol, exportInfo: ExportInfo, state: State): void { - const { importSearches, singleReferences, indirectUsers } = state.getImportSearches(exportSymbol, exportInfo); + const enum SpecialSearchKind { + None, + Constructor, + Class, + } - // For `import { foo as bar }` just add the reference to `foo`, and don't otherwise search in the file. - if (singleReferences.length) { - const addRef = state.referenceAdder(exportSymbol); - for (const singleRef of singleReferences) { - if (shouldAddSingleReference(singleRef, state)) addRef(singleRef); + function getNonModuleSymbolOfMergedModuleSymbol(symbol: Symbol) { + if (!(symbol.flags & (SymbolFlags.Module | SymbolFlags.Transient))) return undefined; + const decl = symbol.declarations && find(symbol.declarations, d => !isSourceFile(d) && !isModuleDeclaration(d)); + return decl && decl.symbol; + } + + /** + * Holds all state needed for the finding references. + * Unlike `Search`, there is only one `State`. + */ + class State { + /** Cache for `explicitlyinheritsFrom`. */ + readonly inheritsFromCache = createMap(); + + /** + * Type nodes can contain multiple references to the same type. For example: + * let x: Foo & (Foo & Bar) = ... + * Because we are returning the implementation locations and not the identifier locations, + * duplicate entries would be returned here as each of the type references is part of + * the same implementation. For that reason, check before we add a new entry. + */ + readonly markSeenContainingTypeReference = nodeSeenTracker(); + + /** + * It's possible that we will encounter the right side of `export { foo as bar } from "x";` more than once. + * For example: + * // b.ts + * export { foo as bar } from "./a"; + * import { bar } from "./b"; + * + * Normally at `foo as bar` we directly add `foo` and do not locally search for it (since it doesn't declare a local). + * But another reference to it may appear in the same source file. + * See `tests/cases/fourslash/transitiveExportImports3.ts`. + */ + readonly markSeenReExportRHS = nodeSeenTracker(); + + constructor( + readonly sourceFiles: readonly SourceFile[], + readonly sourceFilesSet: ReadonlyMap, + readonly specialSearchKind: SpecialSearchKind, + readonly checker: TypeChecker, + readonly cancellationToken: CancellationToken, + readonly searchMeaning: SemanticMeaning, + readonly options: Options, + private readonly result: Push) { + } + + includesSourceFile(sourceFile: SourceFile): boolean { + return this.sourceFilesSet.has(sourceFile.fileName); + } + + private importTracker: ImportTracker | undefined; + /** Gets every place to look for references of an exported symbols. See `ImportsResult` in `importTracker.ts` for more documentation. */ + getImportSearches(exportSymbol: Symbol, exportInfo: ExportInfo): ImportsResult { + if (!this.importTracker) this.importTracker = createImportTracker(this.sourceFiles, this.sourceFilesSet, this.checker, this.cancellationToken); + return this.importTracker(exportSymbol, exportInfo, !!this.options.isForRename); + } + + /** @param allSearchSymbols set of additional symbols for use by `includes`. */ + createSearch(location: Node | undefined, symbol: Symbol, comingFrom: ImportExport | undefined, searchOptions: { text?: string, allSearchSymbols?: Symbol[] } = {}): Search { + // Note: if this is an external module symbol, the name doesn't include quotes. + // Note: getLocalSymbolForExportDefault handles `export default class C {}`, but not `export default C` or `export { C as default }`. + // The other two forms seem to be handled downstream (e.g. in `skipPastExportOrImportSpecifier`), so special-casing the first form + // here appears to be intentional). + const { + text = stripQuotes(unescapeLeadingUnderscores((getLocalSymbolForExportDefault(symbol) || getNonModuleSymbolOfMergedModuleSymbol(symbol) || symbol).escapedName)), + allSearchSymbols = [symbol], + } = searchOptions; + const escapedText = escapeLeadingUnderscores(text); + const parents = this.options.implementations && location ? getParentSymbolsOfPropertyAccess(location, symbol, this.checker) : undefined; + return { symbol, comingFrom, text, escapedText, parents, allSearchSymbols, includes: sym => contains(allSearchSymbols, sym) }; + } + + private readonly symbolIdToReferences: Entry[][] = []; + /** + * Callback to add references for a particular searched symbol. + * This initializes a reference group, so only call this if you will add at least one reference. + */ + referenceAdder(searchSymbol: Symbol): (node: Node, kind?: NodeEntryKind) => void { + const symbolId = getSymbolId(searchSymbol); + let references = this.symbolIdToReferences[symbolId]; + if (!references) { + references = this.symbolIdToReferences[symbolId] = []; + this.result.push({ definition: { type: DefinitionKind.Symbol, symbol: searchSymbol }, references }); + } + return (node, kind) => references.push(nodeEntry(node, kind)); + } + + /** Add a reference with no associated definition. */ + addStringOrCommentReference(fileName: string, textSpan: TextSpan): void { + this.result.push({ + definition: undefined, + references: [{ kind: EntryKind.Span, fileName, textSpan }] + }); + } + + // Source file ID → symbol ID → Whether the symbol has been searched for in the source file. + private readonly sourceFileToSeenSymbols: Map[] = []; + /** Returns `true` the first time we search for a symbol in a file and `false` afterwards. */ + markSearchedSymbols(sourceFile: SourceFile, symbols: readonly Symbol[]): boolean { + const sourceId = getNodeId(sourceFile); + const seenSymbols = this.sourceFileToSeenSymbols[sourceId] || (this.sourceFileToSeenSymbols[sourceId] = createMap()); + + let anyNewSymbols = false; + for (const sym of symbols) { + anyNewSymbols = addToSeen(seenSymbols, getSymbolId(sym)) || anyNewSymbols; + } + return anyNewSymbols; } } - // For each import, find all references to that import in its source file. - for (const [importLocation, importSymbol] of importSearches) { - getReferencesInSourceFile(importLocation.getSourceFile(), state.createSearch(importLocation, importSymbol, ImportExport.Export), state); - } + /** Search for all imports of a given exported symbol using `State.getImportSearches`. */ + function searchForImportsOfExport(exportLocation: Node, exportSymbol: Symbol, exportInfo: ExportInfo, state: State): void { + const { importSearches, singleReferences, indirectUsers } = state.getImportSearches(exportSymbol, exportInfo); - if (indirectUsers.length) { - let indirectSearch: Search | undefined; - switch (exportInfo.exportKind) { - case ExportKind.Named: - indirectSearch = state.createSearch(exportLocation, exportSymbol, ImportExport.Export); - break; - case ExportKind.Default: - // Search for a property access to '.default'. This can't be renamed. - indirectSearch = state.options.isForRename ? undefined : state.createSearch(exportLocation, exportSymbol, ImportExport.Export, { text: "default" }); - break; - case ExportKind.ExportEquals: - break; + // For `import { foo as bar }` just add the reference to `foo`, and don't otherwise search in the file. + if (singleReferences.length) { + const addRef = state.referenceAdder(exportSymbol); + for (const singleRef of singleReferences) { + if (shouldAddSingleReference(singleRef, state)) addRef(singleRef); + } } - if (indirectSearch) { - for (const indirectUser of indirectUsers) { - searchForName(indirectUser, indirectSearch, state); + + // For each import, find all references to that import in its source file. + for (const [importLocation, importSymbol] of importSearches) { + getReferencesInSourceFile(importLocation.getSourceFile(), state.createSearch(importLocation, importSymbol, ImportExport.Export), state); + } + + if (indirectUsers.length) { + let indirectSearch: Search | undefined; + switch (exportInfo.exportKind) { + case ExportKind.Named: + indirectSearch = state.createSearch(exportLocation, exportSymbol, ImportExport.Export); + break; + case ExportKind.Default: + // Search for a property access to '.default'. This can't be renamed. + indirectSearch = state.options.isForRename ? undefined : state.createSearch(exportLocation, exportSymbol, ImportExport.Export, { text: "default" }); + break; + case ExportKind.ExportEquals: + break; + } + if (indirectSearch) { + for (const indirectUser of indirectUsers) { + searchForName(indirectUser, indirectSearch, state); + } } } } - } - export function eachExportReference( - sourceFiles: readonly SourceFile[], - checker: TypeChecker, - cancellationToken: CancellationToken | undefined, - exportSymbol: Symbol, - exportingModuleSymbol: Symbol, - exportName: string, - isDefaultExport: boolean, - cb: (ref: Identifier) => void, - ): void { - const importTracker = createImportTracker(sourceFiles, arrayToSet(sourceFiles, f => f.fileName), checker, cancellationToken); - const { importSearches, indirectUsers } = importTracker(exportSymbol, { exportKind: isDefaultExport ? ExportKind.Default : ExportKind.Named, exportingModuleSymbol }, /*isForRename*/ false); - for (const [importLocation] of importSearches) { - cb(importLocation); - } - for (const indirectUser of indirectUsers) { - for (const node of getPossibleSymbolReferenceNodes(indirectUser, isDefaultExport ? "default" : exportName)) { - // Import specifiers should be handled by importSearches - if (isIdentifier(node) && !isImportOrExportSpecifier(node.parent) && checker.getSymbolAtLocation(node) === exportSymbol) { - cb(node); + export function eachExportReference( + sourceFiles: readonly SourceFile[], + checker: TypeChecker, + cancellationToken: CancellationToken | undefined, + exportSymbol: Symbol, + exportingModuleSymbol: Symbol, + exportName: string, + isDefaultExport: boolean, + cb: (ref: Identifier) => void, + ): void { + const importTracker = createImportTracker(sourceFiles, arrayToSet(sourceFiles, f => f.fileName), checker, cancellationToken); + const { importSearches, indirectUsers } = importTracker(exportSymbol, { exportKind: isDefaultExport ? ExportKind.Default : ExportKind.Named, exportingModuleSymbol }, /*isForRename*/ false); + for (const [importLocation] of importSearches) { + cb(importLocation); + } + for (const indirectUser of indirectUsers) { + for (const node of getPossibleSymbolReferenceNodes(indirectUser, isDefaultExport ? "default" : exportName)) { + // Import specifiers should be handled by importSearches + if (isIdentifier(node) && !isImportOrExportSpecifier(node.parent) && checker.getSymbolAtLocation(node) === exportSymbol) { + cb(node); + } } } } - } - function shouldAddSingleReference(singleRef: Identifier | StringLiteral, state: State): boolean { - if (!hasMatchingMeaning(singleRef, state)) return false; - if (!state.options.isForRename) return true; - // Don't rename an import type `import("./module-name")` when renaming `name` in `export = name;` - if (!isIdentifier(singleRef)) return false; - // At `default` in `import { default as x }` or `export { default as x }`, do add a reference, but do not rename. - return !(isImportOrExportSpecifier(singleRef.parent) && singleRef.escapedText === InternalSymbolName.Default); - } - - // Go to the symbol we imported from and find references for it. - function searchForImportedSymbol(symbol: Symbol, state: State): void { - for (const declaration of symbol.declarations) { - const exportingFile = declaration.getSourceFile(); - // Need to search in the file even if it's not in the search-file set, because it might export the symbol. - getReferencesInSourceFile(exportingFile, state.createSearch(declaration, symbol, ImportExport.Import), state, state.includesSourceFile(exportingFile)); - } - } - - /** Search for all occurences of an identifier in a source file (and filter out the ones that match). */ - function searchForName(sourceFile: SourceFile, search: Search, state: State): void { - if (getNameTable(sourceFile).get(search.escapedText) !== undefined) { - getReferencesInSourceFile(sourceFile, search, state); - } - } - - function getPropertySymbolOfDestructuringAssignment(location: Node, checker: TypeChecker): Symbol | undefined { - return isArrayLiteralOrObjectLiteralDestructuringPattern(location.parent.parent) - ? checker.getPropertySymbolOfDestructuringAssignment(location) - : undefined; - } - - /** - * Determines the smallest scope in which a symbol may have named references. - * Note that not every construct has been accounted for. This function can - * probably be improved. - * - * @returns undefined if the scope cannot be determined, implying that - * a reference to a symbol can occur anywhere. - */ - function getSymbolScope(symbol: Symbol): Node | undefined { - // If this is the symbol of a named function expression or named class expression, - // then named references are limited to its own scope. - const { declarations, flags, parent, valueDeclaration } = symbol; - if (valueDeclaration && (valueDeclaration.kind === SyntaxKind.FunctionExpression || valueDeclaration.kind === SyntaxKind.ClassExpression)) { - return valueDeclaration; + function shouldAddSingleReference(singleRef: Identifier | StringLiteral, state: State): boolean { + if (!hasMatchingMeaning(singleRef, state)) return false; + if (!state.options.isForRename) return true; + // Don't rename an import type `import("./module-name")` when renaming `name` in `export = name;` + if (!isIdentifier(singleRef)) return false; + // At `default` in `import { default as x }` or `export { default as x }`, do add a reference, but do not rename. + return !(isImportOrExportSpecifier(singleRef.parent) && singleRef.escapedText === InternalSymbolName.Default); } - if (!declarations) { - return undefined; - } - - // If this is private property or method, the scope is the containing class - if (flags & (SymbolFlags.Property | SymbolFlags.Method)) { - const privateDeclaration = find(declarations, d => hasModifier(d, ModifierFlags.Private)); - if (privateDeclaration) { - return getAncestor(privateDeclaration, SyntaxKind.ClassDeclaration); + // Go to the symbol we imported from and find references for it. + function searchForImportedSymbol(symbol: Symbol, state: State): void { + for (const declaration of symbol.declarations) { + const exportingFile = declaration.getSourceFile(); + // Need to search in the file even if it's not in the search-file set, because it might export the symbol. + getReferencesInSourceFile(exportingFile, state.createSearch(declaration, symbol, ImportExport.Import), state, state.includesSourceFile(exportingFile)); } - // Else this is a public property and could be accessed from anywhere. - return undefined; } - // If symbol is of object binding pattern element without property name we would want to - // look for property too and that could be anywhere - if (declarations.some(isObjectBindingElementWithoutPropertyName)) { - return undefined; + /** Search for all occurences of an identifier in a source file (and filter out the ones that match). */ + function searchForName(sourceFile: SourceFile, search: Search, state: State): void { + if (getNameTable(sourceFile).get(search.escapedText) !== undefined) { + getReferencesInSourceFile(sourceFile, search, state); + } } - /* - If the symbol has a parent, it's globally visible unless: - - It's a private property (handled above). - - It's a type parameter. - - The parent is an external module: then we should only search in the module (and recurse on the export later). - - But if the parent has `export as namespace`, the symbol is globally visible through that namespace. - */ - const exposedByParent = parent && !(symbol.flags & SymbolFlags.TypeParameter); - if (exposedByParent && !(isExternalModuleSymbol(parent!) && !parent!.globalExports)) { - return undefined; + function getPropertySymbolOfDestructuringAssignment(location: Node, checker: TypeChecker): Symbol | undefined { + return isArrayLiteralOrObjectLiteralDestructuringPattern(location.parent.parent) + ? checker.getPropertySymbolOfDestructuringAssignment(location) + : undefined; } - let scope: Node | undefined; - for (const declaration of declarations) { - const container = getContainerNode(declaration); - if (scope && scope !== container) { - // Different declarations have different containers, bail out + /** + * Determines the smallest scope in which a symbol may have named references. + * Note that not every construct has been accounted for. This function can + * probably be improved. + * + * @returns undefined if the scope cannot be determined, implying that + * a reference to a symbol can occur anywhere. + */ + function getSymbolScope(symbol: Symbol): Node | undefined { + // If this is the symbol of a named function expression or named class expression, + // then named references are limited to its own scope. + const { declarations, flags, parent, valueDeclaration } = symbol; + if (valueDeclaration && (valueDeclaration.kind === SyntaxKind.FunctionExpression || valueDeclaration.kind === SyntaxKind.ClassExpression)) { + return valueDeclaration; + } + + if (!declarations) { return undefined; } - if (!container || container.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(container)) { - // This is a global variable and not an external module, any declaration defined - // within this scope is visible outside the file + // If this is private property or method, the scope is the containing class + if (flags & (SymbolFlags.Property | SymbolFlags.Method)) { + const privateDeclaration = find(declarations, d => hasModifier(d, ModifierFlags.Private)); + if (privateDeclaration) { + return getAncestor(privateDeclaration, SyntaxKind.ClassDeclaration); + } + // Else this is a public property and could be accessed from anywhere. return undefined; } - // The search scope is the container node - scope = container; - } - - // If symbol.parent, this means we are in an export of an external module. (Otherwise we would have returned `undefined` above.) - // For an export of a module, we may be in a declaration file, and it may be accessed elsewhere. E.g.: - // declare module "a" { export type T = number; } - // declare module "b" { import { T } from "a"; export const x: T; } - // So we must search the whole source file. (Because we will mark the source file as seen, we we won't return to it when searching for imports.) - return exposedByParent ? scope!.getSourceFile() : scope; // TODO: GH#18217 - } - - /** Used as a quick check for whether a symbol is used at all in a file (besides its definition). */ - export function isSymbolReferencedInFile(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile): boolean { - return eachSymbolReferenceInFile(definition, checker, sourceFile, () => true) || false; - } - - export function eachSymbolReferenceInFile(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile, cb: (token: Identifier) => T): T | undefined { - const symbol = isParameterPropertyDeclaration(definition.parent, definition.parent.parent) - ? first(checker.getSymbolsOfParameterPropertyDeclaration(definition.parent, definition.text)) - : checker.getSymbolAtLocation(definition); - if (!symbol) return undefined; - for (const token of getPossibleSymbolReferenceNodes(sourceFile, symbol.name)) { - if (!isIdentifier(token) || token === definition || token.escapedText !== definition.escapedText) continue; - const referenceSymbol: Symbol = checker.getSymbolAtLocation(token)!; // See GH#19955 for why the type annotation is necessary - if (referenceSymbol === symbol - || checker.getShorthandAssignmentValueSymbol(token.parent) === symbol - || isExportSpecifier(token.parent) && getLocalSymbolForExportSpecifier(token, referenceSymbol, token.parent, checker) === symbol) { - const res = cb(token); - if (res) return res; + // If symbol is of object binding pattern element without property name we would want to + // look for property too and that could be anywhere + if (declarations.some(isObjectBindingElementWithoutPropertyName)) { + return undefined; } + + /* + If the symbol has a parent, it's globally visible unless: + - It's a private property (handled above). + - It's a type parameter. + - The parent is an external module: then we should only search in the module (and recurse on the export later). + - But if the parent has `export as namespace`, the symbol is globally visible through that namespace. + */ + const exposedByParent = parent && !(symbol.flags & SymbolFlags.TypeParameter); + if (exposedByParent && !(isExternalModuleSymbol(parent!) && !parent!.globalExports)) { + return undefined; + } + + let scope: Node | undefined; + for (const declaration of declarations) { + const container = getContainerNode(declaration); + if (scope && scope !== container) { + // Different declarations have different containers, bail out + return undefined; + } + + if (!container || container.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(container)) { + // This is a global variable and not an external module, any declaration defined + // within this scope is visible outside the file + return undefined; + } + + // The search scope is the container node + scope = container; + } + + // If symbol.parent, this means we are in an export of an external module. (Otherwise we would have returned `undefined` above.) + // For an export of a module, we may be in a declaration file, and it may be accessed elsewhere. E.g.: + // declare module "a" { export type T = number; } + // declare module "b" { import { T } from "a"; export const x: T; } + // So we must search the whole source file. (Because we will mark the source file as seen, we we won't return to it when searching for imports.) + return exposedByParent ? scope!.getSourceFile() : scope; // TODO: GH#18217 } - } - export function eachSignatureCall(signature: SignatureDeclaration, sourceFiles: readonly SourceFile[], checker: TypeChecker, cb: (call: CallExpression) => void): void { - if (!signature.name || !isIdentifier(signature.name)) return; + /** Used as a quick check for whether a symbol is used at all in a file (besides its definition). */ + export function isSymbolReferencedInFile(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile): boolean { + return eachSymbolReferenceInFile(definition, checker, sourceFile, () => true) || false; + } - const symbol = Debug.assertDefined(checker.getSymbolAtLocation(signature.name)); - - for (const sourceFile of sourceFiles) { - for (const name of getPossibleSymbolReferenceNodes(sourceFile, symbol.name)) { - if (!isIdentifier(name) || name === signature.name || name.escapedText !== signature.name.escapedText) continue; - const called = climbPastPropertyAccess(name); - const call = called.parent; - if (!isCallExpression(call) || call.expression !== called) continue; - const referenceSymbol = checker.getSymbolAtLocation(name); - if (referenceSymbol && checker.getRootSymbols(referenceSymbol).some(s => s === symbol)) { - cb(call); + export function eachSymbolReferenceInFile(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile, cb: (token: Identifier) => T): T | undefined { + const symbol = isParameterPropertyDeclaration(definition.parent, definition.parent.parent) + ? first(checker.getSymbolsOfParameterPropertyDeclaration(definition.parent, definition.text)) + : checker.getSymbolAtLocation(definition); + if (!symbol) return undefined; + for (const token of getPossibleSymbolReferenceNodes(sourceFile, symbol.name)) { + if (!isIdentifier(token) || token === definition || token.escapedText !== definition.escapedText) continue; + const referenceSymbol: Symbol = checker.getSymbolAtLocation(token)!; // See GH#19955 for why the type annotation is necessary + if (referenceSymbol === symbol + || checker.getShorthandAssignmentValueSymbol(token.parent) === symbol + || isExportSpecifier(token.parent) && getLocalSymbolForExportSpecifier(token, referenceSymbol, token.parent, checker) === symbol) { + const res = cb(token); + if (res) return res; } } } - } - function getPossibleSymbolReferenceNodes(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile): readonly Node[] { - return getPossibleSymbolReferencePositions(sourceFile, symbolName, container).map(pos => getTouchingPropertyName(sourceFile, pos)); - } + export function eachSignatureCall(signature: SignatureDeclaration, sourceFiles: readonly SourceFile[], checker: TypeChecker, cb: (call: CallExpression) => void): void { + if (!signature.name || !isIdentifier(signature.name)) return; - function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile): readonly number[] { - const positions: number[] = []; + const symbol = Debug.assertDefined(checker.getSymbolAtLocation(signature.name)); - /// TODO: Cache symbol existence for files to save text search - // Also, need to make this work for unicode escapes. + for (const sourceFile of sourceFiles) { + for (const name of getPossibleSymbolReferenceNodes(sourceFile, symbol.name)) { + if (!isIdentifier(name) || name === signature.name || name.escapedText !== signature.name.escapedText) continue; + const called = climbPastPropertyAccess(name); + const call = called.parent; + if (!isCallExpression(call) || call.expression !== called) continue; + const referenceSymbol = checker.getSymbolAtLocation(name); + if (referenceSymbol && checker.getRootSymbols(referenceSymbol).some(s => s === symbol)) { + cb(call); + } + } + } + } + + function getPossibleSymbolReferenceNodes(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile): readonly Node[] { + return getPossibleSymbolReferencePositions(sourceFile, symbolName, container).map(pos => getTouchingPropertyName(sourceFile, pos)); + } + + function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile): readonly number[] { + const positions: number[] = []; + + /// TODO: Cache symbol existence for files to save text search + // Also, need to make this work for unicode escapes. + + // Be resilient in the face of a symbol with no name or zero length name + if (!symbolName || !symbolName.length) { + return positions; + } + + const text = sourceFile.text; + const sourceLength = text.length; + const symbolNameLength = symbolName.length; + + let position = text.indexOf(symbolName, container.pos); + while (position >= 0) { + // If we are past the end, stop looking + if (position > container.end) break; + + // We found a match. Make sure it's not part of a larger word (i.e. the char + // before and after it have to be a non-identifier char). + const endPosition = position + symbolNameLength; + + if ((position === 0 || !isIdentifierPart(text.charCodeAt(position - 1), ScriptTarget.Latest)) && + (endPosition === sourceLength || !isIdentifierPart(text.charCodeAt(endPosition), ScriptTarget.Latest))) { + // Found a real match. Keep searching. + positions.push(position); + } + position = text.indexOf(symbolName, position + symbolNameLength + 1); + } - // Be resilient in the face of a symbol with no name or zero length name - if (!symbolName || !symbolName.length) { return positions; } - const text = sourceFile.text; - const sourceLength = text.length; - const symbolNameLength = symbolName.length; - - let position = text.indexOf(symbolName, container.pos); - while (position >= 0) { - // If we are past the end, stop looking - if (position > container.end) break; - - // We found a match. Make sure it's not part of a larger word (i.e. the char - // before and after it have to be a non-identifier char). - const endPosition = position + symbolNameLength; - - if ((position === 0 || !isIdentifierPart(text.charCodeAt(position - 1), ScriptTarget.Latest)) && - (endPosition === sourceLength || !isIdentifierPart(text.charCodeAt(endPosition), ScriptTarget.Latest))) { - // Found a real match. Keep searching. - positions.push(position); - } - position = text.indexOf(symbolName, position + symbolNameLength + 1); + function getLabelReferencesInNode(container: Node, targetLabel: Identifier): SymbolAndEntries[] { + const sourceFile = container.getSourceFile(); + const labelName = targetLabel.text; + const references = mapDefined(getPossibleSymbolReferenceNodes(sourceFile, labelName, container), node => + // Only pick labels that are either the target label, or have a target that is the target label + node === targetLabel || (isJumpStatementTarget(node) && getTargetLabel(node, labelName) === targetLabel) ? nodeEntry(node) : undefined); + return [{ definition: { type: DefinitionKind.Label, node: targetLabel }, references }]; } - return positions; - } + function isValidReferencePosition(node: Node, searchSymbolName: string): boolean { + // Compare the length so we filter out strict superstrings of the symbol we are looking for + switch (node.kind) { + case SyntaxKind.Identifier: + return (node as Identifier).text.length === searchSymbolName.length; - function getLabelReferencesInNode(container: Node, targetLabel: Identifier): SymbolAndEntries[] { - const sourceFile = container.getSourceFile(); - const labelName = targetLabel.text; - const references = mapDefined(getPossibleSymbolReferenceNodes(sourceFile, labelName, container), node => - // Only pick labels that are either the target label, or have a target that is the target label - node === targetLabel || (isJumpStatementTarget(node) && getTargetLabel(node, labelName) === targetLabel) ? nodeEntry(node) : undefined); - return [{ definition: { type: DefinitionKind.Label, node: targetLabel }, references }]; - } - - function isValidReferencePosition(node: Node, searchSymbolName: string): boolean { - // Compare the length so we filter out strict superstrings of the symbol we are looking for - switch (node.kind) { - case SyntaxKind.Identifier: - return (node as Identifier).text.length === searchSymbolName.length; - - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.StringLiteral: { - const str = node as StringLiteralLike; - return (isLiteralNameOfPropertyDeclarationOrIndexAccess(str) || isNameOfModuleDeclaration(node) || isExpressionOfExternalModuleImportEqualsDeclaration(node) || (isCallExpression(node.parent) && isBindableObjectDefinePropertyCall(node.parent) && node.parent.arguments[1] === node)) && - str.text.length === searchSymbolName.length; - } - - case SyntaxKind.NumericLiteral: - return isLiteralNameOfPropertyDeclarationOrIndexAccess(node as NumericLiteral) && (node as NumericLiteral).text.length === searchSymbolName.length; - - case SyntaxKind.DefaultKeyword: - return "default".length === searchSymbolName.length; - - default: - return false; - } - } - - function getAllReferencesForKeyword(sourceFiles: readonly SourceFile[], keywordKind: SyntaxKind, cancellationToken: CancellationToken, filter?: (node: Node) => boolean): SymbolAndEntries[] | undefined { - const references = flatMap(sourceFiles, sourceFile => { - cancellationToken.throwIfCancellationRequested(); - return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, tokenToString(keywordKind)!, sourceFile), referenceLocation => { - if (referenceLocation.kind === keywordKind && (!filter || filter(referenceLocation))) { - return nodeEntry(referenceLocation); + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.StringLiteral: { + const str = node as StringLiteralLike; + return (isLiteralNameOfPropertyDeclarationOrIndexAccess(str) || isNameOfModuleDeclaration(node) || isExpressionOfExternalModuleImportEqualsDeclaration(node) || (isCallExpression(node.parent) && isBindableObjectDefinePropertyCall(node.parent) && node.parent.arguments[1] === node)) && + str.text.length === searchSymbolName.length; } + + case SyntaxKind.NumericLiteral: + return isLiteralNameOfPropertyDeclarationOrIndexAccess(node as NumericLiteral) && (node as NumericLiteral).text.length === searchSymbolName.length; + + case SyntaxKind.DefaultKeyword: + return "default".length === searchSymbolName.length; + + default: + return false; + } + } + + function getAllReferencesForKeyword(sourceFiles: readonly SourceFile[], keywordKind: SyntaxKind, cancellationToken: CancellationToken, filter?: (node: Node) => boolean): SymbolAndEntries[] | undefined { + const references = flatMap(sourceFiles, sourceFile => { + cancellationToken.throwIfCancellationRequested(); + return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, tokenToString(keywordKind)!, sourceFile), referenceLocation => { + if (referenceLocation.kind === keywordKind && (!filter || filter(referenceLocation))) { + return nodeEntry(referenceLocation); + } + }); }); - }); - return references.length ? [{ definition: { type: DefinitionKind.Keyword, node: references[0].node }, references }] : undefined; - } - - function getReferencesInSourceFile(sourceFile: SourceFile, search: Search, state: State, addReferencesHere = true): void { - state.cancellationToken.throwIfCancellationRequested(); - return getReferencesInContainer(sourceFile, sourceFile, search, state, addReferencesHere); - } - - /** - * Search within node "container" for references for a search value, where the search value is defined as a - * tuple of(searchSymbol, searchText, searchLocation, and searchMeaning). - * searchLocation: a node where the search value - */ - function getReferencesInContainer(container: Node, sourceFile: SourceFile, search: Search, state: State, addReferencesHere: boolean): void { - if (!state.markSearchedSymbols(sourceFile, search.allSearchSymbols)) { - return; + return references.length ? [{ definition: { type: DefinitionKind.Keyword, node: references[0].node }, references }] : undefined; } - for (const position of getPossibleSymbolReferencePositions(sourceFile, search.text, container)) { - getReferencesAtLocation(sourceFile, position, search, state, addReferencesHere); - } - } - - function hasMatchingMeaning(referenceLocation: Node, state: State): boolean { - return !!(getMeaningFromLocation(referenceLocation) & state.searchMeaning); - } - - function getReferencesAtLocation(sourceFile: SourceFile, position: number, search: Search, state: State, addReferencesHere: boolean): void { - const referenceLocation = getTouchingPropertyName(sourceFile, position); - - if (!isValidReferencePosition(referenceLocation, search.text)) { - // This wasn't the start of a token. Check to see if it might be a - // match in a comment or string if that's what the caller is asking - // for. - if (!state.options.implementations && (state.options.findInStrings && isInString(sourceFile, position) || state.options.findInComments && isInNonReferenceComment(sourceFile, position))) { - // In the case where we're looking inside comments/strings, we don't have - // an actual definition. So just use 'undefined' here. Features like - // 'Rename' won't care (as they ignore the definitions), and features like - // 'FindReferences' will just filter out these results. - state.addStringOrCommentReference(sourceFile.fileName, createTextSpan(position, search.text.length)); - } - - return; + function getReferencesInSourceFile(sourceFile: SourceFile, search: Search, state: State, addReferencesHere = true): void { + state.cancellationToken.throwIfCancellationRequested(); + return getReferencesInContainer(sourceFile, sourceFile, search, state, addReferencesHere); } - if (!hasMatchingMeaning(referenceLocation, state)) return; - - const referenceSymbol = state.checker.getSymbolAtLocation(referenceLocation); - if (!referenceSymbol) { - return; - } - - const parent = referenceLocation.parent; - if (isImportSpecifier(parent) && parent.propertyName === referenceLocation) { - // This is added through `singleReferences` in ImportsResult. If we happen to see it again, don't add it again. - return; - } - - if (isExportSpecifier(parent)) { - Debug.assert(referenceLocation.kind === SyntaxKind.Identifier); - getReferencesAtExportSpecifier(referenceLocation as Identifier, referenceSymbol, parent, search, state, addReferencesHere); - return; - } - - const relatedSymbol = getRelatedSymbol(search, referenceSymbol, referenceLocation, state); - if (!relatedSymbol) { - getReferenceForShorthandProperty(referenceSymbol, search, state); - return; - } - - switch (state.specialSearchKind) { - case SpecialSearchKind.None: - if (addReferencesHere) addReference(referenceLocation, relatedSymbol, state); - break; - case SpecialSearchKind.Constructor: - addConstructorReferences(referenceLocation, sourceFile, search, state); - break; - case SpecialSearchKind.Class: - addClassStaticThisReferences(referenceLocation, search, state); - break; - default: - Debug.assertNever(state.specialSearchKind); - } - - getImportOrExportReferences(referenceLocation, referenceSymbol, search, state); - } - - function getReferencesAtExportSpecifier( - referenceLocation: Identifier, - referenceSymbol: Symbol, - exportSpecifier: ExportSpecifier, - search: Search, - state: State, - addReferencesHere: boolean, - alwaysGetReferences?: boolean, - ): void { - Debug.assert(!alwaysGetReferences || !!state.options.providePrefixAndSuffixTextForRename, "If alwaysGetReferences is true, then prefix/suffix text must be enabled"); - - const { parent, propertyName, name } = exportSpecifier; - const exportDeclaration = parent.parent; - const localSymbol = getLocalSymbolForExportSpecifier(referenceLocation, referenceSymbol, exportSpecifier, state.checker); - if (!alwaysGetReferences && !search.includes(localSymbol)) { - return; - } - - if (!propertyName) { - // Don't rename at `export { default } from "m";`. (but do continue to search for imports of the re-export) - if (!(state.options.isForRename && (name.escapedText === InternalSymbolName.Default))) { - addRef(); - } - } - else if (referenceLocation === propertyName) { - // For `export { foo as bar } from "baz"`, "`foo`" will be added from the singleReferences for import searches of the original export. - // For `export { foo as bar };`, where `foo` is a local, so add it now. - if (!exportDeclaration.moduleSpecifier) { - addRef(); - } - - if (addReferencesHere && !state.options.isForRename && state.markSeenReExportRHS(name)) { - addReference(name, Debug.assertDefined(exportSpecifier.symbol), state); - } - } - else { - if (state.markSeenReExportRHS(referenceLocation)) { - addRef(); - } - } - - // For `export { foo as bar }`, rename `foo`, but not `bar`. - if (!isForRenameWithPrefixAndSuffixText(state.options) || alwaysGetReferences) { - const isDefaultExport = referenceLocation.originalKeywordKind === SyntaxKind.DefaultKeyword - || exportSpecifier.name.originalKeywordKind === SyntaxKind.DefaultKeyword; - const exportKind = isDefaultExport ? ExportKind.Default : ExportKind.Named; - const exportSymbol = Debug.assertDefined(exportSpecifier.symbol); - const exportInfo = getExportInfo(exportSymbol, exportKind, state.checker); - if (exportInfo) { - searchForImportsOfExport(referenceLocation, exportSymbol, exportInfo, state); - } - } - - // At `export { x } from "foo"`, also search for the imported symbol `"foo".x`. - if (search.comingFrom !== ImportExport.Export && exportDeclaration.moduleSpecifier && !propertyName && !isForRenameWithPrefixAndSuffixText(state.options)) { - const imported = state.checker.getExportSpecifierLocalTargetSymbol(exportSpecifier); - if (imported) searchForImportedSymbol(imported, state); - } - - function addRef() { - if (addReferencesHere) addReference(referenceLocation, localSymbol, state); - } - } - - function getLocalSymbolForExportSpecifier(referenceLocation: Identifier, referenceSymbol: Symbol, exportSpecifier: ExportSpecifier, checker: TypeChecker): Symbol { - return isExportSpecifierAlias(referenceLocation, exportSpecifier) && checker.getExportSpecifierLocalTargetSymbol(exportSpecifier) || referenceSymbol; - } - - function isExportSpecifierAlias(referenceLocation: Identifier, exportSpecifier: ExportSpecifier): boolean { - const { parent, propertyName, name } = exportSpecifier; - Debug.assert(propertyName === referenceLocation || name === referenceLocation); - if (propertyName) { - // Given `export { foo as bar } [from "someModule"]`: It's an alias at `foo`, but at `bar` it's a new symbol. - return propertyName === referenceLocation; - } - else { - // `export { foo } from "foo"` is a re-export. - // `export { foo };` is not a re-export, it creates an alias for the local variable `foo`. - return !parent.parent.moduleSpecifier; - } - } - - function getImportOrExportReferences(referenceLocation: Node, referenceSymbol: Symbol, search: Search, state: State): void { - const importOrExport = getImportOrExportSymbol(referenceLocation, referenceSymbol, state.checker, search.comingFrom === ImportExport.Export); - if (!importOrExport) return; - - const { symbol } = importOrExport; - - if (importOrExport.kind === ImportExport.Import) { - if (!(isForRenameWithPrefixAndSuffixText(state.options))) { - searchForImportedSymbol(symbol, state); - } - } - else { - searchForImportsOfExport(referenceLocation, symbol, importOrExport.exportInfo, state); - } - } - - function getReferenceForShorthandProperty({ flags, valueDeclaration }: Symbol, search: Search, state: State): void { - const shorthandValueSymbol = state.checker.getShorthandAssignmentValueSymbol(valueDeclaration)!; - const name = valueDeclaration && getNameOfDeclaration(valueDeclaration); - /* - * Because in short-hand property assignment, an identifier which stored as name of the short-hand property assignment - * has two meanings: property name and property value. Therefore when we do findAllReference at the position where - * an identifier is declared, the language service should return the position of the variable declaration as well as - * the position in short-hand property assignment excluding property accessing. However, if we do findAllReference at the - * position of property accessing, the referenceEntry of such position will be handled in the first case. + /** + * Search within node "container" for references for a search value, where the search value is defined as a + * tuple of(searchSymbol, searchText, searchLocation, and searchMeaning). + * searchLocation: a node where the search value */ - if (!(flags & SymbolFlags.Transient) && name && search.includes(shorthandValueSymbol)) { - addReference(name, shorthandValueSymbol, state); - } - } - - function addReference(referenceLocation: Node, relatedSymbol: Symbol | RelatedSymbol, state: State): void { - const { kind, symbol } = "kind" in relatedSymbol ? relatedSymbol : { kind: undefined, symbol: relatedSymbol }; // eslint-disable-line no-in-operator - const addRef = state.referenceAdder(symbol); - if (state.options.implementations) { - addImplementationReferences(referenceLocation, addRef, state); - } - else { - addRef(referenceLocation, kind); - } - } - - /** Adds references when a constructor is used with `new this()` in its own class and `super()` calls in subclasses. */ - function addConstructorReferences(referenceLocation: Node, sourceFile: SourceFile, search: Search, state: State): void { - if (isNewExpressionTarget(referenceLocation)) { - addReference(referenceLocation, search.symbol, state); - } - - const pusher = () => state.referenceAdder(search.symbol); - - if (isClassLike(referenceLocation.parent)) { - Debug.assert(referenceLocation.kind === SyntaxKind.DefaultKeyword || referenceLocation.parent.name === referenceLocation); - // This is the class declaration containing the constructor. - findOwnConstructorReferences(search.symbol, sourceFile, pusher()); - } - else { - // If this class appears in `extends C`, then the extending class' "super" calls are references. - const classExtending = tryGetClassByExtendingIdentifier(referenceLocation); - if (classExtending) { - findSuperConstructorAccesses(classExtending, pusher()); - findInheritedConstructorReferences(classExtending, state); - } - } - } - - function addClassStaticThisReferences(referenceLocation: Node, search: Search, state: State): void { - addReference(referenceLocation, search.symbol, state); - const classLike = referenceLocation.parent; - if (state.options.isForRename || !isClassLike(classLike)) return; - Debug.assert(classLike.name === referenceLocation); - const addRef = state.referenceAdder(search.symbol); - for (const member of classLike.members) { - if (!(isMethodOrAccessor(member) && hasModifier(member, ModifierFlags.Static))) { - continue; - } - if (member.body) { - member.body.forEachChild(function cb(node) { - if (node.kind === SyntaxKind.ThisKeyword) { - addRef(node); - } - else if (!isFunctionLike(node) && !isClassLike(node)) { - node.forEachChild(cb); - } - }); - } - } - } - - /** - * `classSymbol` is the class where the constructor was defined. - * Reference the constructor and all calls to `new this()`. - */ - function findOwnConstructorReferences(classSymbol: Symbol, sourceFile: SourceFile, addNode: (node: Node) => void): void { - const constructorSymbol = getClassConstructorSymbol(classSymbol); - if (constructorSymbol) { - for (const decl of constructorSymbol.declarations) { - const ctrKeyword = findChildOfKind(decl, SyntaxKind.ConstructorKeyword, sourceFile)!; - Debug.assert(decl.kind === SyntaxKind.Constructor && !!ctrKeyword); - addNode(ctrKeyword); - } - } - - if (classSymbol.exports) { - classSymbol.exports.forEach(member => { - const decl = member.valueDeclaration; - if (decl && decl.kind === SyntaxKind.MethodDeclaration) { - const body = (decl).body; - if (body) { - forEachDescendantOfKind(body, SyntaxKind.ThisKeyword, thisKeyword => { - if (isNewExpressionTarget(thisKeyword)) { - addNode(thisKeyword); - } - }); - } - } - }); - } - } - - function getClassConstructorSymbol(classSymbol: Symbol): Symbol | undefined { - return classSymbol.members && classSymbol.members.get(InternalSymbolName.Constructor); - } - - /** Find references to `super` in the constructor of an extending class. */ - function findSuperConstructorAccesses(classDeclaration: ClassLikeDeclaration, addNode: (node: Node) => void): void { - const constructor = getClassConstructorSymbol(classDeclaration.symbol); - if (!constructor) { - return; - } - - for (const decl of constructor.declarations) { - Debug.assert(decl.kind === SyntaxKind.Constructor); - const body = (decl).body; - if (body) { - forEachDescendantOfKind(body, SyntaxKind.SuperKeyword, node => { - if (isCallExpressionTarget(node)) { - addNode(node); - } - }); - } - } - } - - function hasOwnConstructor(classDeclaration: ClassLikeDeclaration): boolean { - return !!getClassConstructorSymbol(classDeclaration.symbol); - } - - function findInheritedConstructorReferences(classDeclaration: ClassLikeDeclaration, state: State): void { - if (hasOwnConstructor(classDeclaration)) return; - const classSymbol = classDeclaration.symbol; - const search = state.createSearch(/*location*/ undefined, classSymbol, /*comingFrom*/ undefined); - getReferencesInContainerOrFiles(classSymbol, state, search); - } - - function addImplementationReferences(refNode: Node, addReference: (node: Node) => void, state: State): void { - // Check if we found a function/propertyAssignment/method with an implementation or initializer - if (isDeclarationName(refNode) && isImplementation(refNode.parent)) { - addReference(refNode); - return; - } - - if (refNode.kind !== SyntaxKind.Identifier) { - return; - } - - if (refNode.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { - // Go ahead and dereference the shorthand assignment by going to its definition - getReferenceEntriesForShorthandPropertyAssignment(refNode, state.checker, addReference); - } - - // Check if the node is within an extends or implements clause - const containingClass = getContainingClassIfInHeritageClause(refNode); - if (containingClass) { - addReference(containingClass); - return; - } - - // If we got a type reference, try and see if the reference applies to any expressions that can implement an interface - // Find the first node whose parent isn't a type node -- i.e., the highest type node. - const typeNode = findAncestor(refNode, a => !isQualifiedName(a.parent) && !isTypeNode(a.parent) && !isTypeElement(a.parent))!; - const typeHavingNode = typeNode.parent; - if (hasType(typeHavingNode) && typeHavingNode.type === typeNode && state.markSeenContainingTypeReference(typeHavingNode)) { - if (hasInitializer(typeHavingNode)) { - addIfImplementation(typeHavingNode.initializer!); - } - else if (isFunctionLike(typeHavingNode) && (typeHavingNode as FunctionLikeDeclaration).body) { - const body = (typeHavingNode as FunctionLikeDeclaration).body!; - if (body.kind === SyntaxKind.Block) { - forEachReturnStatement(body, returnStatement => { - if (returnStatement.expression) addIfImplementation(returnStatement.expression); - }); - } - else { - addIfImplementation(body); - } - } - else if (isAssertionExpression(typeHavingNode)) { - addIfImplementation(typeHavingNode.expression); - } - } - - function addIfImplementation(e: Expression): void { - if (isImplementationExpression(e)) addReference(e); - } - } - - function getContainingClassIfInHeritageClause(node: Node): ClassLikeDeclaration | InterfaceDeclaration | undefined { - return isIdentifier(node) || isPropertyAccessExpression(node) ? getContainingClassIfInHeritageClause(node.parent) - : isExpressionWithTypeArguments(node) ? tryCast(node.parent.parent, isClassLike) : undefined; - } - - /** - * Returns true if this is an expression that can be considered an implementation - */ - function isImplementationExpression(node: Expression): boolean { - switch (node.kind) { - case SyntaxKind.ParenthesizedExpression: - return isImplementationExpression((node).expression); - case SyntaxKind.ArrowFunction: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.ClassExpression: - case SyntaxKind.ArrayLiteralExpression: - return true; - default: - return false; - } - } - - /** - * Determines if the parent symbol occurs somewhere in the child's ancestry. If the parent symbol - * is an interface, determines if some ancestor of the child symbol extends or inherits from it. - * Also takes in a cache of previous results which makes this slightly more efficient and is - * necessary to avoid potential loops like so: - * class A extends B { } - * class B extends A { } - * - * We traverse the AST rather than using the type checker because users are typically only interested - * in explicit implementations of an interface/class when calling "Go to Implementation". Sibling - * implementations of types that share a common ancestor with the type whose implementation we are - * searching for need to be filtered out of the results. The type checker doesn't let us make the - * distinction between structurally compatible implementations and explicit implementations, so we - * must use the AST. - * - * @param symbol A class or interface Symbol - * @param parent Another class or interface Symbol - * @param cachedResults A map of symbol id pairs (i.e. "child,parent") to booleans indicating previous results - */ - function explicitlyInheritsFrom(symbol: Symbol, parent: Symbol, cachedResults: Map, checker: TypeChecker): boolean { - if (symbol === parent) { - return true; - } - - const key = getSymbolId(symbol) + "," + getSymbolId(parent); - const cached = cachedResults.get(key); - if (cached !== undefined) { - return cached; - } - - // Set the key so that we don't infinitely recurse - cachedResults.set(key, false); - - const inherits = symbol.declarations.some(declaration => - getAllSuperTypeNodes(declaration).some(typeReference => { - const type = checker.getTypeAtLocation(typeReference); - return !!type && !!type.symbol && explicitlyInheritsFrom(type.symbol, parent, cachedResults, checker); - })); - cachedResults.set(key, inherits); - return inherits; - } - - function getReferencesForSuperKeyword(superKeyword: Node): SymbolAndEntries[] | undefined { - let searchSpaceNode = getSuperContainer(superKeyword, /*stopOnFunctions*/ false); - if (!searchSpaceNode) { - return undefined; - } - // Whether 'super' occurs in a static context within a class. - let staticFlag = ModifierFlags.Static; - - switch (searchSpaceNode.kind) { - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - staticFlag &= getModifierFlags(searchSpaceNode); - searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class - break; - default: - return undefined; - } - - const sourceFile = searchSpaceNode.getSourceFile(); - const references = mapDefined(getPossibleSymbolReferenceNodes(sourceFile, "super", searchSpaceNode), node => { - if (node.kind !== SyntaxKind.SuperKeyword) { + function getReferencesInContainer(container: Node, sourceFile: SourceFile, search: Search, state: State, addReferencesHere: boolean): void { + if (!state.markSearchedSymbols(sourceFile, search.allSearchSymbols)) { return; } - const container = getSuperContainer(node, /*stopOnFunctions*/ false); - - // If we have a 'super' container, we must have an enclosing class. - // Now make sure the owning class is the same as the search-space - // and has the same static qualifier as the original 'super's owner. - return container && (ModifierFlags.Static & getModifierFlags(container)) === staticFlag && container.parent.symbol === searchSpaceNode.symbol ? nodeEntry(node) : undefined; - }); - - return [{ definition: { type: DefinitionKind.Symbol, symbol: searchSpaceNode.symbol }, references }]; - } - - function isParameterName(node: Node) { - return node.kind === SyntaxKind.Identifier && node.parent.kind === SyntaxKind.Parameter && (node.parent).name === node; - } - - function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined { - let searchSpaceNode = getThisContainer(thisOrSuperKeyword, /* includeArrowFunctions */ false); - - // Whether 'this' occurs in a static context within a class. - let staticFlag = ModifierFlags.Static; - - switch (searchSpaceNode.kind) { - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - if (isObjectLiteralMethod(searchSpaceNode)) { - break; - } - // falls through - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - staticFlag &= getModifierFlags(searchSpaceNode); - searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class - break; - case SyntaxKind.SourceFile: - if (isExternalModule(searchSpaceNode) || isParameterName(thisOrSuperKeyword)) { - return undefined; - } - // falls through - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - break; - // Computed properties in classes are not handled here because references to this are illegal, - // so there is no point finding references to them. - default: - return undefined; + for (const position of getPossibleSymbolReferencePositions(sourceFile, search.text, container)) { + getReferencesAtLocation(sourceFile, position, search, state, addReferencesHere); + } } - const references = flatMap(searchSpaceNode.kind === SyntaxKind.SourceFile ? sourceFiles : [searchSpaceNode.getSourceFile()], sourceFile => { - cancellationToken.throwIfCancellationRequested(); - return getPossibleSymbolReferenceNodes(sourceFile, "this", isSourceFile(searchSpaceNode) ? sourceFile : searchSpaceNode).filter(node => { - if (!isThis(node)) { - return false; + function hasMatchingMeaning(referenceLocation: Node, state: State): boolean { + return !!(getMeaningFromLocation(referenceLocation) & state.searchMeaning); + } + + function getReferencesAtLocation(sourceFile: SourceFile, position: number, search: Search, state: State, addReferencesHere: boolean): void { + const referenceLocation = getTouchingPropertyName(sourceFile, position); + + if (!isValidReferencePosition(referenceLocation, search.text)) { + // This wasn't the start of a token. Check to see if it might be a + // match in a comment or string if that's what the caller is asking + // for. + if (!state.options.implementations && (state.options.findInStrings && isInString(sourceFile, position) || state.options.findInComments && isInNonReferenceComment(sourceFile, position))) { + // In the case where we're looking inside comments/strings, we don't have + // an actual definition. So just use 'undefined' here. Features like + // 'Rename' won't care (as they ignore the definitions), and features like + // 'FindReferences' will just filter out these results. + state.addStringOrCommentReference(sourceFile.fileName, createTextSpan(position, search.text.length)); } - const container = getThisContainer(node, /* includeArrowFunctions */ false); - switch (searchSpaceNode.kind) { - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - return searchSpaceNode.symbol === container.symbol; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - return isObjectLiteralMethod(searchSpaceNode) && searchSpaceNode.symbol === container.symbol; - case SyntaxKind.ClassExpression: - case SyntaxKind.ClassDeclaration: - // Make sure the container belongs to the same class - // and has the appropriate static modifier from the original container. - return container.parent && searchSpaceNode.symbol === container.parent.symbol && (getModifierFlags(container) & ModifierFlags.Static) === staticFlag; - case SyntaxKind.SourceFile: - return container.kind === SyntaxKind.SourceFile && !isExternalModule(container) && !isParameterName(node); - } - }); - }).map(n => nodeEntry(n)); - const thisParameter = firstDefined(references, r => isParameter(r.node.parent) ? r.node : undefined); - return [{ - definition: { type: DefinitionKind.This, node: thisParameter || thisOrSuperKeyword }, - references - }]; - } - - function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] { - const references = flatMap(sourceFiles, sourceFile => { - cancellationToken.throwIfCancellationRequested(); - return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, node.text), ref => - isStringLiteral(ref) && ref.text === node.text ? nodeEntry(ref, EntryKind.StringLiteral) : undefined); - }); - - return [{ - definition: { type: DefinitionKind.String, node }, - references - }]; - } - - // For certain symbol kinds, we need to include other symbols in the search set. - // This is not needed when searching for re-exports. - function populateSearchSymbolSet(symbol: Symbol, location: Node, checker: TypeChecker, isForRename: boolean, providePrefixAndSuffixText: boolean, implementations: boolean): Symbol[] { - const result: Symbol[] = []; - forEachRelatedSymbol(symbol, location, checker, isForRename, !(isForRename && providePrefixAndSuffixText), - (sym, root, base) => { result.push(base || root || sym); }, - /*allowBaseTypes*/ () => !implementations); - return result; - } - - function forEachRelatedSymbol( - symbol: Symbol, location: Node, checker: TypeChecker, isForRenamePopulateSearchSymbolSet: boolean, onlyIncludeBindingElementAtReferenceLocation: boolean, - cbSymbol: (symbol: Symbol, rootSymbol?: Symbol, baseSymbol?: Symbol, kind?: NodeEntryKind) => T | undefined, - allowBaseTypes: (rootSymbol: Symbol) => boolean, - ): T | undefined { - const containingObjectLiteralElement = getContainingObjectLiteralElement(location); - if (containingObjectLiteralElement) { - /* Because in short-hand property assignment, location has two meaning : property name and as value of the property - * When we do findAllReference at the position of the short-hand property assignment, we would want to have references to position of - * property name and variable declaration of the identifier. - * Like in below example, when querying for all references for an identifier 'name', of the property assignment, the language service - * should show both 'name' in 'obj' and 'name' in variable declaration - * const name = "Foo"; - * const obj = { name }; - * In order to do that, we will populate the search set with the value symbol of the identifier as a value of the property assignment - * so that when matching with potential reference symbol, both symbols from property declaration and variable declaration - * will be included correctly. - */ - const shorthandValueSymbol = checker.getShorthandAssignmentValueSymbol(location.parent); // gets the local symbol - if (shorthandValueSymbol && isForRenamePopulateSearchSymbolSet) { - // When renaming 'x' in `const o = { x }`, just rename the local variable, not the property. - return cbSymbol(shorthandValueSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedLocalFoundProperty); + return; } - // If the location is in a context sensitive location (i.e. in an object literal) try - // to get a contextual type for it, and add the property symbol from the contextual - // type to the search set - const contextualType = checker.getContextualType(containingObjectLiteralElement.parent); - const res = contextualType && firstDefined( - getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker, contextualType, /*unionSymbolOk*/ true), - sym => fromRoot(sym, EntryKind.SearchedPropertyFoundLocal)); - if (res) return res; + if (!hasMatchingMeaning(referenceLocation, state)) return; - // If the location is name of property symbol from object literal destructuring pattern - // Search the property symbol - // for ( { property: p2 } of elems) { } - const propertySymbol = getPropertySymbolOfDestructuringAssignment(location, checker); - const res1 = propertySymbol && cbSymbol(propertySymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedPropertyFoundLocal); - if (res1) return res1; + const referenceSymbol = state.checker.getSymbolAtLocation(referenceLocation); + if (!referenceSymbol) { + return; + } - const res2 = shorthandValueSymbol && cbSymbol(shorthandValueSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedLocalFoundProperty); - if (res2) return res2; + const parent = referenceLocation.parent; + if (isImportSpecifier(parent) && parent.propertyName === referenceLocation) { + // This is added through `singleReferences` in ImportsResult. If we happen to see it again, don't add it again. + return; + } + + if (isExportSpecifier(parent)) { + Debug.assert(referenceLocation.kind === SyntaxKind.Identifier); + getReferencesAtExportSpecifier(referenceLocation as Identifier, referenceSymbol, parent, search, state, addReferencesHere); + return; + } + + const relatedSymbol = getRelatedSymbol(search, referenceSymbol, referenceLocation, state); + if (!relatedSymbol) { + getReferenceForShorthandProperty(referenceSymbol, search, state); + return; + } + + switch (state.specialSearchKind) { + case SpecialSearchKind.None: + if (addReferencesHere) addReference(referenceLocation, relatedSymbol, state); + break; + case SpecialSearchKind.Constructor: + addConstructorReferences(referenceLocation, sourceFile, search, state); + break; + case SpecialSearchKind.Class: + addClassStaticThisReferences(referenceLocation, search, state); + break; + default: + Debug.assertNever(state.specialSearchKind); + } + + getImportOrExportReferences(referenceLocation, referenceSymbol, search, state); } - const aliasedSymbol = getMergedAliasedSymbolOfNamespaceExportDeclaration(location, symbol, checker); - if (aliasedSymbol) { - // In case of UMD module and global merging, search for global as well - const res = cbSymbol(aliasedSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.Node); - if (res) return res; - } + function getReferencesAtExportSpecifier( + referenceLocation: Identifier, + referenceSymbol: Symbol, + exportSpecifier: ExportSpecifier, + search: Search, + state: State, + addReferencesHere: boolean, + alwaysGetReferences?: boolean, + ): void { + Debug.assert(!alwaysGetReferences || !!state.options.providePrefixAndSuffixTextForRename, "If alwaysGetReferences is true, then prefix/suffix text must be enabled"); - const res = fromRoot(symbol); - if (res) return res; + const { parent, propertyName, name } = exportSpecifier; + const exportDeclaration = parent.parent; + const localSymbol = getLocalSymbolForExportSpecifier(referenceLocation, referenceSymbol, exportSpecifier, state.checker); + if (!alwaysGetReferences && !search.includes(localSymbol)) { + return; + } - if (symbol.valueDeclaration && isParameterPropertyDeclaration(symbol.valueDeclaration, symbol.valueDeclaration.parent)) { - // For a parameter property, now try on the other symbol (property if this was a parameter, parameter if this was a property). - const paramProps = checker.getSymbolsOfParameterPropertyDeclaration(cast(symbol.valueDeclaration, isParameter), symbol.name); - Debug.assert(paramProps.length === 2 && !!(paramProps[0].flags & SymbolFlags.FunctionScopedVariable) && !!(paramProps[1].flags & SymbolFlags.Property)); // is [parameter, property] - return fromRoot(symbol.flags & SymbolFlags.FunctionScopedVariable ? paramProps[1] : paramProps[0]); - } + if (!propertyName) { + // Don't rename at `export { default } from "m";`. (but do continue to search for imports of the re-export) + if (!(state.options.isForRename && (name.escapedText === InternalSymbolName.Default))) { + addRef(); + } + } + else if (referenceLocation === propertyName) { + // For `export { foo as bar } from "baz"`, "`foo`" will be added from the singleReferences for import searches of the original export. + // For `export { foo as bar };`, where `foo` is a local, so add it now. + if (!exportDeclaration.moduleSpecifier) { + addRef(); + } - // symbolAtLocation for a binding element is the local symbol. See if the search symbol is the property. - // Don't do this when populating search set for a rename when prefix and suffix text will be provided -- just rename the local. - if (!isForRenamePopulateSearchSymbolSet) { - let bindingElementPropertySymbol: Symbol | undefined; - if (onlyIncludeBindingElementAtReferenceLocation) { - bindingElementPropertySymbol = isObjectBindingElementWithoutPropertyName(location.parent) ? getPropertySymbolFromBindingElement(checker, location.parent) : undefined; + if (addReferencesHere && !state.options.isForRename && state.markSeenReExportRHS(name)) { + addReference(name, Debug.assertDefined(exportSpecifier.symbol), state); + } } else { - bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, checker); + if (state.markSeenReExportRHS(referenceLocation)) { + addRef(); + } } - return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol, EntryKind.SearchedPropertyFoundLocal); - } - Debug.assert(isForRenamePopulateSearchSymbolSet); - // due to the above assert and the arguments at the uses of this function, - // (onlyIncludeBindingElementAtReferenceLocation <=> !providePrefixAndSuffixTextForRename) holds - const includeOriginalSymbolOfBindingElement = onlyIncludeBindingElementAtReferenceLocation; + // For `export { foo as bar }`, rename `foo`, but not `bar`. + if (!isForRenameWithPrefixAndSuffixText(state.options) || alwaysGetReferences) { + const isDefaultExport = referenceLocation.originalKeywordKind === SyntaxKind.DefaultKeyword + || exportSpecifier.name.originalKeywordKind === SyntaxKind.DefaultKeyword; + const exportKind = isDefaultExport ? ExportKind.Default : ExportKind.Named; + const exportSymbol = Debug.assertDefined(exportSpecifier.symbol); + const exportInfo = getExportInfo(exportSymbol, exportKind, state.checker); + if (exportInfo) { + searchForImportsOfExport(referenceLocation, exportSymbol, exportInfo, state); + } + } - if (includeOriginalSymbolOfBindingElement) { - const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, checker); - return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol, EntryKind.SearchedPropertyFoundLocal); - } + // At `export { x } from "foo"`, also search for the imported symbol `"foo".x`. + if (search.comingFrom !== ImportExport.Export && exportDeclaration.moduleSpecifier && !propertyName && !isForRenameWithPrefixAndSuffixText(state.options)) { + const imported = state.checker.getExportSpecifierLocalTargetSymbol(exportSpecifier); + if (imported) searchForImportedSymbol(imported, state); + } - function fromRoot(sym: Symbol, kind?: NodeEntryKind): T | undefined { - // If this is a union property: - // - In populateSearchSymbolsSet we will add all the symbols from all its source symbols in all unioned types. - // - In findRelatedSymbol, we will just use the union symbol if any source symbol is included in the search. - // If the symbol is an instantiation from a another symbol (e.g. widened symbol): - // - In populateSearchSymbolsSet, add the root the list - // - In findRelatedSymbol, return the source symbol if that is in the search. (Do not return the instantiation symbol.) - return firstDefined(checker.getRootSymbols(sym), rootSymbol => - cbSymbol(sym, rootSymbol, /*baseSymbol*/ undefined, kind) - // Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions - || (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface) && allowBaseTypes(rootSymbol) - ? getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.name, checker, base => cbSymbol(sym, rootSymbol, base, kind)) - : undefined)); - } - - function getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol: Symbol, checker: TypeChecker): Symbol | undefined { - const bindingElement = getDeclarationOfKind(symbol, SyntaxKind.BindingElement); - if (bindingElement && isObjectBindingElementWithoutPropertyName(bindingElement)) { - return getPropertySymbolFromBindingElement(checker, bindingElement); + function addRef() { + if (addReferencesHere) addReference(referenceLocation, localSymbol, state); } } - } - interface RelatedSymbol { - readonly symbol: Symbol; - readonly kind: NodeEntryKind | undefined; - } - function getRelatedSymbol(search: Search, referenceSymbol: Symbol, referenceLocation: Node, state: State): RelatedSymbol | undefined { - const { checker } = state; - return forEachRelatedSymbol(referenceSymbol, referenceLocation, checker, /*isForRenamePopulateSearchSymbolSet*/ false, - /*onlyIncludeBindingElementAtReferenceLocation*/ !state.options.isForRename || !!state.options.providePrefixAndSuffixTextForRename, - (sym, rootSymbol, baseSymbol, kind): RelatedSymbol | undefined => search.includes(baseSymbol || rootSymbol || sym) - // For a base type, use the symbol for the derived type. For a synthetic (e.g. union) property, use the union symbol. - ? { symbol: rootSymbol && !(getCheckFlags(sym) & CheckFlags.Synthetic) ? rootSymbol : sym, kind } - : undefined, - /*allowBaseTypes*/ rootSymbol => - !(search.parents && !search.parents.some(parent => explicitlyInheritsFrom(rootSymbol.parent!, parent, state.inheritsFromCache, checker)))); - } + function getLocalSymbolForExportSpecifier(referenceLocation: Identifier, referenceSymbol: Symbol, exportSpecifier: ExportSpecifier, checker: TypeChecker): Symbol { + return isExportSpecifierAlias(referenceLocation, exportSpecifier) && checker.getExportSpecifierLocalTargetSymbol(exportSpecifier) || referenceSymbol; + } - /** - * Given an initial searchMeaning, extracted from a location, widen the search scope based on the declarations - * of the corresponding symbol. e.g. if we are searching for "Foo" in value position, but "Foo" references a class - * then we need to widen the search to include type positions as well. - * On the contrary, if we are searching for "Bar" in type position and we trace bar to an interface, and an uninstantiated - * module, we want to keep the search limited to only types, as the two declarations (interface and uninstantiated module) - * do not intersect in any of the three spaces. - */ - export function getIntersectingMeaningFromDeclarations(node: Node, symbol: Symbol): SemanticMeaning { - let meaning = getMeaningFromLocation(node); - const { declarations } = symbol; - if (declarations) { - let lastIterationMeaning: SemanticMeaning; - do { - // The result is order-sensitive, for instance if initialMeaning === Namespace, and declarations = [class, instantiated module] - // we need to consider both as they initialMeaning intersects with the module in the namespace space, and the module - // intersects with the class in the value space. - // To achieve that we will keep iterating until the result stabilizes. + function isExportSpecifierAlias(referenceLocation: Identifier, exportSpecifier: ExportSpecifier): boolean { + const { parent, propertyName, name } = exportSpecifier; + Debug.assert(propertyName === referenceLocation || name === referenceLocation); + if (propertyName) { + // Given `export { foo as bar } [from "someModule"]`: It's an alias at `foo`, but at `bar` it's a new symbol. + return propertyName === referenceLocation; + } + else { + // `export { foo } from "foo"` is a re-export. + // `export { foo };` is not a re-export, it creates an alias for the local variable `foo`. + return !parent.parent.moduleSpecifier; + } + } - // Remember the last meaning - lastIterationMeaning = meaning; + function getImportOrExportReferences(referenceLocation: Node, referenceSymbol: Symbol, search: Search, state: State): void { + const importOrExport = getImportOrExportSymbol(referenceLocation, referenceSymbol, state.checker, search.comingFrom === ImportExport.Export); + if (!importOrExport) return; - for (const declaration of declarations) { - const declarationMeaning = getMeaningFromDeclaration(declaration); + const { symbol } = importOrExport; - if (declarationMeaning & meaning) { - meaning |= declarationMeaning; + if (importOrExport.kind === ImportExport.Import) { + if (!(isForRenameWithPrefixAndSuffixText(state.options))) { + searchForImportedSymbol(symbol, state); + } + } + else { + searchForImportsOfExport(referenceLocation, symbol, importOrExport.exportInfo, state); + } + } + + function getReferenceForShorthandProperty({ flags, valueDeclaration }: Symbol, search: Search, state: State): void { + const shorthandValueSymbol = state.checker.getShorthandAssignmentValueSymbol(valueDeclaration)!; + const name = valueDeclaration && getNameOfDeclaration(valueDeclaration); + /* + * Because in short-hand property assignment, an identifier which stored as name of the short-hand property assignment + * has two meanings: property name and property value. Therefore when we do findAllReference at the position where + * an identifier is declared, the language service should return the position of the variable declaration as well as + * the position in short-hand property assignment excluding property accessing. However, if we do findAllReference at the + * position of property accessing, the referenceEntry of such position will be handled in the first case. + */ + if (!(flags & SymbolFlags.Transient) && name && search.includes(shorthandValueSymbol)) { + addReference(name, shorthandValueSymbol, state); + } + } + + function addReference(referenceLocation: Node, relatedSymbol: Symbol | RelatedSymbol, state: State): void { + const { kind, symbol } = "kind" in relatedSymbol ? relatedSymbol : { kind: undefined, symbol: relatedSymbol }; // eslint-disable-line no-in-operator + const addRef = state.referenceAdder(symbol); + if (state.options.implementations) { + addImplementationReferences(referenceLocation, addRef, state); + } + else { + addRef(referenceLocation, kind); + } + } + + /** Adds references when a constructor is used with `new this()` in its own class and `super()` calls in subclasses. */ + function addConstructorReferences(referenceLocation: Node, sourceFile: SourceFile, search: Search, state: State): void { + if (isNewExpressionTarget(referenceLocation)) { + addReference(referenceLocation, search.symbol, state); + } + + const pusher = () => state.referenceAdder(search.symbol); + + if (isClassLike(referenceLocation.parent)) { + Debug.assert(referenceLocation.kind === SyntaxKind.DefaultKeyword || referenceLocation.parent.name === referenceLocation); + // This is the class declaration containing the constructor. + findOwnConstructorReferences(search.symbol, sourceFile, pusher()); + } + else { + // If this class appears in `extends C`, then the extending class' "super" calls are references. + const classExtending = tryGetClassByExtendingIdentifier(referenceLocation); + if (classExtending) { + findSuperConstructorAccesses(classExtending, pusher()); + findInheritedConstructorReferences(classExtending, state); + } + } + } + + function addClassStaticThisReferences(referenceLocation: Node, search: Search, state: State): void { + addReference(referenceLocation, search.symbol, state); + const classLike = referenceLocation.parent; + if (state.options.isForRename || !isClassLike(classLike)) return; + Debug.assert(classLike.name === referenceLocation); + const addRef = state.referenceAdder(search.symbol); + for (const member of classLike.members) { + if (!(isMethodOrAccessor(member) && hasModifier(member, ModifierFlags.Static))) { + continue; + } + if (member.body) { + member.body.forEachChild(function cb(node) { + if (node.kind === SyntaxKind.ThisKeyword) { + addRef(node); + } + else if (!isFunctionLike(node) && !isClassLike(node)) { + node.forEachChild(cb); + } + }); + } + } + } + + /** + * `classSymbol` is the class where the constructor was defined. + * Reference the constructor and all calls to `new this()`. + */ + function findOwnConstructorReferences(classSymbol: Symbol, sourceFile: SourceFile, addNode: (node: Node) => void): void { + const constructorSymbol = getClassConstructorSymbol(classSymbol); + if (constructorSymbol) { + for (const decl of constructorSymbol.declarations) { + const ctrKeyword = findChildOfKind(decl, SyntaxKind.ConstructorKeyword, sourceFile)!; + Debug.assert(decl.kind === SyntaxKind.Constructor && !!ctrKeyword); + addNode(ctrKeyword); + } + } + + if (classSymbol.exports) { + classSymbol.exports.forEach(member => { + const decl = member.valueDeclaration; + if (decl && decl.kind === SyntaxKind.MethodDeclaration) { + const body = (decl).body; + if (body) { + forEachDescendantOfKind(body, SyntaxKind.ThisKeyword, thisKeyword => { + if (isNewExpressionTarget(thisKeyword)) { + addNode(thisKeyword); + } + }); + } + } + }); + } + } + + function getClassConstructorSymbol(classSymbol: Symbol): Symbol | undefined { + return classSymbol.members && classSymbol.members.get(InternalSymbolName.Constructor); + } + + /** Find references to `super` in the constructor of an extending class. */ + function findSuperConstructorAccesses(classDeclaration: ClassLikeDeclaration, addNode: (node: Node) => void): void { + const constructor = getClassConstructorSymbol(classDeclaration.symbol); + if (!constructor) { + return; + } + + for (const decl of constructor.declarations) { + Debug.assert(decl.kind === SyntaxKind.Constructor); + const body = (decl).body; + if (body) { + forEachDescendantOfKind(body, SyntaxKind.SuperKeyword, node => { + if (isCallExpressionTarget(node)) { + addNode(node); + } + }); + } + } + } + + function hasOwnConstructor(classDeclaration: ClassLikeDeclaration): boolean { + return !!getClassConstructorSymbol(classDeclaration.symbol); + } + + function findInheritedConstructorReferences(classDeclaration: ClassLikeDeclaration, state: State): void { + if (hasOwnConstructor(classDeclaration)) return; + const classSymbol = classDeclaration.symbol; + const search = state.createSearch(/*location*/ undefined, classSymbol, /*comingFrom*/ undefined); + getReferencesInContainerOrFiles(classSymbol, state, search); + } + + function addImplementationReferences(refNode: Node, addReference: (node: Node) => void, state: State): void { + // Check if we found a function/propertyAssignment/method with an implementation or initializer + if (isDeclarationName(refNode) && isImplementation(refNode.parent)) { + addReference(refNode); + return; + } + + if (refNode.kind !== SyntaxKind.Identifier) { + return; + } + + if (refNode.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { + // Go ahead and dereference the shorthand assignment by going to its definition + getReferenceEntriesForShorthandPropertyAssignment(refNode, state.checker, addReference); + } + + // Check if the node is within an extends or implements clause + const containingClass = getContainingClassIfInHeritageClause(refNode); + if (containingClass) { + addReference(containingClass); + return; + } + + // If we got a type reference, try and see if the reference applies to any expressions that can implement an interface + // Find the first node whose parent isn't a type node -- i.e., the highest type node. + const typeNode = findAncestor(refNode, a => !isQualifiedName(a.parent) && !isTypeNode(a.parent) && !isTypeElement(a.parent))!; + const typeHavingNode = typeNode.parent; + if (hasType(typeHavingNode) && typeHavingNode.type === typeNode && state.markSeenContainingTypeReference(typeHavingNode)) { + if (hasInitializer(typeHavingNode)) { + addIfImplementation(typeHavingNode.initializer!); + } + else if (isFunctionLike(typeHavingNode) && (typeHavingNode as FunctionLikeDeclaration).body) { + const body = (typeHavingNode as FunctionLikeDeclaration).body!; + if (body.kind === SyntaxKind.Block) { + forEachReturnStatement(body, returnStatement => { + if (returnStatement.expression) addIfImplementation(returnStatement.expression); + }); + } + else { + addIfImplementation(body); + } + } + else if (isAssertionExpression(typeHavingNode)) { + addIfImplementation(typeHavingNode.expression); + } + } + + function addIfImplementation(e: Expression): void { + if (isImplementationExpression(e)) addReference(e); + } + } + + function getContainingClassIfInHeritageClause(node: Node): ClassLikeDeclaration | InterfaceDeclaration | undefined { + return isIdentifier(node) || isPropertyAccessExpression(node) ? getContainingClassIfInHeritageClause(node.parent) + : isExpressionWithTypeArguments(node) ? tryCast(node.parent.parent, isClassLike) : undefined; + } + + /** + * Returns true if this is an expression that can be considered an implementation + */ + function isImplementationExpression(node: Expression): boolean { + switch (node.kind) { + case SyntaxKind.ParenthesizedExpression: + return isImplementationExpression((node).expression); + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ClassExpression: + case SyntaxKind.ArrayLiteralExpression: + return true; + default: + return false; + } + } + + /** + * Determines if the parent symbol occurs somewhere in the child's ancestry. If the parent symbol + * is an interface, determines if some ancestor of the child symbol extends or inherits from it. + * Also takes in a cache of previous results which makes this slightly more efficient and is + * necessary to avoid potential loops like so: + * class A extends B { } + * class B extends A { } + * + * We traverse the AST rather than using the type checker because users are typically only interested + * in explicit implementations of an interface/class when calling "Go to Implementation". Sibling + * implementations of types that share a common ancestor with the type whose implementation we are + * searching for need to be filtered out of the results. The type checker doesn't let us make the + * distinction between structurally compatible implementations and explicit implementations, so we + * must use the AST. + * + * @param symbol A class or interface Symbol + * @param parent Another class or interface Symbol + * @param cachedResults A map of symbol id pairs (i.e. "child,parent") to booleans indicating previous results + */ + function explicitlyInheritsFrom(symbol: Symbol, parent: Symbol, cachedResults: Map, checker: TypeChecker): boolean { + if (symbol === parent) { + return true; + } + + const key = getSymbolId(symbol) + "," + getSymbolId(parent); + const cached = cachedResults.get(key); + if (cached !== undefined) { + return cached; + } + + // Set the key so that we don't infinitely recurse + cachedResults.set(key, false); + + const inherits = symbol.declarations.some(declaration => + getAllSuperTypeNodes(declaration).some(typeReference => { + const type = checker.getTypeAtLocation(typeReference); + return !!type && !!type.symbol && explicitlyInheritsFrom(type.symbol, parent, cachedResults, checker); + })); + cachedResults.set(key, inherits); + return inherits; + } + + function getReferencesForSuperKeyword(superKeyword: Node): SymbolAndEntries[] | undefined { + let searchSpaceNode = getSuperContainer(superKeyword, /*stopOnFunctions*/ false); + if (!searchSpaceNode) { + return undefined; + } + // Whether 'super' occurs in a static context within a class. + let staticFlag = ModifierFlags.Static; + + switch (searchSpaceNode.kind) { + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + staticFlag &= getModifierFlags(searchSpaceNode); + searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class + break; + default: + return undefined; + } + + const sourceFile = searchSpaceNode.getSourceFile(); + const references = mapDefined(getPossibleSymbolReferenceNodes(sourceFile, "super", searchSpaceNode), node => { + if (node.kind !== SyntaxKind.SuperKeyword) { + return; + } + + const container = getSuperContainer(node, /*stopOnFunctions*/ false); + + // If we have a 'super' container, we must have an enclosing class. + // Now make sure the owning class is the same as the search-space + // and has the same static qualifier as the original 'super's owner. + return container && (ModifierFlags.Static & getModifierFlags(container)) === staticFlag && container.parent.symbol === searchSpaceNode.symbol ? nodeEntry(node) : undefined; + }); + + return [{ definition: { type: DefinitionKind.Symbol, symbol: searchSpaceNode.symbol }, references }]; + } + + function isParameterName(node: Node) { + return node.kind === SyntaxKind.Identifier && node.parent.kind === SyntaxKind.Parameter && (node.parent).name === node; + } + + function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined { + let searchSpaceNode = getThisContainer(thisOrSuperKeyword, /* includeArrowFunctions */ false); + + // Whether 'this' occurs in a static context within a class. + let staticFlag = ModifierFlags.Static; + + switch (searchSpaceNode.kind) { + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + if (isObjectLiteralMethod(searchSpaceNode)) { + break; + } + // falls through + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + staticFlag &= getModifierFlags(searchSpaceNode); + searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class + break; + case SyntaxKind.SourceFile: + if (isExternalModule(searchSpaceNode) || isParameterName(thisOrSuperKeyword)) { + return undefined; + } + // falls through + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + break; + // Computed properties in classes are not handled here because references to this are illegal, + // so there is no point finding references to them. + default: + return undefined; + } + + const references = flatMap(searchSpaceNode.kind === SyntaxKind.SourceFile ? sourceFiles : [searchSpaceNode.getSourceFile()], sourceFile => { + cancellationToken.throwIfCancellationRequested(); + return getPossibleSymbolReferenceNodes(sourceFile, "this", isSourceFile(searchSpaceNode) ? sourceFile : searchSpaceNode).filter(node => { + if (!isThis(node)) { + return false; + } + const container = getThisContainer(node, /* includeArrowFunctions */ false); + switch (searchSpaceNode.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + return searchSpaceNode.symbol === container.symbol; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + return isObjectLiteralMethod(searchSpaceNode) && searchSpaceNode.symbol === container.symbol; + case SyntaxKind.ClassExpression: + case SyntaxKind.ClassDeclaration: + // Make sure the container belongs to the same class + // and has the appropriate static modifier from the original container. + return container.parent && searchSpaceNode.symbol === container.parent.symbol && (getModifierFlags(container) & ModifierFlags.Static) === staticFlag; + case SyntaxKind.SourceFile: + return container.kind === SyntaxKind.SourceFile && !isExternalModule(container) && !isParameterName(node); + } + }); + }).map(n => nodeEntry(n)); + + const thisParameter = firstDefined(references, r => isParameter(r.node.parent) ? r.node : undefined); + return [{ + definition: { type: DefinitionKind.This, node: thisParameter || thisOrSuperKeyword }, + references + }]; + } + + function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] { + const references = flatMap(sourceFiles, sourceFile => { + cancellationToken.throwIfCancellationRequested(); + return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, node.text), ref => + isStringLiteral(ref) && ref.text === node.text ? nodeEntry(ref, EntryKind.StringLiteral) : undefined); + }); + + return [{ + definition: { type: DefinitionKind.String, node }, + references + }]; + } + + // For certain symbol kinds, we need to include other symbols in the search set. + // This is not needed when searching for re-exports. + function populateSearchSymbolSet(symbol: Symbol, location: Node, checker: TypeChecker, isForRename: boolean, providePrefixAndSuffixText: boolean, implementations: boolean): Symbol[] { + const result: Symbol[] = []; + forEachRelatedSymbol(symbol, location, checker, isForRename, !(isForRename && providePrefixAndSuffixText), + (sym, root, base) => { result.push(base || root || sym); }, + /*allowBaseTypes*/ () => !implementations); + return result; + } + + function forEachRelatedSymbol( + symbol: Symbol, location: Node, checker: TypeChecker, isForRenamePopulateSearchSymbolSet: boolean, onlyIncludeBindingElementAtReferenceLocation: boolean, + cbSymbol: (symbol: Symbol, rootSymbol?: Symbol, baseSymbol?: Symbol, kind?: NodeEntryKind) => T | undefined, + allowBaseTypes: (rootSymbol: Symbol) => boolean, + ): T | undefined { + const containingObjectLiteralElement = getContainingObjectLiteralElement(location); + if (containingObjectLiteralElement) { + /* Because in short-hand property assignment, location has two meaning : property name and as value of the property + * When we do findAllReference at the position of the short-hand property assignment, we would want to have references to position of + * property name and variable declaration of the identifier. + * Like in below example, when querying for all references for an identifier 'name', of the property assignment, the language service + * should show both 'name' in 'obj' and 'name' in variable declaration + * const name = "Foo"; + * const obj = { name }; + * In order to do that, we will populate the search set with the value symbol of the identifier as a value of the property assignment + * so that when matching with potential reference symbol, both symbols from property declaration and variable declaration + * will be included correctly. + */ + const shorthandValueSymbol = checker.getShorthandAssignmentValueSymbol(location.parent); // gets the local symbol + if (shorthandValueSymbol && isForRenamePopulateSearchSymbolSet) { + // When renaming 'x' in `const o = { x }`, just rename the local variable, not the property. + return cbSymbol(shorthandValueSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedLocalFoundProperty); + } + + // If the location is in a context sensitive location (i.e. in an object literal) try + // to get a contextual type for it, and add the property symbol from the contextual + // type to the search set + const contextualType = checker.getContextualType(containingObjectLiteralElement.parent); + const res = contextualType && firstDefined( + getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker, contextualType, /*unionSymbolOk*/ true), + sym => fromRoot(sym, EntryKind.SearchedPropertyFoundLocal)); + if (res) return res; + + // If the location is name of property symbol from object literal destructuring pattern + // Search the property symbol + // for ( { property: p2 } of elems) { } + const propertySymbol = getPropertySymbolOfDestructuringAssignment(location, checker); + const res1 = propertySymbol && cbSymbol(propertySymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedPropertyFoundLocal); + if (res1) return res1; + + const res2 = shorthandValueSymbol && cbSymbol(shorthandValueSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedLocalFoundProperty); + if (res2) return res2; + } + + const aliasedSymbol = getMergedAliasedSymbolOfNamespaceExportDeclaration(location, symbol, checker); + if (aliasedSymbol) { + // In case of UMD module and global merging, search for global as well + const res = cbSymbol(aliasedSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.Node); + if (res) return res; + } + + const res = fromRoot(symbol); + if (res) return res; + + if (symbol.valueDeclaration && isParameterPropertyDeclaration(symbol.valueDeclaration, symbol.valueDeclaration.parent)) { + // For a parameter property, now try on the other symbol (property if this was a parameter, parameter if this was a property). + const paramProps = checker.getSymbolsOfParameterPropertyDeclaration(cast(symbol.valueDeclaration, isParameter), symbol.name); + Debug.assert(paramProps.length === 2 && !!(paramProps[0].flags & SymbolFlags.FunctionScopedVariable) && !!(paramProps[1].flags & SymbolFlags.Property)); // is [parameter, property] + return fromRoot(symbol.flags & SymbolFlags.FunctionScopedVariable ? paramProps[1] : paramProps[0]); + } + + // symbolAtLocation for a binding element is the local symbol. See if the search symbol is the property. + // Don't do this when populating search set for a rename when prefix and suffix text will be provided -- just rename the local. + if (!isForRenamePopulateSearchSymbolSet) { + let bindingElementPropertySymbol: Symbol | undefined; + if (onlyIncludeBindingElementAtReferenceLocation) { + bindingElementPropertySymbol = isObjectBindingElementWithoutPropertyName(location.parent) ? getPropertySymbolFromBindingElement(checker, location.parent) : undefined; + } + else { + bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, checker); + } + return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol, EntryKind.SearchedPropertyFoundLocal); + } + + Debug.assert(isForRenamePopulateSearchSymbolSet); + // due to the above assert and the arguments at the uses of this function, + // (onlyIncludeBindingElementAtReferenceLocation <=> !providePrefixAndSuffixTextForRename) holds + const includeOriginalSymbolOfBindingElement = onlyIncludeBindingElementAtReferenceLocation; + + if (includeOriginalSymbolOfBindingElement) { + const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, checker); + return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol, EntryKind.SearchedPropertyFoundLocal); + } + + function fromRoot(sym: Symbol, kind?: NodeEntryKind): T | undefined { + // If this is a union property: + // - In populateSearchSymbolsSet we will add all the symbols from all its source symbols in all unioned types. + // - In findRelatedSymbol, we will just use the union symbol if any source symbol is included in the search. + // If the symbol is an instantiation from a another symbol (e.g. widened symbol): + // - In populateSearchSymbolsSet, add the root the list + // - In findRelatedSymbol, return the source symbol if that is in the search. (Do not return the instantiation symbol.) + return firstDefined(checker.getRootSymbols(sym), rootSymbol => + cbSymbol(sym, rootSymbol, /*baseSymbol*/ undefined, kind) + // Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions + || (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface) && allowBaseTypes(rootSymbol) + ? getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.name, checker, base => cbSymbol(sym, rootSymbol, base, kind)) + : undefined)); + } + + function getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol: Symbol, checker: TypeChecker): Symbol | undefined { + const bindingElement = getDeclarationOfKind(symbol, SyntaxKind.BindingElement); + if (bindingElement && isObjectBindingElementWithoutPropertyName(bindingElement)) { + return getPropertySymbolFromBindingElement(checker, bindingElement); + } + } + } + + interface RelatedSymbol { + readonly symbol: Symbol; + readonly kind: NodeEntryKind | undefined; + } + function getRelatedSymbol(search: Search, referenceSymbol: Symbol, referenceLocation: Node, state: State): RelatedSymbol | undefined { + const { checker } = state; + return forEachRelatedSymbol(referenceSymbol, referenceLocation, checker, /*isForRenamePopulateSearchSymbolSet*/ false, + /*onlyIncludeBindingElementAtReferenceLocation*/ !state.options.isForRename || !!state.options.providePrefixAndSuffixTextForRename, + (sym, rootSymbol, baseSymbol, kind): RelatedSymbol | undefined => search.includes(baseSymbol || rootSymbol || sym) + // For a base type, use the symbol for the derived type. For a synthetic (e.g. union) property, use the union symbol. + ? { symbol: rootSymbol && !(getCheckFlags(sym) & CheckFlags.Synthetic) ? rootSymbol : sym, kind } + : undefined, + /*allowBaseTypes*/ rootSymbol => + !(search.parents && !search.parents.some(parent => explicitlyInheritsFrom(rootSymbol.parent!, parent, state.inheritsFromCache, checker)))); + } + + /** + * Given an initial searchMeaning, extracted from a location, widen the search scope based on the declarations + * of the corresponding symbol. e.g. if we are searching for "Foo" in value position, but "Foo" references a class + * then we need to widen the search to include type positions as well. + * On the contrary, if we are searching for "Bar" in type position and we trace bar to an interface, and an uninstantiated + * module, we want to keep the search limited to only types, as the two declarations (interface and uninstantiated module) + * do not intersect in any of the three spaces. + */ + export function getIntersectingMeaningFromDeclarations(node: Node, symbol: Symbol): SemanticMeaning { + let meaning = getMeaningFromLocation(node); + const { declarations } = symbol; + if (declarations) { + let lastIterationMeaning: SemanticMeaning; + do { + // The result is order-sensitive, for instance if initialMeaning === Namespace, and declarations = [class, instantiated module] + // we need to consider both as they initialMeaning intersects with the module in the namespace space, and the module + // intersects with the class in the value space. + // To achieve that we will keep iterating until the result stabilizes. + + // Remember the last meaning + lastIterationMeaning = meaning; + + for (const declaration of declarations) { + const declarationMeaning = getMeaningFromDeclaration(declaration); + + if (declarationMeaning & meaning) { + meaning |= declarationMeaning; + } + } + } + while (meaning !== lastIterationMeaning); + } + return meaning; + } + + function isImplementation(node: Node): boolean { + return !!(node.flags & NodeFlags.Ambient) ? !(isInterfaceDeclaration(node) || isTypeAliasDeclaration(node)) : + (isVariableLike(node) ? hasInitializer(node) : + isFunctionLikeDeclaration(node) ? !!node.body : + isClassLike(node) || isModuleOrEnumDeclaration(node)); + } + + export function getReferenceEntriesForShorthandPropertyAssignment(node: Node, checker: TypeChecker, addReference: (node: Node) => void): void { + const refSymbol = checker.getSymbolAtLocation(node)!; + const shorthandSymbol = checker.getShorthandAssignmentValueSymbol(refSymbol.valueDeclaration); + + if (shorthandSymbol) { + for (const declaration of shorthandSymbol.getDeclarations()!) { + if (getMeaningFromDeclaration(declaration) & SemanticMeaning.Value) { + addReference(declaration); } } } - while (meaning !== lastIterationMeaning); } - return meaning; - } - function isImplementation(node: Node): boolean { - return !!(node.flags & NodeFlags.Ambient) ? !(isInterfaceDeclaration(node) || isTypeAliasDeclaration(node)) : - (isVariableLike(node) ? hasInitializer(node) : - isFunctionLikeDeclaration(node) ? !!node.body : - isClassLike(node) || isModuleOrEnumDeclaration(node)); - } - - export function getReferenceEntriesForShorthandPropertyAssignment(node: Node, checker: TypeChecker, addReference: (node: Node) => void): void { - const refSymbol = checker.getSymbolAtLocation(node)!; - const shorthandSymbol = checker.getShorthandAssignmentValueSymbol(refSymbol.valueDeclaration); - - if (shorthandSymbol) { - for (const declaration of shorthandSymbol.getDeclarations()!) { - if (getMeaningFromDeclaration(declaration) & SemanticMeaning.Value) { - addReference(declaration); + function forEachDescendantOfKind(node: Node, kind: SyntaxKind, action: (node: Node) => void): void { + forEachChild(node, child => { + if (child.kind === kind) { + action(child); } - } + forEachDescendantOfKind(child, kind, action); + }); } - } - function forEachDescendantOfKind(node: Node, kind: SyntaxKind, action: (node: Node) => void): void { - forEachChild(node, child => { - if (child.kind === kind) { - action(child); - } - forEachDescendantOfKind(child, kind, action); - }); - } + /** Get `C` given `N` if `N` is in the position `class C extends N` or `class C extends foo.N` where `N` is an identifier. */ + function tryGetClassByExtendingIdentifier(node: Node): ClassLikeDeclaration | undefined { + return tryGetClassExtendingExpressionWithTypeArguments(climbPastPropertyAccess(node).parent); + } - /** Get `C` given `N` if `N` is in the position `class C extends N` or `class C extends foo.N` where `N` is an identifier. */ - function tryGetClassByExtendingIdentifier(node: Node): ClassLikeDeclaration | undefined { - return tryGetClassExtendingExpressionWithTypeArguments(climbPastPropertyAccess(node).parent); - } + /** + * If we are just looking for implementations and this is a property access expression, we need to get the + * symbol of the local type of the symbol the property is being accessed on. This is because our search + * symbol may have a different parent symbol if the local type's symbol does not declare the property + * being accessed (i.e. it is declared in some parent class or interface) + */ + function getParentSymbolsOfPropertyAccess(location: Node, symbol: Symbol, checker: TypeChecker): readonly Symbol[] | undefined { + const propertyAccessExpression = isRightSideOfPropertyAccess(location) ? location.parent : undefined; + const lhsType = propertyAccessExpression && checker.getTypeAtLocation(propertyAccessExpression.expression); + const res = mapDefined(lhsType && (lhsType.isUnionOrIntersection() ? lhsType.types : lhsType.symbol === symbol.parent ? undefined : [lhsType]), t => + t.symbol && t.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface) ? t.symbol : undefined); + return res.length === 0 ? undefined : res; + } - /** - * If we are just looking for implementations and this is a property access expression, we need to get the - * symbol of the local type of the symbol the property is being accessed on. This is because our search - * symbol may have a different parent symbol if the local type's symbol does not declare the property - * being accessed (i.e. it is declared in some parent class or interface) - */ - function getParentSymbolsOfPropertyAccess(location: Node, symbol: Symbol, checker: TypeChecker): readonly Symbol[] | undefined { - const propertyAccessExpression = isRightSideOfPropertyAccess(location) ? location.parent : undefined; - const lhsType = propertyAccessExpression && checker.getTypeAtLocation(propertyAccessExpression.expression); - const res = mapDefined(lhsType && (lhsType.isUnionOrIntersection() ? lhsType.types : lhsType.symbol === symbol.parent ? undefined : [lhsType]), t => - t.symbol && t.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface) ? t.symbol : undefined); - return res.length === 0 ? undefined : res; - } - - function isForRenameWithPrefixAndSuffixText(options: Options) { - return options.isForRename && options.providePrefixAndSuffixTextForRename; + function isForRenameWithPrefixAndSuffixText(options: Options) { + return options.isForRename && options.providePrefixAndSuffixTextForRename; + } } } diff --git a/src/services/globalThisShim.ts b/src/services/globalThisShim.ts new file mode 100644 index 00000000000..632a94a6aeb --- /dev/null +++ b/src/services/globalThisShim.ts @@ -0,0 +1,50 @@ +// We polyfill `globalThis` here so re can reliably patch the global scope +// in the contexts we want to in the same way across script and module formats + +// https://mathiasbynens.be/notes/globalthis + +// #region The polyfill starts here. +((() => { + if (typeof globalThis === "object") return; + try { + Object.defineProperty(Object.prototype, "__magic__", { + get() { + return this; + }, + configurable: true + }); + //@ts-ignore + __magic__.globalThis = __magic__; + // The previous line should have made `globalThis` globally + // available, but it fails in Internet Explorer 10 and older. + // Detect this failure and fall back. + if (typeof globalThis === "undefined") { + // Assume `window` exists. + //@ts-ignore + window.globalThis = window; + } + //@ts-ignore + delete Object.prototype.__magic__; + } + catch (error) { + // In IE8, Object.defineProperty only works on DOM objects. + // If we hit this code path, assume `window` exists. + //@ts-ignore + window.globalThis = window; + } +})()); +// #endregion The polyfill ends here. + +// if `process` is undefined, we're probably not running in node - patch legacy members onto the global scope +// @ts-ignore +if (typeof process === "undefined" || process.browser) { + /// TODO: this is used by VS, clean this up on both sides of the interface + //@ts-ignore + globalThis.TypeScript.Services.TypeScriptServicesFactory = ts.TypeScriptServicesFactory; + + // 'toolsVersion' gets consumed by the managed side, so it's not unused. + // TODO: it should be moved into a namespace though. + + //@ts-ignore + globalThis.toolsVersion = ts.versionMajorMinor; +} \ No newline at end of file diff --git a/src/services/shims.ts b/src/services/shims.ts index d500e93791b..66203e09996 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -1266,25 +1266,6 @@ namespace ts { throw new Error("Invalid operation"); } } - - // Here we expose the TypeScript services as an external module - // so that it may be consumed easily like a node module. - declare const module: { exports: {} }; - if (typeof module !== "undefined" && module.exports) { - module.exports = ts; - } } -/* eslint-enable no-in-operator */ - -/// TODO: this is used by VS, clean this up on both sides of the interface -/* @internal */ -namespace TypeScript.Services { - export const TypeScriptServicesFactory = ts.TypeScriptServicesFactory; -} - -// 'toolsVersion' gets consumed by the managed side, so it's not unused. -// TODO: it should be moved into a namespace though. - -/* @internal */ -const toolsVersion = ts.versionMajorMinor; +/* eslint-enable no-in-operator */ \ No newline at end of file diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index a75a96c961e..f3cf70f353a 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -98,5 +98,7 @@ "breakpoints.ts", "transform.ts", "shims.ts", + "globalThisShim.ts", + "exportAsModule.ts" ] } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 315f61c967d..820e95e358d 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -8,9 +8,10 @@ interface PromiseConstructor { /* @internal */ declare let Promise: PromiseConstructor; -// These utilities are common to multiple language service features. /* @internal */ namespace ts { + // These utilities are common to multiple language service features. + //#region export const scanner: Scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true); export const enum SemanticMeaning { @@ -1477,11 +1478,11 @@ namespace ts { } return undefined; } -} -// Display-part writer helpers -/* @internal */ -namespace ts { + // #endregion + + // Display-part writer helpers + // #region export function isFirstDeclarationOfSymbolParameter(symbol: Symbol) { return symbol.declarations && symbol.declarations.length > 0 && symbol.declarations[0].kind === SyntaxKind.Parameter; } @@ -2290,4 +2291,5 @@ namespace ts { export function isInsideNodeModules(fileOrDirectory: string): boolean { return contains(getPathComponents(fileOrDirectory), "node_modules"); } -} + // #endregion +} \ No newline at end of file diff --git a/src/testRunner/unittests/tscWatch/helpers.ts b/src/testRunner/unittests/tscWatch/helpers.ts index 7f6a69aa48d..d14c531f0d3 100644 --- a/src/testRunner/unittests/tscWatch/helpers.ts +++ b/src/testRunner/unittests/tscWatch/helpers.ts @@ -1,8 +1,6 @@ -namespace ts { +namespace ts.tscWatch { export const projects = `/user/username/projects`; export const projectRoot = `${projects}/myproject`; -} -namespace ts.tscWatch { export import WatchedSystem = TestFSWithWatch.TestServerHost; export type File = TestFSWithWatch.File; export type SymLink = TestFSWithWatch.SymLink; diff --git a/src/testRunner/unittests/tsserver/compileOnSave.ts b/src/testRunner/unittests/tsserver/compileOnSave.ts index 47d4571541c..f2e6cdf4081 100644 --- a/src/testRunner/unittests/tsserver/compileOnSave.ts +++ b/src/testRunner/unittests/tsserver/compileOnSave.ts @@ -803,19 +803,19 @@ namespace ts.projectSystem { describe("unittests:: tsserver:: compileOnSave:: CompileOnSaveAffectedFileListRequest with and without projectFileName in request", () => { const core: File = { - path: `${projectRoot}/core/core.ts`, + path: `${tscWatch.projectRoot}/core/core.ts`, content: "let z = 10;" }; const app1: File = { - path: `${projectRoot}/app1/app.ts`, + path: `${tscWatch.projectRoot}/app1/app.ts`, content: "let x = 10;" }; const app2: File = { - path: `${projectRoot}/app2/app.ts`, + path: `${tscWatch.projectRoot}/app2/app.ts`, content: "let y = 10;" }; const app1Config: File = { - path: `${projectRoot}/app1/tsconfig.json`, + path: `${tscWatch.projectRoot}/app1/tsconfig.json`, content: JSON.stringify({ files: ["app.ts", "../core/core.ts"], compilerOptions: { outFile: "build/output.js" }, @@ -823,7 +823,7 @@ namespace ts.projectSystem { }) }; const app2Config: File = { - path: `${projectRoot}/app2/tsconfig.json`, + path: `${tscWatch.projectRoot}/app2/tsconfig.json`, content: JSON.stringify({ files: ["app.ts", "../core/core.ts"], compilerOptions: { outFile: "build/output.js" }, diff --git a/src/testRunner/unittests/tsserver/configuredProjects.ts b/src/testRunner/unittests/tsserver/configuredProjects.ts index 5d2a8730492..ae859be2b3d 100644 --- a/src/testRunner/unittests/tsserver/configuredProjects.ts +++ b/src/testRunner/unittests/tsserver/configuredProjects.ts @@ -83,17 +83,17 @@ namespace ts.projectSystem { it("add and then remove a config file in a folder with loose files", () => { const configFile: File = { - path: `${projectRoot}/tsconfig.json`, + path: `${tscWatch.projectRoot}/tsconfig.json`, content: `{ "files": ["commonFile1.ts"] }` }; const commonFile1: File = { - path: `${projectRoot}/commonFile1.ts`, + path: `${tscWatch.projectRoot}/commonFile1.ts`, content: "let x = 1" }; const commonFile2: File = { - path: `${projectRoot}/commonFile2.ts`, + path: `${tscWatch.projectRoot}/commonFile2.ts`, content: "let y = 1" }; @@ -109,7 +109,7 @@ namespace ts.projectSystem { checkProjectActualFiles(projectService.inferredProjects[0], [commonFile1.path, libFile.path]); checkProjectActualFiles(projectService.inferredProjects[1], [commonFile2.path, libFile.path]); - const watchedFiles = getConfigFilesToWatch(projectRoot).concat(libFile.path); + const watchedFiles = getConfigFilesToWatch(tscWatch.projectRoot).concat(libFile.path); checkWatchedFiles(host, watchedFiles); // Add a tsconfig file @@ -440,19 +440,19 @@ namespace ts.projectSystem { it("open file become a part of configured project if it is referenced from root file", () => { const file1 = { - path: `${projectRoot}/a/b/f1.ts`, + path: `${tscWatch.projectRoot}/a/b/f1.ts`, content: "export let x = 5" }; const file2 = { - path: `${projectRoot}/a/c/f2.ts`, + path: `${tscWatch.projectRoot}/a/c/f2.ts`, content: `import {x} from "../b/f1"` }; const file3 = { - path: `${projectRoot}/a/c/f3.ts`, + path: `${tscWatch.projectRoot}/a/c/f3.ts`, content: "export let y = 1" }; const configFile = { - path: `${projectRoot}/a/c/tsconfig.json`, + path: `${tscWatch.projectRoot}/a/c/tsconfig.json`, content: JSON.stringify({ compilerOptions: {}, files: ["f2.ts", "f3.ts"] }) }; @@ -847,7 +847,7 @@ namespace ts.projectSystem { it("when multiple projects are open, detects correct default project", () => { const barConfig: File = { - path: `${projectRoot}/bar/tsconfig.json`, + path: `${tscWatch.projectRoot}/bar/tsconfig.json`, content: JSON.stringify({ include: ["index.ts"], compilerOptions: { @@ -856,14 +856,14 @@ namespace ts.projectSystem { }) }; const barIndex: File = { - path: `${projectRoot}/bar/index.ts`, + path: `${tscWatch.projectRoot}/bar/index.ts`, content: ` export function bar() { console.log("hello world"); }` }; const fooConfig: File = { - path: `${projectRoot}/foo/tsconfig.json`, + path: `${tscWatch.projectRoot}/foo/tsconfig.json`, content: JSON.stringify({ include: ["index.ts"], compilerOptions: { @@ -872,14 +872,14 @@ export function bar() { }) }; const fooIndex: File = { - path: `${projectRoot}/foo/index.ts`, + path: `${tscWatch.projectRoot}/foo/index.ts`, content: ` import { bar } from "bar"; bar();` }; const barSymLink: SymLink = { - path: `${projectRoot}/foo/node_modules/bar`, - symLink: `${projectRoot}/bar` + path: `${tscWatch.projectRoot}/foo/node_modules/bar`, + symLink: `${tscWatch.projectRoot}/bar` }; const lib2017: File = { @@ -963,11 +963,11 @@ declare var console: { it("should tolerate invalid include files that start in subDirectory", () => { const f = { - path: `${projectRoot}/src/server/index.ts`, + path: `${tscWatch.projectRoot}/src/server/index.ts`, content: "let x = 1" }; const config = { - path: `${projectRoot}/src/server/tsconfig.json`, + path: `${tscWatch.projectRoot}/src/server/tsconfig.json`, content: JSON.stringify({ compiler: { module: "commonjs", diff --git a/src/testRunner/unittests/tsserver/documentRegistry.ts b/src/testRunner/unittests/tsserver/documentRegistry.ts index 51b1bee6604..94098ab1bd9 100644 --- a/src/testRunner/unittests/tsserver/documentRegistry.ts +++ b/src/testRunner/unittests/tsserver/documentRegistry.ts @@ -2,15 +2,15 @@ namespace ts.projectSystem { describe("unittests:: tsserver:: document registry in project service", () => { const importModuleContent = `import {a} from "./module1"`; const file: File = { - path: `${projectRoot}/index.ts`, + path: `${tscWatch.projectRoot}/index.ts`, content: importModuleContent }; const moduleFile: File = { - path: `${projectRoot}/module1.d.ts`, + path: `${tscWatch.projectRoot}/module1.d.ts`, content: "export const a: number;" }; const configFile: File = { - path: `${projectRoot}/tsconfig.json`, + path: `${tscWatch.projectRoot}/tsconfig.json`, content: JSON.stringify({ files: ["index.ts"] }) }; diff --git a/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts b/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts index cb9df9e6372..1807f104ee2 100644 --- a/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts +++ b/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts @@ -7,7 +7,7 @@ namespace ts.projectSystem { function createSessionWithEventHandler(files: File[], useLargeTsFile: boolean) { const largeFile: File = { - path: `${projectRoot}/${getLargeFile(useLargeTsFile)}`, + path: `${tscWatch.projectRoot}/${getLargeFile(useLargeTsFile)}`, content: "export var x = 10;", fileSize: server.maxFileSize + 1 }; @@ -35,11 +35,11 @@ namespace ts.projectSystem { function verifyLargeFile(useLargeTsFile: boolean) { it("when large file is included by tsconfig", () => { const file: File = { - path: `${projectRoot}/src/file.ts`, + path: `${tscWatch.projectRoot}/src/file.ts`, content: "export var y = 10;" }; const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, + path: `${tscWatch.projectRoot}/tsconfig.json`, content: JSON.stringify({ files: ["src/file.ts", getLargeFile(useLargeTsFile)], compilerOptions: { target: 1, allowJs: true } }) }; const files = [file, libFile, tsconfig]; @@ -52,7 +52,7 @@ namespace ts.projectSystem { it("when large file is included by module resolution", () => { const file: File = { - path: `${projectRoot}/src/file.ts`, + path: `${tscWatch.projectRoot}/src/file.ts`, content: `export var y = 10;import {x} from "./large"` }; const files = [file, libFile]; diff --git a/src/testRunner/unittests/tsserver/events/projectLoading.ts b/src/testRunner/unittests/tsserver/events/projectLoading.ts index 2bb0d95761a..c4c7d78b740 100644 --- a/src/testRunner/unittests/tsserver/events/projectLoading.ts +++ b/src/testRunner/unittests/tsserver/events/projectLoading.ts @@ -1,15 +1,15 @@ namespace ts.projectSystem { describe("unittests:: tsserver:: events:: ProjectLoadingStart and ProjectLoadingFinish events", () => { const aTs: File = { - path: `${projects}/a/a.ts`, + path: `${tscWatch.projects}/a/a.ts`, content: "export class A { }" }; const configA: File = { - path: `${projects}/a/tsconfig.json`, + path: `${tscWatch.projects}/a/tsconfig.json`, content: "{}" }; - const bTsPath = `${projects}/b/b.ts`; - const configBPath = `${projects}/b/tsconfig.json`; + const bTsPath = `${tscWatch.projects}/b/b.ts`; + const configBPath = `${tscWatch.projects}/b/tsconfig.json`; const files = [libFile, aTs, configA]; function verifyProjectLoadingStartAndFinish(createSession: (host: TestServerHost) => { @@ -83,14 +83,14 @@ namespace ts.projectSystem { function verify(disableSourceOfProjectReferenceRedirect?: true) { const aDTs: File = { - path: `${projects}/a/a.d.ts`, + path: `${tscWatch.projects}/a/a.d.ts`, content: `export declare class A { } //# sourceMappingURL=a.d.ts.map ` }; const aDTsMap: File = { - path: `${projects}/a/a.d.ts.map`, + path: `${tscWatch.projects}/a/a.d.ts.map`, content: `{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["./a.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;CAAI"}` }; const bTs: File = { @@ -133,7 +133,7 @@ namespace ts.projectSystem { }); describe("with external projects and config files ", () => { - const projectFileName = `${projects}/a/project.csproj`; + const projectFileName = `${tscWatch.projects}/a/project.csproj`; function createSession(lazyConfiguredProjectsFromExternalProject: boolean) { const { session, service, verifyEvent: verifyEventWorker, getNumberOfEvents } = createSessionToVerifyEvent(files); diff --git a/src/testRunner/unittests/tsserver/externalProjects.ts b/src/testRunner/unittests/tsserver/externalProjects.ts index 5e335f1a4fa..b2f9753024d 100644 --- a/src/testRunner/unittests/tsserver/externalProjects.ts +++ b/src/testRunner/unittests/tsserver/externalProjects.ts @@ -824,9 +824,9 @@ namespace ts.projectSystem { }); it("handles creation of external project with jsconfig before jsconfig creation watcher is invoked", () => { - const projectFileName = `${projectRoot}/WebApplication36.csproj`; + const projectFileName = `${tscWatch.projectRoot}/WebApplication36.csproj`; const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, + path: `${tscWatch.projectRoot}/tsconfig.json`, content: "{}" }; const files = [libFile, tsconfig]; @@ -844,7 +844,7 @@ namespace ts.projectSystem { checkProjectActualFiles(configProject, [tsconfig.path]); // write js file, open external project and open it for edit - const jsFilePath = `${projectRoot}/javascript.js`; + const jsFilePath = `${tscWatch.projectRoot}/javascript.js`; host.writeFile(jsFilePath, ""); service.openExternalProjects([{ projectFileName, @@ -859,7 +859,7 @@ namespace ts.projectSystem { // write jsconfig file const jsConfig: File = { - path: `${projectRoot}/jsconfig.json`, + path: `${tscWatch.projectRoot}/jsconfig.json`, content: "{}" }; // Dont invoke file creation watchers as the repro suggests diff --git a/src/testRunner/unittests/tsserver/inferredProjects.ts b/src/testRunner/unittests/tsserver/inferredProjects.ts index 6c647c4e641..04a849353b8 100644 --- a/src/testRunner/unittests/tsserver/inferredProjects.ts +++ b/src/testRunner/unittests/tsserver/inferredProjects.ts @@ -2,7 +2,7 @@ namespace ts.projectSystem { describe("unittests:: tsserver:: Inferred projects", () => { it("create inferred project", () => { const appFile: File = { - path: `${projectRoot}/app.ts`, + path: `${tscWatch.projectRoot}/app.ts`, content: ` import {f} from "./module" console.log(f) @@ -10,7 +10,7 @@ namespace ts.projectSystem { }; const moduleFile: File = { - path: `${projectRoot}/module.d.ts`, + path: `${tscWatch.projectRoot}/module.d.ts`, content: `export let x: number` }; const host = createServerHost([appFile, moduleFile, libFile]); @@ -24,18 +24,18 @@ namespace ts.projectSystem { const project = projectService.inferredProjects[0]; checkArray("inferred project", project.getFileNames(), [appFile.path, libFile.path, moduleFile.path]); - checkWatchedFiles(host, getConfigFilesToWatch(projectRoot).concat(libFile.path, moduleFile.path)); - checkWatchedDirectories(host, [projectRoot], /*recursive*/ false); - checkWatchedDirectories(host, [combinePaths(projectRoot, nodeModulesAtTypes)], /*recursive*/ true); + checkWatchedFiles(host, getConfigFilesToWatch(tscWatch.projectRoot).concat(libFile.path, moduleFile.path)); + checkWatchedDirectories(host, [tscWatch.projectRoot], /*recursive*/ false); + checkWatchedDirectories(host, [combinePaths(tscWatch.projectRoot, nodeModulesAtTypes)], /*recursive*/ true); }); it("should use only one inferred project if 'useOneInferredProject' is set", () => { const file1 = { - path: `${projectRoot}/a/b/main.ts`, + path: `${tscWatch.projectRoot}/a/b/main.ts`, content: "let x =1;" }; const configFile: File = { - path: `${projectRoot}/a/b/tsconfig.json`, + path: `${tscWatch.projectRoot}/a/b/tsconfig.json`, content: `{ "compilerOptions": { "target": "es6" @@ -44,12 +44,12 @@ namespace ts.projectSystem { }` }; const file2 = { - path: `${projectRoot}/a/c/main.ts`, + path: `${tscWatch.projectRoot}/a/c/main.ts`, content: "let x =1;" }; const file3 = { - path: `${projectRoot}/a/d/main.ts`, + path: `${tscWatch.projectRoot}/a/d/main.ts`, content: "let x =1;" }; @@ -346,19 +346,19 @@ namespace ts.projectSystem { it("should still retain configured project created while opening the file", () => { const appFile: File = { - path: `${projectRoot}/app.ts`, + path: `${tscWatch.projectRoot}/app.ts`, content: `const app = 20;` }; const config: File = { - path: `${projectRoot}/tsconfig.json`, + path: `${tscWatch.projectRoot}/tsconfig.json`, content: "{}" }; const jsFile1: File = { - path: `${projectRoot}/jsFile1.js`, + path: `${tscWatch.projectRoot}/jsFile1.js`, content: `const jsFile1 = 10;` }; const jsFile2: File = { - path: `${projectRoot}/jsFile2.js`, + path: `${tscWatch.projectRoot}/jsFile2.js`, content: `const jsFile2 = 10;` }; const host = createServerHost([appFile, libFile, config, jsFile1, jsFile2]); diff --git a/src/testRunner/unittests/tsserver/projectErrors.ts b/src/testRunner/unittests/tsserver/projectErrors.ts index 41eea1de9e7..e857cb8108f 100644 --- a/src/testRunner/unittests/tsserver/projectErrors.ts +++ b/src/testRunner/unittests/tsserver/projectErrors.ts @@ -390,31 +390,31 @@ namespace ts.projectSystem { it("Reports errors correctly when file referenced by inferred project root, is opened right after closing the root file", () => { const app: File = { - path: `${projectRoot}/src/client/app.js`, + path: `${tscWatch.projectRoot}/src/client/app.js`, content: "" }; const serverUtilities: File = { - path: `${projectRoot}/src/server/utilities.js`, + path: `${tscWatch.projectRoot}/src/server/utilities.js`, content: `function getHostName() { return "hello"; } export { getHostName };` }; const backendTest: File = { - path: `${projectRoot}/test/backend/index.js`, + path: `${tscWatch.projectRoot}/test/backend/index.js`, content: `import { getHostName } from '../../src/server/utilities';export default getHostName;` }; const files = [libFile, app, serverUtilities, backendTest]; const host = createServerHost(files); const session = createSession(host, { useInferredProjectPerProjectRoot: true, canUseEvents: true }); - openFilesForSession([{ file: app, projectRootPath: projectRoot }], session); + openFilesForSession([{ file: app, projectRootPath: tscWatch.projectRoot }], session); const service = session.getProjectService(); checkNumberOfProjects(service, { inferredProjects: 1 }); const project = service.inferredProjects[0]; checkProjectActualFiles(project, [libFile.path, app.path]); - openFilesForSession([{ file: backendTest, projectRootPath: projectRoot }], session); + openFilesForSession([{ file: backendTest, projectRootPath: tscWatch.projectRoot }], session); checkNumberOfProjects(service, { inferredProjects: 1 }); checkProjectActualFiles(project, files.map(f => f.path)); checkErrors([backendTest.path, app.path]); closeFilesForSession([backendTest], session); - openFilesForSession([{ file: serverUtilities.path, projectRootPath: projectRoot }], session); + openFilesForSession([{ file: serverUtilities.path, projectRootPath: tscWatch.projectRoot }], session); checkErrors([serverUtilities.path, app.path]); function checkErrors(openFiles: [string, string]) { @@ -865,17 +865,17 @@ declare module '@custom/plugin' { describe("unittests:: tsserver:: Project Errors with resolveJsonModule", () => { function createSessionForTest({ include }: { include: readonly string[]; }) { const test: File = { - path: `${projectRoot}/src/test.ts`, + path: `${tscWatch.projectRoot}/src/test.ts`, content: `import * as blabla from "./blabla.json"; declare var console: any; console.log(blabla);` }; const blabla: File = { - path: `${projectRoot}/src/blabla.json`, + path: `${tscWatch.projectRoot}/src/blabla.json`, content: "{}" }; const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, + path: `${tscWatch.projectRoot}/tsconfig.json`, content: JSON.stringify({ compilerOptions: { resolveJsonModule: true, diff --git a/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts b/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts index 7399078f1ef..84f447a9f36 100644 --- a/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts +++ b/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts @@ -1,7 +1,7 @@ namespace ts.projectSystem { describe("unittests:: tsserver:: with project references and compile on save", () => { - const dependecyLocation = `${projectRoot}/dependency`; - const usageLocation = `${projectRoot}/usage`; + const dependecyLocation = `${tscWatch.projectRoot}/dependency`; + const usageLocation = `${tscWatch.projectRoot}/usage`; const dependencyTs: File = { path: `${dependecyLocation}/fns.ts`, content: `export function fn1() { } @@ -293,7 +293,7 @@ exports.fn2 = fn2; ${appendJs}` }, { - path: `${projectRoot}/decls/fns.d.ts`, + path: `${tscWatch.projectRoot}/decls/fns.d.ts`, content: `export declare function fn1(): void; export declare function fn2(): void; ${appendDts}` diff --git a/src/testRunner/unittests/tsserver/projectReferenceErrors.ts b/src/testRunner/unittests/tsserver/projectReferenceErrors.ts index e6e71263576..a034d41dc52 100644 --- a/src/testRunner/unittests/tsserver/projectReferenceErrors.ts +++ b/src/testRunner/unittests/tsserver/projectReferenceErrors.ts @@ -85,8 +85,8 @@ namespace ts.projectSystem { } describe("unittests:: tsserver:: with project references and error reporting", () => { - const dependecyLocation = `${projectRoot}/dependency`; - const usageLocation = `${projectRoot}/usage`; + const dependecyLocation = `${tscWatch.projectRoot}/dependency`; + const usageLocation = `${tscWatch.projectRoot}/usage`; function verifyErrorsUsingGeterr({ allFiles, openFiles, expectedGetErr }: VerifyScenario) { it("verifies the errors in open file", () => { diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index cde95f6dd52..a928d73bd69 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -92,9 +92,9 @@ namespace ts.projectSystem { }); describe("with main and depedency project", () => { - const dependecyLocation = `${projectRoot}/dependency`; - const dependecyDeclsLocation = `${projectRoot}/decls`; - const mainLocation = `${projectRoot}/main`; + const dependecyLocation = `${tscWatch.projectRoot}/dependency`; + const dependecyDeclsLocation = `${tscWatch.projectRoot}/decls`; + const mainLocation = `${tscWatch.projectRoot}/main`; const dependencyTs: File = { path: `${dependecyLocation}/FnS.ts`, content: `export function fn1() { } @@ -136,11 +136,11 @@ fn5(); }; const randomFile: File = { - path: `${projectRoot}/random/random.ts`, + path: `${tscWatch.projectRoot}/random/random.ts`, content: "let a = 10;" }; const randomConfig: File = { - path: `${projectRoot}/random/tsconfig.json`, + path: `${tscWatch.projectRoot}/random/tsconfig.json`, content: "{}" }; const dtsLocation = `${dependecyDeclsLocation}/FnS.d.ts`; @@ -1302,7 +1302,7 @@ function foo() { it("reusing d.ts files from composite and non composite projects", () => { const configA: File = { - path: `${projectRoot}/compositea/tsconfig.json`, + path: `${tscWatch.projectRoot}/compositea/tsconfig.json`, content: JSON.stringify({ compilerOptions: { composite: true, @@ -1314,27 +1314,27 @@ function foo() { }) }; const aTs: File = { - path: `${projectRoot}/compositea/a.ts`, + path: `${tscWatch.projectRoot}/compositea/a.ts`, content: `import { b } from "@ref/compositeb/b";` }; const a2Ts: File = { - path: `${projectRoot}/compositea/a2.ts`, + path: `${tscWatch.projectRoot}/compositea/a2.ts`, content: `export const x = 10;` }; const configB: File = { - path: `${projectRoot}/compositeb/tsconfig.json`, + path: `${tscWatch.projectRoot}/compositeb/tsconfig.json`, content: configA.content }; const bTs: File = { - path: `${projectRoot}/compositeb/b.ts`, + path: `${tscWatch.projectRoot}/compositeb/b.ts`, content: "export function b() {}" }; const bDts: File = { - path: `${projectRoot}/dist/compositeb/b.d.ts`, + path: `${tscWatch.projectRoot}/dist/compositeb/b.d.ts`, content: "export declare function b(): void;" }; const configC: File = { - path: `${projectRoot}/compositec/tsconfig.json`, + path: `${tscWatch.projectRoot}/compositec/tsconfig.json`, content: JSON.stringify({ compilerOptions: { composite: true, @@ -1347,7 +1347,7 @@ function foo() { }) }; const cTs: File = { - path: `${projectRoot}/compositec/c.ts`, + path: `${tscWatch.projectRoot}/compositec/c.ts`, content: aTs.content }; const files = [libFile, aTs, a2Ts, configA, bDts, bTs, configB, cTs, configC]; @@ -1407,8 +1407,8 @@ function foo() { const aConfig = config("A", extraOptions, ["../B"]); const bConfig = config("B", extraOptions); const bSymlink: SymLink = { - path: `${projectRoot}/node_modules/b`, - symLink: `${projectRoot}/packages/B` + path: `${tscWatch.projectRoot}/node_modules/b`, + symLink: `${tscWatch.projectRoot}/packages/B` }; const files = [libFile, bPackageJson, aConfig, bConfig, aTest, bFoo, bBar, bSymlink]; const host = alreadyBuilt ? @@ -1436,7 +1436,7 @@ function foo() { function config(packageName: string, extraOptions: CompilerOptions, references?: string[]): File { return { - path: `${projectRoot}/packages/${packageName}/tsconfig.json`, + path: `${tscWatch.projectRoot}/packages/${packageName}/tsconfig.json`, content: JSON.stringify({ compilerOptions: { outDir: "lib", @@ -1452,7 +1452,7 @@ function foo() { function file(packageName: string, fileName: string, content: string): File { return { - path: `${projectRoot}/packages/${packageName}/src/${fileName}`, + path: `${tscWatch.projectRoot}/packages/${packageName}/src/${fileName}`, content }; } @@ -1460,7 +1460,7 @@ function foo() { describe("when packageJson has types field and has index.ts", () => { verifySymlinkScenario(() => ({ bPackageJson: { - path: `${projectRoot}/packages/B/package.json`, + path: `${tscWatch.projectRoot}/packages/B/package.json`, content: JSON.stringify({ main: "lib/index.js", types: "lib/index.d.ts" @@ -1478,7 +1478,7 @@ bar();`), describe("when referencing file from subFolder", () => { verifySymlinkScenario(() => ({ bPackageJson: { - path: `${projectRoot}/packages/B/package.json`, + path: `${tscWatch.projectRoot}/packages/B/package.json`, content: "{}" }, aTest: file("A", "test.ts", `import { foo } from 'b/lib/foo'; diff --git a/src/testRunner/unittests/tsserver/projects.ts b/src/testRunner/unittests/tsserver/projects.ts index bd724bd77e3..369b0d8905e 100644 --- a/src/testRunner/unittests/tsserver/projects.ts +++ b/src/testRunner/unittests/tsserver/projects.ts @@ -1137,17 +1137,17 @@ var x = 10;` content: "var x = 10;" }; const configFile: File = { - path: `${projectRoot}/tsconfig.json`, + path: `${tscWatch.projectRoot}/tsconfig.json`, content: "{}" }; const configProjectFile: File = { - path: `${projectRoot}/a.ts`, + path: `${tscWatch.projectRoot}/a.ts`, content: "let y = 10;" }; it("with useInferredProjectPerProjectRoot", () => { const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true }); const session = createSession(host, { useInferredProjectPerProjectRoot: true }); - openFilesForSession([{ file: file.path, projectRootPath: projectRoot }], session); + openFilesForSession([{ file: file.path, projectRootPath: tscWatch.projectRoot }], session); const projectService = session.getProjectService(); checkNumberOfProjects(projectService, { inferredProjects: 1 }); @@ -1172,7 +1172,7 @@ var x = 10;` const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true }); const projectService = createProjectService(host); try { - projectService.openClientFile(file.path, file.content, /*scriptKind*/ undefined, projectRoot); + projectService.openClientFile(file.path, file.content, /*scriptKind*/ undefined, tscWatch.projectRoot); } catch (e) { assert.strictEqual( @@ -1266,15 +1266,15 @@ var x = 10;` it("requests are done on file on pendingReload but has svc for previous version", () => { const file1: File = { - path: `${projectRoot}/src/file1.ts`, + path: `${tscWatch.projectRoot}/src/file1.ts`, content: `import { y } from "./file2"; let x = 10;` }; const file2: File = { - path: `${projectRoot}/src/file2.ts`, + path: `${tscWatch.projectRoot}/src/file2.ts`, content: "export let y = 10;" }; const config: File = { - path: `${projectRoot}/tsconfig.json`, + path: `${tscWatch.projectRoot}/tsconfig.json`, content: "{}" }; const files = [file1, file2, libFile, config]; @@ -1365,18 +1365,18 @@ var x = 10;` it("Orphan source files are handled correctly on watch trigger", () => { const file1: File = { - path: `${projectRoot}/src/file1.ts`, + path: `${tscWatch.projectRoot}/src/file1.ts`, content: `export let x = 10;` }; const file2: File = { - path: `${projectRoot}/src/file2.ts`, + path: `${tscWatch.projectRoot}/src/file2.ts`, content: "export let y = 10;" }; const configContent1 = JSON.stringify({ files: ["src/file1.ts", "src/file2.ts"] }); const config: File = { - path: `${projectRoot}/tsconfig.json`, + path: `${tscWatch.projectRoot}/tsconfig.json`, content: configContent1 }; const files = [file1, file2, libFile, config]; diff --git a/src/testRunner/unittests/tsserver/resolutionCache.ts b/src/testRunner/unittests/tsserver/resolutionCache.ts index 7ebb2ee4d37..690af6a3d86 100644 --- a/src/testRunner/unittests/tsserver/resolutionCache.ts +++ b/src/testRunner/unittests/tsserver/resolutionCache.ts @@ -451,7 +451,7 @@ namespace ts.projectSystem { describe("unittests:: tsserver:: resolutionCache:: tsserverProjectSystem module resolution caching", () => { const configFile: File = { - path: `${projectRoot}/tsconfig.json`, + path: `${tscWatch.projectRoot}/tsconfig.json`, content: JSON.stringify({ compilerOptions: { traceResolution: true } }) }; @@ -558,7 +558,7 @@ namespace ts.projectSystem { } function verifyWatchesWithConfigFile(host: TestServerHost, files: File[], openFile: File, extraExpectedDirectories?: readonly string[]) { - const expectedRecursiveDirectories = arrayToSet([projectRoot, `${projectRoot}/${nodeModulesAtTypes}`, ...(extraExpectedDirectories || emptyArray)]); + const expectedRecursiveDirectories = arrayToSet([tscWatch.projectRoot, `${tscWatch.projectRoot}/${nodeModulesAtTypes}`, ...(extraExpectedDirectories || emptyArray)]); checkWatchedFiles(host, mapDefined(files, f => { if (f === openFile) { return undefined; @@ -577,11 +577,11 @@ namespace ts.projectSystem { describe("from files in same folder", () => { function getFiles(fileContent: string) { const file1: File = { - path: `${projectRoot}/src/file1.ts`, + path: `${tscWatch.projectRoot}/src/file1.ts`, content: fileContent }; const file2: File = { - path: `${projectRoot}/src/file2.ts`, + path: `${tscWatch.projectRoot}/src/file2.ts`, content: fileContent }; return { file1, file2 }; @@ -592,7 +592,7 @@ namespace ts.projectSystem { const module2Name = "../module2"; const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; const { file1, file2 } = getFiles(fileContent); - const { module1, module2 } = getModules(`${projectRoot}/src/module1.ts`, `${projectRoot}/module2.ts`); + const { module1, module2 } = getModules(`${tscWatch.projectRoot}/src/module1.ts`, `${tscWatch.projectRoot}/module2.ts`); const files = [module1, module2, file1, file2, configFile, libFile]; const host = createServerHost(files); const resolutionTrace = createHostModuleResolutionTrace(host); @@ -615,12 +615,12 @@ namespace ts.projectSystem { }); it("non relative module name", () => { - const expectedNonRelativeDirectories = [`${projectRoot}/node_modules`, `${projectRoot}/src`]; + const expectedNonRelativeDirectories = [`${tscWatch.projectRoot}/node_modules`, `${tscWatch.projectRoot}/src`]; const module1Name = "module1"; const module2Name = "module2"; const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; const { file1, file2 } = getFiles(fileContent); - const { module1, module2 } = getModules(`${projectRoot}/src/node_modules/module1/index.ts`, `${projectRoot}/node_modules/module2/index.ts`); + const { module1, module2 } = getModules(`${tscWatch.projectRoot}/src/node_modules/module1/index.ts`, `${tscWatch.projectRoot}/node_modules/module2/index.ts`); const files = [module1, module2, file1, file2, configFile, libFile]; const host = createServerHost(files); const resolutionTrace = createHostModuleResolutionTrace(host); @@ -646,19 +646,19 @@ namespace ts.projectSystem { describe("from files in different folders", () => { function getFiles(fileContent1: string, fileContent2 = fileContent1, fileContent3 = fileContent1, fileContent4 = fileContent1) { const file1: File = { - path: `${projectRoot}/product/src/file1.ts`, + path: `${tscWatch.projectRoot}/product/src/file1.ts`, content: fileContent1 }; const file2: File = { - path: `${projectRoot}/product/src/feature/file2.ts`, + path: `${tscWatch.projectRoot}/product/src/feature/file2.ts`, content: fileContent2 }; const file3: File = { - path: `${projectRoot}/product/test/src/file3.ts`, + path: `${tscWatch.projectRoot}/product/test/src/file3.ts`, content: fileContent3 }; const file4: File = { - path: `${projectRoot}/product/test/file4.ts`, + path: `${tscWatch.projectRoot}/product/test/file4.ts`, content: fileContent4 }; return { file1, file2, file3, file4 }; @@ -676,7 +676,7 @@ namespace ts.projectSystem { const fileContent3 = `import { module1 } from "${module5Name}";import { module2 } from "${module4Name}";`; const fileContent4 = `import { module1 } from "${module6Name}";import { module2 } from "${module2Name}";`; const { file1, file2, file3, file4 } = getFiles(fileContent1, fileContent2, fileContent3, fileContent4); - const { module1, module2 } = getModules(`${projectRoot}/product/src/module1.ts`, `${projectRoot}/product/module2.ts`); + const { module1, module2 } = getModules(`${tscWatch.projectRoot}/product/src/module1.ts`, `${tscWatch.projectRoot}/product/module2.ts`); const files = [module1, module2, file1, file2, file3, file4, configFile, libFile]; const host = createServerHost(files); const resolutionTrace = createHostModuleResolutionTrace(host); @@ -708,12 +708,12 @@ namespace ts.projectSystem { }); it("non relative module name", () => { - const expectedNonRelativeDirectories = [`${projectRoot}/node_modules`, `${projectRoot}/product`]; + const expectedNonRelativeDirectories = [`${tscWatch.projectRoot}/node_modules`, `${tscWatch.projectRoot}/product`]; const module1Name = "module1"; const module2Name = "module2"; const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; const { file1, file2, file3, file4 } = getFiles(fileContent); - const { module1, module2 } = getModules(`${projectRoot}/product/node_modules/module1/index.ts`, `${projectRoot}/node_modules/module2/index.ts`); + const { module1, module2 } = getModules(`${tscWatch.projectRoot}/product/node_modules/module1/index.ts`, `${tscWatch.projectRoot}/node_modules/module2/index.ts`); const files = [module1, module2, file1, file2, file3, file4, configFile, libFile]; const host = createServerHost(files); const resolutionTrace = createHostModuleResolutionTrace(host); @@ -723,8 +723,8 @@ namespace ts.projectSystem { getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace); getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module1, module1Name, getDirectoryPath(file1.path), expectedTrace); getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module2, module2Name, getDirectoryPath(file1.path), expectedTrace); - getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module1, module1Name, `${projectRoot}/product`, expectedTrace); - getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module2, module2Name, `${projectRoot}/product`, expectedTrace); + getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module1, module1Name, `${tscWatch.projectRoot}/product`, expectedTrace); + getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module2, module2Name, `${tscWatch.projectRoot}/product`, expectedTrace); getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module1, module1Name, getDirectoryPath(file4.path), expectedTrace); getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module2, module2Name, getDirectoryPath(file4.path), expectedTrace); verifyTrace(resolutionTrace, expectedTrace); @@ -752,7 +752,7 @@ namespace ts.projectSystem { const file4Name = "../test/file4"; const importModuleContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; const { file1, file2, file3, file4 } = getFiles(`import "${file2Name}"; import "${file4Name}"; import "${file3Name}"; ${importModuleContent}`, importModuleContent, importModuleContent, importModuleContent); - const { module1, module2 } = getModules(`${projectRoot}/product/node_modules/module1/index.ts`, `${projectRoot}/node_modules/module2/index.ts`); + const { module1, module2 } = getModules(`${tscWatch.projectRoot}/product/node_modules/module1/index.ts`, `${tscWatch.projectRoot}/node_modules/module2/index.ts`); const files = [module1, module2, file1, file2, file3, file4, libFile]; const host = createServerHost(files); const resolutionTrace = createHostModuleResolutionTrace(host); @@ -766,19 +766,19 @@ namespace ts.projectSystem { getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace); getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module1, module1Name, getDirectoryPath(file1.path), expectedTrace); getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module2, module2Name, getDirectoryPath(file1.path), expectedTrace); - getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module1, module1Name, `${projectRoot}/product`, expectedTrace); - getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module2, module2Name, `${projectRoot}/product`, expectedTrace); + getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module1, module1Name, `${tscWatch.projectRoot}/product`, expectedTrace); + getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module2, module2Name, `${tscWatch.projectRoot}/product`, expectedTrace); getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module1, module1Name, getDirectoryPath(file4.path), expectedTrace); getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module2, module2Name, getDirectoryPath(file4.path), expectedTrace); verifyTrace(resolutionTrace, expectedTrace); const currentDirectory = getDirectoryPath(file1.path); const watchedFiles = mapDefined(files, f => f === file1 || f.path.indexOf("/node_modules/") !== -1 ? undefined : f.path) - .concat(getConfigFilesToWatch(`${projectRoot}/product/src`)); + .concat(getConfigFilesToWatch(`${tscWatch.projectRoot}/product/src`)); const watchedRecursiveDirectories = getTypeRootsFromLocation(currentDirectory).concat([ - `${currentDirectory}/node_modules`, `${currentDirectory}/feature`, `${projectRoot}/product/${nodeModules}`, - `${projectRoot}/${nodeModules}`, `${projectRoot}/product/test/${nodeModules}`, - `${projectRoot}/product/test/src/${nodeModules}` + `${currentDirectory}/node_modules`, `${currentDirectory}/feature`, `${tscWatch.projectRoot}/product/${nodeModules}`, + `${tscWatch.projectRoot}/${nodeModules}`, `${tscWatch.projectRoot}/product/test/${nodeModules}`, + `${tscWatch.projectRoot}/product/test/src/${nodeModules}` ]); checkWatches(); @@ -808,7 +808,7 @@ namespace ts.projectSystem { describe("when watching directories for failed lookup locations in amd resolution", () => { const nodeFile: File = { - path: `${projectRoot}/src/typings/node.d.ts`, + path: `${tscWatch.projectRoot}/src/typings/node.d.ts`, content: ` declare module "fs" { export interface something { @@ -816,7 +816,7 @@ declare module "fs" { }` }; const electronFile: File = { - path: `${projectRoot}/src/typings/electron.d.ts`, + path: `${tscWatch.projectRoot}/src/typings/electron.d.ts`, content: ` declare module 'original-fs' { import * as fs from 'fs'; @@ -824,19 +824,19 @@ declare module 'original-fs' { }` }; const srcFile: File = { - path: `${projectRoot}/src/somefolder/srcfile.ts`, + path: `${tscWatch.projectRoot}/src/somefolder/srcfile.ts`, content: ` import { x } from "somefolder/module1"; import { x } from "somefolder/module2"; const y = x;` }; const moduleFile: File = { - path: `${projectRoot}/src/somefolder/module1.ts`, + path: `${tscWatch.projectRoot}/src/somefolder/module1.ts`, content: ` export const x = 10;` }; const configFile: File = { - path: `${projectRoot}/src/tsconfig.json`, + path: `${tscWatch.projectRoot}/src/tsconfig.json`, content: JSON.stringify({ compilerOptions: { module: "amd", @@ -853,22 +853,22 @@ export const x = 10;` const files = [...(useNodeFile ? [nodeFile] : []), electronFile, srcFile, moduleFile, configFile, libFile]; const host = createServerHost(files); const service = createProjectService(host); - service.openClientFile(srcFile.path, srcFile.content, ScriptKind.TS, projectRoot); + service.openClientFile(srcFile.path, srcFile.content, ScriptKind.TS, tscWatch.projectRoot); checkProjectActualFiles(service.configuredProjects.get(configFile.path)!, files.map(f => f.path)); checkWatchedFilesDetailed(host, mapDefined(files, f => f === srcFile ? undefined : f.path), 1); if (useNodeFile) { checkWatchedDirectories(host, emptyArray, /*recursive*/ false); // since fs resolves to ambient module, shouldnt watch failed lookup } else { - checkWatchedDirectoriesDetailed(host, [`${projectRoot}`, `${projectRoot}/src`], 1, /*recursive*/ false); // failed lookup for fs + checkWatchedDirectoriesDetailed(host, [`${tscWatch.projectRoot}`, `${tscWatch.projectRoot}/src`], 1, /*recursive*/ false); // failed lookup for fs } const expectedWatchedDirectories = createMap(); - expectedWatchedDirectories.set(`${projectRoot}/src`, 1); // Wild card - expectedWatchedDirectories.set(`${projectRoot}/src/somefolder`, 1); // failedLookup for somefolder/module2 - expectedWatchedDirectories.set(`${projectRoot}/src/node_modules`, 1); // failed lookup for somefolder/module2 - expectedWatchedDirectories.set(`${projectRoot}/somefolder`, 1); // failed lookup for somefolder/module2 - expectedWatchedDirectories.set(`${projectRoot}/node_modules`, 1); // failed lookup for with node_modules/@types/fs - expectedWatchedDirectories.set(`${projectRoot}/src/typings`, useNodeFile ? 1 : 2); // typeroot directory + failed lookup if not using node file + expectedWatchedDirectories.set(`${tscWatch.projectRoot}/src`, 1); // Wild card + expectedWatchedDirectories.set(`${tscWatch.projectRoot}/src/somefolder`, 1); // failedLookup for somefolder/module2 + expectedWatchedDirectories.set(`${tscWatch.projectRoot}/src/node_modules`, 1); // failed lookup for somefolder/module2 + expectedWatchedDirectories.set(`${tscWatch.projectRoot}/somefolder`, 1); // failed lookup for somefolder/module2 + expectedWatchedDirectories.set(`${tscWatch.projectRoot}/node_modules`, 1); // failed lookup for with node_modules/@types/fs + expectedWatchedDirectories.set(`${tscWatch.projectRoot}/src/typings`, useNodeFile ? 1 : 2); // typeroot directory + failed lookup if not using node file checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ true); } @@ -883,15 +883,15 @@ export const x = 10;` describe("ignores files/folder changes in node_modules that start with '.'", () => { const npmCacheFile: File = { - path: `${projectRoot}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`, + path: `${tscWatch.projectRoot}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`, content: JSON.stringify({ something: 10 }) }; const file1: File = { - path: `${projectRoot}/test.ts`, + path: `${tscWatch.projectRoot}/test.ts`, content: `import { x } from "somemodule";` }; const file2: File = { - path: `${projectRoot}/node_modules/somemodule/index.d.ts`, + path: `${tscWatch.projectRoot}/node_modules/somemodule/index.d.ts`, content: `export const x = 10;` }; it("when watching node_modules in inferred project for failed lookup/closed script infos", () => { @@ -910,7 +910,7 @@ export const x = 10;` }); it("when watching node_modules as part of wild card directories in config project", () => { const config: File = { - path: `${projectRoot}/tsconfig.json`, + path: `${tscWatch.projectRoot}/tsconfig.json`, content: "{}" }; const files = [libFile, file1, file2, config]; @@ -929,15 +929,15 @@ export const x = 10;` describe("avoid unnecessary invalidation", () => { it("unnecessary lookup invalidation on save", () => { - const expectedNonRelativeDirectories = [`${projectRoot}/node_modules`, `${projectRoot}/src`]; + const expectedNonRelativeDirectories = [`${tscWatch.projectRoot}/node_modules`, `${tscWatch.projectRoot}/src`]; const module1Name = "module1"; const module2Name = "module2"; const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; const file1: File = { - path: `${projectRoot}/src/file1.ts`, + path: `${tscWatch.projectRoot}/src/file1.ts`, content: fileContent }; - const { module1, module2 } = getModules(`${projectRoot}/src/node_modules/module1/index.ts`, `${projectRoot}/node_modules/module2/index.ts`); + const { module1, module2 } = getModules(`${tscWatch.projectRoot}/src/node_modules/module1/index.ts`, `${tscWatch.projectRoot}/node_modules/module2/index.ts`); const files = [module1, module2, file1, configFile, libFile]; const host = createServerHost(files); const resolutionTrace = createHostModuleResolutionTrace(host); diff --git a/src/testRunner/unittests/tsserver/syntaxOperations.ts b/src/testRunner/unittests/tsserver/syntaxOperations.ts index ec5610b649e..94c1241cb1c 100644 --- a/src/testRunner/unittests/tsserver/syntaxOperations.ts +++ b/src/testRunner/unittests/tsserver/syntaxOperations.ts @@ -16,11 +16,11 @@ namespace ts.projectSystem { it("works when file is removed and added with different content", () => { const app: File = { - path: `${projectRoot}/app.ts`, + path: `${tscWatch.projectRoot}/app.ts`, content: "console.log('Hello world');" }; const unitTest1: File = { - path: `${projectRoot}/unitTest1.ts`, + path: `${tscWatch.projectRoot}/unitTest1.ts`, content: `import assert = require('assert'); describe("Test Suite 1", () => { @@ -35,7 +35,7 @@ describe("Test Suite 1", () => { });` }; const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, + path: `${tscWatch.projectRoot}/tsconfig.json`, content: "{}" }; const files = [app, libFile, tsconfig]; diff --git a/src/testRunner/unittests/tsserver/typeReferenceDirectives.ts b/src/testRunner/unittests/tsserver/typeReferenceDirectives.ts index 42f97733985..c29dc34fd94 100644 --- a/src/testRunner/unittests/tsserver/typeReferenceDirectives.ts +++ b/src/testRunner/unittests/tsserver/typeReferenceDirectives.ts @@ -1,7 +1,7 @@ namespace ts.projectSystem { describe("unittests:: tsserver:: typeReferenceDirectives", () => { it("when typeReferenceDirective contains UpperCasePackage", () => { - const libProjectLocation = `${projectRoot}/lib`; + const libProjectLocation = `${tscWatch.projectRoot}/lib`; const typeLib: File = { path: `${libProjectLocation}/@types/UpperCasePackage/index.d.ts`, content: `declare class BrokenTest { @@ -19,7 +19,7 @@ declare class TestLib { test(): void; }` }; - const testProjectLocation = `${projectRoot}/test`; + const testProjectLocation = `${tscWatch.projectRoot}/test`; const testFile: File = { path: `${testProjectLocation}/test.ts`, content: `class TestClass1 { @@ -57,7 +57,7 @@ declare class TestLib { }); it("when typeReferenceDirective is relative path and in a sibling folder", () => { - const projectPath = `${projectRoot}/background`; + const projectPath = `${tscWatch.projectRoot}/background`; const file: File = { path: `${projectPath}/a.ts`, content: "let x = 10;" @@ -73,7 +73,7 @@ declare class TestLib { }) }; const filesystem: File = { - path: `${projectRoot}/typedefs/filesystem.d.ts`, + path: `${tscWatch.projectRoot}/typedefs/filesystem.d.ts`, content: `interface LocalFileSystem { someProperty: string; }` }; const files = [file, tsconfig, filesystem, libFile]; diff --git a/src/testRunner/unittests/tsserver/typingsInstaller.ts b/src/testRunner/unittests/tsserver/typingsInstaller.ts index cecc6c4383a..d93ff99303c 100644 --- a/src/testRunner/unittests/tsserver/typingsInstaller.ts +++ b/src/testRunner/unittests/tsserver/typingsInstaller.ts @@ -995,13 +995,13 @@ namespace ts.projectSystem { it("should redo resolution that resolved to '.js' file after typings are installed", () => { const file: TestFSWithWatch.File = { - path: `${projects}/a/b/app.js`, + path: `${tscWatch.projects}/a/b/app.js`, content: ` import * as commander from "commander";` }; - const cachePath = `${projects}/a/cache`; + const cachePath = `${tscWatch.projects}/a/cache`; const commanderJS: TestFSWithWatch.File = { - path: `${projects}/node_modules/commander/index.js`, + path: `${tscWatch.projects}/node_modules/commander/index.js`, content: "module.exports = 0", }; @@ -1025,12 +1025,12 @@ namespace ts.projectSystem { checkWatchedDirectories(host, [], /*recursive*/ false); // Does not include cachePath because that is handled by typingsInstaller checkWatchedDirectories(host, [ - `${projects}/node_modules`, - `${projects}/a/node_modules`, - `${projects}/a/b/node_modules`, - `${projects}/a/node_modules/@types`, - `${projects}/a/b/node_modules/@types`, - `${projects}/a/b/bower_components` + `${tscWatch.projects}/node_modules`, + `${tscWatch.projects}/a/node_modules`, + `${tscWatch.projects}/a/b/node_modules`, + `${tscWatch.projects}/a/node_modules/@types`, + `${tscWatch.projects}/a/b/node_modules/@types`, + `${tscWatch.projects}/a/b/bower_components` ], /*recursive*/ true); service.checkNumberOfProjects({ inferredProjects: 1 }); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 9248681aece..013739a4b3f 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -17,8 +17,6 @@ declare namespace ts { const versionMajorMinor = "3.8"; /** The version of the TypeScript compiler release */ const version: string; -} -declare namespace ts { /** * Type of objects whose values are all of the same type. * The `in` and `for-in` operators can *not* be safely used, @@ -3307,8 +3305,6 @@ declare namespace ts { declare namespace ts { function isExternalModuleNameRelative(moduleName: string): boolean; function sortAndDeduplicateDiagnostics(diagnostics: readonly T[]): SortedReadonlyArray; -} -declare namespace ts { function getDefaultLibFileName(options: CompilerOptions): string; function textSpanEnd(span: TextSpan): number; function textSpanIsEmpty(span: TextSpan): boolean; @@ -3469,8 +3465,6 @@ declare namespace ts { */ function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters): readonly TypeParameterDeclaration[]; function getEffectiveConstraintOfTypeParameter(node: TypeParameterDeclaration): TypeNode | undefined; -} -declare namespace ts { function isNumericLiteral(node: Node): node is NumericLiteral; function isBigIntLiteral(node: Node): node is BigIntLiteral; function isStringLiteral(node: Node): node is StringLiteral; @@ -3648,8 +3642,6 @@ declare namespace ts { function isJSDocTypeLiteral(node: Node): node is JSDocTypeLiteral; function isJSDocCallbackTag(node: Node): node is JSDocCallbackTag; function isJSDocSignature(node: Node): node is JSDocSignature; -} -declare namespace ts { /** * True if node is of some token syntax kind. * For example, this is true for an IfKeyword but not for an IfStatement. @@ -4265,7 +4257,7 @@ declare namespace ts { /** * Sets the constant value to emit for an expression. */ - function setConstantValue(node: PropertyAccessExpression | ElementAccessExpression, value: string | number): ElementAccessExpression | PropertyAccessExpression; + function setConstantValue(node: PropertyAccessExpression | ElementAccessExpression, value: string | number): PropertyAccessExpression | ElementAccessExpression; /** * Adds an EmitHelper to a node. */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 94a41969ed6..6078232574c 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -17,8 +17,6 @@ declare namespace ts { const versionMajorMinor = "3.8"; /** The version of the TypeScript compiler release */ const version: string; -} -declare namespace ts { /** * Type of objects whose values are all of the same type. * The `in` and `for-in` operators can *not* be safely used, @@ -3307,8 +3305,6 @@ declare namespace ts { declare namespace ts { function isExternalModuleNameRelative(moduleName: string): boolean; function sortAndDeduplicateDiagnostics(diagnostics: readonly T[]): SortedReadonlyArray; -} -declare namespace ts { function getDefaultLibFileName(options: CompilerOptions): string; function textSpanEnd(span: TextSpan): number; function textSpanIsEmpty(span: TextSpan): boolean; @@ -3469,8 +3465,6 @@ declare namespace ts { */ function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters): readonly TypeParameterDeclaration[]; function getEffectiveConstraintOfTypeParameter(node: TypeParameterDeclaration): TypeNode | undefined; -} -declare namespace ts { function isNumericLiteral(node: Node): node is NumericLiteral; function isBigIntLiteral(node: Node): node is BigIntLiteral; function isStringLiteral(node: Node): node is StringLiteral; @@ -3648,8 +3642,6 @@ declare namespace ts { function isJSDocTypeLiteral(node: Node): node is JSDocTypeLiteral; function isJSDocCallbackTag(node: Node): node is JSDocCallbackTag; function isJSDocSignature(node: Node): node is JSDocSignature; -} -declare namespace ts { /** * True if node is of some token syntax kind. * For example, this is true for an IfKeyword but not for an IfStatement. @@ -4265,7 +4257,7 @@ declare namespace ts { /** * Sets the constant value to emit for an expression. */ - function setConstantValue(node: PropertyAccessExpression | ElementAccessExpression, value: string | number): ElementAccessExpression | PropertyAccessExpression; + function setConstantValue(node: PropertyAccessExpression | ElementAccessExpression, value: string | number): PropertyAccessExpression | ElementAccessExpression; /** * Adds an EmitHelper to a node. */